PageSourceSearch

https://www.mainereview.com/wp-content/themes/northstar-theme/_inc…ry.justifiedGallery.js?ver=3.5.4.1791065083

js mainereview.com collected 2026-10-03 23:10:58 UTC 22,796 bytes, 663 lines download raw bytes

1/*!
2 * Justified Gallery - v3.5.4
3 * http://miromannino.github.io/Justified-Gallery/
4 * Copyright (c) 2015 Miro Mannino
5 * Licensed under the MIT license.
6 */
7(function($) {
8
9	/* Events
10		jg.complete : called when all the gallery has been created
11		jg.resize : called when the gallery has been resized
12	*/
13
14	$.fn.justifiedGallery = function (arg) {
15		// Default options
16		var defaults = {
17			sizeRangeSuffixes : {
18				'lt100': '',  // e.g. Flickr uses '_t'
19				'lt240': '',  // e.g. Flickr uses '_m' 
20				'lt320': '',  // e.g. Flickr uses '_n' 
21				'lt500': '',  // e.g. Flickr uses '' 
22				'lt640': '',  // e.g. Flickr uses '_z'
23				'lt1024': '', // e.g. Flickr uses '_b'
24			},
25			rowHeight : 120,
26			maxRowHeight : 0, // negative value = no limits, 0 = 1.5 * rowHeight
27			margins : 1,
28			border: -1, // negative value = same as margins, 0 = disabled
29
30			lastRow : 'nojustify', // or can be 'justify' or 'hide'
31			justifyThreshold: 0.75, /* if row width / available space > 0.75 it will be always justified 
32																	(i.e. lastRow setting is not considered) */
33			fixedHeight : false,
34			waitThumbnailsLoad : true,
35			captions : true,
36			cssAnimation: false,
37			imagesAnimationDuration : 500, // ignored with css animations
38			captionSettings : { // ignored with css animations
39				animationDuration : 500,
40				visibleOpacity : 0.7, 
41				nonVisibleOpacity : 0.0 
42			},
43			rel : null, // rewrite the rel of each analyzed links
44			target : null, // rewrite the target of all links
45			extension : /\.[^.\\/]+$/,
46			refreshTime : 100,
47			randomize : false
48		};
49
50		function checkSettings (context) {
51			var settings = context.settings;
52
53			function checkSuffixesRange(range) {
54				if (typeof settings.sizeRangeSuffixes[range] !== 'string')
55					throw 'sizeRangeSuffixes.' + range + ' must be a string';
56			}
57
58			function checkOrConvertNumber(parent, settingName) {
59				if (typeof parent[settingName] === 'string') {
60					parent[settingName] = parseFloat(parent[settingName], 10);
61					if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
62				} else if (typeof parent[settingName] === 'number') {
63					if (isNaN(parent[settingName])) throw 'invalid number for ' + settingName;
64				} else {
65					throw settingName + ' must be a number';
66				}
67			}
68
69			if (typeof settings.sizeRangeSuffixes !== 'object')
70				throw 'sizeRangeSuffixes must be defined and must be an object';
71
72			checkSuffixesRange('lt100');
73			checkSuffixesRange('lt240');
74			checkSuffixesRange('lt320');
75			checkSuffixesRange('lt500');
76			checkSuffixesRange('lt640');
77			checkSuffixesRange('lt1024');
78
79			checkOrConvertNumber(settings, 'rowHeight');
80			checkOrConvertNumber(settings, 'maxRowHeight');
81
82			if (settings.maxRowHeight > 0 && 
83					settings.maxRowHeight < settings.rowHeight) {
84				settings.maxRowHeight = settings.rowHeight;
85			}
86			
87			checkOrConvertNumber(settings, 'margins');
88			checkOrConvertNumber(settings, 'border');
89
90			if (settings.lastRow !== 'nojustify' &&
91					settings.lastRow !== 'justify' &&
92					settings.lastRow !== 'hide') {
93				throw 'lastRow must be "nojustify", "justify" or "hide"';
94			}
95
96			checkOrConvertNumber(settings, 'justifyThreshold');
97			if (settings.justifyThreshold < 0 || settings.justifyThreshold > 1)
98				throw 'justifyThreshold must be in the interval [0,1]';
99			if (typeof settings.cssAnimation !== 'boolean') {
100				throw 'cssAnimation must be a boolean'; 
101			}
102			
103			checkOrConvertNumber(settings.captionSettings, 'animationDuration');
104			checkOrConvertNumber(settings, 'imagesAnimationDuration');
105
106			checkOrConvertNumber(settings.captionSettings, 'visibleOpacity');
107			if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
108				throw 'captionSettings.visibleOpacity must be in the interval [0, 1]';
109
110			checkOrConvertNumber(settings.captionSettings, 'nonVisibleOpacity');
111			if (settings.captionSettings.visibleOpacity < 0 || settings.captionSettings.visibleOpacity > 1)
112				throw 'captionSettings.nonVisibleOpacity must be in the interval [0, 1]';
113
114			if (typeof settings.fixedHeight !== 'boolean') {
115				throw 'fixedHeight must be a boolean';  
116			}
117
118			if (typeof settings.captions !== 'boolean') {
119				throw 'captions must be a boolean'; 
120			}
121
122			checkOrConvertNumber(settings, 'refreshTime');
123
124			if (typeof settings.randomize !== 'boolean') {
125				throw 'randomize must be a boolean';  
126			}
127
128		}
129
130		function imgFromEntry($entry) {
131			var $img = $entry.find('img');
132			if ($img.length === 0) $img = $entry.find('a > img');
133			return $img;
134		}
135
136		function startLoadingSpinnerAnimation(spinnerContext) {
137			clearInterval(spinnerContext.intervalId);
138			spinnerContext.intervalId = setInterval(function () {
139				if (spinnerContext.phase < spinnerContext.$points.length) 
140					spinnerContext.$points.eq(spinnerContext.phase).fadeTo(spinnerContext.timeslot, 1);
141				else
142					spinnerContext.$points.eq(spinnerContext.phase - spinnerContext.$points.length)
143												.fadeTo(spinnerContext.timeslot, 0);
144				spinnerContext.phase = (spinnerContext.phase + 1) % (spinnerContext.$points.length * 2);
145			}, spinnerContext.timeslot);
146		}
147
148		function onImageEvent(imageSrc, onLoad, onError) {
149			if (!onLoad && !onError) {
150				return;
151			}
152			/* Check if the image is loaded or not using another image object.
153			 We cannot use the 'complete' image property, because some browsers,
154			 with a 404 set complete = true */
155			var memImage = new Image();
156			var $memImage = $(memImage);
157			if (onLoad) {
158				$memImage.one('load', function () {
159					$memImage.off('load error');
160					onLoad(memImage);
161				});
162			}
163			if (onError) {
164				$memImage.one('error', function() {
165					$memImage.off('load error');
166					onError(memImage);
167				});
168			}
169			memImage.src = imageSrc;
170		}
171
172		function startImgAnalyzer(context, isForResize) {
173			stopImgAnalyzerStarter(context);
174			context.imgAnalyzerTimeout = setTimeout(function () { 
175				analyzeImages(context, isForResize); 
176			}, 0.001);
177			analyzeImages(context, isForResize);
178		}
179
180		function checkWidth(context) {
181			context.checkWidthIntervalId = setInterval(function () {
182				var galleryWidth = parseInt(context.$gallery.width(), 10);
183				if (context.galleryWidth !== galleryWidth) {
184					//DEBUG// console.log("resize. old: " + context.galleryWidth + " new: " + galleryWidth);
185					
186					context.galleryWidth = galleryWidth;
187					rewind(context);
188
189					// Restart to analyze
190					startImgAnalyzer(context, true);
191				}
192			}, context.settings.refreshTime);
193		}
194
195		function stopImgAnalyzerStarter(context) {
196			context.yield.flushed = 0;
197			if (context.imgAnalyzerTimeout !== null) clearTimeout(context.imgAnalyzerTimeout);
198		}
199
200		function analyzeImages(context, isForResize) {
201			
202			/* //DEBUG// 
203			var rnd = parseInt(Math.random() * 10000, 10);
204			console.log('analyzeImages ' + rnd + ' start');
205			console.log('images status: ');
206			for (var i = 0; i < context.entries.length; i++) {
207				var $entry = $(context.entries[i]);
208				var $image = imgFromEntry($entry);
209				console.log(i + ' (alt: ' + $image.attr('alt') + 'loaded: ' + $image.data('jg.loaded') + ')');
210			}*/
211
212			/* The first row */
213			var settings = context.settings;
214			var isLastRow;
215			
216			for (var i = context.lastAnalyzedIndex + 1; i < context.entries.length; i++) {
217				var $entry = $(context.entries[i]);
218				var $image = imgFromEntry($entry);
219
220				if ($image.data('jg.loaded') === true || $image.data('jg.loaded') === 'skipped') {
221					isLastRow = i >= context.entries.length - 1;
222
223					var availableWidth = context.galleryWidth - 2 * context.border - (
224															 (context.buildingRow.entriesBuff.length - 1) * settings.margins);
225					var imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
226					if (availableWidth / (context.buildingRow.aspectRatio + imgAspectRatio) < settings.rowHeight) {
227						flushRow(context, isLastRow);
228						if(++context.yield.flushed >= context.yield.every) {
229							//DEBUG// console.log("yield");
230							startImgAnalyzer(context, isForResize);
231							return;
232						}
233					}
234
235					context.buildingRow.entriesBuff.push($entry);
236					context.buildingRow.aspectRatio += imgAspectRatio;
237					context.buildingRow.width += imgAspectRatio * settings.rowHeight;
238					context.lastAnalyzedIndex = i;
239
240				} else if ($image.data('jg.loaded') !== 'error') {
241					return;
242				}
243			}
244
245			// Last row flush (the row is not full)
246			if (context.buildingRow.entriesBuff.length > 0) flushRow(context, true);
247
248			if (context.spinner.active) {
249				context.spinner.active = false;
250				context.$gallery.height(context.$gallery.height() - context.spinner.$el.innerHeight());
251				context.spinner.$el.detach();
252				stopLoadingSpinnerAnimation(context.spinner);
253			}
254
255			/* Stop, if there is, the timeout to start the analyzeImages.
256					This is because an image can be set loaded, and the timeout can be set,
257					but this image can be analyzed yet. 
258			*/
259			stopImgAnalyzerStarter(context);
260
261			//On complete callback
262			if (!isForResize) 
263				context.$gallery.trigger('jg.complete'); 
264			else 
265				context.$gallery.trigger('jg.resize');
266
267			//DEBUG// console.log('analyzeImages ' + rnd +  ' end');
268		}
269
270		function flushRow(context, isLastRow) {
271			var settings = context.settings;
272			var $entry, $image, minHeight, buildingRowRes, offX = context.border;
273
274			//DEBUG// console.log('flush (isLastRow: ' + isLastRow + ')');
275
276			buildingRowRes = prepareBuildingRow(context, isLastRow);
277			minHeight = buildingRowRes.minHeight;
278			if (isLastRow && settings.lastRow === 'hide' && minHeight === -1) {
279				context.buildingRow.entriesBuff = [];
280				context.buildingRow.aspectRatio = 0;
281				context.buildingRow.width = 0;
282				return;
283			}
284
285			if (settings.maxRowHeight > 0 && settings.maxRowHeight < minHeight)
286				minHeight = settings.maxRowHeight;
287			else if (settings.maxRowHeight === 0 && (1.5 * settings.rowHeight) < minHeight)
288				minHeight = 1.5 * settings.rowHeight;
289
290			for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
291				$entry = context.buildingRow.entriesBuff[i];
292				$image = imgFromEntry($entry);
293				displayEntry($entry, offX, context.offY, $image.data('jg.jimgw'), 
294										 $image.data('jg.jimgh'), minHeight, context);
295				offX += $image.data('jg.jimgw') + settings.margins;
296			}
297
298			//Gallery Height
299			context.$gallery.height(context.offY + minHeight + context.border + 
300				(context.spinner.active ? context.spinner.$el.innerHeight() : 0)
301			);
302
303			if (!isLastRow || (minHeight <= context.settings.rowHeight && buildingRowRes.justify)) {
304				//Ready for a new row
305				context.offY += minHeight + context.settings.margins;
306
307				//DEBUG// console.log('minHeight: ' + minHeight + ' offY: ' + context.offY);
308
309				context.buildingRow.entriesBuff = []; //clear the array creating a new one
310				context.buildingRow.aspectRatio = 0;
311				context.buildingRow.width = 0;
312				context.$gallery.trigger('jg.rowflush');
313			}
314		}
315
316		function prepareBuildingRow(context, isLastRow) {
317			var settings = context.settings;
318			var i, $entry, $image, imgAspectRatio, newImgW, newImgH, justify = true;
319			var minHeight = 0;
320			var availableWidth = context.galleryWidth - 2 * context.border - (
321														(context.buildingRow.entriesBuff.length - 1) * settings.margins);
322			var rowHeight = availableWidth / context.buildingRow.aspectRatio;
323			var justificable = context.buildingRow.width / availableWidth > settings.justifyThreshold;
324
325			//Skip the last row if we can't justify it and the lastRow == 'hide'
326			if (isLastRow && settings.lastRow === 'hide' && !justificable) {
327				for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
328					$entry = context.buildingRow.entriesBuff[i];
329					if (settings.cssAnimation) 
330						$entry.removeClass('entry-visible');            
331					else
332						$entry.stop().fadeTo(0, 0);
333				}
334				return -1;
335			}
336
337			// With lastRow = nojustify, justify if is justificable (the images will not become too big)
338			if (isLastRow && !justificable && settings.lastRow === 'nojustify') justify = false;
339
340			for (i = 0; i < context.buildingRow.entriesBuff.length; i++) {
341				$image = imgFromEntry(context.buildingRow.entriesBuff[i]);
342				imgAspectRatio = $image.data('jg.imgw') / $image.data('jg.imgh');
343
344				if (justify) {
345					newImgW = (i === context.buildingRow.entriesBuff.length - 1) ? availableWidth 
346											: rowHeight * imgAspectRatio;
347					newImgH = rowHeight;
348
349					/* With fixedHeight the newImgH must be greater than rowHeight. 
350					In some cases here this is not satisfied (due to the justification).
351					But we comment it, because is better to have a shorter but justified row instead 
352					to have a cropped image at the end. */
353					/*if (settings.fixedHeight && newImgH < settings.rowHeight) {
354						newImgW = settings.rowHeight * imgAspectRatio;
355						newImgH = settings.rowHeight;
356					}*/
357
358				} else {
359					newImgW = settings.rowHeight * imgAspectRatio;
360					newImgH = settings.rowHeight;
361				}
362
363				availableWidth -= Math.round(newImgW);
364				$image.data('jg.jimgw', Math.round(newImgW));
365				$image.data('jg.jimgh', Math.ceil(newImgH));
366				if (i === 0 || minHeight > newImgH) minHeight = newImgH;
367			}
368
369			if (settings.fixedHeight && minHeight > settings.rowHeight) 
370				minHeight = settings.rowHeight;
371
372			return {minHeight: minHeight, justify: justify};
373		}
374
375		function displayEntry($entry, x, y, imgWidth, imgHeight, rowHeight, context) {
376			var $image = imgFromEntry($entry);
377			$image.css('width', imgWidth);
378			$image.css('height', imgHeight);
379			//if ($entry.get(0) === $image.parent().get(0)) { // this creates an error in link_around_img test
380				$image.css('margin-left', - imgWidth / 2);
381				$image.css('margin-top', - imgHeight / 2);
382			//}
383			$entry.width(imgWidth);
384			$entry.height(rowHeight);
385			$entry.css('top', y);
386			$entry.css('left', x);
387
388			//DEBUG// console.log('displayEntry (w: ' + $image.width() + ' h: ' + $image.height());
389
390			// Image reloading for an high quality of thumbnails
391			var imageSrc = $image.attr('data-url');
392			var img_sizes = $.parseJSON($image.attr('data-sizes'));
393			var newImageSrc = newSrc(imageSrc, imgWidth, imgHeight, context, img_sizes);
394
395			$image.one('error', function () {
396				//DEBUG// console.log('revert the original image');
397				$image.attr('src', $image.data('jg.originalSrc')); //revert to the original thumbnail, we got it.
398			});
399
400			function loadNewImage() {
401				if (imageSrc !== newImageSrc) { //load the new image after the fadeIn
402					$image.attr('src', newImageSrc);
403				}
404			}
405
406			if ($image.data('jg.loaded') === 'skipped') {
407				onImageEvent(imageSrc, function() {
408					showImg($entry, loadNewImage, context);
409					$image.data('jg.loaded', true);
410				});
411			} else {
412				showImg($entry, loadNewImage, context);
413			}
414
415			// Captions ------------------------------
416			var captionMouseEvents = $entry.data('jg.captionMouseEvents');
417			if (context.settings.captions === true) {
418				var $imgCaption = $entry.find('.caption');
419				
420			
421				// Create events (we check again the $imgCaption because it can be still inexistent)
422				if ($imgCaption.length !== 0) {
423					if (!context.settings.cssAnimation) {
424						$imgCaption.stop().fadeTo(context.settings.imagesAnimationDuration, 
425																			context.settings.captionSettings.nonVisibleOpacity); 
426					}
427					if (typeof captionMouseEvents === 'undefined') {
428						captionMouseEvents = {
429							mouseenter: onEntryMouseEnterForCaption,
430							mouseleave: onEntryMouseLeaveForCaption
431						};
432						$entry.on('mouseenter', undefined, context, captionMouseEvents.mouseenter);
433						$entry.on('mouseleave', undefined, context, captionMouseEvents.mouseleave);
434						$entry.data('jg.captionMouseEvents', captionMouseEvents);
435					}
436				}
437			} else {
438				if (typeof captionMouseEvents !== 'undefined') {
439					$entry.off('mouseenter', undefined, context, captionMouseEvents.mouseenter);
440					$entry.off('mouseleave', undefined, context, captionMouseEvents.mouseleave);
441					$entry.removeData('jg.captionMouseEvents');
442				}
443			}
444
445		}
446
447		/* Given an image src, with the width and the height, returns the new image src with the
448			 best suffix to show the best quality thumbnail. */
449		function newSrc(imageSrc, imgWidth, imgHeight, context, img_sizes) {
450
451			var matchRes = imageSrc.match(context.settings.extension);
452			var ext = (matchRes != null) ? matchRes[0] : '';
453			var newImageSrc = imageSrc.replace(context.settings.extension, '');
454			var imgSuffix = '';	
455
456			$.each(img_sizes, function (key, data) {			
457					imgSuffix = "-"+ data['width'] + "x" + data['height'];
458					if ( imgHeight < data['height'] ) {
459						return false;
460					}			
461			});	
462			
463			if ( imgSuffix != '' ) {
464				newImageSrc += imgSuffix + ext;
465			} else {
466				newImageSrc += ext;
467			}
468			
469			return newImageSrc;
470		}
471
472		function showImg($entry, callback, context) {
473			if (context.settings.cssAnimation) {
474				$entry.addClass('entry-visible');
475				callback();
476			} else {
477				$entry.stop().fadeTo(context.settings.imagesAnimationDuration, 1.0, callback);
478			}
479		}
480
481		function onEntryMouseEnterForCaption (ev) {
482			var $caption = $(ev.currentTarget).find('.caption');
483			if (ev.data.settings.cssAnimation) {
484				$caption.addClass('caption-visible').removeClass('caption-hidden');
485			} else {
486				$caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration, 
487															 ev.data.settings.captionSettings.visibleOpacity);
488			}
489		}
490
491		function onEntryMouseLeaveForCaption (ev) {
492			var $caption = $(ev.currentTarget).find('.caption');
493			if (ev.data.settings.cssAnimation) {
494				$caption.removeClass('caption-visible').removeClass('caption-hidden');
495			} else {
496				$caption.stop().fadeTo(ev.data.settings.captionSettings.animationDuration, 
497															 ev.data.settings.captionSettings.nonVisibleOpacity);
498			}
499		}
500
501		function stopLoadingSpinnerAnimation(spinnerContext) {
502			clearInterval(spinnerContext.intervalId);
503			spinnerContext.intervalId = null;
504		}
505
506		function rewind(context) {
507			context.lastAnalyzedIndex = -1;
508			context.buildingRow.entriesBuff = [];
509			context.buildingRow.aspectRatio = 0;
510			context.buildingRow.width = 0;
511			context.offY = context.border;
512		}
513
514		function hideImgImmediately($entry, context) {
515			if (context.settings.cssAnimation) {
516				$entry.removeClass('entry-visible');
517			} else {
518				$entry.stop().fadeTo(0, 0);
519			}
520		}
521
522		return this.each(function (index, gallery) {
523
524			var $gallery = $(gallery);
525			$gallery.addClass('justified-gallery');
526
527			var context = $gallery.data('jg.context');
528			if (typeof context === 'undefined') {
529
530				if (typeof arg !== 'undefined' && arg !== null && typeof arg !== 'object') 
531					throw 'The argument must be an object';
532
533				// Spinner init
534				var $spinner = $('<div class="spinner"><span></span><span></span><span></span></div>');
535				var extendedSettings = $.extend({}, defaults, arg);
536
537				var border = extendedSettings.border >= 0 ? extendedSettings.border : extendedSettings.margins;
538
539				//Context init
540				context = {
541					settings : extendedSettings,
542					imgAnalyzerTimeout : null,
543					entries : null,
544					buildingRow : {
545						entriesBuff : [],
546						width : 0,
547						aspectRatio : 0
548					},
549					lastAnalyzedIndex : -1,
550					yield : {
551						every : 2, /* do a flush every context.yield.every flushes (
552									* must be greater than 1, else the analyzeImages will loop */
553						flushed : 0 //flushed rows without a yield
554					},
555					border : border,
556					offY : border,
557					spinner : {
558						active : false,
559						phase : 0,
560						timeslot : 150,
561						$el : $spinner,
562						$points : $spinner.find('span'),
563						intervalId : null
564					},
565					checkWidthIntervalId : null,
566					galleryWidth :  $gallery.width(),
567					$gallery : $gallery
568				};
569
570				$gallery.data('jg.context', context);
571
572			} else if (arg === 'norewind') {
573				/* Hide the image of the buildingRow to prevent strange effects when the row will be
574					 re-justified again */
575				for (var i = 0; i < context.buildingRow.entriesBuff.length; i++) {
576					hideImgImmediately(context.buildingRow.entriesBuff[i], context);
577				}
578				// In this case we don't rewind, and analyze all the images
579			} else {
580				context.settings = $.extend({}, context.settings, arg);
581				context.border = context.settings.border >= 0 ? context.settings.border : context.settings.margins;
582				rewind(context);
583			}
584			checkSettings(context);
585
586			context.entries = $gallery.find('> figure, > div:not(.spinner)').toArray();
587			if (context.entries.length === 0) return;
588
589			// Randomize
590			if (context.settings.randomize) {
591				context.entries.sort(function () { return Math.random() * 2 - 1; });
592				$.each(context.entries, function () {
593					$(this).appendTo($gallery);
594				});
595			}
596
597			var imagesToLoad = false;
598			var skippedImages = false;
599			$.each(context.entries, function (index, entry) {
600				var $entry = $(entry);
601				var $image = imgFromEntry($entry);
602
603				$entry.addClass('jg-entry');
604
605				if ($image.data('jg.loaded') !== true && $image.data('jg.loaded') !== 'skipped') {
606
607					// Link Rel global overwrite
608					if (context.settings.rel !== null) $entry.attr('rel', context.settings.rel);
609
610					// Link Target global overwrite
611					if (context.settings.target !== null) $entry.attr('target', context.settings.target);
612
613					// Image src
614					var imageSrc = (typeof $image.data('safe-src') !== 'undefined') ? 
615														$image.data('safe-src') : $image.attr('src');
616					$image.data('jg.originalSrc', imageSrc);
617					$image.attr('src', imageSrc);
618
619					var width = parseInt($image.attr('width'), 10);
620					var height = parseInt($image.attr('height'), 10);
621					if(context.settings.waitThumbnailsLoad !== true && !isNaN(width) && !isNaN(height)) {
622						$image.data('jg.imgw', width);
623						$image.data('jg.imgh', height);
624						$image.data('jg.loaded', 'skipped');
625						skippedImages = true;
626						startImgAnalyzer(context, false);
627						return true;
628					}
629
630					$image.data('jg.loaded', false);
631					imagesToLoad = true;
632
633					// Spinner start
634					if (context.spinner.active === false) {
635						context.spinner.active = true;
636						$gallery.append(context.spinner.$el);
637						$gallery.height(context.offY + context.spinner.$el.innerHeight());
638						startLoadingSpinnerAnimation(context.spinner);
639					}
640
641					onImageEvent(imageSrc, function imgLoaded (loadImg) {
642						//DEBUG// console.log('img load (alt: ' + $image.attr('alt') + ')');
643						$image.data('jg.imgw', loadImg.width);
644						$image.data('jg.imgh', loadImg.height);
645						$image.data('jg.loaded', true);
646						startImgAnalyzer(context, false);
647					}, function imgLoadError () {
648						//DEBUG// console.log('img error (alt: ' + $image.attr('alt') + ')');
649						$image.data('jg.loaded', 'error');
650						startImgAnalyzer(context, false);
651					});
652
653				}
654
655			});
656
657			if (!imagesToLoad && !skippedImages) startImgAnalyzer(context, false);
658			checkWidth(context);
659		});
660
661	};
662	
663}(jQuery));

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