//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            cachedElements : {
                $body : $('body'),
                $window : $(window)
            },

            initialize: function() {
                  var self = this;

                  // Set the skope_id
                  try { this.skope_id = _.findWhere( _wpCustomizeSettings.czr_new_skopes, { skope : 'local' }).skope_id; } catch( _er_ ) {
                        this.errare('Preview => error when storing the skope_id', _er_ );
                        return;
                  }

                  // Active UI
                  this.scheduleHighlightActiveLevel();

                  // The loading icon when a level is refreshed
                  self.setupLoader();

                  // DOM READY
                  $( function() {
                        self.setupSortable();
                        self.setupResizable();
                        self.setupUiHoverVisibility();
                        self.scheduleUiClickReactions();

                        self.schedulePanelMsgReactions();
                  });

                  // Make sure we don't force a minimum height to empty columns when a section has at least one module
                  // => allow a better previewing experience and more realistic spacing adjustments
                  // The css class .sek-has-modules is also printed server side
                  // @see php SEK_Front_Render::render()
                  self.cachedElements.$body.on('sek-columns-refreshed sek-modules-refreshed', function( evt, params ) {
                        if ( !_.isUndefined( params ) && !_.isUndefined( params.in_sektion ) && $('[data-sek-id="' + params.in_sektion +'"]').length > 0 ) {
                              var $updatedSektion = $('[data-sek-id="' + params.in_sektion +'"]');
                              $updatedSektion.toggleClass( 'sek-has-modules', $updatedSektion.find('[data-sek-level="module"]').length > 0 );
                        }
                  });

                  // Deactivates the links
                  self.deactivateLinks();

                  self.cachedElements.$body.on([
                        'sek-modules-refreshed',
                        'sek-columns-refreshed',
                        'sek-section-added',
                        'sek-level-refreshed',
                        'sek-edit-module'
                  ].join(' '), function( evt ) {
                        self.deactivateLinks(evt);
                  });


                  // Send the contextually active locations
                  // 1) on init
                  // 2) and when requested by the control panel
                  // introduced for the level tree, https://github.com/presscustomizr/nimble-builder/issues/359
                  var sendActiveLocations = function() {
                        var active_locs = [],
                            active_locs_info = [];// <= introduced for better move up/down of sections https://github.com/presscustomizr/nimble-builder/issues/521
                        $('[data-sek-level="location"]').each( function() {
                              active_locs.push( $(this).data('sek-id') );
                              active_locs_info.push({
                                    id : $(this).data('sek-id'),
                                    is_global : true === $(this).data('sek-is-global-location'),
                                    is_header_footer : true === $(this).data('sek-is-header-location') || true === $(this).data('sek-is-footer-location'),
                                    // added for https://github.com/presscustomizr/nimble-builder-pro/issues/6
                                    is_header : true === $(this).data('sek-is-header-location'),
                                    is_footer : true === $(this).data('sek-is-footer-location')
                              });
                        });
                        api.preview.send('sek-active-locations-in-preview', { active_locations : active_locs, active_locs_info : active_locs_info } );
                  };
                  api.preview.bind('sek-request-active-locations', sendActiveLocations );
                  sendActiveLocations();
            },

            // Fired on initialize()
            // and on user generated events
            deactivateLinks : function( evt ) {
                  evt = evt || {};
                  var _doSafe_ = function() {
                          if ( "yes" === $(this).data('sek-unlinked') )
                            return;
                          // Several cases :
                          // 1- internal link ( <=> api.isLinkPreviewable(... ) = true ) : we allow navigation with shift + click
                          // 2- extenal link => navigation is disabled.
                          // 3- server disabled links, with href attribute set to "javascript:void(0)", this case is checked isJavascriptProtocol
                          var isJavascriptProtocol = _.isString( $(this)[0].protocol ) && -1 !== $(this)[0].protocol.indexOf('javascript');
                          // the check on isJavascriptProtocol fixes issue https://github.com/presscustomizr/nimble-builder/issues/255
                          if ( ! isJavascriptProtocol && api.isLinkPreviewable( $(this)[0] ) ) {
                                $(this).addClass('nimble-shift-clickable');
                                $(this).data('sek-unlinked', "yes").attr('data-nimble-href', $(this).attr('href') ).attr('href', 'javascript:void(0)');
                                // remove target="_blank" if enabled by user
                                // @fixes issue https://github.com/presscustomizr/nimble-builder/issues/542
                                $(this).removeAttr('target');
                                $(this).on('mouseenter', function() {
                                        $(this).attr( 'title', sekPreviewLocalized.i18n['Shift-click to visit the link']);
                                });
                                $(this).on('mouseleave', function() {
                                      $(this).removeAttr( 'title' );
                                });

                                $(this).on('click', function(evt) {
                                      if ( ! evt.shiftKey ) {
                                        return;
                                      }
                                      evt.preventDefault();
                                      window.location.href = $(this).attr('data-nimble-href');
                                });
                          } else {
                                $(this).addClass('nimble-unclickable');
                                $(this).data('sek-unlinked', "yes").attr('data-nimble-href', $(this).attr('href') ).attr('href', 'javascript:void(0)');
                                $(this).on('mouseenter', function() {
                                      $(this).attr( 'title', isJavascriptProtocol ? sekPreviewLocalized.i18n['Link deactivated while previewing'] : sekPreviewLocalized.i18n['External links are disabled when customizing']);
                                }).on('mouseleave', function() {
                                      $(this).removeAttr( 'title' );
                                });
                                $(this).on('click', function(evt) {
                                      evt.preventDefault();
                                });
                          }
                    };
                  this.cachedElements.$body.find('[data-sek-level="module"]').each( function() {
                        $(this).find('a').each( function(){
                              try { _doSafe_.call( $(this) ); } catch(er) { api.errare( '::deactivateLinks => error ', er ); }
                        });
                  });
            },

            // Hightlight the currently level in the preview, corresponding to the active ui in the panel
            //
            // When a new ui is generated, the activeLevelUI is set @see ::schedulePanelMsgReactions()
            // When the level options are modidied ( 'sek-refresh-stylesheet', 'sek-refresh-level' ),
            scheduleHighlightActiveLevel : function() {
                  var self = this;
                  // Stores the currently edited level
                  // aka the one on which the user clicked to edit it
                  this.activeLevelUI = new api.Value('');
                  this.activeLevelEl = new api.Value(null);
                  this.activeUIChangedRecently = new api.Value( false );

                  this.activeLevelUI.bind( function( to, from ) {
                        var $activeLevel = $('[data-sek-id="' + to +'"]'),
                            $previousActiveLevel = $('[data-sek-id="' + from +'"]');

                        if ( $activeLevel.length > 0 ) {
                              $activeLevel.addClass('sek-active-ui sek-highlight-active-ui');
                              // cache $activeLevel
                              self.activeLevelEl( $activeLevel );
                        }
                        if ( $previousActiveLevel.length > 0 ) {
                              $previousActiveLevel.removeClass('sek-active-ui sek-highlight-active-ui');
                        }
                        self.activeUIChangedRecently( Date.now() );
                  });

                  // MAY 2020 : added to focus on the edited element
                  // updated in self.activeLevelUI() and self.schedulePanelMsgReactions()
                  self.activeLevelEl.bind( function($el) {
                        // scroll to focus on the active element
                        // but only if element is offscreen, otherwise clicking on a section edit UI for example, will make it move to the top, which is annoying
                        if ( _.isObject($el) && $el.length > 0 && !nb_.isInScreen( $el[0]) ) {
                              // https://caniuse.com/#search=scrollIntoView
                              try{ $el[0].scrollIntoView(); } catch(er) {
                                    self.errare('activeLevelEl error', er );
                              }
                        }
                  });

                  // apiParams : {
                  //       action : 'sek-refresh-level',
                  //       id : params.uiParams.id,
                  //       level : params.uiParams.level
                  // },
                  // skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),
                  _.each( [ 'sek-refresh-stylesheet', 'sek-refresh-level' ], function( msgId ) {
                        api.preview.bind( msgId, function( params ) {
                              self.activeUIChangedRecently( Date.now() );
                        });
                  });


                  // This api.Value() is updated with Date.now() when something just happened and false after 3000 ms of inactivity
                  // so we can always react to changes, and refresh the timeOut
                  this.activeUIChangedRecently.bind( function( hasChangedRecently ) {
                        var $newActiveLevel = $('[data-sek-id="' + self.activeLevelUI() +'"]');
                        // remove the highlight class if it was previously set to another level
                        if ( $('.sek-highlight-active-ui').length ) {
                              $('.sek-highlight-active-ui').removeClass('sek-highlight-active-ui');
                        }
                        if ( $newActiveLevel.length > 0 ) {
                              $newActiveLevel.toggleClass( 'sek-highlight-active-ui', false !== hasChangedRecently );
                        }

                        clearTimeout( $.data( this, '_ui_change_timer_') );
                        $.data( this, '_ui_change_timer_', setTimeout(function() {
                              self.activeUIChangedRecently( false );
                        }, 3000 ) );
                  });
            }
      });//$.extend()
})( wp.customize, jQuery, _ );//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            // Fired on Dom Ready, in ::initialize()
            setupSortable: function() {
                  var self = this;
                  this.sortableDefaultParams = {
                        //forcePlaceholderSize: true,
                        //handle: '.fa-arrows-alt',<= set by level
                        placeholder: "sortable-placeholder",
                        over: function( event, ui ) {},
                        cursorAt: { top:0, left: 0 },//@fixes https://github.com/presscustomizr/nimble-builder/issues/114
                        tolerance: "pointer",//@fixes https://github.com/presscustomizr/nimble-builder/issues/114
                  };

                  // SEKTIONS
                  // On dom ready
                  $('[data-sek-level="location"]').each( function() {
                        self.makeSektionsSortableInLocation( $(this).data('sek-id') );
                  });

                  // Schedule with delegation
                  self.cachedElements.$body.on( 'sek-section-added sek-level-refreshed sek-location-refreshed', '[data-sek-level="location"]', function( evt, params  ) {
                        self.makeSektionsSortableInLocation( $(this).data('sek-id') );
                  });


                  // COLUMNS
                  // On dom ready
                  $('[data-sek-level="location"]').each( function() {
                        $(this).find( '[data-sek-level="section"]' ).each( function() {
                              self.makeColumnsSortableInSektion( $(this).data('sek-id') );
                        });
                  });
                  // Schedule with delegation
                  self.cachedElements.$body.on( 'sek-columns-refreshed sek-section-added', '[data-sek-level="section"]', function( evt ) {
                        self.makeColumnsSortableInSektion( $(this).data('sek-id') );
                  });
                  // this case occurs when moving a section from one location to another for example
                  self.cachedElements.$body.on( 'sek-level-refreshed', '[data-sek-level="location"]', function( evt, params  ) {
                        $(this).find( '[data-sek-level="section"]' ).each( function() {
                              self.makeColumnsSortableInSektion( $(this).data('sek-id') );
                        });
                  });


                  // MODULES
                  // On dom ready
                  $('[data-sek-level="location"]').each( function() {
                        $(this).find( '[data-sek-level="column"]' ).each( function() {
                              self.makeModulesSortableInColumn( $(this).data('sek-id') );
                        });
                  });
                  // Schedule with delegation
                  self.cachedElements.$body.on( 'sek-modules-refreshed', '[data-sek-level="column"]', function() {
                        self.makeModulesSortableInColumn( $(this).data('sek-id') );
                  });
                  self.cachedElements.$body.on( 'sek-columns-refreshed', '[data-sek-level="section"]', function() {
                        $(this).find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).each( function() {
                              self.makeModulesSortableInColumn( $(this).data('sek-id') );
                        });
                  });
                  // this case occurs when moving a section from one location to another for example
                  self.cachedElements.$body.on( 'sek-level-refreshed', '[data-sek-level="location"]', function( evt, params  ) {
                        $(this).find( '[data-sek-level="column"]' ).each( function() {
                              self.makeModulesSortableInColumn( $(this).data('sek-id') );
                        });
                  });
                  self.cachedElements.$body.on( 'sek-section-added', '[data-sek-level="location"]', function( evt, params  ) {
                        $(this).find( '[data-sek-level="column"]' ).each( function() {
                              self.makeModulesSortableInColumn( $(this).data('sek-id') );
                        });
                  });
                  // added to fix impossibility to move an already inserted module in a freshly added multicolumn section
                  // @see https://github.com/presscustomizr/nimble-builder/issues/538
                  self.cachedElements.$body.on( 'sek-location-refreshed', '[data-sek-level="location"]', function( evt, params  ) {
                        $(this).find( '[data-sek-level="column"]' ).each( function() {
                              self.makeModulesSortableInColumn( $(this).data('sek-id') );
                        });
                  });

                  // NESTED SEKTIONS
                  // $('.sek-column-inner', '[data-sek-level="section"]').children( '[data-sek-level="section"]' ).each( function() {
                  //       defaults = $.extend( true, {}, self.sortableDefaultParams );
                  //       $(this).sortable( _.extend( defaults, {
                  //           handle : '.sek-move-nested-section',
                  //           connectWith: ".sek-column-inner, [data-sek-level="location"]",
                  //           start: function( event, ui ) {
                  //               // store the startOrder
                  //               $('[data-sek-level="location"]').children( '[data-sek-level="section"]' ).each( function() {
                  //                     startOrder.push( $(this).data('sek-id') );
                  //               });
                  //               //console.log('column moved from', from_sektion, ui );
                  //           },
                  //           stop : function( event, ui ) {
                  //               newOrder = [];
                  //               // Restrict to the direct children
                  //               $('[data-sek-level="location"]').children( '[data-sek-level="section"]' ).each( function() {
                  //                     newOrder.push( $(this).data('sek-id') );
                  //               });

                  //               // api.preview.send( 'sek-move', {
                  //               //       id : ui.item.data('sek-id'),
                  //               //       level : 'section',
                  //               //       newOrder : newOrder
                  //               // });
                  //           }
                  //       }));
                  // });


                  // <SORTABLE>
                  // $('.sek-sektion-inner').each( function() {
                  //     $(this).sortable({
                  //         connectWith: ".sek-sektion-inner"
                  //     }).disableSelection();
                  // });

                  return this;
            },//setupSortable()


            // Print all level ui
            // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/521
            printAllLevelsUi : function() {
                  var self = this;
                  self.cachedElements.$body.find( '[data-sek-level]' ).each( function() {
                        self.printLevelUI( $(this) );
                        $(this).find('.sek-dyn-ui-wrapper').stop( true, true ).show();
                  });
            },


            makeSektionsSortableInLocation : function( locationId ) {
                  var self = this;
                  var from_location, to_location, startOrder = [], newOrder = [], defaults;

                  if ( true === $('[data-sek-id="' + locationId +'"]').data('sek-is-global-location') )
                      return;

                  defaults = $.extend( true, {}, self.sortableDefaultParams );

                  $('[data-sek-id="' + locationId +'"]').sortable( _.extend( defaults, {
                        items: '[data-sek-level="section"]',
                        //handle : '.sek-move-section, .sek-section-dyn-ui > .sek-dyn-ui-location-type',//@fixes https://github.com/presscustomizr/nimble-builder/issues/153
                        handle : '.sek-move-section',
                        connectWith : '[data-sek-is-global-location="false"]',
                        placeholder: {
                              element: function(currentItem) {
                                  return $('<div class="sortable-placeholder"><div class="sek-module-placeholder-content"><p>' + sekPreviewLocalized.i18n['Insert here'] + '</p></div></div>')[0];
                              },
                              update: function(container, p) {
                                  return;
                              }
                        },
                        start: function( event, ui ) {
                              self.cachedElements.$body.addClass('sek-moving-section');
                              self.isDraggingElement = true;
                              $sourceLocation = ui.item.closest('[data-sek-level="location"]');
                              from_location = $sourceLocation.data('sek-id');

                              // Print all level ui
                              // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/521
                              self.printAllLevelsUi();

                              // store the startOrder
                              // $sourceLocation.children( '[data-sek-level="section"]' ).each( function() {
                              //       startOrder.push( $(this).data('sek-id') );
                              // });
                        },
                        stop : function( event, ui ) {
                              self.cachedElements.$body.removeClass('sek-moving-section');
                              self.isDraggingElement = false;

                              newOrder = [];
                              $targetLocation = ui.item.closest('[data-sek-level="location"]');
                              to_location = $targetLocation.data('sek-id');

                              // Restrict to the direct children
                              $targetLocation.children( '[data-sek-level="section"]' ).each( function() {
                                    newOrder.push( $(this).data('sek-id') );
                              });

                              api.preview.send( 'sek-move', {
                                    id : ui.item.data('sek-id'),
                                    level : 'section',
                                    newOrder : newOrder,
                                    from_location : from_location,
                                    to_location : to_location
                              });
                        },
                        over : function( event, ui ) {
                              ui.placeholder.addClass('sek-sortable-section-over');
                        },
                        out : function( event, ui  ) {
                              ui.placeholder.removeClass('sek-sortable-section-over');
                        }
                  }));
            },//makeSektionsSortableInLocation







            // Instantiate sortable for a given column Id
            // Columns are a little more complex because we want to emulate the future layouts when moving a column from section to section
            // for that, we need to compute the number of columns and play with css classes.
            // During this process, we use two $.data to store informations :
            //    'sek-is-sender' => tells us if this is the sektion from which we started to drag a column
            //    '_sortable_columns_css_classes_' => stores the current and future css classes
            makeColumnsSortableInSektion : function( sektionId ) {
                  var self = this,
                      defaults = $.extend( true, {}, self.sortableDefaultParams ),
                      $fromLocation,
                      is_global_from_location,
                      $toLocation,
                      is_global_to_location,
                      startOrder = [],
                      newOrder = [],
                      $sortableCandidate = $( '[data-sek-id="' + sektionId + '"]').find('.sek-sektion-inner').first(),
                      getCurrentAndNextColNumberClasses = function( args ) {
                            args = $.extend( { forTarget : true }, args || {} );
                            if ( ! _.isEmpty( $(this).data('_sortable_columns_css_classes_' ) ) )
                              return $(this).data('_sortable_columns_css_classes_' );

                            var $targetSektion          = $(this).closest('[data-sek-level="section"]'),
                                $columnsInTargetSektion = $targetSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ),
                                currentColnumber        = $columnsInTargetSektion.length,
                                currentColCSSSuffix     = Math.floor( 100/currentColnumber );

                            // the future columns number is different for the source and target sektions.
                            var nextColNumber;
                            if ( true === args.forTarget ) {
                                  nextColNumber = 12 < ( currentColnumber + 1 ) ? 12 : currentColnumber + 1;
                            } else {
                                  nextColNumber = 1 > ( currentColnumber - 1 ) ? 1 : currentColnumber -1;
                            }

                            // this css suffix is consistent with the one written server side
                            // @see SEK_Front_Render::render() case 'column'
                            var nextColCSSSuffix        = Math.floor( 100/nextColNumber ),
                                current_columns_css_class = 'sek-col-' + currentColCSSSuffix,
                                next_columns_css_class = 'sek-col-' + nextColCSSSuffix,
                                _classes_ = { current : current_columns_css_class , next : next_columns_css_class  };

                            $(this).data('_sortable_columns_css_classes_', _classes_ );
                            return _classes_;
                      },
                      cleanOnStop = function() {
                            $( '[data-sek-level="section"]').find('.sek-sektion-inner').each( function() {
                                    $(this).data( 'sek-is-sender', null ).data('_sortable_columns_css_classes_', null );
                            });
                      };
                  // if ( $sortableCandidate.children('[data-sek-level="column"]').length > 11 ) {
                  //       self.errare('12 COLUMNS');
                  //       return;
                  // }

                  $sortableCandidate.sortable( _.extend( defaults, {
                        //handle : '.sek-move-column, .sek-column-dyn-ui > .sek-dyn-ui-location-type',//@fixes https://github.com/presscustomizr/nimble-builder/issues/153
                        handle : '.sek-move-column',
                        connectWith: ".sek-sektion-inner",
                        over : function( event, ui ) {
                              var $targetSektion          = $(this).closest('[data-sek-level="section"]'),
                                  $columnsInTargetSektion = $targetSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ),
                                  _classes_;

                              if ( true !== $(this).data('sek-is-sender' ) ) {
                                    _classes_ = getCurrentAndNextColNumberClasses.call( $(this) );
                                    if ( ! _.isEmpty( _classes_ ) ) {
                                          $columnsInTargetSektion.each( function() {
                                                $(this).removeClass( _classes_.current ).addClass( _classes_.next );
                                          });
                                    }
                              } else {
                                    _classes_ = getCurrentAndNextColNumberClasses.call( $(this), { forTarget : false } );
                                    if ( ! _.isEmpty( _classes_ ) ) {
                                          $columnsInTargetSektion.each( function() {
                                                $(this).addClass( _classes_.current ).removeClass( _classes_.next );
                                          });
                                    }
                              }
                        },
                        out : function( event, ui ) {
                              var $outedSektion = $(this).closest('[data-sek-level="section"]'),
                                  $columnsInOutedSektion = $outedSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ),
                                  _classes_;
                              if ( true !== $(this).data('sek-is-sender' ) ) {
                                    _classes_ = getCurrentAndNextColNumberClasses.call( $(this) );
                                    if ( ! _.isEmpty( _classes_ ) ) {
                                          $columnsInOutedSektion.each( function() {
                                                $(this).addClass( _classes_.current ).removeClass( _classes_.next );
                                          });
                                    }
                              } else {
                                    _classes_ = getCurrentAndNextColNumberClasses.call( $(this), { forTarget : false } );
                                    if ( ! _.isEmpty( _classes_ ) ) {
                                          $columnsInOutedSektion.each( function() {
                                                $(this).removeClass( _classes_.current ).addClass( _classes_.next );
                                          });
                                    }
                              }
                        },
                        remove : function( event, ui ) {
                              $toLocation = ui.item.closest('[data-sek-level="location"]');
                              to_location = $toLocation.data( 'sek-id');
                              is_global_to_location = true === $toLocation.data('sek-is-global-location');

                              var _isCrossSkope = is_global_from_location !== is_global_to_location,
                                  _isCrossLocation = to_location != from_location,
                                  _isGlobalToGlobal = true === is_global_from_location && true === is_global_to_location;

                              // Not possible to drag from a local location to a global
                              // Not possible to drag from a global header to a global footer
                              if ( _isCrossSkope || ( _isGlobalToGlobal && _isCrossLocation ) ) {
                                    api.preview.send( 'sek-notify', {
                                          message : sekPreviewLocalized.i18n["Moving elements between global and local sections is not allowed."]
                                    });
                                    return false;
                              }

                              $targetSektionCandidate = ui.item.closest('[data-sek-level="section"]');
                              if ( $targetSektionCandidate.length > 0 && $targetSektionCandidate.find('.sek-sektion-inner').first().children('[data-sek-level="column"]').length > 12 ) {
                                    api.preview.send( 'sek-notify', {
                                          message : sekPreviewLocalized.i18n["You've reached the maximum number of columns allowed in this section."]
                                    });
                                    return false;
                              } else {
                                    return true;
                              }
                        },
                        start: function( event, ui ) {
                              // Store the following for the "remove" callback
                              $fromLocation = ui.item.closest('[data-sek-level="location"]');
                              from_location = $fromLocation.data( 'sek-id');
                              is_global_from_location = true === $fromLocation.data('sek-is-global-location');

                              // Always reset startOrder and newOrder
                              startOrder = [];
                              newOrder = [];

                              //$('.sek-column-inner').css( {'min-height' : '20px'});
                              // Set source
                              from_sektion = ui.item.closest('[data-sek-level="section"]').data( 'sek-id');

                              // store the startOrder
                              ui.item.closest('[data-sek-level="section"]').find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).each( function() {
                                    startOrder.push( $(this).data('sek-id') );
                              });

                              $(this).data('sek-is-sender', true );
                              if ( _.isEmpty( startOrder ) ) {
                                    self.errare( 'column sortable => startOrder should not be empty' );
                                    return;
                              }

                              // Print all level ui
                              // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/553
                              self.printAllLevelsUi();
                        },

                        stop : function( event, ui ) {
                              // set destination
                              $targetSektion = ui.item.closest('[data-sek-level="section"]');
                              to_sektion = $targetSektion.data( 'sek-id');
                              $targetSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ).each( function() {
                                    newOrder.push( $(this).data('sek-id') );
                              });

                              var $stopSektion = $(this).closest('[data-sek-level="section"]'),
                                  $columnsInstopSektion = $stopSektion.find('.sek-sektion-inner').first().children( '[data-sek-level="column"]' ),
                                  _classes_;
                              if ( true !== $(this).data('sek-is-sender' ) ) {
                                    _classes_ = getCurrentAndNextColNumberClasses.call( $(this) );
                                    if ( ! _.isEmpty( _classes_ ) ) {
                                          $columnsInstopSektion.each( function() {
                                                $(this).removeClass( _classes_.current ).addClass( _classes_.next );
                                          });
                                    }
                              } else {
                                    _classes_ = getCurrentAndNextColNumberClasses.call( $(this), { forTarget : false } );
                                    if ( ! _.isEmpty( _classes_ ) ) {
                                          $columnsInstopSektion.each( function() {
                                                $(this).addClass( _classes_.current ).removeClass( _classes_.next );
                                          });
                                    }
                              }
                              cleanOnStop();

                              if ( _.isEmpty( newOrder ) ) {
                                    self.errare( 'column sortable =>  newOrder should not be empty' );
                                    return;
                              }

                              // don't send anything if the source and target columns are the same, and the order is unchanged
                              if ( _.isEqual( newOrder, startOrder ) && to_sektion === from_sektion ) {
                                    if ( sekPreviewLocalized.isDevMode ) {
                                          self.errare( 'preview => makeModulesSortableInColumn => start and stop positions are identical' );
                                    }
                                    return;
                              }
                              api.preview.send( 'sek-move', {
                                    id : ui.item.data('sek-id'),
                                    level : 'column',
                                    newOrder : newOrder,
                                    from_sektion : from_sektion,
                                    to_sektion : to_sektion
                              });

                              // inform the parent section that it's been refreshed
                              //=> will be listened to by columns to re-instantiate sortable, resizable
                              $('div[data-sek-id="' + from_sektion + '"]' ).trigger('sek-level-refreshed');
                              if ( from_sektion !== to_sektion ) {
                                  $('div[data-sek-id="' + to_sektion + '"]' ).trigger('sek-level-refreshed');
                              }

                        }
                  }));
            },//self.makeColumnsSortableInSektion










            // Instantiate sortable for a given column Id
            makeModulesSortableInColumn : function( columnId ) {
                  var from_sektion,
                      to_sektion,
                      from_column,
                      to_column,
                      startOrder = [],
                      newOrder = [],
                      $targetSektion,
                      $targetColumn,
                      defaults,
                      $fromLocation,
                      is_global_from_location,
                      $toLocation,
                      is_global_to_location;

                  var self = this;
                  defaults = $.extend( true, {}, self.sortableDefaultParams );
                  // Restrict to the .sek-column-inner for this very column id with first()
                  $( '[data-sek-id="' + columnId + '"]').find('.sek-column-inner').first().sortable( _.extend( defaults, {
                        //handle : '.sek-move-module, .sek-module-dyn-ui > .sek-dyn-ui-location-type .sek-dyn-ui-level-type',//@fixes https://github.com/presscustomizr/nimble-builder/issues/153
                        handle : '.sek-move-module',
                        connectWith: ".sek-column-inner",
                        over : function( event, ui ) {
                              // Hide the module placeholder while overing, when the column is empty
                              // @see css rule .sek-sortable-overing > .sek-no-modules-column { display: none; }
                              $('[data-sek-level="location"]').find('.sek-sortable-overing').each( function() {
                                    $(this).removeClass('sek-sortable-overing');
                              });
                              $( event.target ).addClass('sek-sortable-overing');
                        },
                        remove : function( event, ui ) {
                              $toLocation = ui.item.closest('[data-sek-level="location"]');
                              to_location = $toLocation.data( 'sek-id');
                              is_global_to_location = true === $toLocation.data('sek-is-global-location');

                              var _isCrossSkope = is_global_from_location !== is_global_to_location,
                                  _isCrossLocation = to_location != from_location,
                                  _isGlobalToGlobal = true === is_global_from_location && true === is_global_to_location;

                              // Not possible to drag from a local location to a global
                              // Not possible to drag from a global header to a global footer
                              if ( _isCrossSkope || ( _isGlobalToGlobal && _isCrossLocation ) ) {
                                    api.preview.send( 'sek-notify', {
                                          message : sekPreviewLocalized.i18n["Moving elements between global and local sections is not allowed."]
                                    });
                                    return false;
                              } else {
                                    return true;
                              }
                        },
                        start: function( event, ui ) {
                              // Store the following for the "remove" callback
                              $fromLocation = ui.item.closest('[data-sek-level="location"]');
                              from_location = $fromLocation.data( 'sek-id');
                              is_global_from_location = true === $fromLocation.data('sek-is-global-location');

                              // Always reset startOrder and newOrder
                              startOrder = [];
                              newOrder = [];

                              self.cachedElements.$body.addClass( 'sek-dragging-element' );
                              //$('.sek-column-inner').css( {'min-height' : '20px'});
                              // Set source
                              from_column = ui.item.closest('[data-sek-level="column"]').data( 'sek-id');
                              from_sektion = ui.item.closest('[data-sek-level="section"]').data( 'sek-id');
                              // store the startOrder
                              ui.item.closest('[data-sek-level="column"]').find('.sek-column-inner').first().children( '[data-sek-level="module"]' ).each( function() {
                                    startOrder.push( $(this).data('sek-id') );
                              });
                              if ( _.isEmpty( startOrder ) ) {
                                    self.errare( 'makeModulesSortableInColumn => startOrder should not be empty' );
                                    return;
                              }

                              // Print all level ui
                              // fixes slowness when dragging @see https://github.com/presscustomizr/nimble-builder/issues/521
                              self.printAllLevelsUi();
                        },

                        stop : function( event, ui ) {
                              // set destination
                              $targetColumn = ui.item.closest('[data-sek-level="column"]');
                              to_column = $targetColumn.data( 'sek-id');

                              $targetColumn.find('.sek-column-inner').first().children( '[data-sek-id]' ).each( function() {
                                    newOrder.push( $(this).data('sek-id') );
                              });
                              if ( _.isEmpty( newOrder ) ) {
                                    self.errare( 'makeModulesSortableInColumn => newOrder should not be empty' );
                                    return;
                              }

                              // don't send anything if the source and target columns are the same, and the order is unchanged
                              if ( _.isEqual( newOrder, startOrder ) && to_column === from_column ) {
                                    self.errare( 'preview => makeModulesSortableInColumn => start and stop positions are identical' );
                                    return;
                              }
                              api.preview.send( 'sek-move', {
                                    id : ui.item.data('sek-id'),
                                    level : 'module',
                                    newOrder : newOrder,
                                    from_column : from_column,
                                    to_column : to_column,
                                    from_sektion : from_sektion,
                                    to_sektion : ui.item.closest('[data-sek-level="section"]').data( 'sek-id')
                              });

                              // Clean some css classes
                              self.cachedElements.$body.removeClass( 'sek-dragging-element' );
                        }
                  }));

                  $( '[data-sek-id="' + columnId + '"]').addClass('sek-module-sortable-setup');
            },//makeModulesSortableInColumn
      });//$.extend()
})( wp.customize, jQuery, _ );//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            // Fired on Dom Ready, in ::initialize()
            setupResizable : function() {
                  var self = this;
                  $('.sektion-wrapper').find( 'div[data-sek-level="section"]' ).each( function() {
                        self.maybeMakeColumnResizableInSektion.call( this );
                  });
                  // Delegate instantiation when a level markup is refreshed
                  // Let the event bubble up to the location, and then visit all children section to maybe re-instantiate resizable
                  // @fixes https://github.com/presscustomizr/nimble-builder/issues/165
                  self.cachedElements.$body.on(
                        'sek-level-refreshed sek-modules-refreshed sek-columns-refreshed sek-section-added sek-location-refreshed',
                        '[data-sek-level="location"]',
                        function( evt ) {
                              $(this).find('[data-sek-level="section"]').each( function() {
                                    self.maybeMakeColumnResizableInSektion.call( this );
                              });
                        }
                  );
                  return this;
            },//setupResizable()

            // this is the parent section jQuery object
            maybeMakeColumnResizableInSektion : function() {
                  var self = this,
                      $parentSektion,
                      parentSektionId,
                      parentSektionWidth,
                      $resizedColumn,
                      resizedColumnWidthInPercent,
                      //calculate the number of column in this section, excluding the columns inside nested sections if any
                      colNumber,
                      $sisterColumn,
                      isLastColumn;

                  // We won't fire resizable for single column sektions
                  var $directColumnChildren = $(this).find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' );
                  if ( 2 > $directColumnChildren.length )
                    return;

                  var $lastCol = $(this).find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).last();

                  $directColumnChildren.not($lastCol).each( function() {
                        $(this).resizable({
                                // handles: { 'e': '.ui-resizable-e', 'w': '.ui-resizable-w' },
                                resize : function( event, ui ) {
                                      $('.sektion-wrapper').data('sek-resizing-columns', true );
                                },
                                start : function( event, ui ) {
                                      $parentSektion = ui.element.closest('div[data-sek-level="section"]');
                                      parentSektionId = $parentSektion.data('sek-id');
                                      parentSektionWidth = $parentSektion.find('.sek-sektion-inner')[0].getBoundingClientRect().width;
                                      //calculate the number of column in this section, excluding the columns inside nested sections if any
                                      colNumber = $parentSektion.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length;

                                      // Resizable should not have been instantiated anyway
                                      if ( 2 > colNumber )
                                        return;

                                      $resizedColumn = ui.element.closest('div[data-sek-level="column"]');
                                      if ( 1 > $resizedColumn.length ) {
                                          throw new Error( 'ERROR => resizable => No valid level dom element found' );
                                      }

                                      isLastColumn = $resizedColumn.index() + 1 == colNumber;

                                      // Assomption : RTL user. LTR should be implemented also.
                                      // If parent section has at least 2 columns, the sister column is the one on the right if not in last position. On the left if last.
                                      $sisterColumn = isLastColumn ? $resizedColumn.prev() : $resizedColumn.next();

                                      // Implement a global state value()
                                      $('.sektion-wrapper').data('sek-resizing-columns', true );

                                      // auto set to false after a moment.
                                      _.delay( function() {
                                            $('.sektion-wrapper').data('sek-resizing-columns', false );
                                      }, 3000 );
                                },
                                stop : function( event, ui ) {
                                      // console.log('ON RESIZE STOP', event, ui, $resizedColumn );
                                      // console.log('colNumber', colNumber, '[data-sek-id="' + parentSektionId +'"] > .sek-sektion-inner' );
                                      // Skip the case when there's only one column
                                      // Resizable should not have been instantiated anyway
                                      if ( 2 > colNumber )
                                        return;

                                      if ( 1 > $resizedColumn.length ) {
                                          throw new Error( 'ERROR => resizable => No valid level dom element found' );
                                      }

                                      // Reset the automatic inline style
                                      $resizedColumn.css({
                                            width : '',
                                            height: ''
                                      });

                                      resizedColumnWidthInPercent = ( ( parseFloat( ui.size.width ) / parseFloat( parentSektionWidth ) ) * 100 ).toFixed(3);

                                      api.preview.send( 'sek-resize-columns', {
                                            action : 'sek-resize-columns',
                                            level : $resizedColumn.data( 'sek-level' ),
                                            in_sektion : $parentSektion.data('sek-id'),
                                            id : $resizedColumn.data('sek-id'),

                                            resized_column : $resizedColumn.data('sek-id'),
                                            sister_column : $sisterColumn.data('sek-id'),

                                            resizedColumnWidthInPercent : resizedColumnWidthInPercent,

                                            col_number : colNumber
                                      });

                                      $('.sektion-wrapper').data('sek-resizing-columns', false );
                                },
                                helper: "ui-resizable-helper",
                                handles :'e'
                        });//$(this).resizable({})

                        // Add a resizable icon in the handle
                        // revealed on section hovering @see sek-preview.css
                        var $column = $(this);
                        _.delay( function() {
                              var $resizableHandle = $column.find('.ui-resizable-handle');
                              if ( $resizableHandle.find('.fa-arrows-alt-h').length < 1 ) {
                                    $column.find('.ui-resizable-handle').append('<i class="fas fa-arrows-alt-h"></i>');
                              }
                        }, 500 );

                  });//$directColumnChildren.each()
            }

      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            printLevelUI : function( $el ) {
                  var self = this;
                  var tmpl,
                      level,
                      params,
                      $levelEl;
                  if ( _.isUndefined( $el ) || $el.length < 1 ) {
                        self.errare('sekPreview::printeLevelUI => invalid level element => ', $el );
                  }
                  level = $el.data('sek-level');
                  // we don't print a ui for locations
                  if ( 'location' == level )
                    return;

                  $levelEl = $el;

                  // stop here if the .sek-dyn-ui-wrapper is already printed for this level AND is not being faded out.
                  // if ( $levelEl.children('.sek-dyn-ui-wrapper').length > 0 && true !== $levelEl.data( 'UIisFadingOut' ) )
                  //   return;

                  if ( $levelEl.children('.sek-dyn-ui-wrapper').length > 0 )
                    return;

                  var levelRect = $levelEl[0].getBoundingClientRect(),
                      levelType = $levelEl.data('sek-level');

                  // Adapt the size of the UI icons and text in narrow containers
                  $levelEl.toggleClass( 'sek-shrink-my-ui', levelRect.width && levelRect.width < ( 'section' === levelType ? 350 : ( 'column' === levelType ? 300 : 200 ) ) );

                  params = {
                        id : $levelEl.data('sek-id'),
                        level : levelType
                  };
                  switch ( level ) {
                        case 'section' :
                              //$el = $('.sektion-wrapper').find('[data-sek-id="' + id + '"]');
                              // Let's prepare the is_last and is_first params that we are going to send to the js template
                              // => this will determine which up / down arrow to display in the UI menu for moving a section
                              var $parentLocation = $levelEl.closest('div[data-sek-level="location"]'),
                                  $parentColumn = $levelEl.closest('div[data-sek-level="column"]'),
                                  $sectionCollection,
                                  _is_last_section,
                                  _is_first_section,
                                  _is_nested =  true === $levelEl.data('sek-is-nested');

                              // information about first and last section is used when rendering the up / down moving arrows
                              if ( _is_nested ) {
                                    if ( $parentColumn.length > 0 ) {
                                          $sectionCollection = $parentColumn.find('.sek-column-inner').first().children( 'div[data-sek-level]' );
                                          _is_last_section = $sectionCollection.length == $levelEl.index() + 1;
                                          _is_first_section = 0 === $levelEl.index();
                                    }
                              } else {
                                    if ( $parentLocation.length > 0 ) {
                                          $sectionCollection = $parentLocation.children( 'div[data-sek-level="section"]' );
                                          _is_last_section = $sectionCollection.length == $levelEl.index() + 1;
                                          _is_first_section = 0 === $levelEl.index();
                                    }
                              }

                              params = _.extend( params, {
                                    is_nested : _is_nested,
                                    can_have_more_columns : $levelEl.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length < 12,
                                    is_global_location : true === $parentLocation.data('sek-is-global-location'),
                                    is_last_section_in_parent : _is_last_section,
                                    is_first_section_in_parent : _is_first_section,
                                    is_header_location : true === $parentLocation.data('sek-is-header-location'),
                                    is_footer_location : true === $parentLocation.data('sek-is-footer-location')
                              });
                        break;
                        case 'column' :
                              var $parent_sektion = $levelEl.closest('div[data-sek-level="section"]');
                              params = _.extend( params, {
                                    parent_can_have_more_columns : $parent_sektion.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length < 12,
                                    parent_is_single_column : $parent_sektion.find('.sek-sektion-inner').first().children( 'div[data-sek-level="column"]' ).length < 2,
                                    parent_is_last_allowed_nested : true === $parent_sektion.data('sek-is-nested'),
                                    has_nested_section : $levelEl.find('[data-sek-is-nested="true"]').length > 0
                              });
                        break;
                        case 'module' :
                              var module_name = self.getRegisteredModuleProperty( $levelEl.data('sek-module-type'), 'name' );
                              params = _.extend( params, {
                                    module_name : 'not_set' != module_name ? module_name : ''
                              });
                        break;
                  }
                  // don't display the column and module ui when resizing columns
                  if ( true === $('.sektion-wrapper').data('sek-resizing-columns') && _.contains( ['column', 'module'], level ) ) {
                        return;
                  }

                  tmpl = self.parseTemplate( '#sek-dyn-ui-tmpl-' + level );
                  $.when( $levelEl.prepend( tmpl( params ) ) ).done( function() {
                        $levelEl.find('.sek-dyn-ui-wrapper').stop( true, true ).fadeIn( {
                              duration : 150,
                              complete : function() {}
                        } );
                  });
            },//printLevelUI()












            // Fired on Dom Ready, in ::initialize()
            setupUiHoverVisibility : function() {
                  var self = this;
                  var tmpl,
                      level,
                      params,
                      $levelEl;

                  var removeLevelUI = function() {
                        $levelEl = $(this);
                        if ( $levelEl.children('.sek-dyn-ui-wrapper').length < 1 )
                          return;
                        // when PHP constant NIMBLE_IS_PREVIEW_UI_DEBUG_MODE is true, the levels UI in the preview are not being auto removed, so we can inspect the markup and CSS
                        if ( sekPreviewLocalized.isPreviewUIDebugMode || self.isDraggingElement )
                          return;

                        //stores the status of 200 ms fading out. => will let us know if we can print again when moving the mouse fast back and forth between two levels.
                        $levelEl.data( 'UIisFadingOut', true );//<= we need to store a fadingOut status to not miss a re-print in case of a fast moving mouse

                        $levelEl.children('.sek-dyn-ui-wrapper').stop( true, true ).fadeOut( {
                              duration : 150,
                              complete : function() {
                                    $(this).remove();
                                    $levelEl.data( 'UIisFadingOut', false );
                              }
                        });
                  };//removeLevelUI


                  var removeAddContentButtons = function() {
                        self.cachedElements.$body.stop( true, true ).find('.sek-add-content-button').each( function() {
                              $(this).fadeOut( {
                                    duration : 200,
                                    complete : function() { $(this).remove(); }
                              });
                        });
                  };

                  // clean add content buttons on section insertion
                  // solves the problem of button not being rendered in some case
                  // @see https://github.com/presscustomizr/nimble-builder/issues/545
                  self.cachedElements.$body.on( 'sek-location-refreshed sek-section-added sek-level-refreshed', '[data-sek-level="location"]', function( evt, params  ) {
                        removeAddContentButtons();
                  });


                  // UI MENU
                  // React to click and mouse actions. Uses delegation.
                  // + schedule auto collapse after n seconds of ui inactivity
                  var autoCollapser = function() {
                        var $menu = $(this);
                        clearTimeout( $menu.data('_toggle_ui_menu_') );
                        $menu.data( '_toggle_ui_menu_', setTimeout(function() {
                              setClassesAndVisibilities.call( $menu );
                        }, 10000 ) );
                      },
                      setClassesAndVisibilities = function( expand ) {
                            var $menu = $(this),
                                $levelTypeAndMenuWrapper = $(this).closest('.sek-dyn-ui-location-type'),
                                $dynUiWrapper = $menu.closest( '.sek-dyn-ui-wrapper').find('.sek-dyn-ui-inner');
                            if ( true === expand ) {
                                  $menu.removeClass('sek-collapsed');
                                  $dynUiWrapper.addClass('sek-is-expanded');
                                  $levelTypeAndMenuWrapper.hide();
                            } else {
                                  $menu.addClass('sek-collapsed');
                                  $dynUiWrapper.removeClass('sek-is-expanded');
                                  $levelTypeAndMenuWrapper.show();
                            }
                      };
                  self.cachedElements.$body.on( 'click', '.sek-dyn-ui-location-inner', function( evt )  {
                        var $menu = $(this).find('.sek-dyn-ui-hamb-menu-wrapper'),
                            $parentSection = $(this).closest('[data-sek-level="section"]');
                        // Close all other expanded ui menu of the column
                        $parentSection.find('.sek-dyn-ui-hamb-menu-wrapper').each( function() {
                              setClassesAndVisibilities.call( $(this) );
                        });
                        // expand the ui menu of the clicked level
                        setClassesAndVisibilities.call( $menu, true );
                        // schedule autocollapsing
                        autoCollapser.call( $menu );
                  });
                  // maintain expanded as long as it's being hovered
                  self.cachedElements.$body.on( 'mouseenter mouseover mouseleave', '.sek-dyn-ui-wrapper', _.throttle( function( evt )  {
                        var $menu = $(this).find('.sek-dyn-ui-hamb-menu-wrapper');
                        if ( _.isUndefined( $menu.data('_toggle_ui_menu_') ) || $menu.hasClass('sek-collapsed') )
                          return;
                        if ( $menu.length > 0 ) {
                              autoCollapser.call( $menu );
                        }
                  }, 50 ) );

                  // minimize on click
                  // solves the problem of a level ui on top of another one
                  // @ee https://github.com/presscustomizr/nimble-builder/issues/138
                  self.cachedElements.$body.on( 'click', '.sek-minimize-ui', function( evt )  {
                        $(this).closest('.sek-dyn-ui-location-type').slideToggle('fast');
                  });






                  // Ui for the WP content.
                  // Generated when is_singular() only
                  // @see SEK_Front::render()
                  var $wpContentEl;
                  self.cachedElements.$body.on( 'mouseenter', '.sek-wp-content-wrapper', function( evt ) {
                        $wpContentEl = $(this);
                        // stop here if the .sek-dyn-ui-wrapper is already printed for this level AND is not being faded out.
                        if ( $wpContentEl.children('.sek-dyn-ui-wrapper').length > 0 && true !== $wpContentEl.data( 'UIisFadingOut' ) )
                          return;

                        tmpl = self.parseTemplate( '#sek-dyn-ui-tmpl-wp-content');
                        $.when( $wpContentEl.prepend( tmpl( {} ) ) ).done( function() {
                              $wpContentEl.find('.sek-dyn-ui-wrapper').stop( true, true ).fadeIn( {
                                    duration : 150,
                                    complete : function() {}
                              } );
                        });
                  }).on( 'mouseleave', '.sek-wp-content-wrapper', function( evt ) {
                        $(this).data( 'UIisFadingOut', true );//<= we need to store a fadingOut status to not miss a re-print in case of a fast moving mouse
                        $wpContentEl = $(this);
                        $(this).children('.sek-dyn-ui-wrapper').stop( true, true ).fadeOut( {
                              duration : 150,
                              complete : function() {
                                    $(this).remove();
                                    $wpContentEl.data( 'UIisFadingOut', false );
                              }
                        });
                  });



                  // ADD SECTION BUTTONS
                  // Add content button between sections
                  // <script type="text/html" id="sek-tmpl-add-content-button">
                  //     <div class="sek-add-content-button <# if ( data.is_last ) { #>is_last<# } #>">
                  //       <div class="sek-add-content-button-wrapper">
                  //         <button data-sek-click-on="add-content" data-sek-add="section" class="sek-add-content-btn" style="--sek-add-content-btn-width:60px;">
                  //           <span title="<?php _e('Add Content', 'text_doma' ); ?>" class="sek-click-on-button-icon fas fa-plus-circle sek-click-on"></span><span class="action-button-text"><?php _e('Add Content', 'text_doma' ); ?></span>
                  //         </button>
                  //       </div>
                  //     </div>
                  // </script>
                  // fired on mousemove and scroll, every 50ms
                  var _printAddContentButtons = function() {
                        var _location, _is_global_location;
                        self.cachedElements.$body.find( 'div[data-sek-level="location"]' ).each( function() {
                              $sectionCollection = $(this).children( 'div[data-sek-level="section"]' );
                              tmpl = self.parseTemplate( '#sek-tmpl-add-content-button' );
                              var $btn_el;
                              _location = $(this).data('sek-id');
                              _is_global_location = true === $(this).data('sek-is-global-location');

                              // nested sections are not included
                              $sectionCollection.each( function() {
                                    if ( $(this).find('.sek-add-content-button').length > 0 )
                                      return;

                                    $.when( $(this).prepend( tmpl({
                                          location : _location,
                                          is_global_location : _is_global_location
                                    }) ) ).done( function() {
                                          $btn_el = $(this).find('.sek-add-content-button');
                                          if ( $(this).data('sek-id') ) {
                                                $btn_el.attr('data-sek-before-section', $(this).data('sek-id') );//Will be used to insert the section at the right place
                                          }
                                          $btn_el.fadeIn( 300 );
                                    });
                                    //console.log('$sectionCollection.length', $sectionCollection.length, $(this).index() + 1 );
                                    //if is last section, append also
                                    //console.log('IS LAST ? => ', $sectionCollection.length, $(this).index() );
                                    if ( $sectionCollection.length == $(this).index() + 1 ) {
                                          $.when( $(this).append( tmpl({
                                                is_last : true,
                                                location : _location,
                                                is_global_location : _is_global_location
                                          }) ) ).done( function() {
                                                $btn_el = $(this).find('.sek-add-content-button').last();
                                                if ( $(this).data('sek-id') ) {
                                                      $btn_el.attr('data-sek-after-section', $(this).data('sek-id') );//Will be used to insert the section at the right place
                                                }
                                                $btn_el.fadeIn( 300 );
                                          });
                                    }
                              });//$sectionCollection.each( function() )
                        });//$( 'div[data-sek-level="location"]' ).each( function() {})



                        // .sek-empty-location-placeholder container is printed when the location has no section yet in its collection
                        $('.sek-empty-location-placeholder').each( function() {
                              if ( $(this).find('.sek-add-content-button').length > 0 )
                                return;

                              _location = $(this).closest( 'div[data-sek-level="location"]' ).data('sek-id');
                              _is_global_location = true === $(this).closest( 'div[data-sek-level="location"]' ).data('sek-is-global-location');

                              $.when( $(this).append( tmpl({
                                          location : _location,
                                          is_global_location : _is_global_location
                              } ) ) ).done( function() {
                                    $btn_el = $(this).find('.sek-add-content-button');
                                    $btn_el.attr('data-sek-is-first-section', true );
                                    $btn_el.fadeIn( 300 );
                              });
                        });
                  };//_printAddContentButtons

                  // fired on mousemove and scroll, every 50ms
                  var _sniffAndRevealButtons = function( position ) {
                        self.cachedElements.$body.find('.sek-add-content-button').each( function() {
                              var btnWrapperRect = $(this)[0].getBoundingClientRect(),
                                  yPos = position.y,
                                  xPos = position.x,
                                  isCloseThreshold = 40,
                                  mouseToBottom = Math.abs( yPos - btnWrapperRect.bottom ),
                                  mouseToTop = Math.abs( btnWrapperRect.top - yPos ),
                                  mouseToRight = xPos - btnWrapperRect.right,
                                  mouseToLeft = btnWrapperRect.left - xPos,
                                  isCloseVertically = ( mouseToBottom < isCloseThreshold ) || ( mouseToTop < isCloseThreshold ),
                                  isCloseHorizontally =  ( mouseToRight > 0 && mouseToRight < isCloseThreshold ) || ( mouseToLeft > 0 && mouseToLeft < isCloseThreshold ),
                                  isInHorizontally = xPos <= btnWrapperRect.right && btnWrapperRect.left <= xPos,
                                  isInVertically = yPos >= btnWrapperRect.top && btnWrapperRect.bottom >= yPos;

                              // var html = '';
                              // html += ' | mouseToBottom : ' + mouseToBottom + ' | mouseToTop : ' + mouseToTop;
                              // html += ' isCloseVertically : ' + isCloseVertically + ' | isInVertically : ' + isInVertically;
                              // $(this).html( '<span style="font-size:12px">' + html + '</span>');

                              $(this).toggleClass(
                                    'sek-mouse-is-close',
                                    ( isCloseVertically || isInVertically ) && ( isCloseHorizontally || isInHorizontally )
                              );
                        });
                  };

                  // Print / remove ui according to the mouse position
                  // The mouse position is provided by self.mouseMovedRecently()
                  // If the ui is expanded, remove after a delay to let user access all ui buttons, even those outside the $level.
                  // => the ui can be "outside" ( <=> out vertically and horizontally ) when columns are narrow.
                  var _sniffLevelsAndPrintUI = function( position, $candidateForRemoval ) {
                        var collectionOfLevelsToWalk = [], sniffCase;
                        if ( _.isUndefined( $candidateForRemoval ) || $candidateForRemoval.length < 1 ) {
                              // data-sek-preview-level-guid has been introduced in https://github.com/presscustomizr/nimble-builder/issues/494
                              // to fix a wrong UI generation leading to user unable to edit content
                              self.cachedElements.$body.find('[data-sek-level][data-sek-preview-level-guid="' + sekPreviewLocalized.previewLevelGuid +'"]').each( function() {
                                    collectionOfLevelsToWalk.push( $(this) );
                              });
                              sniffCase = 'printOrScheduleRemoval';
                        } else {
                              collectionOfLevelsToWalk.push( $candidateForRemoval );
                              sniffCase = 'mayBeRemove';
                        }

                        _.each( collectionOfLevelsToWalk, function( $levelToWalk ) {
                              var levelWrapperRect = $levelToWalk[0].getBoundingClientRect(),
                                isInHorizontally = position.x <= levelWrapperRect.right && levelWrapperRect.left <= position.x,
                                isInVertically = position.y >= levelWrapperRect.top && levelWrapperRect.bottom >= position.y,
                                $levelEl = $levelToWalk;

                              switch( sniffCase ) {
                                    case 'mayBeRemove' :
                                          $levelEl.data('sek-ui-removal-scheduled', false );
                                          if ( ! isInHorizontally || ! isInVertically ) {
                                                removeLevelUI.call( $levelEl );
                                          }
                                    break;
                                    case 'printOrScheduleRemoval' :
                                          if ( isInHorizontally && isInVertically ) {
                                                $levelEl.data('sek-ui-removal-scheduled', false );
                                                self.printLevelUI( $levelEl );
                                          } else {
                                                if ( true !== $levelEl.data('sek-ui-removal-scheduled') ) {
                                                      $levelEl.data('sek-ui-removal-scheduled', true );
                                                      if ( $levelEl.children('.sek-dyn-ui-wrapper').find('.sek-is-expanded').length < 1 ) {
                                                            _sniffLevelsAndPrintUI( position, $levelEl );
                                                      } else {
                                                            _.delay( function() {
                                                                  if ( true === $levelEl.data('sek-ui-removal-scheduled') ) {
                                                                        // using the latest self.mouseMovedRecently(), instead of the initial "position" param, makes sure we don't miss the latest mouse movements
                                                                        _sniffLevelsAndPrintUI( self.mouseMovedRecently(), $levelEl );
                                                                  }
                                                            }, 3500 );
                                                      }
                                                }
                                          }
                                    break;
                              }
                        });//collectionOfLevelsToWalk.each
                  };


                  // SCHEDULE
                  // - the printing / removal of the add content button
                  // - the printing of the level's UI
                  self.mouseMovedRecently = new api.Value( {} );
                  self.mouseMovedRecently.bind( function( position ) {
                        if ( ! _.isEmpty( position) ) {
                              // print the buttons ( display:none)
                              _printAddContentButtons();
                              // sniff sections around pointer and reveal add content button for the collection of candidates
                              _sniffAndRevealButtons( position );
                              // sniff levels and print UI
                              _sniffLevelsAndPrintUI( position );
                        } else {
                              // when PHP constant NIMBLE_IS_PREVIEW_UI_DEBUG_MODE is true, the levels UI in the preview are not being auto removed, so we can inspect the markup and CSS
                              if ( ! sekPreviewLocalized.isPreviewUIDebugMode ) {
                                    // Mouse didn't move recently?
                                    // => remove all UIs
                                    // 1) add content buttons
                                    removeAddContentButtons();

                                    // 2) level UI's
                                    self.cachedElements.$body.stop( true, true ).find('[data-sek-level]').each( function() {
                                          // preserve if the ui menu is expanded, otherwise remove
                                          if ( $(this).children('.sek-dyn-ui-wrapper').find('.sek-is-expanded').length < 1 ) {
                                                removeLevelUI.call( $(this) );
                                          }
                                    });
                              }
                        }
                  });
                  // @return void()
                  var resetMouseMoveTrack = function() {
                        clearTimeout( self.cachedElements.$window.data('_scroll_move_timer_') );
                        self.mouseMovedRecently.set({});
                  };

                  self.cachedElements.$window.on( 'mousemove scroll', _.throttle( function( evt ) {
                        self.mouseMovedRecently( { x : evt.clientX, y : evt.clientY } );
                        clearTimeout( self.cachedElements.$window.data('_scroll_move_timer_') );
                        self.cachedElements.$window.data('_scroll_move_timer_', setTimeout(function() {
                              self.mouseMovedRecently.set({});
                        }, 4000 ) );
                  }, 50 ) );

                  // Always reset the move timer and the mouseMove Value when
                  // - a dragging action is started
                  // - a section is added <= fixes the addition of multiple "Add Section" button in the same location
                  api.preview.bind( 'sek-drag-start', function() {
                        resetMouseMoveTrack();
                  });

                  self.cachedElements.$body.on( 'sek-section-added', '[data-sek-level="location"]', function( evt, params  ) {
                        resetMouseMoveTrack();
                  });

                  return this;
            }//setupUiHoverVisibility

      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            //Fired on Dom ready in initialize()
            scheduleUiClickReactions : function() {
                  var self = this;

                  var _reactOnClick = function( evt ) {
                        // First clean any currently highlighted target drop zone
                        // implemented for double-click insertion https://github.com/presscustomizr/nimble-builder/issues/317
                        api.preview.send( 'sek-clean-target-drop-zone' );

                        var clickedOn = 'inactiveZone',
                              $el = $(evt.target),
                              $hookLocation = $el.closest('[data-sek-level="location"][data-sek-preview-level-guid="' + sekPreviewLocalized.previewLevelGuid +'"]'),
                              $closestLevelWrapper = $el.closest('[data-sek-preview-level-guid="' + sekPreviewLocalized.previewLevelGuid +'"]'),
                              $closestActionIcon = $el.closest('[data-sek-click-on]'),
                              _action,
                              _location_id = $hookLocation.data('sek-id'),
                              _level = $closestLevelWrapper.data('sek-level'),
                              _id = $closestLevelWrapper.data('sek-id');

                        if ( 'add-content' == $el.data('sek-click-on') || ( $el.closest('[data-sek-click-on]').length > 0 && 'add-content' == $el.closest('[data-sek-click-on]').data('sek-click-on') ) ) {
                              clickedOn = 'addContentButton';
                        } else if ( ! _.isEmpty( $el.data( 'sek-click-on' ) ) || $closestActionIcon.length > 0 ) {
                              clickedOn = 'UIIcon';
                        } else if ( 'module' == $closestLevelWrapper.data('sek-level') ) {
                              clickedOn = 'moduleWrapper';
                        } else if ( 'column' == $closestLevelWrapper.data('sek-level') && true === $closestLevelWrapper.data('sek-no-modules') ) {
                              clickedOn = 'noModulesColumn';
                        } else if ( $el.hasClass('sek-to-json') ) {
                              clickedOn = 'sekToJson';
                        } else if ( 'column' == $closestLevelWrapper.data('sek-level') ) {
                              clickedOn = 'columnOutsideModules';
                        } else if ( 'section' == $closestLevelWrapper.data('sek-level') ) {
                              clickedOn = 'sectionOutsideColumns';
                        } else if ( ! _.isEmpty( $el.data( 'sek-add' ) ) ) {
                              clickedOn = 'addSektion';
                        } else if ( $el.hasClass('sek-wp-content-wrapper') || $el.hasClass( 'sek-wp-content-dyn-ui') ) {
                              clickedOn = 'wpContent';
                        } else if ( $el.hasClass('sek-edit-wp-content') ) {
                              clickedOn = 'editWpContent';
                        } else {
                              clickedOn = 'inactiveZone';
                        }

                        if ( $hookLocation.length > 0 && _.isEmpty( _location_id ) ) {
                              self.errare( '::scheduleUiClickReactions => error location id can not be empty' );
                        }

                        //console.log('ALORS CLICKED ?', clickedOn, $el );

                        switch( clickedOn ) {
                              case 'addContentButton' :
                                    var is_first_section = true === $el.closest('[data-sek-is-first-section]').data('sek-is-first-section');

                                    api.preview.send( 'sek-add-section', {
                                          location : _location_id,
                                          level : 'section',
                                          before_section : $el.closest('[data-sek-before-section]').data('sek-before-section'),
                                          after_section : $el.closest('[data-sek-after-section]').data('sek-after-section'),
                                          is_first_section : is_first_section,
                                          send_to_preview : ! is_first_section
                                    });
                              break;
                              case 'UIIcon' :
                                    if ( 1 > $closestLevelWrapper.length ) {
                                          throw new Error( 'ERROR => sek-front-preview => No valid level dom element found' );
                                    }
                                    _action = $el.closest('[data-sek-click-on]').data('sek-click-on');

                                    if ( _.isEmpty( _action ) ) {
                                          throw new Error( 'Invalid action' );
                                    }
                                    if ( _.isEmpty( _level ) || _.isEmpty( _id ) ) {
                                          throw new Error( 'ERROR => sek-front-preview => No valid level id found' );
                                    }
                                    self._send_( $el, {
                                          action : _action,
                                          location : _location_id,
                                          level : _level,
                                          id : _id,
                                          was_triggered : false //<= indicates that the user clicked.
                                    });
                              break;
                              case 'moduleWrapper' :
                                    // stop here if the ui icons block was clicked
                                    if ( $el.parent('.sek-dyn-ui-icons').length > 0 )
                                          return;

                                    self._send_( $el, {
                                          action : 'edit-module',
                                          level : _level,
                                          id : _id
                                    });
                              break;
                              case 'noModulesColumn' :
                                    // stop here if the ui icons block was clicked
                                    if ( $el.parent('.sek-dyn-ui-icons').length > 0 )
                                          return;

                                    self._send_( $el, {
                                          action : 'edit-options',
                                          location : _location_id,
                                          level : _level,
                                          id : _id
                                    });
                                    //self._send_( $el, { action : 'pick-content', content_type : 'module', level : _level , id : _id } );
                              break;
                              case 'columnOutsideModules' :
                              case 'sectionOutsideColumns' :
                                    self._send_( $el, {
                                          action : 'edit-options',
                                          location : _location_id,
                                          level : _level,
                                          id : _id
                                    });
                              break;
                              case 'addSektion' :
                                    api.preview.send( 'sek-add-section', {
                                          location : _location_id,
                                          level : $el.data('sek-add')
                                    });
                              break;
                              case 'sekToJson' :
                                    api.preview.send( 'sek-to-json', { id : _id } );
                              break;
                              case 'wpContent' :
                                    api.preview.send( 'sek-notify', {
                                          type : 'info',
                                          duration : 8000,
                                          message : sekPreviewLocalized.i18n['This content has been created with the WordPress editor.']
                                    });
                              break;
                              case 'editWpContent' :
                                    // note : the edit url is printed as a data attribute to prevent being automatically parsed by wp when customizing and turned into a changeset url
                                    var edit_url = $el.closest('[data-sek-wp-edit-link]').data('sek-wp-edit-link');
                                    if ( ! _.isEmpty( edit_url ) ) {
                                          window.open( edit_url,'_blank' );
                                    }

                              break;
                              case 'inactiveZone' :
                                    api.preview.send( 'sek-click-on-inactive-zone');//<= for example, collapses the tinyMce editor if expanded
                                    //self._send_( $el, { action : 'pick-content' } );
                              break;
                        }
                  };//_reactOnClick()

                  self.cachedElements.$body.on('click',_reactOnClick );//$('body').on('click', function( evt ) {}

            },//scheduleUserReactions()


            _send_ : function( $el, params ) {
                  //console.log('IN _send_', $el, params );
                  var clonedParams = $.extend( true, {}, params ),
                      syncedTinyMceInputId = '',
                      $moduleWrapper = $el.closest('div[data-sek-level="module"]'),
                      _module_type_ = 'module' === params.level ? $moduleWrapper.data( 'sek-module-type') : '';

                  if ( 'module' === params.level ) {
                        if ( 'czr_tiny_mce_editor_module' === _module_type_ ) {
                              syncedTinyMceInputId = $moduleWrapper.find('div[data-sek-input-id]').length > 0 ? $moduleWrapper.find('div[data-sek-input-id]').data('sek-input-id') : '';
                        }
                  }
                  api.preview.send( 'sek-' + params.action, _.extend( {
                        location : params.location,
                        level : params.level,
                        id : params.id,
                        content_type : $el.data( 'sek-content-type'),
                        module_type : _module_type_,
                        in_column : $el.closest('div[data-sek-level="column"]').length > 0 ? $el.closest('div[data-sek-level="column"]').data( 'sek-id') : '',
                        in_sektion : $el.closest('div[data-sek-level="section"]').length > 0 ? $el.closest('div[data-sek-level="section"]').data( 'sek-id') : '',
                        clicked_input_type : $el.closest('div[data-sek-input-type]').length > 0 ? $el.closest('div[data-sek-input-type]').data('sek-input-type') : '',
                        clicked_input_id : $el.closest('div[data-sek-input-id]').length > 0 ? $el.closest('div[data-sek-input-id]').data('sek-input-id') : '',
                        was_triggered : params.was_triggered,
                        // the syncedTinyMceInputId is used in the control::setupTinyMceEditor()
                        // Fixes : https://github.com/presscustomizr/nimble-builder/issues/251
                        syncedTinyMceInputId : syncedTinyMceInputId
                  }, clonedParams ) );
            }
      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            //Fired in ::initialize()
            setupLoader : function() {
                  var self = this;
                  // Cache our loader
                  this._css_loader_html = '<div class="sek-css-loader sek-mr-loader" style="display:none"><div></div><div></div><div></div></div>';

                  // Loader Cleaning <= the element printed when refreshing a level
                  // @see ::mayBePrintLoader
                  self.cachedElements.$body.on([
                        'sek-modules-refreshed',
                        'sek-columns-refreshed',
                        'sek-section-added',
                        'sek-level-refreshed',
                        'sek-stylesheet-refreshed',
                        'sek-ajax-error'
                  ].join(' '), function( evt ) {
                        self.cleanLoader();
                  });
            },

            // @return void()
            // Insert a clone ( same dimensions ) div element of a level currently being refreshed, including a centered loading animation
            // + adds a .sek-refreshing css class to the element being refreshed
            //
            // Invoked when
            // - user click on an icon action in the preview that trigger a partial reflush of the DOM. For example, adding a column, duplicating a module, etc.
            // - a module / section is dropped in the preview
            // - a module is being edited
            // - a column is resized
            // - on 'sek-maybe-print-loader'
            // @params {
            //    element : $(),
            //    action : '',
            //    level,
            //    loader_located_in_level_id,
            //    fullPageLoader: false,
            //    duration:4000
            // }
            mayBePrintLoader : function( params ) {
                  params = _.isObject( params ) ? params : {};
                  var self = this,
                      levelIdForTheLoader = params.loader_located_in_level_id;

                  if ( ! _.isEmpty( levelIdForTheLoader ) ) {
                        var $levelElementForTheLoader = $('[data-sek-id="' + levelIdForTheLoader +'"]');
                        if ( $levelElementForTheLoader.length > 0 && 1 > $('.sek-level-clone ').length ) {
                              $levelClone = $('<div>', { class : 'sek-level-clone' });
                              // blur all children levels
                              $levelElementForTheLoader.find('[data-sek-level]').each( function() {
                                    $(this).addClass('sek-refreshing');
                              });

                              // print the absolute positionned clone on top
                              $levelElementForTheLoader.prepend( $levelClone );
                              $levelClone.css({
                                    width : $levelElementForTheLoader.outerWidth() +'px',
                                    height : $levelElementForTheLoader.outerHeight() + 'px'
                              }).append( self._css_loader_html ).find('.sek-css-loader').fadeIn( 'fast' );

                              // Start the countdown for auto-cleaning
                              clearTimeout( $.data( this, '_nimble_loader_active_timer_') );
                              $.data( this, '_nimble_loader_active_timer_', setTimeout(function() {
                                    self.cleanLoader();
                              }, params.duration || 4000 ) );
                        }
                  }
                  if ( true === params.fullPageLoader ) {
                        var $loaderWrapper = $('<div>', { id : 'nimble-full-page-loader-wrapper', class: 'white-loader'} );
                        self.cachedElements.$body.append($loaderWrapper);
                        $loaderWrapper.fadeIn('fast').append( self._css_loader_html ).find('.sek-css-loader').fadeIn( 'fast' );
                        // Blur locations
                        $('[data-sek-level="location"]').each( function() {
                              $(this).addClass('sek-blur');
                        });

                        // Start the countdown for auto-cleaning
                        clearTimeout( $.data( this, '_nimble_full_page_loader_active_timer_') );
                        $.data( this, '_nimble_full_page_loader_active_timer_', setTimeout(function() {
                              self.cleanLoader( { cleanFullPageLoader : true });
                        }, params.duration || 6000 ) );
                  }
            },

            // scheduled in ::initialize(), on 'sek-modules-refreshed sek-columns-refreshed sek-section-added sek-refresh-level'
            // invoked in ::mayBePrintLoader() in an auto-clean scenario
            // or on wp.customize.send('sek-clean-loader', { cleanFullPageLoader : true })
            // {
            //  cleanFullPageLoader : true
            // }
            cleanLoader : function( params ) {
                  var self = this;
                  $('.sek-level-clone').remove();
                  $('[data-sek-level]').each( function() {
                        $(this).removeClass('sek-refreshing');
                  });
                  params = params || {};
                  if ( true === params.cleanFullPageLoader ) {
                        // Unblur locations
                        $('[data-sek-level="location"]').each( function() {
                              $(this).removeClass('sek-blur');
                        });
                        $('#nimble-full-page-loader-wrapper').remove();
                  }
            }

      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            // Fired in ::initialize()
            schedulePanelMsgReactions : function() {
                  var self = this,
                      apiParams = {},
                      uiParams = {},
                      msgCollection = {
                            // DOM MODIFICATION CASES
                            'sek-add-section' : 'ajaxAddSektion',
                            'sek-add-content-in-new-sektion' : 'ajaxAddSektion',
                            'sek-add-content-in-new-nested-sektion' : 'ajaxAddSektion',
                            'sek-add-column' : 'ajaxRefreshColumns',
                            'sek-add-module' : 'ajaxRefreshModulesAndNestedSections',
                            'sek-refresh-stylesheet-in-the-background-for-custom-css' : 'ajaxRefreshStylesheet',
                            'sek-refresh-stylesheet' : 'ajaxRefreshStylesheet',

                            'sek-resize-columns' : 'ajaxResizeColumns',

                            'sek-maybe-print-loader' : function( params ) {
                                  try { self.mayBePrintLoader( params ); } catch( er ) {
                                        api.errare( 'sek-print-loader => error', er );
                                  }
                            },
                            'sek-clean-loader' : function( params ) {
                                  try { self.cleanLoader( params ); } catch( er ) {
                                        api.errare( 'sek-clean-loader => error', er );
                                  }
                            },
                            'sek-remove' : function( params ) {
                                  var removeCandidateId = params.apiParams.id,
                                      $candidateEl = $('div[data-sek-id="' + removeCandidateId + '"]' ),
                                      dfd;
                                  switch ( params.apiParams.action ) {
                                        case 'sek-remove-section' :
                                              // will be cleaned on ajax.done()
                                              // @see ::scheduleTheLoaderCleaning
                                              self.mayBePrintLoader({
                                                    loader_located_in_level_id : params.apiParams.location
                                              });
                                              if ( true === params.apiParams.is_nested ) {
                                                    dfd = self.ajaxRefreshModulesAndNestedSections( params );
                                              } else {
                                                    if ( _.isEmpty( removeCandidateId ) || 1 > $candidateEl.length ) {
                                                          self.errare( 'reactToPanelMsg => sek-remove => invalid candidate id => ', removeCandidateId );
                                                    }
                                                    self.cachedElements.$body.find( $candidateEl ).remove();
                                                    // say it
                                                    // listened to clean the loader just in time
                                                    $('[data-sek-id="' + params.apiParams.location + '"]').trigger( 'sek-level-refreshed');
                                              }
                                              //self.ajaxRefreshModulesAndNestedSections( params );
                                        break;
                                        case 'sek-remove-column' :
                                              dfd = self.ajaxRefreshColumns( params );
                                        break;
                                        case 'sek-remove-module' :
                                              dfd = self.ajaxRefreshModulesAndNestedSections( params );
                                        break;
                                        default :
                                        break;
                                  }
                                  // We should always return a promise
                                  return _.isEmpty( dfd ) ? $.Deferred( function() { this.resolve(); } ) : dfd;
                            },

                            'sek-duplicate' : function( params ) {
                                  var dfd;
                                  switch ( params.apiParams.action ) {
                                        case 'sek-duplicate-section' :
                                              // replace the original id by the new cloneId registered in the main setting, and sent by the panel
                                              params.apiParams.id = params.cloneId;
                                              dfd = self.ajaxAddSektion( params );
                                        break;
                                        case 'sek-duplicate-column' :
                                              // replace the original id by the new cloneId registered in the main setting, and sent by the panel
                                              params.apiParams.id = params.cloneId;
                                              dfd = self.ajaxRefreshColumns( params );
                                        break;
                                        case 'sek-duplicate-module' :
                                              // replace the original id by the new cloneId registered in the main setting, and sent by the panel
                                              params.apiParams.id = params.cloneId;
                                              dfd = self.ajaxRefreshModulesAndNestedSections( params );
                                        break;
                                  }
                                  return dfd;
                            },

                            // Re-print a level
                            // Can be invoked when setting the section layout option boxed / wide, when we need to add a css class server side
                            // @params {
                            //   action : 'sek-refresh-level',
                            //   level : params.level,
                            //   id : params.id
                            // }
                            'sek-refresh-level' : function( params ) {
                                  // will be cleaned on 'sek-module-refreshed'
                                  self.mayBePrintLoader({
                                        loader_located_in_level_id : params.apiParams.id
                                  });
                                  return self.doAjax({
                                        location_skope_id : params.location_skope_id,
                                        local_skope_id : params.local_skope_id,
                                        action : 'sek_get_content',
                                        id : params.apiParams.id,
                                        level : params.apiParams.level,
                                        sek_action : params.apiParams.action
                                  }).fail( function( _r_ ) {
                                        self.errare( 'ERROR reactToPanelMsg => sek-refresh-level => ' , _r_ );
                                        $( '[data-sek-id="' + params.apiParams.id + '"]' ).trigger( 'sek-ajax-error' );
                                  }).done( function( _r_ ) {
                                        var html_content = '';
                                        //@see php SEK_Front_Ajax::sek_get_level_content_for_injection
                                        if ( _r_.data && _r_.data.contents ) {
                                              html_content = _r_.data.contents;
                                        } else {
                                              self.errare( 'SekPreviewPrototype::sek-refresh-level => ajax_response.data.contents is undefined ', _r_ );
                                              self.errare( 'params ?', params );
                                        }
                                        // _r_ is an array
                                        // @see SEK_Front_Ajax::sek_get_level_content_for_injection
                                        // _r_ = array(
                                        //     'contents' => $html,
                                        //     'setting_validities' => $exported_setting_validities
                                        // );
                                        var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.id + '"></span>',
                                            $currentLevelEl = $( 'div[data-sek-id="' + params.apiParams.id + '"]' );
                                        if ( $currentLevelEl.length < 1 ) {
                                              self.errare( 'reactToPanelMsg => sek-refresh-level ajax done => the level to refresh is not rendered in the page', _r_ );
                                              return;
                                        }
                                        $currentLevelEl.before( placeholderHtml );
                                        var $placeHolder = $( '[data-sek-placeholder-for="' + params.apiParams.id + '"]' );

                                        $currentLevelEl.remove();

                                        if ( _.isUndefined( html_content ) ) {
                                              self.errare( 'reactToPanelMsg => sek-refresh-level ajax done => missing html_content', _r_ );
                                        } else {
                                              $placeHolder.after( html_content );
                                        }
                                        if ( $placeHolder.length > 0 ) {
                                          $placeHolder.remove();
                                        }

                                        //=> 'sek-level-refreshed' is listened to ( for example ) clean the loader overlay in time
                                        $( '[data-sek-id="' + params.apiParams.id + '"]' ).trigger( 'sek-level-refreshed', { level : params.apiParams.level, id : params.apiParams.id } );

                                        // When completing actions 'sek-move-section-down' && 'sek-move-section-up', a 'sek-refresh-level' is triggered.
                                        // We pass the moved_level_id so we can focus on it after it's been re-located in the DOM
                                        // implemented for https://github.com/presscustomizr/nimble-builder/issues/471
                                        if ( params.apiParams.moved_level_id ) {
                                              api.preview.trigger( 'sek-animate-to-level', { id : params.apiParams.moved_level_id } );
                                        }
                                  });
                            },






                            // EDITING MODULE AND OPTIONS
                            'sek-move' : function( params ) {
                                  switch ( params.apiParams.action ) {
                                        // case 'sek-move-section' :
                                        //       //always re-render the source sektion and target sektion if different
                                        //       //=> this will ensure a reset of the column's widths
                                        //       if ( params.apiParams.from_location != params.apiParams.to_location ) {
                                        //             var paramsForSourceSektion = $.extend( true, {}, params );
                                        //             var paramsForTargetSektion = $.extend( true, {}, params );

                                        //             // SOURCE SEKTION
                                        //             // if the source sektion has been emptied, let's populate it with a new column
                                        //             if ( $('[data-sek-id="' + params.apiParams.from_sektion +'"]', '.sektion-wrapper').find('div[data-sek-level="column"]').length < 1 ) {
                                        //                   api.preview.send( 'sek-add-column', {
                                        //                         in_sektion : params.apiParams.from_sektion,
                                        //                         autofocus:false//<= because we want to focus on the column that has been moved away from the section
                                        //                   });
                                        //             } else {
                                        //                   paramsForSourceSektion.apiParams =  _.extend( paramsForSourceSektion.apiParams, {
                                        //                         in_sektion : params.apiParams.from_sektion,
                                        //                         action : 'sek-refresh-columns-in-sektion'
                                        //                   });
                                        //                   self.ajaxRefreshColumns( paramsForSourceSektion );
                                        //             }

                                        //             // TARGET SEKTION
                                        //             paramsForTargetSektion.apiParams =  _.extend( paramsForTargetSektion.apiParams, {
                                        //                   in_sektion : params.apiParams.to_sektion,
                                        //                   action : 'sek-refresh-columns-in-sektion'
                                        //             });
                                        //             self.ajaxRefreshColumns( paramsForTargetSektion );

                                        //       }
                                        // break;
                                        case 'sek-move-column' :
                                              //always re-render the source sektion and target sektion if different
                                              //=> this will ensure a reset of the column's widths
                                              if ( params.apiParams.from_sektion != params.apiParams.to_sektion ) {
                                                    var paramsForSourceSektion = $.extend( true, {}, params );
                                                    var paramsForTargetSektion = $.extend( true, {}, params );

                                                    // SOURCE SEKTION
                                                    // if the source sektion has been emptied, let's populate it with a new column
                                                    if ( $('[data-sek-id="' + params.apiParams.from_sektion +'"]', '.sektion-wrapper').find('div[data-sek-level="column"]').length < 1 ) {
                                                          api.preview.send( 'sek-add-column', {
                                                                in_sektion : params.apiParams.from_sektion,
                                                                autofocus:false//<= because we want to focus on the column that has been moved away from the section
                                                          });
                                                    } else {
                                                          paramsForSourceSektion.apiParams =  _.extend( paramsForSourceSektion.apiParams, {
                                                                in_sektion : params.apiParams.from_sektion,
                                                                action : 'sek-refresh-columns-in-sektion'
                                                          });
                                                          self.ajaxRefreshColumns( paramsForSourceSektion );
                                                    }

                                                    // TARGET SEKTION
                                                    paramsForTargetSektion.apiParams =  _.extend( paramsForTargetSektion.apiParams, {
                                                          in_sektion : params.apiParams.to_sektion,
                                                          action : 'sek-refresh-columns-in-sektion'
                                                    });
                                                    self.ajaxRefreshColumns( paramsForTargetSektion );

                                              }
                                        break;
                                        case 'sek-move-module' :
                                              var paramsForSourceColumn = $.extend( true, {}, params ),
                                                  paramsForTargetColumn = $.extend( true, {}, params );
                                              // SOURCE COLUMN
                                              //always re-render the source column if different than the target column
                                              //=> this will ensure that we have the drop-zone placeholder printed for a no-module column
                                              //+ will refresh the sortable()
                                              if ( paramsForSourceColumn.apiParams.from_column != paramsForSourceColumn.apiParams.to_column ) {
                                                    paramsForSourceColumn.apiParams = _.extend( paramsForSourceColumn.apiParams, {
                                                          in_column : paramsForSourceColumn.apiParams.from_column,
                                                          in_sektion : paramsForSourceColumn.apiParams.from_sektion,
                                                          action : 'sek-refresh-modules-in-column'
                                                    });
                                                    self.ajaxRefreshModulesAndNestedSections( paramsForSourceColumn );
                                              }

                                              // TARGET COLUMN
                                              params.apiParams = _.extend( paramsForTargetColumn.apiParams, {
                                                    in_column : paramsForTargetColumn.apiParams.to_column,
                                                    in_sektion : paramsForTargetColumn.apiParams.to_sektion,
                                                    action : 'sek-refresh-modules-in-column'
                                              });
                                              self.ajaxRefreshModulesAndNestedSections( paramsForTargetColumn );

                                              // Re-instantiate sortable for the target column
                                              $('[data-sek-id="' + params.apiParams.to_column +'"]', '.sektion-wrapper').find('.sek-column-inner').sortable( "refresh" );
                                        break;
                                  }
                            },





                            // GENERATE UI ELEMENTS
                            // when the options ui has been generated in the panel for a level, we receive back this msg
                            // 'sek-generate-level-options-ui' : function( params ) {
                            //       api.infoLog('PANEL REACT? ', 'sek-generate-level-options-ui', params );
                            // },

                            'sek-edit-options' : function( params ) {
                                  // ::activeLevelUI is declared in ::initialized()
                                  self.activeLevelUI( params.uiParams.id );
                            },
                            'sek-edit-module' : function( params ) {
                                  // ::activeLevelUI is declared in ::initialized()
                                  self.activeLevelUI( params.uiParams.id );
                            },









                            // @params =  {
                            //   location_skope_id : api.czr_skopeBase.getSkopeProperty( 'skope_id' ),//<= send skope id to the preview so we can use it when ajaxing
                            //   apiParams : apiParams,
                            //   uiParams : uiParams
                            // }
                            // uiParams = {
                            //       action : 'sek-edit-module',
                            //       level : params.level,
                            //       id : params.id,
                            //       in_sektion : params.in_sektion,
                            //       in_column : params.in_column,
                            //       options : params.options || []
                            // };
                            //
                            // when the module ui has been generated in the panel, we receive back this msg
                            //'sek-generate-module-ui' : function( params ) {},

                            //@params {
                            //    content_type : module || preset_section,
                            //    eligible_for_module_dropzones : boolean //<= typically useful for multicolumn "modules" that are in reality preset_section @see https://github.com/presscustomizr/nimble-builder/issues/540
                            // }
                            'sek-drag-start' : function( params ) {
                                  // append the drop zones between sections
                                  var i = 1, previousSectionIsEmpty = false;
                                  $('[data-sek-level="location"]').children('[data-sek-level="section"]').each( function() {
                                        var sectionId = $(this).data('sek-id'),
                                            columnNb = $(this).find('[data-sek-level="column"]').length,
                                            moduleNb = $(this).find('[data-sek-level="module"]').length,
                                            isEmptySection = columnNb < 2 && moduleNb < 1,
                                            canPrintBefore = ! previousSectionIsEmpty && ! isEmptySection;

                                        // Print a dropzone before if the previous section and current section are not empty.
                                        if ( canPrintBefore && $('[data-drop-zone-before-section="' + sectionId +'"]').length < 1 ) {
                                              $(this).before(
                                                '<div class="sek-content-' + params.content_type + '-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-sections" data-drop-zone-before-section="' + sectionId +'"></div>'
                                              );
                                        }
                                        // After the last one
                                        if ( ! isEmptySection && i == $('.sektion-wrapper').children('[data-sek-level="section"]').length ) {
                                              $(this).after(
                                                '<div class="sek-content-' + params.content_type + '-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-sections" data-drop-zone-after-section="' + sectionId +'"></div>'
                                              );
                                        }
                                        i++;
                                        previousSectionIsEmpty = isEmptySection;
                                  });

                                  // Append the drop zone in empty locations
                                  $('.sek-empty-location-placeholder').each( function() {
                                        $.when( $(this).append(
                                              '<div class="sek-content-' + params.content_type + '-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="in-empty-location"></div>'
                                        ));
                                  });

                                  // Append a drop zone between modules and nested sections in columns
                                  // preset_sections like multicolumn structure are part of the module list
                                  // they fall under the second part of the conditional statement below
                                  // introduced for https://github.com/presscustomizr/nimble-builder/issues/540
                                  if ( 'module' == params.content_type || ( 'preset_section' == params.content_type && true === params.eligible_for_module_dropzones ) ) {
                                        $('[data-sek-level="column"]').each( function() {
                                              // Our candidates are the modules and nested section which are direct children of this column
                                              // We don't want to include the modules inserted in the columns of a nested section.
                                              var $modules_and_nested_sections = $(this).children('.sek-column-inner').children( '[data-sek-level="module"]' );
                                              var $nested_sections = $(this).children('.sek-column-inner').children( '[data-sek-is-nested="true"]' );
                                              $modules_and_nested_sections = $modules_and_nested_sections.add( $nested_sections );

                                              var j = 1;
                                              $modules_and_nested_sections.each( function() {
                                                    // Always before
                                                    if ( $('[data-drop-zone-before-module-or-nested-section="' + $(this).data('sek-id') +'"]').length < 1 ) {
                                                          $(this).before(
                                                              '<div class="sek-content-module-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-modules-and-nested-sections" data-drop-zone-before-module-or-nested-section="' + $(this).data('sek-id') +'"></div>'
                                                          );
                                                    }
                                                    // After the last one
                                                    if (  j == $modules_and_nested_sections.length && $('[data-drop-zone-after-module-or-nested-section="' + $(this).data('sek-id') +'"]').length < 1 ) {
                                                          $(this).after(
                                                            '<div class="sek-content-module-drop-zone sek-dynamic-drop-zone sek-drop-zone" data-sek-location="between-modules-and-nested-sections" data-drop-zone-after-module-or-nested-section="' + $(this).data('sek-id') +'"></div>'
                                                          );
                                                    }
                                                    j++;
                                              });
                                        });
                                  }

                                  // toggle a parent css classes controlling some css rules @see preview.css
                                  self.cachedElements.$body.addClass('sek-dragging');

                                  // Reveal all dynamic dropzones after a delay
                                  _.delay( function() {
                                        $('.sek-dynamic-drop-zone').css({ opacity : 1 });
                                  }, 100 );

                            },
                            // is sent on dragend and drop
                            'sek-drag-stop' : function( params ) {
                                  self.cachedElements.$body.removeClass('sek-dragging');
                                  // Clean any remaining placeholder
                                  $('.sortable-placeholder').remove();

                                  // Remove the drop zone dynamically add on sek-drag-start
                                  $('.sek-dynamic-drop-zone').remove();
                            },















                            // FOCUS
                            // Sent from the panel when duplicating a section level for example
                            // focus on a level
                            'sek-animate-to-level' : function( params ) {
                                    var $elToFocusOn = $('[data-sek-id="' + params.id + '"]' );
                                    if ( 0 < $elToFocusOn.length && !nb_.isInScreen( $elToFocusOn[0]) ) {
                                          $elToFocusOn[0].scrollIntoView({
                                                behavior: 'auto',
                                                block: 'center',
                                                inline: 'center'
                                          });
                                    }
                                    // if ( $elToFocusOn.length > 0 ) {
                                    //       console.log( 'EL IN WINDOW ?', nb_.elOrFirstVisibleParentIsInWindow( $elToFocusOn ), $elToFocusOn );
                                    //       $elToFocusOn[0].scrollIntoView();
                                    //       $('html, body').animate({
                                    //             scrollTop : $elToFocusOn.offset().top - 100
                                    //       }, 200 );
                                    // }
                            },


                            // LEVEL UI's
                            'sek-clean-level-uis' : function( params ) {
                                  $('.sek-dyn-ui-wrapper').each( function() {
                                        $(this).remove();
                                  });
                            },
                            // triggered when navigating the level tree
                            'sek-display-level-ui' : function( params ) {
                                  var $elToFocusOn = $('[data-sek-id="' + params.id + '"]' );
                                  if ( $elToFocusOn.length > 0 ) {
                                        //$elToFocusOn.trigger('click'); //<= the click is not needed anymore since June 2019, we trigger the generation of the level options on 'click' in the level tree
                                        self.printLevelUI($elToFocusOn);
                                  }
                            },


                            // DOUBLE CLICK INSERTION => HIGHLIGHTED TARGET
                            // implemented for double-click insertion
                            // https://github.com/presscustomizr/nimble-builder/issues/317
                            'sek-set-double-click-target' : function( params ) {
                                  // First clean any other highlighted target
                                  $('.sek-target-for-double-click-insertion').removeClass('sek-target-for-double-click-insertion');

                                  if ( _.isObject( params ) && params.id ) {
                                        var $elToHighlight = $('[data-sek-id="' + params.id + '"]' );
                                        if( 1 === $elToHighlight.length ) {
                                              $elToHighlight.addClass('sek-target-for-double-click-insertion');
                                        }
                                  }
                            },
                            'sek-reset-double-click-target' : function( params ) {
                                  $('.sek-target-for-double-click-insertion').removeClass('sek-target-for-double-click-insertion');
                            },

                            // introduced for https://github.com/presscustomizr/nimble-builder/issues/403
                            // this is fired for module with postMessage refresh, like text editor
                            // @see control::refreshMarkupWhenNeededForInput()
                            // July 2019 => since the new UI rendering with JS template ( https://github.com/presscustomizr/nimble-builder/issues/465 ), this action is fired too early when inserting a new module with postMessage refresh
                            // resulting in the target element not being rendered on first call
                            'sek-update-html-in-selector' : function( params ) {
                                  var $level_el = $('[data-sek-id="' + params.id + '"]' ),
                                      $target_el;

                                  // for multi-item modules, the changed item id is passed
                                  if ( !_.isEmpty( params.changed_item_id ) ) {
                                        // if a selector is provided in param 'refresh_markup'
                                        if ( params.selector ) {
                                          $target_el = $( '[data-sek-item-id="' + params.changed_item_id + '"] ' + params.selector, $level_el);
                                        } else {
                                                $target_el = $( '[data-sek-item-id="' + params.changed_item_id + '"]', $level_el);
                                          }
                                  } else {
                                        $target_el = $(params.selector, $level_el);
                                  }

                                  if ( $level_el.length > 0 && $target_el.length > 0 ) {
                                        $target_el.html( params.html );
                                  } else {
                                        self.errare( 'reactToPanelMsg => sek-update-html-in-selector => missing level or target dom element', params );
                                  }
                            },
                             // introduced for CUSTOM CSS see https://github.com/presscustomizr/nimble-builder-pro/issues/201
                            // fired when refresh_css_via_post_message = true in input registration params
                            'sek-update-css-with-postmessage' : function( params ) {
                                    //console.log('ALORS PARAMS ?', params );
                                    if ( _.isUndefined(params.css_content) || !_.isString(params.css_content) ) {
                                          self.errare( 'error => sek-update-css-with-postmessage => css content is not a string' );
                                          return;
                                    }

                                    // Comments removal
                                    // see https://stackoverflow.com/questions/5989315/regex-for-match-replacing-javascript-comments-both-multiline-and-inline
                                    params.css_content = params.css_content.replace(/\/\*.+?\*\/|\/\/.*(?=[\n\r])/g, '');

                                    var custom_css_sel,
                                          $custom_css_el,
                                          _level_selector;

                                    if ( params.is_current_page_custom_css ) {
                                          custom_css_sel = 'nb-custom-css-for-local-page';
                                          _level_selector = '';
                                    } else {
                                          custom_css_sel = 'nb-custom-css-for-level' + params.id;
                                          _level_selector = 'body .sektion-wrapper [data-sek-id="' + params.id +'"]';
                                    }
                                    $custom_css_el = $('#'  + custom_css_sel );

                                    if ( $custom_css_el.length < 1 ) {
                                          $('head').append( $('<style/>' , {
                                                id : custom_css_sel,
                                          }) );
                                          $custom_css_el = $('#'  + custom_css_sel );
                                    }

                                    // Apply the same treatment made server side in sek_add_css_rules_for_level_custom_css()
                                    var _exploded_rules,
                                          _rules_with_level_specificity = [],
                                          _specific_rules = '',
                                          _rule_selectors,
                                          _rule_selectors_without_space,
                                          _comma_exploded_selectors;

                                    // 1) FIRST => Explode by } and add level specificity
                                    _exploded_rules = params.css_content.split('}');

                                    _.each( _exploded_rules, function( _rule ){
                                          // remove all line breaks
                                          _rule = _rule.replace(/\n|\r/g, "" );
                                          if ( _.isEmpty(_rule) || -1 === _rule.indexOf('{') )
                                                return;
                                          _rule = _rule.replace(/.nimble-level/g, '' );
                                          _rule_selectors = _rule.substr(0, _rule.indexOf('{'));
                                          _rule_selectors_without_space = _rule_selectors.replace(/ /g,'');

                                          _rule = _rule.replace(_rule_selectors, '' );
                                          // If no selectors specified, simply use the level selector
                                          if ( _.isEmpty( _rule_selectors_without_space ) ) {
                                                _rules_with_level_specificity.push(_level_selector + _rule + '}css_delimiter');
                                          } else {
                                                // => handle selectors separated by commas, in this case, the previous treatment has only added specificity to the first selector of the list
                                                _comma_exploded_selectors = _rule_selectors.split(',');
                                                _.each( _comma_exploded_selectors, function( _sel ){
                                                      if ( _.isEmpty(_sel) )
                                                            return;
                                                      _rules_with_level_specificity.push(_level_selector + ' ' + _sel + _rule + '}css_delimiter');
                                                });
                                          }
                                    });

                                    if ( !_.isEmpty(_rules_with_level_specificity) ) {
                                          _specific_rules = _rules_with_level_specificity.join('css_delimiter');
                                          _specific_rules = _specific_rules.replace(/css_delimiter/g, '' );
                                    }

                                    // If there are no rules to write, refresh the stylesheet ajaxily ( because it might contain previous CSS rules that should be cleaned up )
                                    // + remove the custom CSS style element
                                    if ( !_.isEmpty( _specific_rules.replace(/ /g, '' ) ) ) {
                                          $custom_css_el.html( _specific_rules );
                                    } else {
                                          $custom_css_el.remove();
                                    }
                                    params = $.extend({}, true, params );
                                    params.dont_print_loader = true;
                                    // Let's refresh the stylesheet ajaxily in the background to make sure that previously saved rules do not override new ones
                                    api.preview.trigger('sek-refresh-stylesheet-in-the-background-for-custom-css', params);
                              },
                              // march 2020 : print confettis when displaying the review request
                              'sek-print-confettis' : function( params ) {
                                  if (!window.confetti || !window.requestAnimationFrame)
                                    return;
                                  params = params || {};
                                  var end = params.duration || Date.now() + (1 * 1000);
                                  var colors = ['#f18700', '#684F2F', '#eea236'];

                                  (function frame() {
                                    confetti({
                                      particleCount: 2,
                                      angle: 60,
                                      spread: 55,
                                      origin: {
                                        x: 0,
                                        y: 0.8
                                      },
                                      colors: colors
                                    });
                                    confetti({
                                      particleCount: 4,
                                      angle: 120,
                                      spread: 55,
                                      origin: {
                                        x: 1,
                                        y: 0.8
                                      },
                                      colors: colors
                                    });

                                    if (Date.now() < end) {
                                      window.requestAnimationFrame(frame);
                                    }
                                  }());
                            }
                      };//msgCollection




                  var $_activeElement; // <= will be used to cache self.activeLevelEl()

                  var _apiPreviewCallback = function( params, callbackFn, msgId ) {
                        params = _.extend( {
                            location_skope_id : '',
                            apiParams : {},
                            uiParams : {}
                        }, params || {} );

                        // If the ajax response is an array formed this way ( @see sek-refresh-level case ) :
                        // @see SEK_Front_Ajax::sek_get_level_content_for_injection
                        // _ajaxResponse_ = array(
                        //     'contents' => $html,
                        //     'setting_validities' => $exported_setting_validities
                        // );
                        // Then we send an additional setting-validity message to the control panel
                        // This is the same mechanism used by WP to handle the setting validity of the partial refresh

                        var sendSuccessDataToPanel = function( _ajaxResponse_ ) {
                              // always send back the {msgId}_done message, so the control panel can fire the "complete" callback.
                              // @see api.czr_sektions::reactToPreviewMsg
                              api.preview.send( [ msgId, 'done'].join('_'), params );

                              // For multi-items module, when the level is refreshed, we want to focus on the changed_item
                              // @see CZRSeksPrototype::doSektionThinksOnApiReady
                              if ( params.apiParams.is_multi_items && params.apiParams.action === 'sek-refresh-level' ) {
                                    api.preview.send( 'multi-items-module-refreshed', params );
                              }

                              if ( _.isUndefined( _ajaxResponse_ ) )
                                return;

                              if ( _ajaxResponse_.data && _ajaxResponse_.data.setting_validities ) {
                                    api.preview.send( 'selective-refresh-setting-validities', _ajaxResponse_.data.setting_validities );
                              }
                        };
                        // the action being processed is added as a css class to the body of the preview
                        // it's used to enable/disable specific css properties during the action
                        // for example, we don't want css transitions while duplicating or removing a column
                        self.cachedElements.$body.addClass( msgId );
                        try {
                              $.when( _.isFunction( callbackFn ) ? callbackFn( params ) : self[callbackFn].call( self, params ) )
                                    .done( function( _ajaxResponse_ ) {
                                          sendSuccessDataToPanel( _ajaxResponse_ );
                                    })
                                    .fail( function() {
                                          api.preview.send( 'sek-notify', { type : 'error', duration : 10000, message : sekPreviewLocalized.i18n['Something went wrong, please refresh this page.'] });
                                    })
                                    .always( function( _ajaxResponse_ ) {
                                          self.cachedElements.$body.removeClass( msgId );
                                    })
                                    .then( function() {
                                          api.preview.trigger( 'control-panel-requested-action-done', { action : msgId, args : params } );
                                          // Focus on the edited level
                                          self.mayBeAnimateToEditedLevel( params );
                                    });
                        } catch( _er_ ) {
                              self.errare( 'reactToPanelMsg => Error when firing the callback of ' + msgId , _er_  );
                              self.cachedElements.$body.removeClass( msgId );
                        }

                        // set the activeElement if needed/possible
                        if ( _.isObject( params ) && params.apiParams && params.apiParams.id ) {
                              $_activeElement = self.activeLevelEl();

                              if ( !$_activeElement || !_.isObject($_activeElement) || $_activeElement.length < 1 || self.activeLevelUI() !== params.apiParams.id ) {
                                    self.activeLevelEl( $('[data-sek-id="' + params.apiParams.id + '"]' ) );
                                    $_activeElement = self.activeLevelEl();
                              }
                        }
                        // Focus on the edited level
                        self.mayBeAnimateToEditedLevel( params );
                  };//_apiPreviewCallback


                  // Bind api preview
                  _.each( msgCollection, function( callbackFn, msgId ) {
                        if ( 'sek-refresh-stylesheet' === msgId ) {
                              api.preview.bind( msgId, function( params ) {
                                    _apiPreviewCallback( params, callbackFn, msgId );
                              });// api.preview.bind( msgId, function( params ) {
                              // September 2020 : added a debounced callback when generating stylesheet in order to prevent
                              // css value may not be taken into account when typed fast, for example an height in pixels
                              // https://github.com/presscustomizr/nimble-builder/issues/742
                              api.preview.bind( msgId, _.debounce( function( params ) {
                                    _apiPreviewCallback( params, callbackFn, msgId );
                                    if ( params && params.apiParams && params.apiParams.id ) {
                                          api.preview.trigger('sek-animate-to-level', { id : params.apiParams.id });
                                    }
                              }, 1000 ));// api.preview.bind( msgId, function( params ) {
                        } else if ( 'sek-refresh-stylesheet-in-the-background-for-custom-css' === msgId ) {
                              api.preview.bind( msgId, _.debounce( function( params ) {
                                    _apiPreviewCallback( params, callbackFn, msgId );
                                    if ( params && params.apiParams && params.apiParams.id ) {
                                          api.preview.trigger('sek-animate-to-level', { id : params.apiParams.id });
                                    }
                              }, 500 ));// api.preview.bind( msgId, function( params ) {
                        } else {
                              api.preview.bind( msgId, function( params ) {
                                    _apiPreviewCallback( params, callbackFn, msgId );
                              });// api.preview.bind( msgId, function( params ) {
                        }

                  });
            },//schedulePanelMsgReactions()

            mayBeAnimateToEditedLevel : function( params ) {
                  var self = this;
                  // MAY 2020 : focus on the edited element
                  if ( _.isObject( params ) && params.apiParams && params.apiParams.id ) {
                        $elToFocusOn = $('[data-sek-id="' + params.apiParams.id + '"]' );
                        // if user scrolled while editing an element, let's focus again
                        if ( 0 < $elToFocusOn.length && !nb_.isInScreen( $elToFocusOn[0]) ) {
                              $elToFocusOn[0].scrollIntoView({
                                    behavior: 'auto',
                                    block: 'center',
                                    inline: 'center'
                              });
                        }
                  }
            }
      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            // this method is used when creating or duplicating a sektion
            // @return a promise()
            ajaxAddSektion : function( params ) {
                  var self = this;
                  // will be cleaned on ajax.done()
                  // @see ::scheduleTheLoaderCleaning
                  self.mayBePrintLoader({
                        loader_located_in_level_id : params.apiParams.location
                  });
                  return self.doAjax({
                        action : 'sek_get_content',
                        id : params.apiParams.id,
                        in_sektion : params.apiParams.in_sektion,
                        in_column : params.apiParams.in_column,
                        location_skope_id : params.location_skope_id,
                        local_skope_id : params.local_skope_id,
                        sek_action : params.apiParams.action,
                        is_nested : params.apiParams.is_nested,

                        // The following params have been introduced when implementing support for multi-section pre-build sections
                        // @see https://github.com/presscustomizr/nimble-builder/issues/489
                        content_type : ( params.apiParams && params.apiParams.content_type ) ? params.apiParams.content_type : null,
                        collection_of_preset_section_id : ( params.apiParams && params.apiParams.collection_of_preset_section_id ) ? params.apiParams.collection_of_preset_section_id : []
                  }).done( function( _r_ ) {
                        var html_content = '';
                        //@see php SEK_Front_Ajax::sek_get_level_content_for_injection
                        if ( _r_.data && _r_.data.contents ) {
                              html_content = _r_.data.contents;
                        } else {
                              self.errare( 'SekPreviewPrototype::ajaxAddSektion => ajax_response.data.contents is undefined ', _r_ );
                              self.errare( 'params ?', params );
                        }

                        // Embed
                        // is it a nested sektion ?
                        var $parentColumn;
                        if ( params.apiParams.is_nested ) {
                              $parentColumn = $( '.sektion-wrapper').find( 'div[data-sek-id="' + params.apiParams.in_column + '"]' );
                              if ( 1 > $parentColumn.length ) {
                                    self.errare( 'preview => reactToPanelMsg => sek-add-column => no DOM node for parent column => ', params.apiParams.in_column );
                              }
                              var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.in_column + '"></span>';
                              $parentColumn.before( placeholderHtml );
                              // remove and re-render the entire column
                              $parentColumn.remove();
                              $( '.sektion-wrapper').find( '.sek-placeholder' ).after( html_content );
                              $( '.sektion-wrapper').find( '.sek-placeholder' ).remove();
                        } else {
                              // DUPLICATE CASE
                              // Insert the clone section right after its cloned sister
                              if ( 'sek-duplicate-section' == params.apiParams.action && ! _.isEmpty( params.cloneId ) ) {
                                    $( '.sektion-wrapper').find( 'div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).after( html_content );
                              }
                              // GENERATED WHEN ADDING A MODULE
                              else {
                                    // When a section has been created by adding a module ( @see sek-add-content-in-new-sektion )
                                    // we need to append it to a specific location
                                    // otherwise, we append it at the end of the section collection
                                    $beforeCandidate = $( '.sektion-wrapper[data-sek-id="' + params.apiParams.location + '"]').find( 'div[data-sek-id="' + params.apiParams.before_section + '"]' );
                                    $afterCandidate = $( '.sektion-wrapper[data-sek-id="' + params.apiParams.location + '"]').find( 'div[data-sek-id="' + params.apiParams.after_section + '"]' );

                                    if ( ! _.isEmpty( params.apiParams.before_section ) && $beforeCandidate.length > 0 ) {
                                          $beforeCandidate.before( html_content );
                                    } else if ( ! _.isEmpty( params.apiParams.after_section ) && $afterCandidate.length > 0 ) {
                                          $afterCandidate.after( html_content );
                                    } else {
                                          $( '[data-sek-id="' + params.apiParams.location + '"]').append( html_content );
                                    }
                              }
                        }

                        // When a section is duplicated, fire sortable for the inner-column modules
                        if ( 'sek-duplicate-section' == params.apiParams.action ) {
                              // re-instantiate sortable in the refreshed columns of the section
                              // + make columns resizable
                              $( 'div[data-sek-id="' + params.cloneId + '"]', '.sektion-wrapper').each( function() {
                                    $(this).trigger('sek-columns-refreshed');
                              });
                        }

                        // refresh sortable for the inner column if nested sektion case
                        if ( params.apiParams.is_nested ) {
                              self.makeModulesSortableInColumn( params.apiParams.in_column );
                              $( '.sektion-wrapper').find( 'div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).each( function() {
                                    self.maybeMakeColumnResizableInSektion.call( this );
                              });
                        }

                        // say it to the parent sektion
                        //=> will be listened to clean the loader overlay just in time
                        if ( params.cloneId ) {
                              $( 'div[data-sek-id="' + params.cloneId + '"]' ).trigger('sek-section-added', params );
                        }
                        // @todo is the params.apiParams.id correct ? Does it actually exists in the DOM?
                        $( 'div[data-sek-id="' + params.apiParams.id + '"]' ).trigger('sek-section-added', params );

                        // added to fix resizable not instantiated when adding column modules
                        // @see https://github.com/presscustomizr/nimble-builder/issues/523
                        $( 'div[data-sek-id="' + params.apiParams.location + '"]' ).trigger('sek-location-refreshed', params );
                  }).fail( function( _r_ ) {
                        self.errare( 'ERROR in sek_get_html_for_injection ? ' , _r_ );
                        $( '[data-sek-id="' + params.apiParams.id + '"]' )
                              .trigger( 'sek-ajax-error' );
                  });
            }//ajaxAddSektion()

      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            // refresh column is used to
            // 1) Add a new column
            // 2) re-render the column collection in a sektion
            ajaxRefreshColumns : function( params ) {
                  var self = this;
                  // will be cleaned on 'sek-columns-refreshed'
                  self.mayBePrintLoader({
                        loader_located_in_level_id : params.apiParams.in_sektion
                  });
                  return self.doAjax( {
                        action : 'sek_get_content',
                        id : params.apiParams.id,
                        in_sektion : params.apiParams.in_sektion,
                        location_skope_id : params.location_skope_id,
                        local_skope_id : params.local_skope_id,
                        sek_action : params.apiParams.action// sek-add-column || sek-remove-column
                  }).done( function( _r_ ) {
                        var html_content = '';
                        //@see php SEK_Front_Ajax::sek_get_level_content_for_injection
                        if ( _r_.data && _r_.data.contents ) {
                              html_content = _r_.data.contents;
                        } else {
                              self.errare( 'SekPreviewPrototype::ajaxRefreshColumns => ajax_response.data.contents is undefined ', _r_ );
                              self.errare( 'params ?', params );
                        }

                        var $parentSektion = $( 'div[data-sek-id="' + params.apiParams.in_sektion + '"]' );
                        if ( 1 > $parentSektion.length ) {
                              self.errare( 'reactToPanelMsg => ' + params.apiParams.action + ' => no DOM node for parent sektion => ', params.apiParams.in_sektion );
                        }
                        var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.in_sektion + '"></span>';
                        $parentSektion.before( placeholderHtml );
                        // remove and re-render the entire sektion
                        $parentSektion.remove();
                        $( '.sektion-wrapper').find( '.sek-placeholder' ).after( html_content );
                        $( '.sektion-wrapper').find( '.sek-placeholder' ).remove();

                        // re-generate the stylesheet => this will take into account the reset width of each column
                        api.preview.trigger( 'sek-refresh-stylesheet', params );

                        // say it to the parent sektion
                        //=> will be listened to by the column to re-instantiate sortable, resizable
                        //=> also listened to clean the loader overalay in time
                        $('div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).trigger('sek-columns-refreshed', { in_sektion : params.apiParams.in_sektion } );
                  }).fail( function( _r_ ) {
                        self.errare( 'ERROR reactToPanelMsg => sek-add-column => ' , _r_ );
                        $( '[data-sek-id="' + params.apiParams.id + '"]' )
                              .trigger( 'sek-ajax-error' );
                  });
            },//ajaxRefreshColumns()


            ajaxResizeColumns : function( params ) {
                  var self = this;
                  // will be cleaned on 'sek-module-refreshed'
                  self.mayBePrintLoader({
                        loader_located_in_level_id : params.apiParams.in_sektion
                  });
                  return self.doAjax( {
                        action : 'sek_get_content',
                        resized_column : params.apiParams.resized_column,
                        sister_column : params.apiParams.sister_column,
                        location_skope_id : params.location_skope_id,
                        local_skope_id : params.local_skope_id,
                        sek_action : 'sek-resize-columns'
                  }).done( function( _r_ ) {
                        var html_content = '';
                        //@see php SEK_Front_Ajax::sek_get_level_content_for_injection
                        if ( _r_.data && _r_.data.contents ) {
                              html_content = _r_.data.contents;
                        } else {
                              self.errare( 'SekPreviewPrototype::ajaxResizeColumns => ajax_response.data.contents is undefined ', _r_ );
                              self.errare( 'params ?', params );
                        }
                        //self.errare('sek-preview => resize-column ajax response => ', html_content );
                        // Reset the automatic default resizable inline styling
                        $( '[data-sek-id="' + params.apiParams.resized_column + '"]' ).css({
                              width : '',
                              height: ''
                        });

                        //Append
                        self.appendDynStyleSheet( params.location_skope_id, html_content );

                        // say it
                        // listened to clean the loader just in time
                        $('div[data-sek-id="' + params.apiParams.in_sektion + '"]' ).trigger('sek-columns-refreshed');
                  }).fail( function( _r_ ) {
                        self.errare( 'ERROR reactToPanelMsg => sek-resize-columns => ' , _r_ );
                        $( '[data-sek-id="' + params.apiParams.in_sektion + '"]' )
                              .trigger( 'sek-ajax-error' );
                  });
            }
      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
              // refresh module is used to
              // 1) Add a new module
              // 2) re-render the module collection in a column, typically after a sortable move, or a module removal
              ajaxRefreshModulesAndNestedSections : function( params ) {
                    var self = this;
                    // will be cleaned on 'sek-module-refreshed'
                    self.mayBePrintLoader({
                          loader_located_in_level_id : params.apiParams.in_column
                    });
                    return self.doAjax( {
                          action : 'sek_get_content',
                          id : params.apiParams.id,
                          in_sektion : params.apiParams.in_sektion,
                          in_column : params.apiParams.in_column,
                          location_skope_id : params.location_skope_id,
                          local_skope_id : params.local_skope_id,
                          sek_action : params.apiParams.action, // can be sek-add-module / refresh-modules-in-column
                          is_nested : params.apiParams.is_nested
                    }).done( function( _r_ ) {
                          var html_content = '';
                          //@see php SEK_Front_Ajax::sek_get_level_content_for_injection
                          if ( _r_.data && _r_.data.contents ) {
                                html_content = _r_.data.contents;
                          } else {
                                self.errare( 'SekPreviewPrototype::ajaxRefreshModulesAndNestedSections => ajax_response.data.contents is undefined ', _r_ );
                                self.errare( 'params ?', params );
                          }

                          var $parentColumn = $('[data-sek-id="' + params.apiParams.in_column + '"]' );
                          if ( 1 > $parentColumn.length ) {
                                self.errare( 'reactToPanelMsg => ajaxRefreshModulesAndNestedSections => no DOM node for parent column => ', params.apiParams.in_column );
                          }
                          var placeholderHtml = '<span class="sek-placeholder" data-sek-placeholder-for="' + params.apiParams.in_column + '"></span>';
                          $parentColumn.before( placeholderHtml );
                          // remove and re-render the entire column
                          $parentColumn.remove();
                          $( '[data-sek-placeholder-for="' + params.apiParams.in_column + '"]' ).after( html_content );
                          $( '[data-sek-placeholder-for="' + params.apiParams.in_column + '"]' ).remove();

                          // say it to the column
                          //=> will be listened to by the column to re-instantiate sortable, resizable and fittext
                          $( '[data-sek-id="' + params.apiParams.in_column + '"]' ).trigger('sek-modules-refreshed', { in_column : params.apiParams.in_column, in_sektion : params.apiParams.in_sektion });

                    }).fail( function( _r_ ) {
                          self.errare( 'ERROR reactToPanelMsg => sek-add-module => ' , _r_ );
                          $( '[data-sek-id="' + params.apiParams.id + '"]' )
                              .trigger( 'sek-ajax-error' );
                    });
              }//ajaxRefreshModulesAndNestedSections()
      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            ajaxRefreshStylesheet : function( params ) {
                  var self = this;
                  // will be cleaned on 'sek-module-refreshed'
                  if ( true !== params.dont_print_loader ) {
                        self.mayBePrintLoader({
                              loader_located_in_level_id : params.apiParams.id
                        });
                  }
                  return self.doAjax( {
                        action : 'sek_get_content',
                        location_skope_id : params.location_skope_id,
                        local_skope_id : params.local_skope_id,
                        sek_action : 'sek-refresh-stylesheet'
                  }).done( function( _r_ ) {
                        var html_content = '';
                        //@see php SEK_Front_Ajax::sek_get_level_content_for_injection
                        if ( _r_.data && _r_.data.contents ) {
                              html_content = _r_.data.contents;
                        }
                        self.appendDynStyleSheet( params.location_skope_id, html_content );
                        //=> 'sek-level-refreshed' is listened to clean the loader overlay in time
                        $( '[data-sek-id="' + params.apiParams.id + '"]' )
                              .trigger( 'sek-stylesheet-refreshed', { level : params.apiParams.level, id : params.apiParams.id } );
                  }).fail( function( _r_ ) {
                        self.errare('sek-refresh-stylesheet fail !');
                        $( '[data-sek-id="' + params.apiParams.id + '"]' )
                              .trigger( 'sek-ajax-error' );
                  });
            },

            appendDynStyleSheet : function( location_skope_id, styleMarkup ) {
                var _stylesheet_id_ = '#sek-' + location_skope_id,//@see php Sek_Dyn_CSS_Handler
                    _gfonts_id_ = '#' + sekPreviewLocalized.googleFontsStyleId,//@see php Sek_Dyn_CSS_Handler
                    _global_option_inline_style_id_ = '#' + sekPreviewLocalized.globalOptionsStyleId;

                // Remove a dynamic inline stylesheet if already printed
                if ( 0 < $('head').find( _stylesheet_id_ ).length ) {
                      $('head').find( _stylesheet_id_ ).remove();
                }
                if ( 0 < $('head').find( _gfonts_id_ ).length ) {
                      $('head').find( _gfonts_id_ ).remove();
                }
                if ( 0 < $('head').find( _global_option_inline_style_id_ ).length ) {
                      $('head').find( _global_option_inline_style_id_ ).remove();
                }
                if ( !_.isEmpty( styleMarkup ) ) {
                      $('head').append( styleMarkup );
                }
                // Has it be printed ?
                // if we have something to print ( styleMarkup not empty ), there should be a dom element
                if ( !_.isEmpty( styleMarkup ) &&  1 > $('head').find( _stylesheet_id_ ).length && 1 > $('head').find( _gfonts_id_ ).length && 1 > $('head').find( _global_option_inline_style_id_ ).length ) {
                      this.errare( 'sek-preview => problem when printing the dynamic inline style for : '+ _stylesheet_id_, styleMarkup );
                } else {
                      $('head').find( _stylesheet_id_ ).attr('sek-data-origin', 'customizer' );
                }
            }//appendDynStyleSheet()
      });//$.extend()
})( wp.customize, jQuery, _ );
//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
( function( api, $, _ ) {
      $.extend( SekPreviewPrototype, {
            // inspired from wp.template in wp-includes/js/wp-util.js
            parseTemplate : _.memoize(function ( id ) {
                  var self = this;
                  var compiled,
                    //
                    // Underscore's default ERB-style templates are incompatible with PHP
                    // when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax.
                    //
                    // @see trac ticket #22344.
                    //
                    options = {
                          evaluate:    /<#([\s\S]+?)#>/g,
                          interpolate: /\{\{\{([\s\S]+?)\}\}\}/g,
                          escape:      /\{\{([^\}]+?)\}\}(?!\})/g,
                          variable:    'data'
                    };

                  return function ( data ) {
                        if ( $( id ).length < 1 ) {
                            self.errare( 'preview => parseTemplate => the requested tmpl does not exist =>' + id );
                            return '';
                        }
                        try { compiled = compiled || _.template( $( id ).html(),  options );} catch( _er_ ) {
                              self.errare( 'preview => parseTemplate => problem when parsing tmpl =>' + id, _er_ );
                        }
                        return compiled( data );
                  };
            }),



            //@return [] for console method
            //@bgCol @textCol are hex colors
            //@arguments : the original console arguments
            _prettyPrintLog : function( args ) {
                  var _defaults = {
                        bgCol : '#5ed1f5',
                        textCol : '#000',
                        consoleArguments : []
                  };
                  args = _.extend( _defaults, args );

                  var _toArr = Array.from( args.consoleArguments ),
                      _truncate = function( string ){
                            if ( ! _.isString( string ) )
                              return '';
                            return string.length > 300 ? string.substr( 0, 299 ) + '...' : string;
                      };

                  //if the array to print is not composed exclusively of strings, then let's stringify it
                  //else join(' ')
                  if ( ! _.isEmpty( _.filter( _toArr, function( it ) { return ! _.isString( it ); } ) ) ) {
                        _toArr =  JSON.stringify( _toArr.join(' ') );
                  } else {
                        _toArr = _toArr.join(' ');
                  }
                  return [
                        '%c ' + _truncate( _toArr ),
                        [ 'background:' + args.bgCol, 'color:' + args.textCol, 'display: block;' ].join(';')
                  ];
            },

            _wrapLogInsideTags : function( title, msg, bgColor ) {
                  //fix for IE, because console is only defined when in F12 debugging mode in IE
                  if ( ( _.isUndefined( console ) && typeof window.console.log != 'function' ) )
                    return;
                  if ( sekPreviewLocalized.isDevMode ) {
                        if ( _.isUndefined( msg ) ) {
                              console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ '<' + title + '>' ] } ) );
                        } else {
                              console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ '<' + title + '>' ] } ) );
                              console.log( msg );
                              console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ '</' + title + '>' ] } ) );
                        }
                  } else {
                        console.log.apply( console, this._prettyPrintLog( { bgCol : bgColor, textCol : '#000', consoleArguments : [ title ] } ) );
                  }
            },

            errare : function( title, msg ) { this._wrapLogInsideTags( title, msg, '#ffd5a0' ); },
            infoLog : function( title, msg ) { this._wrapLogInsideTags( title, msg, '#5ed1f5' ); },

            //encapsulates a WordPress ajax request in a normalize method
            //@param queryParams = {}
            //
            //Important : we MUST use $.ajax() because when previewing, the ajax requests params are amended with a preFilter ( see customize-preview.js, $.ajaxPrefilter( prefilterAjax ) )in order to include params
            //in particular the 'customized' dirty values, that NB absolutely needs to dynamically register settings that have not yet been instantiated by WP_Customize_Manager
            // see WP Core => class WP_Customize_Manager, add_action( 'customize_register', array( $this, 'register_dynamic_settings' ), 11 );
            // see NB => class SEK_CZR_Dyn_Register
            // see NB => Nimble_Collection_Setting::filter_previewed_sek_get_skoped_seks => this is how we can get the sektions collection while customizing, see sek_get_skoped_seks()
            doAjax : function( queryParams ) {
                  var self = this;
                  //do we have a queryParams ?
                  queryParams = queryParams || ( _.isObject( queryParams ) ? queryParams : {} );

                  var ajaxUrl = queryParams.ajaxUrl || sekPreviewLocalized.ajaxUrl,//the ajaxUrl can be specified when invoking doAjax
                      nonce = sekPreviewLocalized.frontNonce,//{ 'id' => 'HuFrontNonce', 'handle' => wp_create_nonce( 'hu-front-nonce' ) },
                      dfd = $.Deferred(),
                      _query_ = _.extend( {
                                  action : '',
                                  withNonce : false
                            },
                            queryParams
                      );

                  // Check if the ajax url passes WP core customize-preview test
                  // Nov 2020 : added when fixing WPML compat https://github.com/presscustomizr/nimble-builder/issues/753
                  var urlParser = document.createElement( 'a' );
                      urlParser.href = ajaxUrl;
                  // Abort if the request is not for this site.
                  if ( ! api.isLinkPreviewable( urlParser, { allowAdminAjax: true } ) ) {
                      self.errare( 'self.doAjax => error => !api.isLinkPreviewable for action ' + _query_.action, urlParser );
                      return dfd.resolve().promise();
                  }

                  // HTTP ajaxurl when site is HTTPS causes Access-Control-Allow-Origin failure in Desktop and iOS Safari
                  if ( "https:" == document.location.protocol ) {
                        ajaxUrl = ajaxUrl.replace( "http://", "https://" );
                  }

                  //check if we're good
                  if ( _.isEmpty( _query_.action ) || ! _.isString( _query_.action ) ) {
                        self.errare( 'self.doAjax : unproper action provided' );
                        return dfd.resolve().promise();
                  }
                  //setup nonce
                  //Note : the nonce might be checked server side ( not in all cases, only when writing in db )  with check_ajax_referer( 'hu-front-nonce', 'HuFrontNonce' )
                  _query_[ nonce.id ] = nonce.handle;
                  if ( ! _.isObject( nonce ) || _.isUndefined( nonce.id ) || _.isUndefined( nonce.handle ) ) {
                        self.errare( 'self.doAjax : unproper nonce' );
                        return dfd.resolve().promise();
                  }

                  // introduced for https://github.com/presscustomizr/nimble-builder/issues/494
                  // september 2019
                  // this guid is used to differentiate dynamically rendered content from static content that may include a Nimble generated HTML structure
                  // an attribute "data-sek-preview-level-guid" is added to each rendered level when customizing or ajaxing
                  // otherwise the preview UI can be broken
                  _query_[ 'preview-level-guid' ] = sekPreviewLocalized.previewLevelGuid;

                  // introduced in october 2019 for https://github.com/presscustomizr/nimble-builder/issues/401
                  // Those query params are used for template tags related to the WP Query.
                  // Like "{{the_title}}" => in this case, using "get_the_title()" as callback when ajaxing with Nimble will return nothing. We need get_the_title( $post_id )
                  // That's why those static query params are written in the preview frame, and used as ajax params that we can access server side via php $_POST.
                  _query_.czr_query_params = JSON.stringify( _.isObject( _wpCustomizeSettings.czr_query_params ) ? _wpCustomizeSettings.czr_query_params : [] );

                  // note that the $_POST['customized'] param is set by core WP customizer-preview.js with $.ajaxPrefilter
                  $.post( ajaxUrl, _query_ )
                        .done( function( _r ) {
                              // Check if the user is logged out.
                              if ( '0' === _r ||  '-1' === _r || false === _r.success ) {
                                    self.errare( 'self.doAjax : done ajax error for action : ' + _query_.action , _r );
                                    dfd.reject( _r );
                              }
                              dfd.resolve( _r );

                        })
                        .fail( function( _r ) {
                              self.errare( 'self.doAjax : failed ajax error for : ' + _query_.action, _r );
                              dfd.reject( _r );
                        });
                        //.always( function( _r ) { dfd.resolve( _r ); });
                  return dfd.promise();
            },//doAjax






            // @return boolean
            isModuleRegistered : function( moduleType ) {
                  return sekPreviewLocalized.registeredModules && ! _.isUndefined( sekPreviewLocalized.registeredModules[ moduleType ] );
            },


            //@return mixed
            getRegisteredModuleProperty : function( moduleType, property ) {
                  if ( ! this.isModuleRegistered( moduleType ) ) {
                        return 'not_set';
                  }
                  return sekPreviewLocalized.registeredModules[ moduleType ][ property ];
            },

            // @params = { id : '', level : '' }
            // Recursively walk the level tree until a match is found
            // @return the level model object
            getLevelModel : function( id, collection ) {
                  var self = this, _data_ = 'no_match';
                  // do we have a collection ?
                  // if not, let's use the root one
                  if ( _.isUndefined( collection ) ) {
                        self.errare( 'getLevelModel => a collection must be provided' );
                  }
                  _.each( collection, function( levelData ) {
                        // did we have a match recursively ?
                        if ( 'no_match' != _data_ )
                          return;
                        if ( id === levelData.id ) {
                              _data_ = levelData;
                        } else {
                              if ( _.isArray( levelData.collection ) ) {
                                    _data_ = self.getLevelModel( id, levelData.collection );
                              }
                        }
                  });
                  return _data_;
            },
      });//$.extend()
})( wp.customize, jQuery, _ );//global sekPreviewLocalized
var SekPreviewPrototype = SekPreviewPrototype || {};
(function( api, $, _ ) {
      $.extend( SekPreviewPrototype, api.Events );
      var SekPreviewConstructor   = api.Class.extend( SekPreviewPrototype );
      api.bind( 'preview-ready', function(){
              api.preview.bind( 'active', function() {
                  try { api.sekPreview = new SekPreviewConstructor(); } catch( _er_ ) {
                        SekPreviewPrototype.errare( 'SekPreviewConstructor => problem on instantiation', _er_ );
                  }
            });
      });
})( wp.customize, jQuery, _ );
