PageSourceSearch

https://yemailarquitectura.co/wp-content/plugins/cmssuperheroes/assets/js/jquery.shuffle.js?ver=7.1.3

js yemailarquitectura.co collected 2026-10-10 01:22:43 UTC 44,501 bytes, 1,582 lines download raw bytes

1/*!
2 * Shuffle.js by @Vestride
3 * Categorize, sort, and filter a responsive grid of items.
4 * Dependencies: jQuery 1.9+, Modernizr 2.6.2+
5 * @license MIT license
6 * @version 3.0.0
7 */
8(function (factory) {
9  if (typeof define === 'function' && define.amd) {
10    define(['jquery', 'modernizr'], factory);
11  } else {
12    window.Shuffle = factory(window.jQuery, window.Modernizr);
13  }
14})(function($, Modernizr, undefined) {
15
16'use strict';
17
18
19// Validate Modernizr exists.
20// Shuffle requires `csstransitions`, `csstransforms`, `csstransforms3d`,
21// and `prefixed` to exist on the Modernizr object.
22if (typeof Modernizr !== 'object') {
23  throw new Error('Shuffle.js requires Modernizr.\n' +
24      'http://vestride.github.io/Shuffle/#dependencies');
25}
26
27
28/**
29 * Returns css prefixed properties like `-webkit-transition` or `box-sizing`
30 * from `transition` or `boxSizing`, respectively.
31 * @param {(string|boolean)} prop Property to be prefixed.
32 * @return {string} The prefixed css property.
33 */
34function dashify( prop ) {
35  if (!prop) {
36    return '';
37  }
38
39  // Replace upper case with dash-lowercase,
40  // then fix ms- prefixes because they're not capitalized.
41  return prop.replace(/([A-Z])/g, function( str, m1 ) {
42    return '-' + m1.toLowerCase();
43  }).replace(/^ms-/,'-ms-');
44}
45
46// Constant, prefixed variables.
47var TRANSITION = Modernizr.prefixed('transition');
48var TRANSITION_DELAY = Modernizr.prefixed('transitionDelay');
49var TRANSITION_DURATION = Modernizr.prefixed('transitionDuration');
50
51// Note(glen): Stock Android 4.1.x browser will fail here because it wrongly
52// says it supports non-prefixed transitions.
53// https://github.com/Modernizr/Modernizr/issues/897
54var TRANSITIONEND = {
55  'WebkitTransition' : 'webkitTransitionEnd',
56  'transition' : 'transitionend'
57}[ TRANSITION ];
58
59var TRANSFORM = Modernizr.prefixed('transform');
60var CSS_TRANSFORM = dashify(TRANSFORM);
61
62// Constants
63var CAN_TRANSITION_TRANSFORMS = Modernizr.csstransforms && Modernizr.csstransitions;
64var HAS_TRANSFORMS_3D = Modernizr.csstransforms3d;
65var SHUFFLE = 'shuffle';
66var COLUMN_THRESHOLD = 0.3;
67
68// Configurable. You can change these constants to fit your application.
69// The default scale and concealed scale, however, have to be different values.
70var ALL_ITEMS = 'all';
71var FILTER_ATTRIBUTE_KEY = 'groups';
72var DEFAULT_SCALE = 1;
73var CONCEALED_SCALE = 0.001;
74
75
76// Underscore's throttle function.
77function throttle(func, wait, options) {
78  var context, args, result;
79  var timeout = null;
80  var previous = 0;
81  options = options || {};
82  var later = function() {
83    previous = options.leading === false ? 0 : $.now();
84    timeout = null;
85    result = func.apply(context, args);
86    context = args = null;
87  };
88  return function() {
89    var now = $.now();
90    if (!previous && options.leading === false) {
91      previous = now;
92    }
93    var remaining = wait - (now - previous);
94    context = this;
95    args = arguments;
96    if (remaining <= 0 || remaining > wait) {
97      clearTimeout(timeout);
98      timeout = null;
99      previous = now;
100      result = func.apply(context, args);
101      context = args = null;
102    } else if (!timeout && options.trailing !== false) {
103      timeout = setTimeout(later, remaining);
104    }
105    return result;
106  };
107}
108
109function each(obj, iterator, context) {
110  for (var i = 0, length = obj.length; i < length; i++) {
111    if (iterator.call(context, obj[i], i, obj) === {}) {
112      return;
113    }
114  }
115}
116
117function defer(fn, context, wait) {
118  return setTimeout( $.proxy( fn, context ), wait );
119}
120
121function arrayMax( array ) {
122  return Math.max.apply( Math, array );
123}
124
125function arrayMin( array ) {
126  return Math.min.apply( Math, array );
127}
128
129
130/**
131 * Always returns a numeric value, given a value.
132 * @param {*} value Possibly numeric value.
133 * @return {number} `value` or zero if `value` isn't numeric.
134 * @private
135 */
136function getNumber(value) {
137  return $.isNumeric(value) ? value : 0;
138}
139
140
141/**
142 * Represents a coordinate pair.
143 * @param {number} [x=0] X.
144 * @param {number} [y=0] Y.
145 */
146var Point = function(x, y) {
147  this.x = getNumber( x );
148  this.y = getNumber( y );
149};
150
151
152/**
153 * Whether two points are equal.
154 * @param {Point} a Point A.
155 * @param {Point} b Point B.
156 * @return {boolean}
157 */
158Point.equals = function(a, b) {
159  return a.x === b.x && a.y === b.y;
160};
161
162
163// Used for unique instance variables
164var id = 0;
165var $window = $( window );
166
167
168/**
169 * Categorize, sort, and filter a responsive grid of items.
170 *
171 * @param {Element} element An element which is the parent container for the grid items.
172 * @param {Object} [options=Shuffle.options] Options object.
173 * @constructor
174 */
175var Shuffle = function( element, options ) {
176  options = options || {};
177  $.extend( this, Shuffle.options, options, Shuffle.settings );
178
179  this.$el = $(element);
180  this.element = element;
181  this.unique = 'shuffle_' + id++;
182
183  this._fire( Shuffle.EventType.LOADING );
184  this._init();
185
186  // Dispatch the done event asynchronously so that people can bind to it after
187  // Shuffle has been initialized.
188  defer(function() {
189    this.initialized = true;
190    this._fire( Shuffle.EventType.DONE );
191  }, this, 16);
192};
193
194
195/**
196 * Events the container element emits with the .shuffle namespace.
197 * For example, "done.shuffle".
198 * @enum {string}
199 */
200Shuffle.EventType = {
201  LOADING: 'loading',
202  DONE: 'done',
203  LAYOUT: 'layout',
204  REMOVED: 'removed'
205};
206
207
208/** @enum {string} */
209Shuffle.ClassName = {
210  BASE: SHUFFLE,
211  SHUFFLE_ITEM: 'shuffle-item',
212  FILTERED: 'filtered',
213  CONCEALED: 'concealed'
214};
215
216
217// Overrideable options
218Shuffle.options = {
219  group: ALL_ITEMS, // Initial filter group.
220  speed: 250, // Transition/animation speed (milliseconds).
221  easing: 'ease-out', // CSS easing function to use.
222  itemSelector: '', // e.g. '.picture-item'.
223  sizer: null, // Sizer element. Use an element to determine the size of columns and gutters.
224  gutterWidth: 0, // A static number or function that tells the plugin how wide the gutters between columns are (in pixels).
225  columnWidth: 0, // A static number or function that returns a number which tells the plugin how wide the columns are (in pixels).
226  delimeter: null, // If your group is not json, and is comma delimeted, you could set delimeter to ','.
227  buffer: 0, // Useful for percentage based heights when they might not always be exactly the same (in pixels).
228  initialSort: null, // Shuffle can be initialized with a sort object. It is the same object given to the sort method.
229  throttle: throttle, // By default, shuffle will throttle resize events. This can be changed or removed.
230  throttleTime: 300, // How often shuffle can be called on resize (in milliseconds).
231  sequentialFadeDelay: 150, // Delay between each item that fades in when adding items.
232  supported: CAN_TRANSITION_TRANSFORMS // Whether to use transforms or absolute positioning.
233};
234
235
236// Not overrideable
237Shuffle.settings = {
238  useSizer: false,
239  itemCss : { // default CSS for each item
240    position: 'absolute',
241    top: 0,
242    left: 0,
243    visibility: 'visible'
244  },
245  revealAppendedDelay: 300,
246  lastSort: {},
247  lastFilter: ALL_ITEMS,
248  enabled: true,
249  destroyed: false,
250  initialized: false,
251  _animations: [],
252  styleQueue: []
253};
254
255
256// Expose for testing.
257Shuffle.Point = Point;
258
259
260/**
261 * Static methods.
262 */
263
264/**
265 * If the browser has 3d transforms available, build a string with those,
266 * otherwise use 2d transforms.
267 * @param {Point} point X and Y positions.
268 * @param {number} scale Scale amount.
269 * @return {string} A normalized string which can be used with the transform style.
270 * @private
271 */
272Shuffle._getItemTransformString = function(point, scale) {
273  if ( HAS_TRANSFORMS_3D ) {
274    return 'translate3d(' + point.x + 'px, ' + point.y + 'px, 0) scale3d(' + scale + ', ' + scale + ', 1)';
275  } else {
276    return 'translate(' + point.x + 'px, ' + point.y + 'px) scale(' + scale + ')';
277  }
278};
279
280
281/**
282 * Retrieve the computed style for an element, parsed as a float. This should
283 * not be used for width or height values because jQuery mangles them and they
284 * are not precise enough.
285 * @param {Element} element Element to get style for.
286 * @param {string} style Style property.
287 * @return {number} The parsed computed value or zero if that fails because IE
288 *     will return 'auto' when the element doesn't have margins instead of
289 *     the computed style.
290 * @private
291 */
292Shuffle._getNumberStyle = function( element, style ) {
293  return Shuffle._getFloat( $( element ).css( style )  );
294};
295
296
297/**
298 * Parse a string as an integer.
299 * @param {string} value String integer.
300 * @return {number} The string as an integer or zero.
301 * @private
302 */
303Shuffle._getInt = function(value) {
304  return getNumber( parseInt( value, 10 ) );
305};
306
307/**
308 * Parse a string as an float.
309 * @param {string} value String float.
310 * @return {number} The string as an float or zero.
311 * @private
312 */
313Shuffle._getFloat = function(value) {
314  return getNumber( parseFloat( value ) );
315};
316
317
318/**
319 * Returns the outer width of an element, optionally including its margins.
320 * The `offsetWidth` property must be used because having a scale transform
321 * on the element affects the bounding box. Sadly, Firefox doesn't return an
322 * integer value for offsetWidth (yet).
323 * @param {Element} element The element.
324 * @param {boolean} [includeMargins] Whether to include margins. Default is false.
325 * @return {number} The width.
326 */
327Shuffle._getOuterWidth = function( element, includeMargins ) {
328  var width = element.offsetWidth;
329
330  // Use jQuery here because it uses getComputedStyle internally and is
331  // cross-browser. Using the style property of the element will only work
332  // if there are inline styles.
333  if ( includeMargins ) {
334    var marginLeft = Shuffle._getNumberStyle( element, 'marginLeft');
335    var marginRight = Shuffle._getNumberStyle( element, 'marginRight');
336    width += marginLeft + marginRight;
337  }
338
339  return width;
340};
341
342
343/**
344 * Returns the outer height of an element, optionally including its margins.
345 * @param {Element} element The element.
346 * @param {boolean} [includeMargins] Whether to include margins. Default is false.
347 * @return {number} The height.
348 */
349Shuffle._getOuterHeight = function( element, includeMargins ) {
350  var height = element.offsetHeight;
351
352  if ( includeMargins ) {
353    var marginTop = Shuffle._getNumberStyle( element, 'marginTop');
354    var marginBottom = Shuffle._getNumberStyle( element, 'marginBottom');
355    height += marginTop + marginBottom;
356  }
357
358  return height;
359};
360
361
362/**
363 * Change a property or execute a function which will not have a transition
364 * @param {Element} element DOM element that won't be transitioned
365 * @param {Function} callback A function which will be called while transition
366 *     is set to 0ms.
367 * @param {Object} [context] Optional context for the callback function.
368 * @private
369 */
370Shuffle._skipTransition = function( element, callback, context ) {
371  var duration = element.style[ TRANSITION_DURATION ];
372
373  // Set the duration to zero so it happens immediately
374  element.style[ TRANSITION_DURATION ] = '0ms'; // ms needed for firefox!
375
376  callback.call( context );
377
378  // Force reflow
379  var reflow = element.offsetWidth;
380  // Avoid jshint warnings: unused variables and expressions.
381  reflow = null;
382
383  // Put the duration back
384  element.style[ TRANSITION_DURATION ] = duration;
385};
386
387
388/**
389 * Instance methods.
390 */
391
392Shuffle.prototype._init = function() {
393  this.$items = this._getItems();
394
395  this.sizer = this._getElementOption( this.sizer );
396
397  if ( this.sizer ) {
398    this.useSizer = true;
399  }
400
401  // Add class and invalidate styles
402  this.$el.addClass( Shuffle.ClassName.BASE );
403
404  // Set initial css for each item
405  this._initItems();
406
407  // Bind resize events
408  // http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer
409  $window.on('resize.' + SHUFFLE + '.' + this.unique, this._getResizeFunction());
410
411  // Get container css all in one request. Causes reflow
412  var containerCSS = this.$el.css(['position', 'overflow']);
413  var containerWidth = Shuffle._getOuterWidth( this.element );
414
415  // Add styles to the container if it doesn't have them.
416  this._validateStyles( containerCSS );
417
418  // We already got the container's width above, no need to cause another reflow getting it again...
419  // Calculate the number of columns there will be
420  this._setColumns( containerWidth );
421
422  // Kick off!
423  this.shuffle( this.group, this.initialSort );
424
425  // The shuffle items haven't had transitions set on them yet
426  // so the user doesn't see the first layout. Set them now that the first layout is done.
427  if ( this.supported ) {
428    defer(function() {
429      this._setTransitions();
430      this.element.style[ TRANSITION ] = 'height ' + this.speed + 'ms ' + this.easing;
431    }, this);
432  }
433};
434
435
436/**
437 * Returns a throttled and proxied function for the resize handler.
438 * @return {Function}
439 * @private
440 */
441Shuffle.prototype._getResizeFunction = function() {
442  var resizeFunction = $.proxy( this._onResize, this );
443  return this.throttle ?
444      this.throttle( resizeFunction, this.throttleTime ) :
445      resizeFunction;
446};
447
448
449/**
450 * Retrieve an element from an option.
451 * @param {string|jQuery|Element} option The option to check.
452 * @return {?Element} The plain element or null.
453 * @private
454 */
455Shuffle.prototype._getElementOption = function( option ) {
456  // If column width is a string, treat is as a selector and search for the
457  // sizer element within the outermost container
458  if ( typeof option === 'string' ) {
459    return this.$el.find( option )[0] || null;
460
461  // Check for an element
462  } else if ( option && option.nodeType && option.nodeType === 1 ) {
463    return option;
464
465  // Check for jQuery object
466  } else if ( option && option.jquery ) {
467    return option[0];
468  }
469
470  return null;
471};
472
473
474/**
475 * Ensures the shuffle container has the css styles it needs applied to it.
476 * @param {Object} styles Key value pairs for position and overflow.
477 * @private
478 */
479Shuffle.prototype._validateStyles = function(styles) {
480  // Position cannot be static.
481  if ( styles.position === 'static' ) {
482    this.element.style.position = 'relative';
483  }
484
485  // Overflow has to be hidden
486  if ( styles.overflow !== 'hidden' ) {
487    this.element.style.overflow = 'hidden';
488  }
489};
490
491
492/**
493 * Filter the elements by a category.
494 * @param {string} [category] Category to filter by. If it's given, the last
495 *     category will be used to filter the items.
496 * @param {ArrayLike} [$collection] Optionally filter a collection. Defaults to
497 *     all the items.
498 * @return {jQuery} Filtered items.
499 * @private
500 */
501Shuffle.prototype._filter = function( category, $collection ) {
502  category = category || this.lastFilter;
503  $collection = $collection || this.$items;
504
505  var set = this._getFilteredSets( category, $collection );
506
507  // Individually add/remove concealed/filtered classes
508  this._toggleFilterClasses( set.filtered, set.concealed );
509
510  // Save the last filter in case elements are appended.
511  this.lastFilter = category;
512
513  // This is saved mainly because providing a filter function (like searching)
514  // will overwrite the `lastFilter` property every time its called.
515  if ( typeof category === 'string' ) {
516    this.group = category;
517  }
518
519  return set.filtered;
520};
521
522
523/**
524 * Returns an object containing the filtered and concealed elements.
525 * @param {string|Function} category Category or function to filter by.
526 * @param {ArrayLike.<Element>} $items A collection of items to filter.
527 * @return {!{filtered: jQuery, concealed: jQuery}}
528 * @private
529 */
530Shuffle.prototype._getFilteredSets = function( category, $items ) {
531  var $filtered = $();
532  var $concealed = $();
533
534  // category === 'all', add filtered class to everything
535  if ( category === ALL_ITEMS ) {
536    $filtered = $items;
537
538  // Loop through each item and use provided function to determine
539  // whether to hide it or not.
540  } else {
541    each($items, function( el ) {
542      var $item = $(el);
543      if ( this._doesPassFilter( category, $item ) ) {
544        $filtered = $filtered.add( $item );
545      } else {
546        $concealed = $concealed.add( $item );
547      }
548    }, this);
549  }
550
551  return {
552    filtered: $filtered,
553    concealed: $concealed
554  };
555};
556
557
558/**
559 * Test an item to see if it passes a category.
560 * @param {string|Function} category Category or function to filter by.
561 * @param {jQuery} $item A single item, wrapped with jQuery.
562 * @return {boolean} Whether it passes the category/filter.
563 * @private
564 */
565Shuffle.prototype._doesPassFilter = function( category, $item ) {
566  if ( $.isFunction( category ) ) {
567    return category.call( $item[0], $item, this );
568
569  // Check each element's data-groups attribute against the given category.
570  } else {
571    var groups = $item.data( FILTER_ATTRIBUTE_KEY );
572    var keys = this.delimeter && !$.isArray( groups ) ?
573        groups.split( this.delimeter ) :
574        groups;
575    return $.inArray(category, keys) > -1;
576  }
577};
578
579
580/**
581 * Toggles the filtered and concealed class names.
582 * @param {jQuery} $filtered Filtered set.
583 * @param {jQuery} $concealed Concealed set.
584 * @private
585 */
586Shuffle.prototype._toggleFilterClasses = function( $filtered, $concealed ) {
587  $filtered
588    .removeClass( Shuffle.ClassName.CONCEALED )
589    .addClass( Shuffle.ClassName.FILTERED );
590  $concealed
591    .removeClass( Shuffle.ClassName.FILTERED )
592    .addClass( Shuffle.ClassName.CONCEALED );
593};
594
595
596/**
597 * Set the initial css for each item
598 * @param {jQuery} [$items] Optionally specifiy at set to initialize
599 */
600Shuffle.prototype._initItems = function( $items ) {
601  $items = $items || this.$items;
602  $items.addClass([
603    Shuffle.ClassName.SHUFFLE_ITEM,
604    Shuffle.ClassName.FILTERED
605  ].join(' '));
606  $items.css( this.itemCss ).data('point', new Point()).data('scale', DEFAULT_SCALE);
607};
608
609
610/**
611 * Updates the filtered item count.
612 * @private
613 */
614Shuffle.prototype._updateItemCount = function() {
615  this.visibleItems = this._getFilteredItems().length;
616};
617
618
619/**
620 * Sets css transform transition on a an element.
621 * @param {Element} element Element to set transition on.
622 * @private
623 */
624Shuffle.prototype._setTransition = function( element ) {
625  element.style[ TRANSITION ] = CSS_TRANSFORM + ' ' + this.speed + 'ms ' +
626    this.easing + ', opacity ' + this.speed + 'ms ' + this.easing;
627};
628
629
630/**
631 * Sets css transform transition on a group of elements.
632 * @param {ArrayLike.<Element>} $items Elements to set transitions on.
633 * @private
634 */
635Shuffle.prototype._setTransitions = function( $items ) {
636  $items = $items || this.$items;
637  each($items, function( el ) {
638    this._setTransition( el );
639  }, this);
640};
641
642
643/**
644 * Sets a transition delay on a collection of elements, making each delay
645 * greater than the last.
646 * @param {ArrayLike.<Element>} $collection Array to iterate over.
647 */
648Shuffle.prototype._setSequentialDelay = function( $collection ) {
649  if ( !this.supported ) {
650    return;
651  }
652
653  // $collection can be an array of dom elements or jquery object
654  each($collection, function( el, i ) {
655    // This works because the transition-property: transform, opacity;
656    el.style[ TRANSITION_DELAY ] = '0ms,' + ((i + 1) * this.sequentialFadeDelay) + 'ms';
657  }, this);
658};
659
660
661Shuffle.prototype._getItems = function() {
662  return this.$el.children( this.itemSelector );
663};
664
665
666Shuffle.prototype._getFilteredItems = function() {
667  return this.$items.filter('.' + Shuffle.ClassName.FILTERED);
668};
669
670
671Shuffle.prototype._getConcealedItems = function() {
672  return this.$items.filter('.' + Shuffle.ClassName.CONCEALED);
673};
674
675
676/**
677 * Returns the column size, based on column width and sizer options.
678 * @param {number} containerWidth Size of the parent container.
679 * @param {number} gutterSize Size of the gutters.
680 * @return {number}
681 * @private
682 */
683Shuffle.prototype._getColumnSize = function( containerWidth, gutterSize ) {
684  var size;
685
686  // If the columnWidth property is a function, then the grid is fluid
687  if ( $.isFunction( this.columnWidth ) ) {
688    size = this.columnWidth(containerWidth);
689
690  // columnWidth option isn't a function, are they using a sizing element?
691  } else if ( this.useSizer ) {
692    size = Shuffle._getOuterWidth(this.sizer);
693
694  // if not, how about the explicitly set option?
695  } else if ( this.columnWidth ) {
696    size = this.columnWidth;
697
698  // or use the size of the first item
699  } else if ( this.$items.length > 0 ) {
700    size = Shuffle._getOuterWidth(this.$items[0], true);
701
702  // if there's no items, use size of container
703  } else {
704    size = containerWidth;
705  }
706
707  // Don't let them set a column width of zero.
708  if ( size === 0 ) {
709    size = containerWidth;
710  }
711
712  return size + gutterSize;
713};
714
715
716/**
717 * Returns the gutter size, based on gutter width and sizer options.
718 * @param {number} containerWidth Size of the parent container.
719 * @return {number}
720 * @private
721 */
722Shuffle.prototype._getGutterSize = function( containerWidth ) {
723  var size;
724  if ( $.isFunction( this.gutterWidth ) ) {
725    size = this.gutterWidth(containerWidth);
726  } else if ( this.useSizer ) {
727    size = Shuffle._getNumberStyle(this.sizer, 'marginLeft');
728  } else {
729    size = this.gutterWidth;
730  }
731
732  return size;
733};
734
735
736/**
737 * Calculate the number of columns to be used. Gets css if using sizer element.
738 * @param {number} [theContainerWidth] Optionally specify a container width if it's already available.
739 */
740Shuffle.prototype._setColumns = function( theContainerWidth ) {
741  var containerWidth = theContainerWidth || Shuffle._getOuterWidth( this.element );
742  var gutter = this._getGutterSize( containerWidth );
743  var columnWidth = this._getColumnSize( containerWidth, gutter );
744  var calculatedColumns = (containerWidth + gutter) / columnWidth;
745
746  // Widths given from getComputedStyle are not precise enough...
747  if ( Math.abs(Math.round(calculatedColumns) - calculatedColumns) < COLUMN_THRESHOLD ) {
748    // e.g. calculatedColumns = 11.998876
749    calculatedColumns = Math.round( calculatedColumns );
750  }
751
752  this.cols = Math.max( Math.floor(calculatedColumns), 1 );
753  this.containerWidth = containerWidth;
754  this.colWidth = columnWidth;
755};
756
757/**
758 * Adjust the height of the grid
759 */
760Shuffle.prototype._setContainerSize = function() {
761  this.$el.css( 'height', this._getContainerSize() );
762};
763
764
765/**
766 * Based on the column heights, it returns the biggest one.
767 * @return {number}
768 * @private
769 */
770Shuffle.prototype._getContainerSize = function() {
771  return arrayMax( this.positions );
772};
773
774
775/**
776 * Fire events with .shuffle namespace
777 */
778Shuffle.prototype._fire = function( name, args ) {
779  this.$el.trigger( name + '.' + SHUFFLE, args && args.length ? args : [ this ] );
780};
781
782
783/**
784 * Zeros out the y columns array, which is used to determine item placement.
785 * @private
786 */
787Shuffle.prototype._resetCols = function() {
788  var i = this.cols;
789  this.positions = [];
790  while (i--) {
791    this.positions.push( 0 );
792  }
793};
794
795
796/**
797 * Loops through each item that should be shown and calculates the x, y position.
798 * @param {Array.<Element>} items Array of items that will be shown/layed out in order in their array.
799 *     Because jQuery collection are always ordered in DOM order, we can't pass a jq collection.
800 * @param {boolean} [isOnlyPosition=false] If true this will position the items with zero opacity.
801 */
802Shuffle.prototype._layout = function( items, isOnlyPosition ) {
803  each(items, function( item ) {
804    this._layoutItem( item, !!isOnlyPosition );
805  }, this);
806
807  // `_layout` always happens after `_shrink`, so it's safe to process the style
808  // queue here with styles from the shrink method.
809  this._processStyleQueue();
810
811  // Adjust the height of the container.
812  this._setContainerSize();
813};
814
815
816/**
817 * Calculates the position of the item and pushes it onto the style queue.
818 * @param {Element} item Element which is being positioned.
819 * @param {boolean} isOnlyPosition Whether to position the item, but with zero
820 *     opacity so that it can fade in later.
821 * @private
822 */
823Shuffle.prototype._layoutItem = function( item, isOnlyPosition ) {
824  var $item = $(item);
825  var itemData = $item.data();
826  var currPos = itemData.point;
827  var currScale = itemData.scale;
828  var itemSize = {
829    width: Shuffle._getOuterWidth( item, true ),
830    height: Shuffle._getOuterHeight( item, true )
831  };
832  var pos = this._getItemPosition( itemSize );
833
834  // If the item will not change its position, do not add it to the render
835  // queue. Transitions don't fire when setting a property to the same value.
836  if ( Point.equals(currPos, pos) && currScale === DEFAULT_SCALE ) {
837    return;
838  }
839
840  // Save data for shrink
841  itemData.point = pos;
842  itemData.scale = DEFAULT_SCALE;
843
844  this.styleQueue.push({
845    $item: $item,
846    point: pos,
847    scale: DEFAULT_SCALE,
848    opacity: isOnlyPosition ? 0 : 1,
849    skipTransition: isOnlyPosition,
850    callfront: function() {
851      if ( !isOnlyPosition ) {
852        $item.css( 'visibility', 'visible' );
853      }
854    },
855    callback: function() {
856      if ( isOnlyPosition ) {
857        $item.css( 'visibility', 'hidden' );
858      }
859    }
860  });
861};
862
863
864/**
865 * Determine the location of the next item, based on its size.
866 * @param {{width: number, height: number}} itemSize Object with width and height.
867 * @return {Point}
868 * @private
869 */
870Shuffle.prototype._getItemPosition = function( itemSize ) {
871  var columnSpan = this._getColumnSpan( itemSize.width, this.colWidth, this.cols );
872
873  var setY = this._getColumnSet( columnSpan, this.cols );
874
875  // Finds the index of the smallest number in the set.
876  var shortColumnIndex = this._getShortColumn( setY, this.buffer );
877
878  // Position the item
879  var point = new Point(
880    Math.round( this.colWidth * shortColumnIndex ),
881    Math.round( setY[shortColumnIndex] ));
882
883  // Update the columns array with the new values for each column.
884  // e.g. before the update the columns could be [250, 0, 0, 0] for an item
885  // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0].
886  var setHeight = setY[shortColumnIndex] + itemSize.height;
887  var setSpan = this.cols + 1 - setY.length;
888  for ( var i = 0; i < setSpan; i++ ) {
889    this.positions[ shortColumnIndex + i ] = setHeight;
890  }
891
892  return point;
893};
894
895
896/**
897 * Determine the number of columns an items spans.
898 * @param {number} itemWidth Width of the item.
899 * @param {number} columnWidth Width of the column (includes gutter).
900 * @param {number} columns Total number of columns
901 * @return {number}
902 * @private
903 */
904Shuffle.prototype._getColumnSpan = function( itemWidth, columnWidth, columns ) {
905  var columnSpan = itemWidth / columnWidth;
906
907  // If the difference between the rounded column span number and the
908  // calculated column span number is really small, round the number to
909  // make it fit.
910  if ( Math.abs(Math.round( columnSpan ) - columnSpan ) < COLUMN_THRESHOLD ) {
911    // e.g. columnSpan = 4.0089945390298745
912    columnSpan = Math.round( columnSpan );
913  }
914
915  // Ensure the column span is not more than the amount of columns in the whole layout.
916  return Math.min( Math.ceil( columnSpan ), columns );
917};
918
919
920/**
921 * Retrieves the column set to use for placement.
922 * @param {number} columnSpan The number of columns this current item spans.
923 * @param {number} columns The total columns in the grid.
924 * @return {Array.<number>} An array of numbers represeting the column set.
925 * @private
926 */
927Shuffle.prototype._getColumnSet = function( columnSpan, columns ) {
928  // The item spans only one column.
929  if ( columnSpan === 1 ) {
930    return this.positions;
931
932  // The item spans more than one column, figure out how many different
933  // places it could fit horizontally.
934  // The group count is the number of places within the positions this block
935  // could fit, ignoring the current positions of items.
936  // Imagine a 2 column brick as the second item in a 4 column grid with
937  // 10px height each. Find the places it would fit:
938  // [10, 0, 0, 0]
939  //  |   |  |
940  //  *   *  *
941  //
942  // Then take the places which fit and get the bigger of the two:
943  // max([10, 0]), max([0, 0]), max([0, 0]) = [10, 0, 0]
944  //
945  // Next, find the first smallest number (the short column).
946  // [10, 0, 0]
947  //      |
948  //      *
949  //
950  // And that's where it should be placed!
951  } else {
952    var groupCount = columns + 1 - columnSpan;
953    var groupY = [];
954
955    // For how many possible positions for this item there are.
956    for ( var i = 0; i < groupCount; i++ ) {
957      // Find the bigger value for each place it could fit.
958      groupY[i] = arrayMax( this.positions.slice( i, i + columnSpan ) );
959    }
960
961    return groupY;
962  }
963};
964
965
966/**
967 * Find index of short column, the first from the left where this item will go.
968 *
969 * @param {Array.<number>} positions The array to search for the smallest number.
970 * @param {number} buffer Optional buffer which is very useful when the height
971 *     is a percentage of the width.
972 * @return {number} Index of the short column.
973 * @private
974 */
975Shuffle.prototype._getShortColumn = function( positions, buffer ) {
976  var minPosition = arrayMin( positions );
977  for (var i = 0, len = positions.length; i < len; i++) {
978    if ( positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer ) {
979      return i;
980    }
981  }
982  return 0;
983};
984
985
986/**
987 * Hides the elements that don't match our filter.
988 * @param {jQuery} $collection jQuery collection to shrink.
989 * @private
990 */
991Shuffle.prototype._shrink = function( $collection ) {
992  var $concealed = $collection || this._getConcealedItems();
993
994  each($concealed, function( item ) {
995    var $item = $(item);
996    var itemData = $item.data();
997
998    // Continuing would add a transitionend event listener to the element, but
999    // that listener would not execute because the transform and opacity would
1000    // stay the same.
1001    if ( itemData.scale === CONCEALED_SCALE ) {
1002      return;
1003    }
1004
1005    itemData.scale = CONCEALED_SCALE;
1006
1007    this.styleQueue.push({
1008      $item: $item,
1009      point: itemData.point,
1010      scale : CONCEALED_SCALE,
1011      opacity: 0,
1012      callback: function() {
1013        $item.css( 'visibility', 'hidden' );
1014      }
1015    });
1016  }, this);
1017};
1018
1019
1020/**
1021 * Resize handler.
1022 * @private
1023 */
1024Shuffle.prototype._onResize = function() {
1025  // If shuffle is disabled, destroyed, don't do anything
1026  if ( !this.enabled || this.destroyed || this.isTransitioning ) {
1027    return;
1028  }
1029
1030  // Will need to check height in the future if it's layed out horizontaly
1031  var containerWidth = Shuffle._getOuterWidth( this.element );
1032
1033  // containerWidth hasn't changed, don't do anything
1034  if ( containerWidth === this.containerWidth ) {
1035    return;
1036  }
1037
1038  this.update();
1039};
1040
1041
1042/**
1043 * Returns styles for either jQuery animate or transition.
1044 * @param {Object} opts Transition options.
1045 * @return {!Object} Transforms for transitions, left/top for animate.
1046 * @private
1047 */
1048Shuffle.prototype._getStylesForTransition = function( opts ) {
1049  var styles = {
1050    opacity: opts.opacity
1051  };
1052
1053  if ( this.supported ) {
1054    styles[ TRANSFORM ] = Shuffle._getItemTransformString( opts.point, opts.scale );
1055  } else {
1056    styles.left = opts.point.x;
1057    styles.top = opts.point.y;
1058  }
1059
1060  return styles;
1061};
1062
1063
1064/**
1065 * Transitions an item in the grid
1066 *
1067 * @param {Object} opts options.
1068 * @param {jQuery} opts.$item jQuery object representing the current item.
1069 * @param {Point} opts.point A point object with the x and y coordinates.
1070 * @param {number} opts.scale Amount to scale the item.
1071 * @param {number} opts.opacity Opacity of the item.
1072 * @param {Function} opts.callback Complete function for the animation.
1073 * @param {Function} opts.callfront Function to call before transitioning.
1074 * @private
1075 */
1076Shuffle.prototype._transition = function( opts ) {
1077  var styles = this._getStylesForTransition( opts );
1078  this._startItemAnimation( opts.$item, styles, opts.callfront || $.noop, opts.callback || $
1078.noop );
1079};
1080
1081
1082Shuffle.prototype._startItemAnimation = function( $item, styles, callfront, callback ) {
1083  // Transition end handler removes its listener.
1084  function handleTransitionEnd( evt ) {
1085    // Make sure this event handler has not bubbled up from a child.
1086    if ( evt.target === evt.currentTarget ) {
1087      $( evt.target ).off( TRANSITIONEND, handleTransitionEnd );
1088      callback();
1089    }
1090  }
1091
1092  callfront();
1093
1094  // Transitions are not set until shuffle has loaded to avoid the initial transition.
1095  if ( !this.initialized ) {
1096    $item.css( styles );
1097    callback();
1098    return;
1099  }
1100
1101  // Use CSS Transforms if we have them
1102  if ( this.supported ) {
1103    $item.css( styles );
1104    $item.on( TRANSITIONEND, handleTransitionEnd );
1105
1106  // Use jQuery to animate left/top
1107  } else {
1108    // Save the deferred object which jQuery returns.
1109    var anim = $item.stop( true ).animate( styles, this.speed, 'swing', callback );
1110    // Push the animation to the list of pending animations.
1111    this._animations.push( anim.promise() );
1112  }
1113};
1114
1115
1116/**
1117 * Execute the styles gathered in the style queue. This applies styles to elements,
1118 * triggering transitions.
1119 * @param {boolean} noLayout Whether to trigger a layout event.
1120 * @private
1121 */
1122Shuffle.prototype._processStyleQueue = function( noLayout ) {
1123  var $transitions = $();
1124
1125  // Iterate over the queue and keep track of ones that use transitions.
1126  each(this.styleQueue, function( transitionObj ) {
1127    if ( transitionObj.skipTransition ) {
1128      this._styleImmediately( transitionObj );
1129    } else {
1130      $transitions = $transitions.add( transitionObj.$item );
1131      this._transition( transitionObj );
1132    }
1133  }, this);
1134
1135
1136  if ( $transitions.length > 0 && this.initialized ) {
1137    // Set flag that shuffle is currently in motion.
1138    this.isTransitioning = true;
1139
1140    if ( this.supported ) {
1141      this._whenCollectionDone( $transitions, TRANSITIONEND, this._movementFinished );
1142
1143    // The _transition function appends a promise to the animations array.
1144    // When they're all complete, do things.
1145    } else {
1146      this._whenAnimationsDone( this._movementFinished );
1147    }
1148
1149  // A call to layout happened, but none of the newly filtered items will
1150  // change position. Asynchronously fire the callback here.
1151  } else if ( !noLayout ) {
1152    defer( this._layoutEnd, this );
1153  }
1154
1155  // Remove everything in the style queue
1156  this.styleQueue.length = 0;
1157};
1158
1159
1160/**
1161 * Apply styles without a transition.
1162 * @param {Object} opts Transitions options object.
1163 * @private
1164 */
1165Shuffle.prototype._styleImmediately = function( opts ) {
1166  Shuffle._skipTransition(opts.$item[0], function() {
1167    opts.$item.css( this._getStylesForTransition( opts ) );
1168  }, this);
1169};
1170
1171Shuffle.prototype._movementFinished = function() {
1172  this.isTransitioning = false;
1173  this._layoutEnd();
1174};
1175
1176Shuffle.prototype._layoutEnd = function() {
1177  this._fire( Shuffle.EventType.LAYOUT );
1178};
1179
1180Shuffle.prototype._addItems = function( $newItems, addToEnd, isSequential ) {
1181  // Add classes and set initial positions.
1182  this._initItems( $newItems );
1183
1184  // Add transition to each item.
1185  this._setTransitions( $newItems );
1186
1187  // Update the list of
1188  this.$items = this._getItems();
1189
1190  // Shrink all items (without transitions).
1191  this._shrink( $newItems );
1192  each(this.styleQueue, function( transitionObj ) {
1193    transitionObj.skipTransition = true;
1194  });
1195
1196  // Apply shrink positions, but do not cause a layout event.
1197  this._processStyleQueue( true );
1198
1199  if ( addToEnd ) {
1200    this._addItemsToEnd( $newItems, isSequential );
1201  } else {
1202    this.shuffle( this.lastFilter );
1203  }
1204};
1205
1206
1207Shuffle.prototype._addItemsToEnd = function( $newItems, isSequential ) {
1208  // Get ones that passed the current filter
1209  var $passed = this._filter( null, $newItems );
1210  var passed = $passed.get();
1211
1212  // How many filtered elements?
1213  this._updateItemCount();
1214
1215  this._layout( passed, true );
1216
1217  if ( isSequential && this.supported ) {
1218    this._setSequentialDelay( passed );
1219  }
1220
1221  this._revealAppended( passed );
1222};
1223
1224
1225/**
1226 * Triggers appended elements to fade in.
1227 * @param {ArrayLike.<Element>} $newFilteredItems Collection of elements.
1228 * @private
1229 */
1230Shuffle.prototype._revealAppended = function( newFilteredItems ) {
1231  defer(function() {
1232    each(newFilteredItems, function( el ) {
1233      var $item = $( el );
1234      this._transition({
1235        $item: $item,
1236        opacity: 1,
1237        point: $item.data('point'),
1238        scale: DEFAULT_SCALE
1239      });
1240    }, this);
1241
1242    this._whenCollectionDone($(newFilteredItems), TRANSITIONEND, function() {
1243      $(newFilteredItems).css( TRANSITION_DELAY, '0ms' );
1244      this._movementFinished();
1245    });
1246  }, this, this.revealAppendedDelay);
1247};
1248
1249
1250/**
1251 * Execute a function when an event has been triggered for every item in a collection.
1252 * @param {jQuery} $collection Collection of elements.
1253 * @param {string} eventName Event to listen for.
1254 * @param {Function} callback Callback to execute when they're done.
1255 * @private
1256 */
1257Shuffle.prototype._whenCollectionDone = function( $collection, eventName, callback ) {
1258  var done = 0;
1259  var items = $collection.length;
1260  var self = this;
1261
1262  function handleEventName( evt ) {
1263    if ( evt.target === evt.currentTarget ) {
1264      $( evt.target ).off( eventName, handleEventName );
1265      done++;
1266
1267      // Execute callback if all items have emitted the correct event.
1268      if ( done === items ) {
1269        callback.call( self );
1270      }
1271    }
1272  }
1273
1274  // Bind the event to all items.
1275  $collection.on( eventName, handleEventName );
1276};
1277
1278
1279/**
1280 * Execute a callback after jQuery `animate` for a collection has finished.
1281 * @param {Function} callback Callback to execute when they're done.
1282 * @private
1283 */
1284Shuffle.prototype._whenAnimationsDone = function( callback ) {
1285  $.when.apply( null, this._animations ).always( $.proxy( function() {
1286    this._animations.length = 0;
1287    callback.call( this );
1288  }, this ));
1289};
1290
1291
1292/**
1293 * Public Methods
1294 */
1295
1296/**
1297 * The magic. This is what makes the plugin 'shuffle'
1298 * @param {string|Function} [category] Category to filter by. Can be a function
1299 * @param {Object} [sortObj] A sort object which can sort the filtered set
1300 */
1301Shuffle.prototype.shuffle = function( category, sortObj ) {
1302  if ( !this.enabled || this.isTransitioning ) {
1303    return;
1304  }
1305
1306  if ( !category ) {
1307    category = ALL_ITEMS;
1308  }
1309
1310  this._filter( category );
1311
1312  // How many filtered elements?
1313  this._updateItemCount();
1314
1315  // Shrink each concealed item
1316  this._shrink();
1317
1318  // Update transforms on .filtered elements so they will animate to their new positions
1319  this.sort( sortObj );
1320};
1321
1322
1323/**
1324 * Gets the .filtered elements, sorts them, and passes them to layout.
1325 * @param {Object} opts the options object for the sorted plugin
1326 */
1327Shuffle.prototype.sort = function( opts ) {
1328  if ( this.enabled && !this.isTransitioning ) {
1329    this._resetCols();
1330
1331    var sortOptions = opts || this.lastSort;
1332    var items = this._getFilteredItems().sorted( sortOptions );
1333
1334    this._layout( items );
1335
1336    this.lastSort = sortOptions;
1337  }
1338};
1339
1340
1341/**
1342 * Reposition everything.
1343 * @param {boolean} isOnlyLayout If true, column and gutter widths won't be
1344 *     recalculated.
1345 */
1346Shuffle.prototype.update = function( isOnlyLayout ) {
1347  if ( this.enabled && !this.isTransitioning ) {
1348
1349    if ( !isOnlyLayout ) {
1350      // Get updated colCount
1351      this._setColumns();
1352    }
1353
1354    // Layout items
1355    this.sort();
1356  }
1357};
1358
1359
1360/**
1361 * Use this instead of `update()` if you don't need the columns and gutters updated
1362 * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations
1363 * could be off.
1364 */
1365Shuffle.prototype.layout = function() {
1366  this.update( true );
1367};
1368
1369
1370/**
1371 * New items have been appended to shuffle. Fade them in sequentially
1372 * @param {jQuery} $newItems jQuery collection of new items
1373 * @param {boolean} [addToEnd=false] If true, new items will be added to the end / bottom
1374 *     of the items. If not true, items will be mixed in with the current sort order.
1375 * @param {boolean} [isSequential=true] If false, new items won't sequentially fade in
1376 */
1377Shuffle.prototype.appended = function( $newItems, addToEnd, isSequential ) {
1378  this._addItems( $newItems, addToEnd === true, isSequential !== false );
1379};
1380
1381
1382/**
1383 * Disables shuffle from updating dimensions and layout on resize
1384 */
1385Shuffle.prototype.disable = function() {
1386  this.enabled = false;
1387};
1388
1389
1390/**
1391 * Enables shuffle again
1392 * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters
1393 */
1394Shuffle.prototype.enable = function( isUpdateLayout ) {
1395  this.enabled = true;
1396  if ( isUpdateLayout !== false ) {
1397    this.update();
1398  }
1399};
1400
1401
1402/**
1403 * Remove 1 or more shuffle items
1404 * @param {jQuery} $collection A jQuery object containing one or more element in shuffle
1405 * @return {Shuffle} The shuffle object
1406 */
1407Shuffle.prototype.remove = function( $collection ) {
1408
1409  // If this isn't a jquery object, exit
1410  if ( !$collection.length || !$collection.jquery ) {
1411    return;
1412  }
1413
1414  function handleRemoved() {
1415    // Remove the collection in the callback
1416    $collection.remove();
1417
1418    // Update things now that elements have been removed.
1419    this.$items = this._getItems();
1420    this._updateItemCount();
1421
1422    this._fire( Shuffle.EventType.REMOVED, [ $collection, this ] );
1423
1424    // Let it get garbage collected
1425    $collection = null;
1426  }
1427
1428  // Hide collection first.
1429  this._toggleFilterClasses( $(), $collection );
1430  this._shrink( $collection );
1431
1432  this.sort();
1433
1434  this.$el.one( Shuffle.EventType.LAYOUT + '.' + SHUFFLE, $.proxy( handleRemoved, this ) );
1435};
1436
1437
1438/**
1439 * Destroys shuffle, removes events, styles, and classes
1440 */
1441Shuffle.prototype.destroy = function() {
1442  // If there is more than one shuffle instance on the page,
1443  // removing the resize handler from the window would remove them
1444  // all. This is why a unique value is needed.
1445  $window.off('.' + this.unique);
1446
1447  // Reset container styles
1448  this.$el
1449      .removeClass( SHUFFLE )
1450      .removeAttr('style')
1451      .removeData( SHUFFLE );
1452
1453  // Reset individual item styles
1454  this.$items
1455      .removeAttr('style')
1456      .removeData('point')
1457      .removeData('scale')
1458      .removeClass([
1459        Shuffle.ClassName.CONCEALED,
1460        Shuffle.ClassName.FILTERED,
1461        Shuffle.ClassName.SHUFFLE_ITEM
1462      ].join(' '));
1463
1464  // Null DOM references
1465  this.$items = null;
1466  this.$el = null;
1467  this.sizer = null;
1468  this.element = null;
1469
1470  // Set a flag so if a debounced resize has been triggered,
1471  // it can first check if it is actually destroyed and not doing anything
1472  this.destroyed = true;
1473};
1474
1475
1476// Plugin definition
1477$.fn.shuffle = function( opts ) {
1478  var args = Array.prototype.slice.call( arguments, 1 );
1479  return this.each(function() {
1480    var $this = $( this );
1481    var shuffle = $this.data( SHUFFLE );
1482
1483    // If we don't have a stored shuffle, make a new one and save it
1484    if ( !shuffle ) {
1485      shuffle = new Shuffle( this, opts );
1486      $this.data( SHUFFLE, shuffle );
1487    } else if ( typeof opts === 'string' && shuffle[ opts ] ) {
1488      shuffle[ opts ].apply( shuffle, args );
1489    }
1490  });
1491};
1492
1493
1494// http://stackoverflow.com/a/962890/373422
1495function randomize( array ) {
1496  var tmp, current;
1497  var top = array.length;
1498
1499  if ( !top ) {
1500    return array;
1501  }
1502
1503  while ( --top ) {
1504    current = Math.floor( Math.random() * (top + 1) );
1505    tmp = array[ current ];
1506    array[ current ] = array[ top ];
1507    array[ top ] = tmp;
1508  }
1509
1510  return array;
1511}
1512
1513
1514// You can return `undefined` from the `by` function to revert to DOM order
1515// This plugin does NOT return a jQuery object. It returns a plain array because
1516// jQuery sorts everything in DOM order.
1517$.fn.sorted = function(options) {
1518  var opts = $.extend({}, $.fn.sorted.defaults, options);
1519  var arr = this.get();
1520  var revert = false;
1521
1522  if ( !arr.length ) {
1523    return [];
1524  }
1525
1526  if ( opts.randomize ) {
1527    return randomize( arr );
1528  }
1529
1530  // Sort the elements by the opts.by function.
1531  // If we don't have opts.by, default to DOM order
1532  if ( $.isFunction( opts.by ) ) {
1533    arr.sort(function(a, b) {
1534
1535      // Exit early if we already know we want to revert
1536      if ( revert ) {
1537        return 0;
1538      }
1539
1540      var valA = opts.by($(a));
1541      var valB = opts.by($(b));
1542
1543      // If both values are undefined, use the DOM order
1544      if ( valA === undefined && valB === undefined ) {
1545        revert = true;
1546        return 0;
1547      }
1548
1549      if ( valA < valB || valA === 'sortFirst' || valB === 'sortLast' ) {
1550        return -1;
1551      }
1552
1553      if ( valA > valB || valA === 'sortLast' || valB === 'sortFirst' ) {
1554        return 1;
1555      }
1556
1557      return 0;
1558    });
1559  }
1560
1561  // Revert to the original array if necessary
1562  if ( revert ) {
1563    return this.get();
1564  }
1565
1566  if ( opts.reverse ) {
1567    arr.reverse();
1568  }
1569
1570  return arr;
1571};
1572
1573
1574$.fn.sorted.defaults = {
1575  reverse: false, // Use array.reverse() to reverse the results
1576  by: null, // Sorting function
1577  randomize: false // If true, this will skip the sorting and return a randomized order in the array
1578};
1579
1580return Shuffle;
1581
1582});

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