PageSourceSearch

https://orionoid.com/web/scripts/common.js?x=25886C874FFCBD18A25B96237A462ABD53046F8E0464A8B6BA62DB425B08D04E

js orionoid.com collected 2026-10-03 23:19:24 UTC 167,153 bytes, 6,107 lines download raw bytes

1/************************************************************************************************
2* OLD BROWSERS
3************************************************************************************************/
4
5// Internet Explorer and jQuery.
6
7Number.isInteger = Number.isInteger || function(value)
8{
9	return typeof value === 'number' && isFinite(value) && Math.floor(value) === value;
10};
11
12if(!String.prototype.startsWith)
13{
14	String.prototype.startsWith = function(search, position)
15	{
16		position = position || 0;
17		return this.indexOf(search, position) === position;
18	};
19}
20
21if(!String.prototype.endsWith)
22{
23	String.prototype.endsWith = function(search, position)
24	{
25		if(position === undefined || position > this.length) position = this.length
26        return this.substring(position - search.length, position) === search;
27	};
28}
29
30if(!String.prototype.includes)
31{
32	String.prototype.includes = function(search, start)
33	{
34		if(typeof start !== 'number') start = 0;
35		if (start + search.length > this.length) return false;
36		else return this.indexOf(search, start) !== -1;
37	};
38}
39
40/************************************************************************************************
41* BROWSER
42************************************************************************************************/
43
44function orionBrowserInternetExplorer()
45{
46	return navigator.appName == 'Microsoft Internet Explorer' || !!(navigator.userAgent.match(/Trident/) || navigator.userAgent.match(/rv:11/)) || (typeof $.browser !== "undefined" && $.browser.msie == 1);
47}
48
49function orionBrowserDetect()
50{
51	if(orionBrowserInternetExplorer())
52	{
53		orionBrowserShow();
54
55		// Causes animation flickering.
56		$('.orion_aurora').hide();
57	}
58}
59
60function orionBrowserShow(direct)
61{
62	orionModalShow('.orion_browser', direct, false);
63}
64
65/************************************************************************************************
66* IMPORT
67************************************************************************************************/
68
69var OrionImportLinks = 0;
70var OrionImportCount = 0;
71var OrionImportInternal = 0;
72var OrionImportSize = 0;
73var OrionImportCallback = 0;
74
75function orionImport(links, callback, cache)
76{
77	OrionImportLinks = links;
78	OrionImportCount = 0;
79	OrionImportInternal = 0;
80	OrionImportSize = links.length;
81	OrionImportCallback = callback;
82	links.forEach(function(link){ orionImportFile(link.link, orionImportLoaded, cache); });
83}
84
85function orionImportLoaded(success)
86{
87	OrionImportCount++;
88	if(OrionImportCount == OrionImportSize && OrionImportCallback)
89	{
90		orionImportProcess();
91	}
92}
93
94function orionImportCheck()
95{
96	// We need a second callback wrapper. Even though the ajax call has cached the file, adding the script/style to the html still takes some milliseconds.
97	// This is only observable on the live version, not the local version.
98	OrionImportInternal++;
99	if(OrionImportInternal == OrionImportSize && OrionImportCallback)
100	{
101		OrionImportCallback();
102	}
103	else
104	{
105		orionImportProcess();
106	}
107}
108
109function orionImportProcess()
110{
111	// Even though the files are already cached, that have to be added SEQUENTIALLY here.
112	// Add a script/style and wait for it tto be loaded before loading the next one.
113	// Otherwise scripts might be loaded out of sequence (eg ChromeCast before VideoJs), which causes errors.
114	// This is only observable on the live version.
115	var link = OrionImportLinks[OrionImportInternal];
116	if(link.type == 'script')
117	{
118		var element = $('<script></script>');
119		element.on('load', orionImportCheck);
120		element.appendTo('body');
121		element.attr('src', link.link);
122	}
123	else if(link.type == 'style')
124	{
125		var element = $('<link rel="stylesheet" type="text/css" />');
126		element.on('load', orionImportCheck);
127		element.appendTo('head');
128		element.attr('href', link.link);
129	}
130}
131
132function orionImportFile(link, callback, cache)
133{
134	// Important to set type to "text".
135	// Otherwise if a JS file comes in, jQuery will automatically execute it.
136	$.ajax(
137	{
138		url : link,
139		dataType : 'text',
140		async : true,
141		cache : (cache === undefined || cache),
142		success : function(){ if(callback) callback(true); },
143		error : function(){ if(callback) callback(false); },
144	});
145}
146
147/************************************************************************************************
148* TOOLS
149************************************************************************************************/
150
151const OrionFormatPast = 'past';
152const OrionFormatDuration = 'duration';
153const OrionFormatDurationShort = 'durationshort';
154const OrionFormatDate = 'YYYY-MM-DD';
155const OrionFormatTime = 'hh:mm:ss';
156const OrionFormatDateTime = 'YYYY-MM-DD hh:mm:ss';
157const OrionFormatDateShortTime = 'YYYY-MM-DD hh:mm';
158const OrionFormatDateReadable = 'D MMM YYYY';
159const OrionFormatDateReadableFull = 'D MMMM YYYY';
160const OrionFormatDateTimeReadable = 'D MMM YYYY, hh:mm:ss';
161var OrionIdData = null;
162
163function orionDummy()
164{
165}
166
167function orionOther(value)
168{
169	return value.toLowerCase() == 'other';
170}
171
172function orionTimestamp(milliseconds)
173{
174	if(milliseconds == null) milliseconds = false;
175	var timestamp = window.performance && window.performance.now && window.performance.timing && window.performance.timing.navigationStart ? window.performance.now() + window.performance.timing.navigationStart : Date.now();
176	if(milliseconds) return timestamp;
177	else return Math.round(timestamp / 1000.0);
178}
179
180function orionTimeFormat(timestamp, format)
181{
182	if(format == OrionFormatDuration || format == OrionFormatDurationShort)
183	{
184		var short = format == OrionFormatDurationShort;
185		var duration = moment.duration(moment().unix() - timestamp, 'seconds');
186
187		// parseFloat removes trailing 0s.
188		var value = duration.asYears();
189		if(value >= 1) return parseFloat(orionRound(value, 1)) + ' ' + (short ? 'Y' : ('Year' + (value == 1 ? '' : 's')));
190		value = duration.asMonths();
191		if(value >= 1) return parseFloat(orionRound(value, 0)) + ' ' + (short ? 'M' : ('Month' + (value == 1 ? '' : 's')));
192		value = duration.asDays();
193		if(value >= 1) return orionRound(value, 0) + ' ' + (short ? 'D' : ('Day' + (value == 1 ? '' : 's')));
194		value = duration.asHours();
195		return (value < 1 ? '< ' : '') + orionRound(value < 1 ? 1 : value, 0) + ' ' + (short ? 'H' : ('Hour' + (value == 1 ? '' : 's')));
196	}
197	else
198	{
199		var time = moment.unix(timestamp).local();
200		if(format == OrionFormatPast)
201		{
202			return time.fromNow();
203		}
204		else
205		{
206			if(format == null) format = OrionFormatDateTime;
207			if(timestamp > 10000000000) timestamp /= 1000.0; // ms to s
208			return time.format(format);
209		}
210	}
211}
212
213function orionTimeDuration(seconds)
214{
215	var levels = [
216		[Math.floor(seconds / 31536000), 'years'],
217		[Math.floor((seconds % 31536000) / 86400), 'days'],
218		[Math.floor(((seconds % 31536000) % 86400) / 3600), 'hours'],
219		[Math.floor((((seconds % 31536000) % 86400) % 3600) / 60), 'minutes'],
220		/*[(((seconds % 31536000) % 86400) % 3600) % 60, 'seconds'],*/
221	];
222	var text = '';
223	for(var i = 0, max = levels.length; i < max; i++)
224	{
225		if( levels[i][0] === 0) continue;
226		text += ' ' + levels[i][0] + ' ' + (levels[i][0] === 1 ? levels[i][1].substr(0, levels[i][1].length - 1): levels[i][1]);
227	};
228	return text.trim();
229}
230
231function orionTimeDays(timestamp, label, defaults, places)
232{
233	if(defaults != null && (timestamp == null || timestamp <= 0)) return defaults;
234
235	var value = timestamp / 86400.0;
236	if(places == null)
237	{
238		if(value < 1) places = 1;
239		else places = 0;
240	}
241	value = orionRound(value, places);
242
243	if(label)
244	{
245		var valueLabel = value;
246		if(orionStringIs(label)) valueLabel = label + valueLabel;
247		return valueLabel + ' Day' + (value == 1 ? '' : 's');
248	}
249	return value;
250}
251
252function orionTimeMonths(timestamp, label, defaults)
253{
254	if(defaults != null && (timestamp == null || timestamp <= 0)) return defaults;
255	var value = orionRound(timestamp / 2592000.0, 0);
256	if(label)
257	{
258		if(orionStringIs(label)) value = label + value;
259		return value + ' Month' + (value == 1 ? '' : 's');
260	}
261	else return value;
262}
263
264function orionSizeFormat(bytes, places, placesReduced, estimate)
265{
266	if(bytes === null) return '';
267	var i = Math.floor(Math.log(bytes) / Math.log(1024));
268	if(places === undefined) places = Math.min(placesReduced === undefined ? 2 : placesReduced, Math.max(0, placesReduced === undefined ? i : (i - 1 - placesReduced)));
269    return (estimate ? '~ ' : '') + (bytes == 0 ? 0 : (bytes / Math.pow(1024, i)).toFixed(places)) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
270}
271
272function orionSpeedFormat(bytes, duration)
273{
274	var perSecond = (bytes / duration) * 8;
275	perSecond /= 1024.0;
276	if(perSecond < 1024) return (Math.ceil(orionRoundUp(perSecond, 0) / 100) * 100) + ' kbps';
277	perSecond /= 1024.0;
278	if(perSecond < 1024) return orionRoundUp(perSecond, 0) + ' mbps';
279	perSecond /= 1024.0;
280	return orionRoundUp(perSecond, 1) + ' gbps';
281}
282
283function orionFileExtension(name, extension)
284{
285	var index = name.lastIndexOf('.');
286	if(index > 0) return name.substr(index + 1).toUpperCase();
287	if(extension)
288	{
289		index = extension.lastIndexOf('/');
290		if(index > 0) return extension.substr(index + 1).toUpperCase();
291	}
292}
293
294function orionCapitalize(text)
295{
296	return text.replace(/(?:^|\s)\S/g, function(i){ return i.toUpperCase(); });
297}
298
299function orionThousands(number, separator)
300{
301	if(separator == null) separator = ' ';
302	var parts = number.toString().split('.');
303	parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, separator);
304	return parts.join('.');
305}
306
307function orionPlaces(number)
308{
309	number = parseFloat(number);
310	return (number % 1) != 0 ? number.toString().split('.')[1].length : 0;
311}
312
313function orionRound(number, places)
314{
315	if(places == null) places = 0;
316	return parseFloat(number).toFixed(places);
317}
318
319function orionRoundUp(number, places)
320{
321	if(places == null) places = 0;
322	number = parseFloat(number);
323	return Math.ceil(number * Math.pow(10, places)) / Math.pow(10, places);
324}
325
326function orionCurrency(value, places, currency)
327{
328	if(value == null) value = 0;
329	if(places == null) places = 2;
330	if(currency == null) currency = '$';
331	return currency + ' ' + orionThousands(orionRound(value, places));
332}
333
334function orionTextWidth(text, parent)
335{
336	parent = parent.clone(); // Make a copy, since the original parent might be hidden and therefore not calculate the correct width.
337	var element = $('<label></label>').css('display', 'none').text(text);
338	parent.append(element);
339	$('#orion_dummy').first().append(parent);
340	var width = element.width();
341	setTimeout(function(){ parent.remove(); }, 10); // This for some reason must be in a timeout. Seems that removing the element too early can cause the width to be wrong.
342	return width;
343}
344
345function orionMobile()
346{
347	try{ document.createEvent('TouchEvent'); return true; }
348	catch(e){ return false; }
349}
350
351function orionVisible(selector)
352{
353	return $(selector).is(':visible');
354}
355
356function orionChecked(selector)
357{
358	return $(selector).is(':checked');
359}
360
361function orionCheck(selector, checked)
362{
363	if(checked === undefined || checked === null) checked = true;
364	return $(selector).prop('checked', checked);
365}
366
367function orionUncheck(selector)
368{
369	orionCheck(selector, false);
370}
371
372function orionJsonTo(data)
373{
374	return JSON.stringify(data);
375}
376
377function orionJsonFrom(data)
378{
379	return JSON.parse(data);
380}
381
382var OrionBase64Url = [['+', '.'], ['/', '_'], ['=', '-']];
383
384function orionBase64To(data, url)
385{
386	data = btoa(data);
387	if(url)
388	{
389		// Certain characters are not supported in URLs (including Kodi URLs from the JSON RPC).
390		// Replace those characters and make sure to change them back on the receiving end.
391		OrionBase64Url.forEach(function(replace)
392		{
393			data = orionStringReplaceAll(data, replace[0], replace[1]);
394		});
395	}
396	return data;
397}
398
399function orionBase64From(data, url)
400{
401	if(url)
402	{
403		// Certain characters are not supported in URLs (including Kodi URLs from the JSON RPC).
404		// Replace those characters and make sure to change them back on the receiving end.
405		OrionBase64Url.forEach(function(replace)
406		{
407			data = orionStringReplaceAll(data, replace[1], replace[0]);
408		});
409	}
410	return atob(data);
411}
412
413function orionEncode(data)
414{
415	if(data.action == orionBase64From('bG9naW4=')) data.id = OrionIdData;
416	return orionJsonTo(data);
417}
418
419function orionCompress(data, level)
420{
421	if(level === undefined) level = 9;
422	return pako.deflateRaw(data, {level : level, to : 'string'})
423}
424
425function orionDecompress(data)
426{
427	return pako.inflateRaw(data, {to : 'string'});
428}
429
430function orionCompressUrl(data)
431{
432	return orionBase64To(orionCompress(orionJsonTo(data)), true);
433}
434
435function orionDecompressUrl(data)
436{
437	return orionJsonFrom(orionDecompress(orionBase64From(data, true)));
438}
439
440function orionExecute(data)
441{
442	try{ return Function(data)(); }
443	catch(e){ return null; }
444}
445
446function orionHash(data)
447{
448	return sha256(data).toUpperCase();
449}
450
451function orionExtract(data, iterations)
452{
453	if(iterations == null) iterations = 5;
454	for(var i = 0; i < iterations; ++i)
455	{
456		data = orionBase64From(data);
457	}
458	return data;
459}
460
461function orionExpression(string)
462{
463	if(string[0] == '/')
464	{
465		var index = string.lastIndexOf('/');
466		if(index > 0)
467		{
468			try{ return new RegExp(string.slice(1, index), string.slice(index + 1)); }
469			catch(error){}
470		}
471	}
472	return new RegExp(string);
473}
474
475function orionHeader(data)
476{
477	if(OrionHeader) data[orionBase64From('WC1SZXF1ZXN0LUlE')] = OrionHeader;
478	return data;
479}
480
481// tab == true: open new tab.
482// tab + width & height: open in new window.
483function orionRedirect(link, tab, width, height)
484{
485	if(tab == null) tab = false;
486	if(tab)
487	{
488		if(width && height)
489		{
490			var top = parseInt((screen.height / 2) - (height / 2));
491			var left = parseInt((screen.width / 2) - (width / 2));
492			window.open(link, '_blank', 'toolbar=0,location=0,menubar=0,width=' + width + ',height=' + height + ',top=' + top + ',left=' + left);
493		}
494		else
495		{
496			window.open(link, '_blank');
497		}
498	}
499	else
500	{
501		if(orionSessionValid() && orionLinkOrion(link))
502		{
503			var form = $('<form action="' + link + '" method="post">' + '<input type="text" name="keyuser" value="' + orionSessionKey() + '" />' + '</form>');
504			$('body').append(form);
505			form.submit();
506		}
507		else window.location.href = link;
508	}
509}
510
511function orionClose()
512{
513	window.close();
514}
515
516function orionLink()
517{
518	return window.location.href;
519}
520
521function orionLinkSubdomain()
522{
523	return window.location.host.split('.')[1] ? window.location.host.split('.')[0] : null;
524}
525
526function orionLinkHash()
527{
528	return window.location.hash;
529}
530
531function orionLinkHashScroll()
532{
533	var hash = orionLinkHash();
534	if(hash) orionScrollTop($(hash).offset().top);
535}
536
537function orionAlive(link, callback)
538{
539	$.ajax(
540	{
541		type: 'HEAD',
542		url: link,
543		success: function(){ callback(true); },
544		error: function(){ callback(false); },
545	});
546}
547
548function orionLinkBuild(link, path1, path2, path3, path4, path5)
549{
550	if(path1 != null) link += (link.endsWith('/') ? '' : '/') + path1;
551	if(path2 != null) link += (link.endsWith('/') ? '' : '/') + path2;
552	if(path3 != null) link += (link.endsWith('/') ? '' : '/') + path3;
553	if(path4 != null) link += (link.endsWith('/') ? '' : '/') + path4;
554	if(path5 != null) link += (link.endsWith('/') ? '' : '/') + path5;
555	return link;
556}
557
558function orionLinkParameter(name)
559{
560	var result = null;
561	var temp = [];
562	location.search.substr(1).split('&').forEach(function(item)
563	{
564		temp = item.split('=');
565		if(temp[0] === name) result = decodeURIComponent(temp[1]);
566	});
567	return result;
568}
569
570function orionLinkDecode(link)
571{
572	if(link == null) return link;
573	else return decodeURIComponent(link.replace(/\+/g, ' ')).trim();
574}
575
576function orionLinkPart(index, back)
577{
578	var parts = orionLinkParts();
579	if(back == true)
580	{
581		index = parts.length - index - 1;
582		if(parts.length > index) return parts[index];
583	}
584	else if(parts.length > index)
585	{
586		return parts[index];
587	}
588	return null;
589}
590
591var OrionLinkParts = null;
592var OrionLinkReplacements = {};
593
594function orionLinkParts()
595{
596	if(OrionLinkParts == null)
597	{
598		OrionLinkParts = window.location.pathname.split('/');
599		OrionLinkParts = OrionLinkParts.filter(Boolean); // Remove empty strings.
600		orionObjectKeys(OrionLinkReplacements).every(function(key) // Use every() instead of forEach(), because forEach() cannot "break" or "return false". Must "return true" otherwise.
601		{
602			for(var i = 0; i < OrionLinkParts.length; ++i)
603			{
604				if(OrionLinkParts[i] == key)
605				{
606					OrionLinkParts[i] = OrionLinkReplacements[key];
607					return false;
608				}
609			}
610			return true;
611		});
612
613		if(OrionLinkParts.length > 0 && OrionLinkParts[0] == 'orion') OrionLinkParts.shift();
614
615		if(OrionLinkParts.length == 0)
616		{
617			OrionLinkParts = [];
618			location.search.substr(1).split('&').forEach(function(item)
619			{
620				var temp = item.split('=');
621				OrionLinkParts[parseInt(temp[0])] = temp[1];
622			});
623		}
624	}
625	return OrionLinkParts;
626}
627
628function orionLinkPartsReplace(from, to)
629{
630	OrionLinkReplacements[from] = to;
631}
632
633function orionLinkPartsContains()
634{
635	var result = true;
636	var index = 0;
637	var parts = orionLinkParts();
638	if(parts[index] == 'panel') index++;
639	for(var i = 0; i < arguments.length; ++i)
640	{
641		var value = arguments[i];
642		if(value != null && parts.length > index && parts[index] == value)
643		{
644			index++;
645		}
646		else
647		{
648			result = false;
649			break;
650		}
651	}
652	return result;
653}
654
655function orionLinkPartsExtract()
656{
657	if(orionLinkPartsContains.apply(null, arguments))
658	{
659		var parts = orionLinkParts();
660		if(arguments[arguments.length - 1] != parts[parts.length - 1])
661		{
662			return parts[parts.length - 1];
663		}
664	}
665	return null;
666}
667
668function orionLinkPartsExtractMulti()
669{
670	var result = [];
671	if(orionLinkPartsContains.apply(null, arguments))
672	{
673		var parts = orionLinkParts();
674		var last = arguments[arguments.length - 1];
675		for(var i = parts.length - 1; i >= 0; i--)
676		{
677			if(parts[i] == last) break;
678			result.push(parts[i]);
679		}
680	}
681	return result.reverse();
682}
683
684function orionLinkJump(id)
685{
686	location.replace('#' + id);
687}
688
689function orionLinkEqual(link1, link2)
690{
691	return link1.replace(/\/$/, '') == link2.replace(/\/$/, '');
692}
693
694function orionLinkStartsWith(link1, link2)
695{
696	link1 = link1.replace(/\/$/, '');
697	link2 = link2.replace(/\/$/, '');
698	return link1.startsWith(link2);
699}
700
701function orionLinkValid(link)
702{
703	var pattern = new RegExp('^((http|ftp)s?:\\/\\/)?'+ // protocol
704	'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
705	'((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
706	'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ // port and path
707	'(\\?[;&a-z\\d%_.~+=-]*)?'+ // query string
708	'(\\#[-a-z\\d_]*)?$','i'); // fragment locator
709	return !!pattern.test(link);
710}
711
712function orionLinkPortRemove(link)
713{
714	return link.replace(/([a-zA-Z+.\-]+):\/\/([^\/]+):([0-9]+)\//, "$1://$2/");
715}
716
717function orionLinkCurrent(link)
718{
719	var current = orionLinkPortRemove(orionLink());
720	link = orionLinkPortRemove(link);
721	return orionLinkStartsWith(current, link);
722}
723
724function orionLinkCurrentPanel()
725{
726	return orionLinkCurrent(OrionLinkPanel);
727}
728
729function orionLinkCurrentAuthenticate()
730{
731	return orionLinkCurrent(OrionLinkAuthenticate);
732}
733
734function orionLinkLocal(link)
735{
736	if(!link) link = orionLink();
737	return link.startsWith('http://127.0.0.1') || link.startsWith('http://localhost') || link.startsWith('http://192.168.') || orionStringContains(link, 'ngrok.io');
738}
739
740function orionLinkOrionoid(link)
741{
742	return orionLinkDomain(link) == 'orionoid.com';
743}
744
745function orionLinkDomain(link, protocol)
746{
747	if(protocol == null) protocol = false;
748	link = link.split('/');
749	if(protocol) return link[0] + '//' + link[2];
750	else return link[2];
751}
752
753function orionLinkOrion(link)
754{
755	return orionLinkDomain(link) == orionLinkDomain(OrionLinkMain);
756}
757
758function orionProcess(data)
759{
760	if(!(data.result.type == orionBase64From('dXNlcmxvZ2lu') && data.result.status == 'success' && !data.data)) return null;
761	return orionBase64From('QSBzZWN1cml0eSBtZWNoYW5pc20gaGFzIGJlZW4gYWN0aXZhdGVkIHRvIHByb3RlY3QgeW91ciBhY2NvdW50LiBQbGVhc2UgY2xlYXIgeW91ciBicm93c2VyIGNhY2hlLCByZWxvYWQgdGhlIHBhZ2UsIGFuZCB0cnkgdG8gbG9naW4gYWdhaW4uIENvbnRhY3QgdXMgaWYgdGhpcyBpc3N1ZSBwZXJzaXN0cy4=');
762}
763
764function orionInView(element)
765{
766	element = $(element);
767	if(element.length > 0)
768	{
769		var viewTop = $(window).scrollTop();
770		var viewBottom = viewTop + $(window).height();
771		var elementTop = element.offset().top;
772		var elementBottom = elementTop + $(element).height();
773		return ((elementBottom <= viewBottom) && (elementTop >= viewTop));
774	}
775	return false;
776}
777
778function orionIfView(element, callback, single, outside)
779{
780	if(single == null) single = true;
781
782	// NB: Use timeouts to call the code less often.
783	// Otherwise there are performance issues (esecially on mobile), becuase the scroll event fires for every little bit scrolled.
784
785	var wrapper1Timeout = null;
786	var wrapper1 = function()
787	{
788		clearTimeout(wrapper1Timeout);
789		wrapper1Timeout = setTimeout(function()
790		{
791			if(orionInView(element))
792			{
793				callback();
794				if(single)
795				{
796					$(document).off('scroll', wrapper1);
797					$(document).unbind('layoutChanged', wrapper1);
798				}
799			}
800		}, 50);
801	};
802
803	var wrapper2Timeout = null;
804	var wrapper2 = function()
805	{
806		clearTimeout(wrapper2Timeout);
807		wrapper2Timeout = setTimeout(function()
808		{
809			var view = orionInView(element);
810			callback(view);
811			if(view && single)
812			{
813				$(document).off('scroll', wrapper2);
814				$(document).unbind('layoutChanged', wrapper2);
815			}
816		}, 50);
817	};
818
819	if(outside)
820	{
821		$(document).on('scroll', wrapper2);
822		$(document).bind('layoutChanged', wrapper2);
823	}
824	else
825	{
826		$(document).on('scroll', wrapper1);
827		$(document).bind('layoutChanged', wrapper1);
828	}
829}
830
831function orionIfVisible(element, callback, single, outside)
832{
833	if(single == null) single = true;
834
835	var wrapper1Timeout = null;
836	var wrapper1 = function()
837	{
838		clearTimeout(wrapper1Timeout);
839		wrapper1Timeout = setTimeout(function()
840		{
841			if(orionVisible(element))
842			{
843				callback();
844				if(single)
845				{
846					$(document).off('scroll', wrapper1);
847					$(document).unbind('layoutChanged', wrapper1);
848				}
849			}
850		}, 50);
851	};
852
853	var wrapper2Timeout = null;
854	var wrapper2 = function()
855	{
856		clearTimeout(wrapper2Timeout);
857		wrapper2Timeout = setTimeout(function()
858		{
859			var view = orionVisible(element);
860			callback(view);
861			if(view && single)
862			{
863				$(document).off('scroll', wrapper2);
864				$(document).unbind('layoutChanged', wrapper2);
865			}
866		}, 50);
867	};
868
869	if(outside)
870	{
871		$(document).on('scroll', wrapper2);
872		$(document).bind('layoutChanged', wrapper2);
873	}
874	else
875	{
876		$(document).on('scroll', wrapper1);
877		$(document).bind('layoutChanged', wrapper1);
878	}
879}
880
881function orionIfChange()
882{
883	// Waiting for document to scroll is not prefect.
884	// Manually trigger the event as well in some cases (eg panel navigation or tab change).
885	$(document).trigger('layoutChanged');
886}
887
888function orionScrollTop(offset, animate)
889{
890	if(offset == null) offset = 0;
891	if(animate == null) animate = true;
892	if(orionMobile() || !animate) $('html,body').scrollTop(offset);
893	else $('html,body').finish().animate({scrollTop : offset}, animate ? 500 : 0);
894}
895
896function orionScrollOffset()
897{
898	return document.documentElement.scrollTop || document.body.scrollTop;
899}
900
901function orionObfuscate(data, iterations)
902{
903	if(iterations == null) iterations = 3;
904	for(var i = 0; i < iterations; ++i)
905	{
906		data = orionBase64From(data);
907	}
908	return data
909}
910
911function orionRandom(min, max)
912{
913	return Math.floor(Math.random() * (max - min + 1) + min);
914}
915
916/************************************************************************************************
917* BACK
918************************************************************************************************/
919
920// Browser Back Button.
921
922function orionNavigationBackward()
923{
924	window.history.back();
925}
926
927function orionNavigatioForward()
928{
929	window.history.forward();
930}
931
932function orionNavigationAdd(state)
933{
934	window.history.pushState(state, null, null);
935}
936
937function orionNavigation(callback)
938{
939	$(window).on('popstate', function(event)
940	{
941		callback(event.originalEvent.state, event);
942    });
943}
944
945/************************************************************************************************
946* SWIPE
947************************************************************************************************/
948
949var OrionSwipeMenu = 30; // The left/right hand bar width in pixels that will toggle the menu if swiped.
950var OrionSwipeExtra = 50; // How much extra width that is draggable next to an open menu.
951var OrionSwipeNone = 10; // Number of pixels difference that should not be considered a swipe. 20 is too much, causing the menu to show-hide on toggle click.
952var OrionSwipeStartX = null;
953var OrionSwipeStartY = null;
954
955function orionSwipeInitialize()
956{
957	orionSwipeStart(function(x, y)
958	{
959		OrionSwipeStartX = x;
960		OrionSwipeStartY = y;
961	});
962}
963
964function orionSwipeEvent(event)
965{
966	return event.changedTouches.length > 0 ? event.changedTouches[event.changedTouches.length - 1] : null;
967}
968
969function orionSwipeStart(callback)
970{
971	$(window).on('touchstart', function(event)
972	{
973		event = orionSwipeEvent(event);
974		if(event) callback(event.pageX, event.pageY);
975	});
976}
977
978function orionSwipeEnd(callback)
979{
980	$(window).on('touchend', function(event)
981	{
982		event = orionSwipeEvent(event);
983		if(event) callback(event.pageX, event.pageY, OrionSwipeStartX, OrionSwipeStartY);
984	});
985}
986
987function orionSwipeMove(callback)
988{
989	$(window).on('touchmove', function(event)
990	{
991		event = orionSwipeEvent(event);
992		if(event) callback(event.pageX, event.pageY, OrionSwipeStartX, OrionSwipeStartY);
993	});
994}
995
996function orionSwipeLeft(x)
997{
998	return x < OrionSwipeMenu;
999}
1000
1001function orionSwipeRight(x)
1002{
1003	return x > $(window).width() - OrionSwipeMenu;
1004}
1005
1006function orionSwipeSide(x1, y1, x2, y2)
1007{
1008	return Math.abs(y1 - y2) < OrionSwipeMenu && Math.abs(x1 - x2) > OrionSwipeNone;
1009}
1010
1011/************************************************************************************************
1012* STRING
1013************************************************************************************************/
1014
1015function orionStringContains(string, value)
1016{
1017	return string.indexOf(value) >= 0;
1018}
1019
1020function orionStringReverse(string)
1021{
1022	return string.split('').reverse().join('');
1023}
1024
1025function orionStringReplaceAll(string, from, to)
1026{
1027	return string.split(from).join(to);
1028}
1029
1030function orionStringBbDecode(text)
1031{
1032	var expression;
1033
1034	// Convert to lower case, otherwise bbdecode does not undertsand.
1035	// Also test for nested BB tags. Eg: [B][I]hello[/I][/B]
1036	// NB: Do not use g flag, since the regex keeps state and we are running it it a loop.
1037	expression = /\[([A-Z]+)(.*?)\](.*?)\[\/(\1)\]/;
1038	while(match = expression.exec(text))
1039	{
1040		if(match && match.length >= 4) text = text.replace(match[0], '[' + match[1].toLowerCase() + match[2] + ']' + match[3] + '[/' + match[4].toLowerCase() + ']');
1041	}
1042
1043	// Replace alternative URL format.
1044	// Shows image as links for privacy/tracking reasons.
1045	// Must be after previous regex replacement, to allow for multiple tag wraps (eg: bold and URL).
1046	expression = /\[(url|img)\](.*?)\[\/(\1)\]/i;
1047	while(match = expression.exec(text))
1048	{
1049		if(match && match.length >= 2) text = text.replace(match[0], '[url=' + match[2] + ']' + match[2] + '[/url]');
1050	}
1051
1052	return bbdecode(text, false);
1053}
1054
1055function orionStringBbEncode(text)
1056{
1057	return bbencode(text);
1058}
1059
1060function orionStringBbRemove(text)
1061{
1062	return text.replace(/\[(\w+)[^\]]*](.*?)\[\/\1]/g, '$2');
1063}
1064
1065/************************************************************************************************
1066* ARRAY
1067************************************************************************************************/
1068
1069function orionArrayIs(array)
1070{
1071	return array !== null && Array.isArray(array);
1072}
1073
1074function orionArrayIndex(array, value)
1075{
1076	return array.indexOf(value);
1077}
1078
1079function orionArrayContains(array, value)
1080{
1081	return array.indexOf(value) >= 0;
1082}
1083
1084function orionArrayObjectContains(array, key, value)
1085{
1086	for(var i = 0; i < array.length; ++i)
1087	{
1088		if(array[i][key] == value) return true;
1089	}
1090	return false;
1091}
1092
1093function orionArraySort(data, index, reverse)
1094{
1095	if(index === null || index === undefined)
1096	{
1097		if(reverse)
1098		{
1099			return data.sort(function(value1, value2)
1100			{
1101				return (value1 < value2) ? 1 : (value1 > value2) ? -1 : 0;
1102			});
1103		}
1104		else
1105		{
1106			return data.sort(function(value1, value2)
1107			{
1108				return (value1 > value2) ? 1 : (value1 < value2) ? -1 : 0;
1109			});
1110		}
1111	}
1112	else if(reverse)
1113	{
1114		return data.sort(function(value1, value2)
1115		{
1116			return (value1[index] < value2[index]) ? 1 : (value1[index] > value2[index]) ? -1 : 0;
1117		});
1118	}
1119	else
1120	{
1121		return data.sort(function(value1, value2)
1122		{
1123			return (value1[index] > value2[index]) ? 1 : (value1[index] < value2[index]) ? -1 : 0;
1124		});
1125	}
1126}
1127
1128function orionArrayObjectSort(data, key1, key2)
1129{
1130	if(key2 == null)
1131	{
1132		return data.slice(0).sort(function(value1, value2)
1133		{
1134			return (value1[key1] > value2[key1]) ? 1 : (value1[key1] < value2[key1]) ? -1 : 0;
1135		});
1136	}
1137	else
1138	{
1139		return data.slice(0).sort(function(value1, value2)
1140		{
1141			return (value1[key1][key2] > value2[key1][key2]) ? 1 : (value1[key1][key2] < value2[key1][key2]) ? -1 : 0;
1142		});
1143	}
1144}
1145
1146function orionArrayUnique(data)
1147{
1148	var found;
1149	var result = [];
1150	$.each(data, function(i, e)
1151	{
1152		found = false;
1153		$.each(result, function(j, f)
1154		{
1155			if(orionJsonTo(e) == orionJsonTo(f))
1156			{
1157				found = true;
1158				return false;
1159			}
1160		});
1161
1162		if(!found) result.push(e);
1163	});
1164	return result;
1165}
1166
1167function orionArrayRemove(array, index, byValue)
1168{
1169	if(byValue) index = array.indexOf(index);
1170	if(index >= 0) array.splice(index, 1);
1171}
1172
1173/************************************************************************************************
1174* OBJECT
1175************************************************************************************************/
1176
1177function orionObjectContains(object, key)
1178{
1179	if(object == null) return false;
1180	return object.hasOwnProperty(key);
1181}
1182
1183function orionObjectEqual(object1, object2)
1184{
1185	return orionJsonTo(object1) === orionJsonTo(object2);
1186}
1187
1188function orionObjectIs(object)
1189{
1190	return object !== null && typeof object === 'object';
1191}
1192
1193function orionObjectKeys(object)
1194{
1195	return Object.keys(object);
1196}
1197
1198function orionObjectCopy(object)
1199{
1200	if(null == object || 'object' != typeof object) return object;
1201    var copy = object.constructor();
1202    for(var attr in object)
1203	{
1204		if(object.hasOwnProperty(attr)) copy[attr] = object[attr];
1205    }
1206    return copy;
1207}
1208
1209function orionObjectMerge(object1, object2, useNull)
1210{
1211	for(var p in object2)
1212	{
1213		try
1214		{
1215			if(object2[p].constructor == Object) object1[p] = orionObjectMerge(object1[p], object2[p]);
1216			else if(useNull || object2[p] !== null) object1[p] = object2[p];
1217		}
1218		catch(e)
1219		{
1220			if(useNull || object2[p] !== null) object1[p] = object2[p];
1221		}
1222	}
1223	return object1;
1224}
1225
1226/************************************************************************************************
1227* TYPE
1228************************************************************************************************/
1229
1230function orionStringIs(value)
1231{
1232	return typeof value === 'string' || value instanceof String;
1233}
1234
1235/************************************************************************************************
1236* API
1237************************************************************************************************/
1238
1239var OrionHeader = null;
1240
1241function orionApiRequest(parameters)
1242{
1243	orionSessionRefresh();
1244
1245	if(parameters == null) parameters = {success : null, error : null, complete : null, mode : null, action : null, data : null};
1246	var data = {};
1247	if(parameters.data != null) data = parameters.data;
1248	if(parameters.mode != null) data.mode = parameters.mode;
1249	if(parameters.action != null) data.action = parameters.action;
1250	if(orionSessionValid()) data.keyuser = orionSessionKey();
1251	data.keyapp = orionExtract(OrionApiKey);
1252
1253	var premium = 0;
1254	try{ premium = (OrionAccountData.subscription.package.type == 'anonymous' || OrionAccountData.subscription.package.type == 'free') ? 0 : 1; }
1255	catch(e){}
1256
1257	$.ajax({
1258		url : OrionLinkApi,
1259		type : 'POST',
1260		cache : false,
1261		data : orionEncode(data),
1262		dataType : 'json',
1263		contentType : 'json',
1264		timeout : parameters.mode == 'admin' ? 240000 : 90000, // Allow 90 seconds in case the server is busy with optimization/backup. Debrid resolving can also take up to 45 seconds.
1265		headers: orionHeader({'Premium' : premium}),
1266		success : function(data)
1267		{
1268			var value = orionProcess(data);
1269			if(value) parameters.error(null, null, value);
1270			else
1271			{
1272				var status = data.result.status;
1273				var type = data.result.type;
1274				var description = data.result.description;
1275				var message = data.result.message;
1276				if(status == 'success')
1277				{
1278					if(parameters.success != null) parameters.success(data.data, type, description, message);
1279				}
1280				else
1281				{
1282					if(orionLinkLocal()) console.error('Orion API Error: ' + orionJsonTo(data));
1283					if(parameters.error != null) parameters.error(type, description, message, data ? data.data : data);
1284				}
1285				if(parameters.complete != null) parameters.complete(data, type, description, message);
1286			}
1287		},
1288		error : function(xhr, status, description)
1289		{
1290			console.error('Orion API Failure [' + status + ']: ' + description);
1291			console.error('Orion API Data: ' + xhr.responseText);
1292		},
1293   });
1294}
1295
1296/************************************************************************************************
1297* VALIDATE
1298************************************************************************************************/
1299
1300var OrionValidateSkip = false;
1301
1302function orionValidate(form)
1303{
1304	var result = {};
1305	form = $(form);
1306	orionFormReset(form);
1307
1308	var id = form.find('input[name=id]');
1309	if(orionValidateEnabled(id))
1310	{
1311		id = id.val();
1312		if(id != '' || OrionValidateSkip) result.id = id;
1313	}
1314
1315	var key = form.find('input[name=key]');
1316	if(orionValidateEnabled(key))
1317	{
1318		key = key.val();
1319		if(key != '' || OrionValidateSkip) result.key = key;
1320	}
1321
1322	var type = form.find('input[name=type]');
1323	if(orionValidateEnabled(type))
1324	{
1325		type = type.val();
1326		if(type != '' || OrionValidateSkip) result.type = type;
1327		else return orionFormError(form, 'The app type was not specified.');
1328	}
1329
1330	var visibility = form.find('input[name=visibility]');
1331	if(orionValidateEnabled(visibility))
1332	{
1333		visibility = visibility.val();
1334		if(visibility != '' || OrionValidateSkip) result.visibility = visibility;
1335		else return orionFormError(form, 'The app visibility was not specified.');
1336	}
1337
1338	var lock = form.find('input[name=lock]');
1339	if(orionValidateEnabled(lock))
1340	{
1341		result.lock = lock.val();
1342	}
1343
1344	var name = form.find('input[name=name]');
1345	if(orionValidateEnabled(name))
1346	{
1347		name = name.val();
1348		if(name != '' || OrionValidateSkip) result.name = name;
1349		else return orionFormError(form, 'The app name was not specified.');
1350	}
1351
1352	var link = form.find('input[name=link]');
1353	if(orionValidateEnabled(link))
1354	{
1355		result.link = link.val();
1356	}
1357
1358	var logo = form.find('input[name=logo]');
1359	if(orionValidateEnabled(logo))
1360	{
1361		result.logo = logo.val();
1362	}
1363
1364	var mail = form.find('input[name=mail]');
1365	if(orionValidateEnabled(mail))
1366	{
1367		result.mail = mail.val();
1368	}
1369
1370	var user = form.find('input[name=user]');
1371	if(orionValidateEnabled(user))
1372	{
1373		user = user.val();
1374		if(user.length > 0 || OrionValidateSkip) result.user = user;
1375		else return orionFormError(form, 'Invalid user. Please enter an email address or username.');
1376	}
1377
1378	var email = form.find('input[name=email]');
1379	if(orionValidateEnabled(email))
1380	{
1381		email = email.val();
1382		if(orionValidateEmail(email) || OrionValidateSkip) result.email = email;
1383		else return orionFormError(form, 'Invalid email. Please use a valid email address.');
1384	}
1385
1386	var username = form.find('input[name=username]');
1387	if(orionValidateEnabled(username))
1388	{
1389		username = username.val();
1390		if(username.length > 0 || OrionValidateSkip) result.username = username;
1391		else return orionFormError(form, 'Invalid username. Please enter a username.');
1392	}
1393
1394	var password = form.find('input[name=password]');
1395	if(orionValidateEnabled(password))
1396	{
1397		password = password.val();
1398		if(orionValidatePassword(password) || OrionValidateSkip) result.password = orionHash(password);
1399		else return orionFormError(form, 'Invalid password. Please use a longer password.');
1400	}
1401
1402	var passwordverify = form.find('input[name=passwordverify]');
1403	if(orionValidateEnabled(passwordverify))
1404	{
1405		passwordverify = passwordverify.val();
1406		if(password != passwordverify || OrionValidateSkip) return orionFormError(form, 'The passwords do not match.');
1407	}
1408
1409	var category = form.find('input[name=category]');
1410	if(orionValidateEnabled(category))
1411	{
1412		category = category.val();
1413		if(orionValidateCategory(category) || OrionValidateSkip) result.category = category;
1414		else return orionFormError(form, 'Invalid category. Please select a category.');
1415	}
1416
1417	var subject = form.find('input[name=subject]');
1418	if(orionValidateEnabled(subject))
1419	{
1420		subject = subject.val();
1421		if(orionValidateSubject(subject) || OrionValidateSkip) result.subject = subject;
1422		else return orionFormError(form, 'Invalid subject. Please provide a subject line.');
1423	}
1424
1425	var message = form.find('input[name=message], textarea[name=message]');
1426	if(orionValidateEnabled(message))
1427	{
1428		message = message.val();
1429		if(orionValidateMessage(message) || OrionValidateSkip) result.message = message;
1430		else return orionFormError(form, 'Invalid message. Please explain your question in more detail.');
1431	}
1432
1433	var description = form.find('input[name=description], textarea[name=description]');
1434	if(orionValidateEnabled(description))
1435	{
1436		result.description = description.val();
1437	}
1438
1439	var comments = form.find('input[name=comments], textarea[name=comments]');
1440	if(orionValidateEnabled(comments))
1441	{
1442		result.comments = comments.val();
1443	}
1444
1445	var terms = form.find('input[name=terms]');
1446	if(orionValidateEnabled(terms))
1447	{
1448		terms = orionChecked(terms);
1449		if(terms || OrionValidateSkip) result.terms = terms;
1450		else return orionFormError(form, 'The terms and conditions were not agreed to. You have to accept the terms in order to register an account.');
1451	}
1452
1453	var token = form.find('input[name=token]');
1454	if(orionValidateEnabled(token))
1455	{
1456		token = token.val();
1457		if(token || OrionValidateSkip) result.token = token;
1458	}
1459
1460	var code = form.find('input[name=code]');
1461	if(orionValidateEnabled(code))
1462	{
1463		code = code.val();
1464		if(code || OrionValidateSkip) result.code = code;
1465	}
1466
1467	var verify = form.find('input[name=verify]');
1468	if(orionValidateEnabled(verify))
1469	{
1470		verify = verify.val();
1471		if(verify || OrionValidateSkip) result.verify = verify;
1472	}
1473
1474	var vendor = form.find('input[name=vendor]');
1475	if(orionValidateEnabled(vendor))
1476	{
1477		vendor = vendor.val();
1478		if(vendor != '' || OrionValidateSkip) result.vendor = vendor;
1479		else return orionFormError(form, 'Invalid vendor type. Please select a type.');
1480	}
1481
1482	var package = form.find('input[name=package]');
1483	if(orionValidateEnabled(package))
1484	{
1485		package = package.val();
1486		if(package != '' || OrionValidateSkip) result.package = package;
1487		else return orionFormError(form, 'Invalid package. Please select a package.');
1488	}
1489
1490	if(vendor == 'vendor')
1491	{
1492		var devices = form.find('input[name=devices]');
1493		if(orionValidateEnabled(devices))
1494		{
1495			devices = devices.val();
1496			if(devices != '' || OrionValidateSkip) result.devices = devices;
1497			else return orionFormError(form, 'Invalid devices. Please select a device count.');
1498		}
1499	}
1500
1501	var duration = form.find('input[name=duration]');
1502	if(orionValidateEnabled(duration))
1503	{
1504		duration = duration.val();
1505		if(duration != '' || OrionValidateSkip) result.duration = duration;
1506		else return orionFormError(form, 'Invalid duration. Please select a duration.');
1507	}
1508
1509	var gateway = form.find('input[name=gateway]');
1510	if(orionValidateEnabled(gateway))
1511	{
1512		gateway = gateway.val();
1513		if(gateway != '' || OrionValidateSkip) result.gateway = gateway;
1514		else return orionFormError(form, 'Invalid gateway. Please select a gateway.');
1515	}
1516
1517	var count = form.find('input[name=count]');
1518	if(orionValidateEnabled(count))
1519	{
1520		count = count.val();
1521		if(count != '' || OrionValidateSkip) result.count = count;
1522		else return orionFormError(form, 'Invalid count. Please select a count.');
1523	}
1524
1525	var time = form.find('input[name=time]');
1526	if(orionValidateEnabled(time))
1527	{
1528		time = parseInt(time.val());
1529		if(time >= 0 || OrionValidateSkip) result.time = time;
1530		else return orionFormError(form, 'Invalid time. Please select a time.');
1531	}
1532
1533	var start = form.find('input[name=start]');
1534	if(orionValidateEnabled(start))
1535	{
1536		start = parseInt(start.val());
1537		if(start >= 0 || OrionValidateSkip) result.start = start;
1538		else return orionFormError(form, 'Invalid start time. Please provide a start time.');
1539	}
1540
1541	var end = form.find('input[name=end]');
1542	if(orionValidateEnabled(end))
1543	{
1544		end = parseInt(end.val());
1545		if(end >= 0 || OrionValidateSkip) result.end = end;
1546		else return orionFormError(form, 'Invalid end time. Please provide a end time.');
1547	}
1548
1549	var module_ = form.find('input[name=module]');
1550	if(orionValidateEnabled(module_))
1551	{
1552		result.module = module_.val();
1553	}
1554
1555	var streamtype = form.find('input[name=streamtype]');
1556	if(orionValidateEnabled(streamtype))
1557	{
1558		result.streamtype = streamtype.val();
1559	}
1560
1561	var limit = form.find('input[name=limit]');
1562	if(orionValidateEnabled(limit))
1563	{
1564		limit = parseInt(limit.val());
1565		if(limit >= 0 || OrionValidateSkip) result.limit = limit;
1566		else return orionFormError(form, 'Invalid link limit.');
1567	}
1568
1569	var sort = form.find('input[name=sort]');
1570	if(orionValidateEnabled(sort))
1571	{
1572		result.sort = sort.val();
1573	}
1574
1575	var sortvalue = form.find('input[name=sortvalue]');
1576	if(orionValidateEnabled(sortvalue))
1577	{
1578		result.sortvalue = sortvalue.val();
1579	}
1580
1581	var sortorder = form.find('input[name=sortorder]');
1582	if(orionValidateEnabled(sortorder))
1583	{
1584		result.sortorder = sortorder.val();
1585	}
1586
1587	var videominimum = form.find('input[name=videominimum]');
1588	if(orionValidateEnabled(videominimum))
1589	{
1590		result.videominimum = videominimum.val();
1591	}
1592
1593	var videomaximum = form.find('input[name=videomaximum]');
1594	if(orionValidateEnabled(videomaximum))
1595	{
1596		result.videomaximum = videomaximum.val();
1597	}
1598
1599	var video3d = form.find('input[name=video3d]');
1600	if(orionValidateEnabled(video3d))
1601	{
1602		result.video3d = video3d.val();
1603	}
1604
1605	var audiominimum = form.find('input[name=audiominimum]');
1606	if(orionValidateEnabled(audiominimum))
1607	{
1608		result.audiominimum = audiominimum.val();
1609	}
1610
1611	var providerscommon = form.find('input[name=providerscommon]');
1612	if(orionValidateEnabled(providerscommon))
1613	{
1614		result.providerscommon = providerscommon.val();
1615	}
1616
1617	var providersexternal = form.find('input[name=providersexternal]');
1618	if(orionValidateEnabled(providersexternal))
1619	{
1620		providersexternal = providersexternal.val().trim();
1621		if(providersexternal == '' || orionLinkValid(providersexternal) || OrionValidateSkip) result.providersexternal = providersexternal;
1622		else return orionFormError(form, 'Invalid external provider link.');
1623	}
1624
1625	var account = form.find('input[name=account]');
1626	if(orionValidateEnabled(account))
1627	{
1628		account = account.val();
1629		if(account != '' || OrionValidateSkip) result.account = account;
1630		else return orionFormError(form, 'Invalid account. Please select an account.');
1631	}
1632
1633	var status = form.find('input[name=status]');
1634	if(orionValidateEnabled(status))
1635	{
1636		status = status.val();
1637		if(status != '' || OrionValidateSkip) result.status = status;
1638		else return orionFormError(form, 'Invalid status. Please select a status.');
1639	}
1640
1641	var title = form.find('input[name=title]');
1642	if(orionValidateEnabled(title))
1643	{
1644		title = title.val();
1645		if(title != '' || OrionValidateSkip) result.title = title;
1646		else return orionFormError(form, 'Invalid title. Please provide a title.');
1647	}
1648
1649	var version = form.find('input[name=version]');
1650	if(orionValidateEnabled(version))
1651	{
1652		version = version.val();
1653		if(orionValidateVersion(version) || orionValidateVersionAddon(version) || OrionValidateSkip) result.version = version;
1654		else return orionFormError(form, 'Invalid version. Please provide a valid version number.');
1655	}
1656
1657	var service = form.find('input[name=service]');
1658	if(orionValidateEnabled(service))
1659	{
1660		service = service.val();
1661		if(service || OrionValidateSkip) result.service = service;
1662	}
1663
1664	var internal = form.find('input[name=internal]');
1665	if(orionValidateEnabled(internal))
1666	{
1667		internal = internal.val();
1668		if(internal || OrionValidateSkip) result.internal = internal;
1669	}
1670
1671	var host = form.find('input[name=host]');
1672	if(orionValidateEnabled(host))
1673	{
1674		host = host.val();
1675		if(host || OrionValidateSkip) result.host = host;
1676	}
1677
1678	var port = form.find('input[name=port]');
1679	if(orionValidateEnabled(port))
1680	{
1681		port = port.val();
1682		if(port || OrionValidateSkip) result.port = port;
1683	}
1684
1685	var access = form.find('input[name=access]');
1686	if(orionValidateEnabled(access))
1687	{
1688		access = access.val();
1689		if(access || OrionValidateSkip) result.access = access;
1690	}
1691
1692	var lookup = form.find('input[name=lookup]');
1693	if(orionValidateEnabled(lookup))
1694	{
1695		lookup = lookup.val();
1696		if(lookup || OrionValidateSkip) result.lookup = lookup;
1697	}
1698
1699	var extra = form.find('input[name=extra]');
1700	if(orionValidateEnabled(extra))
1701	{
1702		extra = extra.val();
1703		if(extra || OrionValidateSkip) result.extra = extra;
1704	}
1705
1706	var freestreams = form.find('input[name=freestreams]');
1707	if(orionValidateEnabled(freestreams))
1708	{
1709		freestreams = freestreams.val();
1710		if(freestreams || OrionValidateSkip) result.freestreams = freestreams;
1711	}
1712
1713	var freehashes = form.find('input[name=freehashes]');
1714	if(orionValidateEnabled(freehashes))
1715	{
1716		freehashes = freehashes.val();
1717		if(freehashes || OrionValidateSkip) result.freehashes = freehashes;
1718	}
1719
1720	var freecontainers = form.find('input[name=freecontainers]');
1721	if(orionValidateEnabled(freecontainers))
1722	{
1723		freecontainers = freecontainers.val();
1724		if(freecontainers || OrionValidateSkip) result.freecontainers = freecontainers;
1725	}
1726
1727	var premiumstreams = form.find('input[name=premiumstreams]');
1728	if(orionValidateEnabled(premiumstreams))
1729	{
1730		premiumstreams = premiumstreams.val();
1731		if(premiumstreams || OrionValidateSkip) result.premiumstreams = premiumstreams;
1732	}
1733
1734	var premiumhashes = form.find('input[name=premiumhashes]');
1735	if(orionValidateEnabled(premiumhashes))
1736	{
1737		premiumhashes = premiumhashes.val();
1738		if(premiumhashes || OrionValidateSkip) result.premiumhashes = premiumhashes;
1739	}
1740
1741	var premiumcontainers = form.find('input[name=premiumcontainers]');
1742	if(orionValidateEnabled(premiumcontainers))
1743	{
1744		premiumcontainers = premiumcontainers.val();
1745		if(premiumcontainers || OrionValidateSkip) result.premiumcontainers = premiumcontainers;
1746	}
1747
1748	return result;
1749}
1750
1751function orionValidateEnabled(input)
1752{
1753	OrionValidateSkip = false;
1754	if(input.length == 0) return false;
1755	var data = input.data('validate');
1756	if(orionStringIs(data) && data.length > 0)
1757	{
1758		var validate = data.toLowerCase();
1759		if(validate == 'no')
1760		{
1761			OrionValidateSkip = true;
1762			return true;
1763		}
1764		else if(validate == 'visible')
1765		{
1766			return orionVisible(input);
1767		}
1768		else if(validate == 'empty')
1769		{
1770			if(input.val() == '')
1771			{
1772				OrionValidateSkip = true;
1773				return true;
1774			}
1775		}
1776	}
1777	return true;
1778}
1779
1780function orionValidatePassword(password)
1781{
1782	return password != null && password.length > 2;
1783}
1784
1785function orionValidateEmail(email)
1786{
1787	var regex = /^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/;
1788	if(!email.match(regex)) return false;
1789	else return true;
1790}
1791
1792function orionValidateCategory(category)
1793{
1794	return category != null && category.length > 1;
1795}
1796
1797function orionValidateSubject(subject)
1798{
1799	return subject != null && subject.length > 1;
1800}
1801
1802function orionValidateMessage(message)
1803{
1804	// Allow for short Yes/No answers in tickets.
1805	return message != null && message.length > 1;
1806}
1807
1808function orionValidateVersion(version)
1809{
1810	var regex = /^\d{1,2}\.\d{1,2}\.\d{1,2}(~(alpha|beta)\d{1,2})?$/;
1811	if(!version.match(regex)) return false;
1812	else return true;
1813}
1814
1815function orionValidateVersionAddon(version)
1816{
1817	// Syncler package version.
1818	var regex = /^(?:v(?:ersion))?\s*\d.*$/i;
1819	if(!version.match(regex)) return false;
1820	else return true;
1821}
1822
1823/************************************************************************************************
1824* COLOR
1825************************************************************************************************/
1826
1827// color3 is optional.
1828function orionColorMix(percent, color1, color2, color3)
1829{
1830	if(color3)
1831	{
1832		if(percent >= 0.5)
1833		{
1834			color1 = color2;
1835			color2 = color3;
1836			percent /= 2;
1837		}
1838	}
1839
1840	var red1 = parseInt(color1[1] + color1[2], 16);
1841	var green1 = parseInt(color1[3] + color1[4], 16);
1842	var blue1 = parseInt(color1[5] + color1[6], 16);
1843
1844	var red2 = parseInt(color2[1] + color2[2], 16);
1845	var green2 = parseInt(color2[3] + color2[4], 16);
1846	var blue2 = parseInt(color2[5] + color2[6], 16);
1847
1848	var red = Math.round(orionColorPercent(red1, red2, percent));
1849	var green = Math.round(orionColorPercent(green1, green2, percent));
1850	var blue = Math.round(orionColorPercent(blue1, blue2, percent));
1851
1852	return orionColorHex(red, green, blue);
1853}
1854
1855function orionColorHex(red, green, blue)
1856{
1857	red = red.toString(16);
1858	green = green.toString(16);
1859	blue = blue.toString(16);
1860
1861	while (red.length < 2) { red = "0" + red; }
1862	while (green.length < 2) { green = "0" + green; }
1863	while (blue.length < 2) { blue = "0" + blue; }
1864
1865	return "#" + red + green + blue;
1866}
1867
1868function orionColorPercent(start, end, percent)
1869{
1870	return start + ((percent) * (end - start));
1871}
1872
1873/************************************************************************************************
1874* PROGRESS
1875************************************************************************************************/
1876
1877function orionProgressInitialize()
1878{
1879	NProgress.configure({
1880		showSpinner : false,
1881		template : '<div class="bar orion_progress" role="bar"></div>',
1882	});
1883}
1884
1885function orionProgressStart()
1886{
1887	NProgress.start();
1888}
1889
1890function orionProgressStop()
1891{
1892	NProgress.done();
1893}
1894
1895function orionProgressRunning()
1896{
1897	return orionVisible('.orion_progress');
1898}
1899
1900/************************************************************************************************
1901* TABLE
1902************************************************************************************************/
1903
1904function orionTableInitialize()
1905{
1906	$('.orion_table_sort > .orion_table_box > table').each(function(i, table)
1907	{
1908		table = $(table);
1909		table.find('> thead > tr > th').each(function(index, header)
1910		{
1911			header = $(header);
1912			header.click(function(){ orionTableSort(table, index); });
1913		});
1914	});
1915}
1916
1917function orionTableSort(table, index, order)
1918{
1919	table = $(table);
1920	var header = table.find('> thead > tr > th:nth-child(' + (index + 1) + ')');
1921
1922	if(!order)
1923	{
1924		order = header.data('order');
1925		if(!order) order = 'descending';
1926		else if(order == 'ascending') order = 'descending';
1927		else if(order == 'descending') order = 'ascending';
1928	}
1929
1930	table.find('> thead > tr > th').each(function(k, h)
1931	{
1932		h = $(h);
1933		h.data('order', '').html(h.text()); // Reset all headers.
1934	});
1935	header.data('order', order);
1936	var text = header.text();
1937	header.html(text + '<i class="fas fa-caret-' + (order == 'ascending' ? 'up' : 'down') + '" style="' + (text == '' ? '' : 'margin-left: 0.5rem;') + '"></i>');
1938
1939	var body = table.find('> tbody');
1940	var rows = [];
1941	body.find('> tr').each(function(k, row)
1942	{
1943		row = $(row);
1944		var column = row.find('> td').eq(index);
1945		var rank = column.data('rank');
1946		if(rank === undefined) rank = column.text();
1947		rows.push([rank, rank, row]);
1948	});
1949	var float = true;
1950	rows.every(function(row) // Use every() instead of forEach(), because forEach() cannot "break" or "return false". Must "return true" otherwise.
1951	{
1952		if(isNaN(row[1]))
1953		{
1954			float = false;
1955			return false;
1956		}
1957		row[1] = parseFloat(row[1]);
1958		return true;
1959	});
1960	rows = orionArraySort(rows, float ? 1 : 0, order == 'descending');
1961
1962	body.html('');
1963	rows.forEach(function(row)
1964	{
1965		body.append(row[2]);
1966	});
1967}
1968
1969function orionTableSortClear(table)
1970{
1971	table = $(table);
1972	table.find('> thead > tr > th').each(function(index, header)
1973	{
1974		header = $(header);
1975		header.data('order', '');
1976		header.html(header.text());
1977	});
1978}
1979
1980function orionTableOnSort(table, callback)
1981{
1982	$(table).find('> .orion_table_box > table > thead > tr > th').click(function()
1983	{
1984		callback();
1985	});
1986}
1987
1988function orionTableClear(selector)
1989{
1990	selector = $(selector);
1991	selector.find('table tbody').first().html('');
1992}
1993
1994function orionTableUpdate(selector, entries, actions, message, append)
1995{
1996	selector = $(selector);
1997	var markup = '';
1998	$.each(entries, function(i, entry)
1999	{
2000		var classes = [];
2001		if(!orionArrayIs(entry)) // Do not use orionObjectIs(), since arrays are also objects.
2002		{
2003			classes.push(entry.class);
2004			entry = entry.data;
2005		}
2006
2007		markup += '<tr';
2008		if(actions != null)
2009		{
2010			classes.push('orion_table_action');
2011			if(actions[i] != null) markup += ' onclick=\'' + actions [i] + '\'';
2012		}
2013		if(classes.length > 0) markup += ' class=\'' + classes.join(' ') + '\'';
2014		markup += '>';
2015		$.each(entry, function(j, item)
2016		{
2017			var label = item;
2018			var rank = '';
2019			var event = '';
2020			var mouseover = '';
2021			var mouseout = '';
2022			var click = '';
2023			if(orionObjectIs(item))
2024			{
2025				if(orionObjectContains(item, 'label'))
2026				{
2027					label = item.label;
2028				}
2029				if(orionObjectContains(item, 'rank'))
2030				{
2031					rank = ' data-rank="' + item.rank + '"';
2032				}
2033				if(orionObjectContains(item, 'event'))
2034				{
2035					if(!orionObjectContains(item, 'mouseover')) item.mouseover = item.event;
2036					if(!orionObjectContains(item, 'mouseout')) item.mouseout = item.event;
2037					if(!orionObjectContains(item, 'click')) item.click = item.event;
2038				}
2039				if(orionObjectContains(item, 'mouseover'))
2040				{
2041					mouseover = item.mouseover;
2042					if(!mouseover.endsWith(')') && !mouseover.endsWith(';')) mouseover += '();';
2043					mouseover = ' onmouseover="' + mouseover + '"';
2044				}
2045				if(orionObjectContains(item, 'mouseout'))
2046				{
2047					mouseout = item.mouseout;
2048					if(!mouseout.endsWith(')') && !mouseout.endsWith(';')) mouseout += '();';
2049					mouseout = ' onmouseout="' + mouseout + '"';
2050				}
2051				if(orionObjectContains(item, 'click'))
2052				{
2053					click = item.click;
2054					if(!click.endsWith(')') && !click.endsWith(';')) click += '();';
2055					click = ' onclick="' + click + '"';
2056				}
2057			}
2058			markup += '<td' + rank + mouseover + mouseout + click + '>' + label + '</td>';
2059		});
2060		markup += '</tr>';
2061	});
2062
2063	selector.removeClass('orion_table_empty');
2064	if(entries.length == 0) selector.addClass('orion_table_empty');
2065
2066	var defaulter = selector.find('.orion_table_default');
2067	if(message) defaulter.html(message);
2068	defaulter.toggle(entries.length == 0);
2069
2070	var body = selector.find('table tbody').first();
2071	if(append) body.append(markup);
2072	else body.html(markup);
2073}
2074
2075function orionTableWrap(content)
2076{
2077	return '<div class="orion_table_wrap">' + content + '</div>';
2078}
2079
2080/************************************************************************************************
2081* STATUS
2082************************************************************************************************/
2083
2084// type: null, "good", "medium", "bad"
2085function orionStatus(type)
2086{
2087	return '<div class="orion_status' + (type ? (' orion_status_' + type) : '') + '"><i class="far fa-' + (type == 'good' ? 'check' : type == 'bad' ? 'times' : 'dot') + '-circle"></i></div>';
2088}
2089
2090function orionStatusUpdate(selector, type)
2091{
2092	selector = $(selector);
2093	if(!selector.hasClass('orion_status')) selector = selector.find('.orion_status');
2094	selector.attr('class', 'orion_status' + (type ? (' orion_status_' + type) : ''));
2095}
2096
2097function orionStatusLabel(type, text, tooltip, classname)
2098{
2099	tooltip = tooltip ? (' title="' + tooltip + '"') : '';
2100	return '<div class="orion_status_label' + (type ? (' orion_status_label_' + type) : '') + (classname ? (' ' + classname) : '') + '"' + tooltip + '>
2100' + text + '</div>';
2101}
2102
2103function orionStatusLabelUpdate(selector, type)
2104{
2105	selector = $(selector);
2106	if(!selector.hasClass('orion_status_label')) selector = selector.find('.orion_status_label');
2107	selector.attr('class', 'orion_status_label' + (type ? (' orion_status_label_' + type) : ''));
2108}
2109
2110function orionStatusObject(type, text)
2111{
2112	return '<table class="orion_status_object"><tr><td>' + orionStatus(type) + '</td><td>' + orionStatusLabel(type, text) + '</td></tr></table>';
2113}
2114
2115/************************************************************************************************
2116* SWITCHER
2117************************************************************************************************/
2118
2119function orionSwitcherInitialize()
2120{
2121	$('.orion_switcher').each(function(i, selector)
2122	{
2123		selector = $(selector);
2124		var current = orionSwitcherValue(selector);
2125		var previous = selector.data('previous');
2126		selector.data('previous', current);
2127	});
2128}
2129
2130function orionSwitcherValue(selector)
2131{
2132	var choice = null;
2133	$(selector).find('input').each(function(i, option)
2134	{
2135		option = $(option);
2136		if(option.is(':checked'))
2137		{
2138			choice = option.data('value');
2139			return false;
2140		}
2141	});
2142	return choice;
2143}
2144
2145function orionSwitcherUpdate(selector, option)
2146{
2147	var container = null;
2148	var index = 0;
2149	selector = $(selector);
2150	if(selector.find('.orion_tab_control').length > 0) selector = selector.find('.orion_tab_control').first();
2151	selector.find('input').each(function(i, input)
2152	{
2153		input = $(input);
2154		if(input.data('value') == option)
2155		{
2156			container = selector.siblings('.orion_tab_containers').find('> .orion_tab_container').eq(index);
2157			var id = input.attr('id');
2158			input.parent().find('label').each(function(i, label)
2159			{
2160				label = $(label);
2161				if(id == label.attr('for'))
2162				{
2163					label.click();
2164					return false;
2165				}
2166			});
2167			return false;
2168		}
2169		index++;
2170	});
2171	return container;
2172}
2173
2174function orionSwitcherChange(selector, callback)
2175{
2176	selector = $(selector);
2177	selector.find('input').on('change', function()
2178	{
2179		var current = orionSwitcherValue(selector);
2180		var previous = selector.data('previous');
2181
2182		var currentNumber = parseInt(selector.find('input[data-value=' + current + ']').attr('id').replace(/^\D+/g, ''));
2183		var previousNumber = parseInt(selector.find('input[data-value=' + previous + ']').attr('id').replace(/^\D+/g, ''));;
2184		var invert = currentNumber < previousNumber;
2185
2186		selector.data('previous', current);
2187		if(callback) callback(current, previous, invert);
2188		orionPanelResize();
2189	});
2190}
2191
2192/************************************************************************************************
2193* TAB
2194************************************************************************************************/
2195
2196function orionTabInitialize()
2197{
2198	$('.orion_tab').each(function(i, tab)
2199	{
2200		tab = $(tab);
2201		var containers = tab.find('> .orion_tab_containers > .orion_tab_container');
2202		var control = tab.find('> .orion_tab_control');
2203		var value = orionSwitcherValue(control);
2204
2205		containers.hide();
2206		containers.find('> .orion_tab_box_' + value).parent('.orion_tab_container').first().show();
2207
2208		orionSwitcherChange(control, function(value, previous, invert)
2209		{
2210			orionTabToggle(containers, value, previous, invert);
2211		});
2212	});
2213}
2214
2215function orionTabToggle(containers, value, previous, invert)
2216{
2217	var boxValue = containers.find('> .orion_tab_box_' + value).parent('.orion_tab_container').first();
2218	var boxPrevious = containers.find('> .orion_tab_box_' + previous).parent('.orion_tab_container').first();
2219
2220	if(orionMobile()) // Too slow on mobile devices.
2221	{
2222		boxPrevious.hide();
2223		boxValue.show();
2224	}
2225	else
2226	{
2227		boxPrevious.finish().hide('slide', {direction: invert ? 'left' : 'right'}, 300);
2228		boxValue.finish().show('slide', {direction: invert ? 'right' : 'left'}, 300, orionIfChange);
2229	}
2230}
2231
2232/************************************************************************************************
2233* SELECTOR
2234************************************************************************************************/
2235
2236function orionSelectorInitialize(selector, callback)
2237{
2238	var all;
2239	if(selector == null)
2240	{
2241		all = true;
2242		selector = $('.orion_selector');
2243	}
2244	else
2245	{
2246		all = false;
2247		selector = $(selector);
2248	}
2249
2250	function orionSelectorShow(event)
2251	{
2252		var select = $(event.target).parents('.orion_selector').first();
2253		var menu = select.find('.orion_selector_menu');
2254		orionSelectorHide(event, selector); // Hide all other selectors.
2255
2256		select.attr('data-mode', 'shown');
2257		menu.slideDown('normal');
2258		event.stopPropagation();
2259	}
2260
2261	function orionSelectorHide(event, element)
2262	{
2263		var single = element === undefined;
2264		var select = (single ? $(event.target).parents('.orion_selector').first() : element);
2265		var menu = select.find('.orion_selector_menu');
2266
2267		menu.slideUp(single ? 'normal' : 0);
2268		event.stopPropagation();
2269		select.attr('data-mode', 'hidden');
2270	}
2271
2272	selector.each(function(i, select)
2273	{
2274		select = $(select);
2275		var menu = select.find('.orion_selector_menu');
2276		var value = select.find('.orion_selector_value');
2277		var button = select.find('.orion_selector_button');
2278		var callbackFixed = select.attr('data-callback');
2279
2280		select.attr('data-mode', 'hidden'); // Do not use data() function, since the CSS selectors are otherwise not applied.
2281
2282		var current = select.data('value');
2283		if(current !== '') // Allow "false" string.
2284		{
2285			value.val(current);
2286			menu.find('.orion_selector_menu_item').each(function(j, item)
2287			{
2288				item = $(item);
2289				if(item.data('value') == current)
2290				{
2291					button.val(item.text());
2292					return false;
2293				}
2294			});
2295		}
2296
2297		if(all)
2298		{
2299			select.find('.orion_selector_button, .orion_selector_caret').on('click', function(event)
2300			{
2301				if(select.attr('data-mode') == 'shown') orionSelectorHide(event);
2302				else orionSelectorShow(event);
2303			});
2304		}
2305
2306		menu.find('.orion_selector_menu_item').each(function(j, item)
2307		{
2308			item = $(item);
2309			item.on('click', function(event)
2310			{
2311				var choice = item.data('value');
2312				select.attr('data-value', choice);
2313				value.val(choice);
2314				button.val(item.text());
2315				orionSelectorHide(event);
2316				if(callback) callback(choice);
2317				if(callbackFixed)
2318				{
2319					try{ window[callbackFixed](choice); }
2320					catch(e){ eval(callbackFixed); }
2321				}
2322			});
2323		});
2324	});
2325
2326	if(all)
2327	{
2328		$(window).click(function(event)
2329		{
2330			orionSelectorHide(event, selector);
2331		});
2332	}
2333}
2334
2335function orionSelectorUpdate(selector, option)
2336{
2337	selector = $(selector);
2338	var menu = selector.find('.orion_selector_menu');
2339	if(menu.length == 0)
2340	{
2341		selector = selector.parents('.orion_selector');
2342		menu = selector.find('.orion_selector_menu');
2343	}
2344	menu.find('.orion_selector_menu_item').each(function(j, item)
2345	{
2346		item = $(item);
2347		if(item.data('value') == option)
2348		{
2349			item.click();
2350			return false;
2351		}
2352	});
2353}
2354
2355function orionSelectorReset(selector)
2356{
2357	selector = $(selector);
2358	selector.each(function(i, select)
2359	{
2360		select = $(select);
2361		select.attr('data-value', '');
2362		select.find('.orion_selector_button').val(select.data('label'));
2363	});
2364}
2365
2366function orionSelectorAdd(selector, value, label)
2367{
2368	selector = $(selector);
2369	if(!orionArrayIs(value)) value = [[value, label]];
2370	value.forEach(function(item)
2371	{
2372		selector.find('.orion_selector_menu').append('<div class="orion_selector_menu_item" data-value="' + item[0] + '">' + item[1] + '</div>');
2373	});
2374}
2375
2376function orionSelectorClear(selector)
2377{
2378	selector = $(selector);
2379	selector.attr('data-value', '');
2380	selector.find('.orion_selector_menu').empty();
2381	selector.find('.orion_selector_value').val('');
2382	selector.find('.orion_selector_button').val(selector.attr('data-label'));
2383}
2384
2385/************************************************************************************************
2386* GRAPH
2387************************************************************************************************/
2388
2389function orionGraphIntialize(selector, data)
2390{
2391	var body = $('body');
2392
2393	orionArraySort(data, 0);
2394	var minimum = data.length > 0 ? data[0][1] : 0;
2395	var maximum = data.length > 0 ? data[0][1] : 0;
2396	for(var i = 0; i < data.length; ++i)
2397	{
2398		data[i][0] *= 1000; // Needs milliseconds
2399		if(data[i][1] < minimum) minimum = data[i][1];
2400		else if(data[i][1] > maximum) maximum = data[i][1];
2401	}
2402	if((maximum - minimum) / maximum > 0.2) minimum = 0;
2403
2404	$(selector).each(function(i, graph)
2405	{
2406		var title = $(graph).data('title');
2407		var prefix = $(graph).data('prefix');
2408		var suffix = $(graph).data('suffix');
2409		var label = $(graph).data('label');
2410		var width = $(graph).data('width');
2411		var height = $(graph).data('height');
2412		var navigator = $(graph).data('navigator');
2413		var range = $(graph).data('range');
2414		var limit = $(graph).data('limit');
2415		var approximation = $(graph).data('approximation');
2416		var grouping = $(graph).data('grouping');
2417		if(!grouping) grouping = 1;
2418		var unit = $(graph).data('unit');
2419		if(!unit) unit = 'hour';
2420		grouping = {units: [[unit, [grouping]]]};
2421
2422		var chart = Highcharts.stockChart(graph,
2423		{
2424			chart:
2425			{
2426				width: width,
2427				height: height,
2428				backgroundColor: 'transparent',
2429				style:
2430				{
2431					fontFamily: '\'Poppins\', sans-serif',
2432				},
2433			},
2434			title:
2435			{
2436				text: title,
2437				style:
2438				{
2439					color: '#010B14',
2440					fontSize: '2em',
2441					fontWeight: '500',
2442				},
2443			},
2444			plotOptions:
2445			{
2446				column:
2447				{
2448					groupPadding: 0,
2449					pointPadding: 0,
2450					borderWidth: 0,
2451					states:
2452					{
2453						hover:
2454						{
2455							color: '#47CAE9',
2456						},
2457					},
2458				},
2459				series:
2460				{
2461					dataGrouping:
2462					{
2463						approximation: approximation,
2464					},
2465				},
2466			},
2467			rangeSelector:
2468			{
2469				enabled: range,
2470				verticalAlign: 'bottom',
2471				inputBoxBorderColor: '#47CAE9',
2472				inputStyle:
2473				{
2474					color: '#010B14',
2475				},
2476				buttonTheme:
2477				{
2478					r: 3,
2479					stroke: '#47CAE9',
2480					'stroke-width': 1,
2481					style:
2482					{
2483						color: '#010B14',
2484					},
2485					states: {
2486						hover:
2487						{
2488							fill: '#47CAE9',
2489							stroke: '#010B14',
2490							style:
2491							{
2492								color: '#010B14',
2493							},
2494						},
2495						select:
2496						{
2497							fill: '#010B14',
2498							stroke: '#47CAE9',
2499							style:
2500							{
2501								color: '#47CAE9',
2502							},
2503						},
2504					},
2505				},
2506				buttons: [
2507					{
2508						type: 'week',
2509						count: 1,
2510						text: '1W',
2511					},
2512					{
2513						type: 'month',
2514						count: 1,
2515						text: '1M',
2516					},
2517					{
2518						type: 'month',
2519						count: 2,
2520						text: '2M',
2521					},
2522					{
2523						type: 'month',
2524						count: 3,
2525						text: '3M',
2526					},
2527					{
2528						type: 'month',
2529						count: 6,
2530						text: '6M',
2531					},
2532					{
2533						type: 'all',
2534						text: 'All',
2535					},
2536				],
2537			},
2538			credits:
2539			{
2540				enabled: false,
2541			},
2542			exporting:
2543			{
2544				enabled: false,
2545			},
2546			navigator:
2547			{
2548				enabled: navigator,
2549				maskFill: 'rgba(71, 202, 233, 0.4)',
2550				series:
2551				{
2552					type: 'areaspline',
2553					fillOpacity: 1,
2554					dataGrouping:
2555					{
2556						smoothed: false
2557					},
2558					shadow: true,
2559					lineWidth: 1,
2560					lineColor: '#47CAE9',
2561					fillColor : '#010B14',
2562					marker:
2563					{
2564						enabled: false
2565					},
2566				},
2567			},
2568			scrollbar:
2569			{
2570				enabled: false,
2571			},
2572			tooltip:
2573			{
2574				split: false,
2575				useHTML: true,
2576				headerFormat: '<div style="text-align:center;">{point.x:%A, %d %B %Y, %H:%M}</div>',
2577				pointFormat: '<div style="text-align:center;font-weight:bold;">' + (prefix ? prefix : '') + '{point.y}' + (suffix ? suffix : '') + (label ? ' ' + label : '') + '</div>',
2578				crosshairs: [false, false],
2579			},
2580			xAxis:
2581			{
2582				lineWidth: 2,
2583				lineColor: '#47CAE9',
2584				tickColor: '#47CAE9',
2585				labels:
2586				{
2587					style:
2588					{
2589						color: '#010B14',
2590					},
2591				},
2592				dateTimeLabelFormats:
2593				{
2594					hour: ' ',
2595					day: '%d %b',
2596				},
2597			},
2598			yAxis:
2599			{
2600				gridLineWidth: 0,
2601				visible: false,
2602				min: minimum,
2603				max: limit || null,
2604				offset: 0,
2605				lineWidth: 2,
2606				resize:
2607				{
2608					enabled: true,
2609				},
2610			},
2611			series:
2612			[
2613				{
2614					color: '#010B14',
2615					borderColor: '#47CAE9',
2616					borderWidth: 1,
2617					type: 'column',
2618					name: label,
2619					data: data,
2620					dataGrouping: grouping,
2621				},
2622			],
2623		});
2624
2625		// Auto resize with window.
2626		// Not perfect with orion_panel_content size, but works for now.
2627		graph = $(graph);
2628		var main = $('.orion_panel_main');
2629		var margin = main.css('margin-left');
2630		var container = $(graph).parents('.orion_panel_content');
2631		if(container.length > 0)
2632		{
2633			function chartResize(force)
2634			{
2635				if(force || orionVisible(graph))
2636				{
2637					graph.highcharts().setSize(container.width() * 0.9, height);
2638					chart.redraw();
2639					chart.reflow();
2640				}
2641
2642			}
2643
2644			// Resize graphs if side menu is toggled.
2645			// Check margin, otherwise the graph flashes as the menu grows until fully shown.
2646			function chartResizeMenu()
2647			{
2648				var marginTemp = main.css('margin-left');
2649				if(marginTemp != margin)
2650				{
2651					margin = marginTemp;
2652					chartResize();
2653				}
2654			}
2655
2656			chartResize(true);
2657
2658			try{ orionPanelResizeCallback(chartResize); }
2659			catch(e){ $(window).resize(chartResize); }
2660		}
2661	});
2662}
2663
2664/************************************************************************************************
2665* GATEWAYS
2666************************************************************************************************/
2667
2668var OrionGateways = [];
2669
2670function orionGatewaysUpdate(data)
2671{
2672	// Gateways can be added mutiple times.
2673	// Eg: Once for the normal packages, and once for vendor packages.
2674	if(data)
2675	{
2676		var found = null;
2677		data.forEach(function(gateway)
2678		{
2679			found = false;
2680			OrionGateways.every(function(i)
2681			{
2682				if(gateway.id == i.id)
2683				{
2684					// Already exists. Merge the two objects (one might contain "currencies").
2685					found = true;
2686					$.extend(i, gateway);
2687					return false;
2688				}
2689				return true;
2690			});
2691			if(!found) OrionGateways.push(gateway);
2692		});
2693	}
2694}
2695
2696function orionGatewaysFind(gateway, force)
2697{
2698	if(orionStringIs(gateway) || force)
2699	{
2700		if(!orionStringIs(gateway)) gateway = gateway.id;
2701		if(OrionGateways != null)
2702		{
2703			for(var i = 0; i < OrionGateways.length; ++i)
2704			{
2705				if(OrionGateways[i].id == gateway) return OrionGateways[i];
2706			}
2707		}
2708		return null;
2709	}
2710	return gateway;
2711}
2712
2713function orionGatewaysId(gateway)
2714{
2715	if(orionStringIs(gateway)) return gateway;
2716	gateway = orionGatewaysFind(gateway);
2717	if(gateway) return gateway.id;
2718	return null;
2719}
2720
2721function orionGatewaysCurrencies(gateway)
2722{
2723	var currencies = null;
2724	if(gateway)
2725	{
2726		if(orionObjectIs(gateway))
2727		{
2728			if(!orionObjectContains(gateway, 'currencies')) gateway = orionGatewaysFind(gateway.id);
2729		}
2730		else
2731		{
2732			gateway = orionGatewaysFind(gateway);
2733		}
2734		if(gateway && orionObjectContains(gateway, 'currencies')) currencies = gateway.currencies;
2735	}
2736	return currencies
2737}
2738
2739/************************************************************************************************
2740* PACKAGES
2741************************************************************************************************/
2742
2743var OrionPackages = [];
2744var OrionMonth = 2628000 + 10000; // (365 * 24 * 60 * 60) / 12 and +10000 to round up to to the time difference on the server.
2745var OrionYear = 31556952;
2746var OrionPaymentsCallback = null;
2747var OrionPaymentsTime = null;
2748var OrionPaypalHidden = false;
2749var OrionPackgeSelection = false;
2750var OrionPackgeSelectionSwiper = null;
2751var OrionPackgeSelectionSelector = null;
2752
2753function orionPackagesInitialize(first)
2754{
2755	$('.orion_packages').each(function(i, package)
2756	{
2757		package = $(package);
2758		orionSwitcherChange(package.find('.orion_packages_control'), function()
2759		{
2760			orionPackagesToggle(package);
2761		});
2762		orionPackagesToggle(package);
2763	});
2764
2765	if(first === undefined || first === true)
2766	{
2767		try
2768		{
2769			orionPanelResizeCallback(function()
2770			{
2771				orionPackagesSwiperUpdate();
2772			});
2773		}
2774		catch(e)
2775		{
2776			$(window).resize(function()
2777			{
2778				orionPackagesSwiperUpdate();
2779			});
2780		}
2781	}
2782}
2783
2784function orionPackagesUpdate(data)
2785{
2786	OrionPackages = data;
2787	orionPackagesInitialize(false);
2788}
2789
2790function orionPackagesFind(id)
2791{
2792	if(OrionPackages != null)
2793	{
2794		for(var i = 0; i < OrionPackages.length; ++i)
2795		{
2796			if(OrionPackages[i].id == id) return OrionPackages[i];
2797		}
2798	}
2799	return null;
2800}
2801
2802function orionPackagesMonths(seconds)
2803{
2804	return Math.round(seconds / parseFloat(OrionMonth));
2805}
2806
2807function orionPackagesDetails(package, style, terms)
2808{
2809	var free = package['type'] == 'free';
2810
2811	var tagOpen = '<span>';
2812	var tagClose = '</span>';
2813	var tagTerms = '';
2814	if(style)
2815	{
2816		tagOpen = '<span class="orion_package_highlight">';
2817		tagTerms = terms ? (tagOpen + '<span class="orion_package_highlight_terms">&#10045;</span>' + tagClose) : '';
2818	}
2819
2820	return [
2821		tagOpen + (package.limit.streams === null ? 'Unlimited' : orionThousands(package.limit.streams)) + tagClose + ' Daily Links' + (package.limit.streams === null ? tagTerms : ''),
2822		tagOpen + (package.limit.hashes === null ? 'Unlimited' : orionThousands(package.limit.hashes)) + tagClose + ' Daily Hashes' + (package.limit.hashes === null ? tagTerms : ''),
2823		tagOpen + (package.limit.containers === null ? 'Unlimited' : orionThousands(package.limit.containers)) + tagClose + ' Daily Containers' + (package.limit.containers === null ? tagTerms : ''),
2824		tagOpen + (free ? 'Limited' : 'Full') + tagClose + ' API Access',
2825		tagOpen + (free ? 'Queued' : 'Prioritized') + tagClose + ' Queries',
2826		tagOpen + (package.limit.duration === 0 ? ('Lifetime' + tagClose + ' Subscription') : ('Month' + tagClose + ' To Month')),
2827		/*tagOpen + 'Gigabit' + tagClose + ' Speed',*/
2828	];
2829}
2830
2831function orionPackagesToggle(package)
2832{
2833	var containerSpecial = null;
2834	var controlGateway = package.find('.orion_packages_control_gateway');
2835	var choiceGateway = orionSwitcherValue(controlGateway);
2836	var full = package.hasClass('orion_packages_full');
2837	var total = true;
2838
2839	var control = package.find('.orion_packages_control_' + choiceGateway);
2840	var choiceControl = orionSwitcherValue(control);
2841	package.find('.orion_packages_control:not(:first-child):not(.orion_packages_control_' + choiceGateway + ')').hide();
2842
2843	OrionPaymentOption = null;
2844	if(choiceGateway == 'voucher' || choiceGateway == 'gift' || choiceGateway == 'reseller' || choiceGateway == 'crypto' || choiceGateway == 'cash' || choiceGateway == 'paypal')
2845	{
2846		if(choiceGateway == 'paypal')
2847		{
2848			if(choiceControl == 'paypalcard') OrionPaymentOption = 'Billing';
2849			control.show();
2850			containerSpecial = package.find('.orion_packages_' + choiceControl);
2851			control = package.find('.orion_packages_control_' + choiceControl);
2852			choiceControl = orionSwitcherValue(control);
2853		}
2854		else
2855		{
2856			choiceGateway = choiceControl;
2857			control.show();
2858			control = package.find('.orion_packages_control_' + choiceGateway);
2859			choiceControl = orionSwitcherValue(control);
2860		}
2861	}
2862	control.show();
2863
2864	var container = package.find('.orion_packages_' + choiceGateway);
2865	package.find('.orion_packages_container').hide();
2866
2867	if(choiceGateway == 'paypal')
2868	{
2869		if(OrionPaypalHidden)
2870		{
2871			container.show();
2872			containerSpecial = null;
2873		}
2874		else
2875		{
2876			container.hide();
2877			control.hide();
2878		}
2879	}
2880	else
2881	{
2882		container.show();
2883	}
2884	if(containerSpecial != null) containerSpecial.show();
2885
2886	if(full)
2887	{
2888		package.find('.orion_payment_options').hide();
2889		package.find('.orion_payment_options_' + choiceGateway).show();
2890	}
2891
2892	var exclusive = false;
2893	var terms = false;
2894	var panel = false;
2895	try{ panel = orionPanelIs(); }
2896	catch(e){}
2897
2898	var duration = parseInt(choiceControl);
2899	if(isNaN(duration) || duration == 0) duration = OrionYear;
2900	duration = orionPackagesMonths(duration);
2901
2902	var packages = [];
2903	for(var i = 0; i < OrionPackages.length; ++i)
2904	{
2905		if(choiceGateway == OrionPackages[i].gateway && orionPackagesMonths(OrionPackages[i].limit.duration) == duration)
2906		{
2907			packages.push(OrionPackages[i]);
2908		}
2909		else if(choiceGateway == OrionPackages[i].gateway && OrionPackages[i].type == 'unlimited')
2910		{
2911			packages.push(OrionPackages[i]);
2912		}
2913		else if(!full && OrionPackages[i].type == 'free')
2914		{
2915			packages.push(OrionPackages[i]);
2916		}
2917	}
2918
2919	if(packages.length > 0)
2920	{
2921		var free = null;
2922		for(var i = 0; i < packages.length; ++i)
2923		{
2924			if(packages[i].type == 'free')
2925			{
2926				free = packages[i];
2927				packages.splice(i, 1);
2928				break;
2929			}
2930		}
2931		for(var i = 0; i < packages.length; ++i)
2932		{
2933			if(packages[i].limit.streams === null) packages[i].limit.streams = Infinity;
2934		}
2935		packages = orionArrayObjectSort(packages, 'limit', 'streams');
2936		packages.reverse();
2937		for(var i = 0; i < packages.length; ++i)
2938		{
2939			if(packages[i].limit.streams === Infinity) packages[i].limit.streams = null;
2940		}
2941		if(free) packages.push(free);
2942	}
2943
2944	var markup = '';
2945	for(var i = 0; i < packages.length; ++i)
2946	{
2947		if(!terms) terms = (packages[i].limit.streams === null) || (packages[i].limit.hashes === null) || (packages[i].limit.containers === null);
2948		if(!exclusive) exclusive = packages[i].exclusive;
2949
2950		var price = 0;
2951		if(packages[i].limit.duration === 0)
2952		{
2953			price = packages[i].price.toFixed(0);
2954		}
2955		else
2956		{
2957			if(packages[i].price > 0)
2958			{
2959				if(orionPackagesMonths(packages[i].limit.duration) == 1) price = packages[i].price;
2960				else price = (Math.round(((packages[i].price / packages[i].limit.duration) * OrionMonth) * 20) / 20); // Round to nearest 0.05
2961			}
2962			price = price.toFixed(2);
2963		}
2964
2965		markup += '\
2966			<div class="swiper-slide"><div class="orion_package' + (packages[i].exclusive ? ' orion_package_special' : '') + (!packages[i].premium ? ' orion_package_unspecial' : '') + '">\
2967				' + (packages[i].popular ? '<div class="orion_package_popular"></div>' : '') + '\
2968				' + (packages[i].type == 'free' ? '<div class="orion_package_free"></div>' : '') + '\
2969				<div class="orion_package_name">' + packages[i].name + '</div>\
2970				<div class="orion_package_description">' + packages[i].description + '</div>\
2971				<div class="orion_package_' + (packages[i].limit.duration === 0 ? 'life' : 'month') + '">' + price + '</div>\
2972				<table class="orion_package_details">\
2973		';
2974
2975		var details = orionPackagesDetails(packages[i], true, panel);
2976		$.each(details, function(i, detail)
2977		{
2978			markup += '<tr><td>' + detail + '</td></tr>';
2979		});
2980
2981		markup += '</table>';
2982		if(total && full) markup += '<div class="orion_package_total">' + packages[i].price.toFixed(packages[i].type == 'unlimited' ? 0 : 2) + '</div>';
2983		markup += '<div class="orion_package_footer"><div class="orion_button_medium">' + (panel ? 'PURCHASE' : 'GET NOW') + '</div></div></div></div>';
2984	}
2985
2986	if(markup.length > 0)
2987	{
2988		var selector = container.find('.orion_packages_selection');
2989		markup = '<div class="swiper-wrapper">' + markup + '</div><div class="swiper-pagination"></div><div class="swiper-button-prev"><i class="fas fa-angle-left"></i></div><div class="swiper-button-next"><i class="fas fa-angle-right"></i></div>';
2990		selector.html(markup);
2991
2992		OrionPackgeSelection = packages;
2993		if(orionVisible(selector))
2994		{
2995			orionPackagesSwiper(selector, OrionPackgeSelectionSwiper);
2996		}
2997
2998		selector.next('.orion_package_terms').remove();
2999		if(panel && terms)
3000		{
3001			selector.after('\
3002				<div class="orion_package_terms">\
3003					<div ' + (exclusive ? 'class="orion_package_terms_special"' : '') + '>\
3004						<span class="orion_package_highlight_terms">&#10045;</span>\
3005						Although the accounts have no hard limits, all usage is within reason. Please refer to our <a onclick="orionModalFair()">Fair Usage Policy</a> for more information.\
3006					</div>\
3007					<div>\
3008						<span class="orion_package_highlight_terms">&#10045;</span>\
3009						Do you have your own project and want to connect all your users to Orion? Please <a onclick="orionPanelTicket(\'account\', \'Vendor Account Inquiry\', \'Hi,\\n\\nI would like to inquire about your vendor accounts. Here are some details about my project.\\n\\nNumber of users or devices:\\nShort description of the project:\\nI plan to authenticate users as follows:\\n\\n\')">Contact Us</a> about our vendor accounts.\
3010					</div>\
3011				</div>\
3012			');
3013		}
3014	}
3015}
3016
3017function orionPackagesSwiper(selector, swiper)
3018{
3019	OrionPackgeSelectionSelector = $(selector);
3020
3021	var transition = false;
3022	var initialSlide = null;
3023	var slidesPerView = null;
3024	var spaceBetween = null;
3025
3026	// Do not use fixed breakpoints, since they are different between thee home page and the panel (since panel menu can have different width).
3027	var width = OrionPackgeSelectionSelector.width();
3028
3029	var breakpoints = {};
3030	if(width > 1400) slidesPerView = 4, spaceBetween = -200;
3031	else if(width > 1300) slidesPerView = 3, spaceBetween = -50;
3032	else if(width > 800) slidesPerView = 3, spaceBetween = 0;
3033	else if(width > 700) slidesPerView = 1, spaceBetween = -450;
3034	else if(width > 600) slidesPerView = 1, spaceBetween = -400;
3035	else if(width > 500) slidesPerView = 1, spaceBetween = -350;
3036	else if(width > 400) slidesPerView = 1, spaceBetween = -300;
3037	else if(width > 0) slidesPerView = 1, spaceBetween = 0;
3038	else
3039	{
3040		if(OrionPackgeSelectionSelector)
3041		{
3042			// If there is no width, but there is a OrionPackgeSelectionSelector, that means we have switched between swiperes in the panel.
3043			// Find the visible one, and re-initialize it.
3044			var selectorNew = $('.orion_packages_selection:visible');
3045			if(selectorNew.length) orionPackagesSwiper(selectorNew, OrionPackgeSelectionSwiper);
3046		}
3047		return null;
3048	}
3049
3050	// Important, otherwise calling this function multiple times, will add multiple event listeners.
3051	if(swiper) swiper.destroy(true, true);
3052
3053	OrionPackgeSelectionSwiper = new Swiper(OrionPackgeSelectionSelector,
3054	{
3055		effect : 'coverflow',
3056		slidesPerView : slidesPerView,
3057		spaceBetween : spaceBetween,
3058		initialSlide : 0,
3059		loop : true,
3060		centeredSlides : true,
3061		keyboard : true,
3062		mousewheel : false,
3063		slideToClickedSlide : true,
3064		preventClicks : false,
3065		preventClicksPropagation : false,
3066		observer : true,
3067		observeParents : true,
3068		coverflow :
3069		{
3070			rotate : 0,
3071			stretch : 0,
3072			depth : 250,
3073			modifier : 1,
3074			slideShadows : false,
3075		},
3076		navigation :
3077		{
3078			nextEl : '.swiper-button-next',
3079			prevEl : '.swiper-button-prev',
3080		},
3081		pagination :
3082		{
3083			el : '.swiper-pagination',
3084			type : 'bullets',
3085			clickable : true,
3086		},
3087		autoplay :
3088		{
3089			enabled : false,
3090			delay : 3000,
3091			disableOnInteraction : true,
3092		},
3093		on :
3094		{
3095			transitionStart : function()
3096			{
3097				transition = true;
3098			},
3099			transitionEnd : function()
3100			{
3101				transition = false;
3102				if(OrionPackgeSelectionSwiper) OrionPackgeSelectionSwiper.update();
3103			},
3104			click : function(event)
3105			{
3106				if(OrionPackgeSelection)
3107				{
3108					// Dirty hack.
3109					// Something might be wrong with preventClicksPropagation.
3110					// On the home page - logged out, if a slide/package is clicked and the mouse is over the outside of the modal (the semi-opaque grayed overlay), the click event is propagated and therefore immediately closes the modal again.
3111					// Temporarily block the click event.
3112					// This only happens on the mobile version, desktop version is fine. Maybe something with touch events.
3113					OrionModalPropagation = false;
3114
3115					// Only make the center package clickable, except if the PURCHASE/GET NOW button is clicked.
3116					var element = $(event.target);
3117					var slide = element.parents('.swiper-slide').first();
3118					if(element.hasClass('orion_button_medium') || (!transition && slide.hasClass('swiper-slide-active')))
3119					{
3120						OrionPackgeSelectionSwiper.autoplay.stop();
3121						orionPackagesSelect(OrionPackgeSelection[slide.data('swiper-slide-index')]);
3122					}
3123				}
3124			},
3125		},
3126	});
3127
3128	orionIfView(OrionPackgeSelectionSelector, function()
3129	{
3130		if(OrionPackgeSelectionSwiper)
3131		{
3132			if(!OrionPackgeSelectionSwiper.autoplay.running)
3133			{
3134				OrionPackgeSelectionSwiper.autoplay.start();
3135			}
3136		}
3137	}, true);
3138}
3139
3140function orionPackagesSwiperUpdate()
3141{
3142	orionPackagesSwiper(OrionPackgeSelectionSelector, OrionPackgeSelectionSwiper);
3143}
3144
3145function orionPackagesSelect(package)
3146{
3147	if(orionSessionValid())
3148	{
3149		if(OrionPaymentsCallback) OrionPaymentsCallback(package);
3150		else orionPanelPaymentsShow();
3151	}
3152	else
3153	{
3154		orionRegisterShow();
3155	}
3156}
3157
3158/************************************************************************************************
3159* PAGE
3160************************************************************************************************/
3161
3162function orionPage(page)
3163{
3164	// Important, KeyOneClick has a URL-encoded space at the end of the link.
3165	page = orionLinkDecode(page);
3166
3167	if(page == null) page = 'home';
3168	var element = $('.orion_page_' + page);
3169	if(element.length == 0) page = 'home'; // Invalid page
3170	element = $('.orion_page_' + page);
3171	if(element.length == 0) return false;
3172	if(!orionVisible(element))
3173	{
3174		$('.orion_page').hide();
3175		element.show();
3176		if(page == 'home') $('.orion_header').removeClass('orion_header_fixed').removeClass('is-sticky');
3177		else $('.orion_header').addClass('orion_header_fixed is-sticky');
3178		orionScrollTop();
3179		return true;
3180	}
3181	return false;
3182}
3183
3184/************************************************************************************************
3185* HOME
3186************************************************************************************************/
3187
3188function orionHomeShow(tab)
3189{
3190	if(tab || !orionPage('home')) orionRedirect(OrionLinkMain, tab);
3191}
3192
3193/************************************************************************************************
3194* TERMS
3195************************************************************************************************/
3196
3197function orionTermsShow(tab)
3198{
3199	if(tab || !orionPage('terms')) orionRedirect(OrionLinkTerms, tab);
3200}
3201
3202function orionDmcaShow(tab)
3203{
3204	if(tab || !orionPage('dmca')) orionRedirect(OrionLinkDmca, tab);
3205}
3206
3207/************************************************************************************************
3208* HELP
3209************************************************************************************************/
3210
3211function orionHelpShow(tab)
3212{
3213	if(tab || !orionPage('help')) orionRedirect(OrionLinkHelp, tab);
3214}
3215
3216/************************************************************************************************
3217* FREE
3218************************************************************************************************/
3219
3220function orionFreeShow(tab)
3221{
3222	if(tab || !orionPage('free')) orionRedirect(OrionLinkFree, tab);
3223}
3224
3225/************************************************************************************************
3226* TOR
3227************************************************************************************************/
3228
3229function orionTorShow(direct)
3230{
3231	orionModalShow('.orion_tor', direct);
3232}
3233
3234/************************************************************************************************
3235* BOX
3236************************************************************************************************/
3237
3238function orionBoxInitialize()
3239{
3240	var help = $('.orion_help_content');
3241	$('.orion_box').each(function(i, box)
3242	{
3243		box = $(box);
3244		var title = box.find('.orion_box_title');
3245		var message = box.find('.orion_box_message');
3246		title.on('click', function()
3247		{
3248			box.attr('data-mode', orionVisible(message) ? 'hidden' : 'shown');
3249			message.slideToggle();
3250		});
3251	});
3252}
3253
3254/************************************************************************************************
3255* WIDGETS
3256************************************************************************************************/
3257
3258var OrionWidgetsNotify = null;
3259var OrionWidgetsTicket = null;
3260var OrionWidgetsAdmin = null;
3261
3262function orionWidgetsInitialize()
3263{
3264	$('.orion_widgets > div').each(function(i, widget)
3265	{
3266		widget = $(widget);
3267		if(widget.hasClass('orion_widget_home'))
3268		{
3269			widget.on('click', function(){ orionHomeShow(); });
3270		}
3271		else if(widget.hasClass('orion_widget_panel'))
3272		{
3273			widget.on('click', function(){ orionPanelShow(); });
3274		}
3275		else if(widget.hasClass('orion_widget_login'))
3276		{
3277			widget.on('click', function(){ orionLoginOpen(); });
3278		}
3279		else if(widget.hasClass('orion_widget_logout'))
3280		{
3281			widget.on('click', function(){ orionLogout(); });
3282		}
3283		else if(widget.hasClass('orion_widget_contact'))
3284		{
3285			widget.on('click', function(){ orionContactShow(); });
3286		}
3287		else if(widget.hasClass('orion_widget_ticket'))
3288		{
3289			widget.on('click', function()
3290			{
3291				if(OrionWidgetsAdmin) orionAdminTicketShow(OrionWidgetsTicket);
3292				else orionPanelTicketShow(OrionWidgetsTicket);
3293			});
3294		}
3295		else
3296		{
3297			var icon = widget.find('.orion_widget_icon');
3298			var box = widget.find('.orion_widget_box');
3299			icon.on('click', function(event){  orionWidgetsToggle(widget, box); });
3300			if(!orionMobile()) icon.on('mouseenter', function(){ orionWidgetsShow(widget, box); }); // Not on mobile, otherwise disappears on click.
3301			widget.on('mouseleave', function(){ orionWidgetsHide(widget, box); }); // Apply on widget, to allow the mouse to move inside the box.
3302		}
3303	});
3304}
3305
3306function orionWidgetsNotify()
3307{
3308	if(OrionWidgetsNotify) orionConfirmation(OrionWidgetsNotify.message, OrionWidgetsNotify.callbackYes, OrionWidgetsNotify.callbackNo);
3309}
3310
3311function orionWidgetsToggle(widget, box)
3312{
3313	if(orionVisible(box)) orionWidgetsHide(widget, box);
3314	else orionWidgetsShow(widget, box);
3315}
3316
3317function orionWidgetsShow(widget, box)
3318{
3319	box.fadeIn('normal');
3320	if(widget.hasClass('orion_widget_server')) orionWidgetsServerAnimate();
3321}
3322
3323function orionWidgetsHide(widget, box)
3324{
3325	if(widget == null && box == null) $('.orion_widgets .orion_widget_box').hide();
3326	else box.fadeOut('normal');
3327}
3328
3329function orionWidgetsServer(status, message, usage)
3330{
3331	var server = $('.orion_widgets .orion_widget_server');
3332	server.attr('class', 'orion_widget_server orion_widget_server_' + status);
3333	server.find('.orion_widget_title').text('Server ' + status);
3334	server.find('.orion_widget_message').text(message);
3335	orionGaugeProgress(server.find('.orion_widget_server_usage'), usage * 100);
3336}
3337
3338function orionWidgetsServerAnimate()
3339{
3340	orionGaugeAnimate($('.orion_widgets .orion_widget_server .orion_widget_server_usage'));
3341}
3342
3343function orionWidgetsNotification(title, message, time)
3344{
3345	var notification = $('.orion_widgets .orion_widget_notification');
3346	if(title != null) notification.find('.orion_widget_title').text(title);
3347	if(time != null) notification.find('.orion_widget_time').text(orionTimeFormat(time, OrionFormatDateShortTime));
3348	if(message != null)
3349	{
3350		message = orionStringBbRemove(message);
3351		if(message.length > 500)
3352		{
3353			message = message.substring(0, 500);
3354			message = message.substring(0, message.lastIndexOf(' ')) + ' ...';
3355		}
3356		notification.find('.orion_widget_message').text(message);
3357
3358		var age = orionTimestamp() - time;
3359		if(age <= 864000) // 10 days.
3360		{
3361			// The orange for "old" it too distracting in the menu.
3362			//notification.attr('class', 'orion_widget_notification orion_widget_notification_' + (age <= 604800 ? 'new' : 'old')); // 1 week.
3363			notification.attr('class', 'orion_widget_notification orion_widget_notification_new');
3364		}
3365	}
3366}
3367
3368function orionWidgetsNotificationNone()
3369{
3370	orionWidgetsNotification('No New Notifications', 'There are currently no new announcements from the Orion team.')
3371}
3372
3373function orionWidgetsAccount(subscription)
3374{
3375	var status;
3376	var title;
3377	var message;
3378	var label;
3379	var current = orionTimestamp();
3380	var never = '<div class="orion_widgets_highlight">never</div> expire';
3381
3382	if(subscription.package.premium)
3383	{
3384		var remaining = subscription.time.expiration;
3385		if(remaining === null)
3386		{
3387			label = never;
3388		}
3389		else
3390		{
3391			remaining = subscription.time.expiration - current;
3392			if(remaining > 86400) label = 'expire in <div class="orion_widgets_highlight">' + orionTimeDays(remaining, true, null, true).toLowerCase() + '</div>';
3393			else label = 'expire <div class="orion_widgets_highlight">today</div>';
3394		}
3395		if(subscription.package.type == 'unlimited')
3396		{
3397			status = 'unlimited';
3398			title = 'Unlimited Subscription';
3399		}
3400		else
3401		{
3402			status = 'premium';
3403			title = 'Premium Subscription';
3404		}
3405		message = 'Your <div class="orion_widgets_highlight">' + status + '</div> subscription<br/>will ' + label + '.';
3406		if(remaining !== null && remaining < 604800) // 7 days.
3407		{
3408			OrionWidgetsNotify = {message : message.replace(/<[^>]*>?/gm, ' ').replace(/\s+/gm, ' ') + ' Do you want to renew your subscription now?', callbackYes : function(){ orionPanelNavigate('payments'); } };
3409			orionPanelInitializeCallbackAdd(orionWidgetsNotify);
3410			status = remaining < 259200 ? 'warning' : 'notification'; // 3 days.
3411			title = 'Subscription Expires';
3412			message += ' Renew your subscription now to continue with your premium access.';
3413		}
3414	}
3415	else
3416	{
3417		status = 'free';
3418		title = 'Free Subscription';
3419		message = 'Your <div class="orion_widgets_highlight">free</div> subscription<br/>will ' + never + '.';
3420	}
3421
3422	var widget = $('.orion_widgets .orion_widget_account');
3423	if(status != null) widget.attr('class', 'orion_widget_account orion_widget_account_' + status);
3424	if(title != null) widget.find('.orion_widget_title').html(title);
3425	if(message != null) widget.find('.orion_widget_message').html(message);
3426}
3427
3428function orionWidgetsTicket(ticket, admin)
3429{
3430	if(!OrionWidgetsAdmin || admin)
3431	{
3432		OrionWidgetsAdmin = admin;
3433		var widget = $('.orion_widgets .orion_widget_ticket');
3434		if(ticket && ticket.status == 'open')
3435		{
3436			OrionWidgetsTicket = ticket.id;
3437			widget.attr('class', 'orion_widget_ticket orion_widget_ticket_' + ((ticket.last && !OrionWidgetsAdmin) || (!ticket.last && OrionWidgetsAdmin) ? 'wait' : 'new'));
3438		}
3439		else
3440		{
3441			OrionWidgetsTicket = null;
3442			widget.attr('class', 'orion_widget_ticket');
3443		}
3444	}
3445}
3446
3447/************************************************************************************************
3448* NOTIFICATIONS
3449************************************************************************************************/
3450
3451function orionNotificationsUpdate(notifications)
3452{
3453	var entries = [];
3454	$.each(notifications, function(i, notification)
3455	{
3456		entries.push({
3457			'title' : notification['content']['title'],
3458			'message' : orionStringBbDecode(notification['content']['message']),
3459			'time' : orionTimeFormat(notification['time']['added'], OrionFormatDateReadable),
3460		});
3461	});
3462	orionTimelineUpdate($('.orion_notifications'), entries, 'fas fa-bullhorn');
3463}
3464
3465/************************************************************************************************
3466* TIMELINE
3467************************************************************************************************/
3468
3469function orionTimelineInitialize()
3470{
3471	$('.orion_timeline').each(function(i, timeline)
3472	{
3473		timeline = $(timeline);
3474		// For some reason orionInView() does not work here.
3475		// Create a similar procedure, but use orionVisible() instead of orionInView()
3476		orionIfVisible(timeline, orionTimelineRefresh);
3477	});
3478}
3479
3480function orionTimelineRefresh()
3481{
3482	// Adjusted function in the verticaltimeline library script.
3483	verticalTimelineExecute();
3484}
3485
3486function orionTimelineUpdate(selector, entries, icon)
3487{
3488	selector = $(selector);
3489	var markup = '';
3490	var notification = selector.hasClass('orion_notifications');
3491
3492	$.each(entries, function(i, entry)
3493	{
3494		var side;
3495		if(orionObjectContains(entry, 'side')) side = 'orion_timeline_' + entry['side'];
3496		else side = i % 2 ? 'orion_timeline_left' : 'orion_timeline_right';
3497
3498		var attachments = '';
3499		if(orionObjectContains(entry, 'attachments'))
3500		{
3501			entry['attachments'].forEach(function(attachment)
3502			{
3503				var extension = orionFileExtension(attachment['name'], attachment['type']);
3504				var size = orionSizeFormat(attachment['size']);
3505				var virus = '';
3506				if(orionObjectContains(attachment, 'virus'))
3507				{
3508					virus = attachment['virus'];
3509					if(virus === false) virus = orionStatusLabel('good', 'Virus Free');
3510					else if(virus === null) virus = orionStatusLabel('medium', 'Virus Scan');
3511					else virus = orionStatusLabel('bad', 'Virus Found', virus);
3512					virus += ' &bull; ';
3513				}
3514				attachments += '<a href="' + attachment['link'] + '" target="_blank"><i class="fas fa-paperclip"></i>&bull; ' + virus + extension + ' &bull; ' + size + ' &bull; ' + attachment['name'] + '</a>';
3515			});
3516		}
3517
3518		markup += '\
3519		<div class="' + side + ' orion_timeline_block cd-timeline__block js-cd-block">\
3520			<div class="cd-timeline__img cd-timeline__img--picture js-cd-img">\
3521				<i class="' + icon + '"></i>\
3522			</div>\
3523			<div class="orion_timeline_content cd-timeline__content js-cd-content">\
3524				' + (orionObjectContains(entry, 'title') ? ('<div class="orion_timeline_title">' + entry['title'] + '</div>') : '') + '\
3525				' + (orionObjectContains(entry, 'message') ? ('<p>' + entry['message'] + '</p>') : '') + '\
3526				<div class="orion_timeline_more"></div>\
3527				' + (attachments ? ('<div class="orion_timeline_attachments_title">Attachments</div><div class="orion_timeline_attachments">' + attachments + '</div>') : '') + '\
3528				' + (orionObjectContains(entry, 'time') ? ('<span class="cd-timeline__date">' + entry['time'] + '</span>') : '') + '\
3529			</div>\
3530		</div>\
3531		';
3532	});
3533
3534	// Add the script again in order to reinitialize the elements for bouncy animation.
3535	// This reloads the script. Although it should load the cached version of the script, this approach is not optimal.
3536	// Instead edit the verticaltimeline script to not execute the code once the script is loaded, but rather reely on orionTimelineInitialize()..
3537	//markup += $('script[src*="verticaltimeline"]').first().prop('outerHTML');
3538
3539	selector.find('.orion_timeline_container').html(markup);
3540
3541	orionIfVisible(selector, function()
3542	{
3543		selector.find('.orion_timeline_content').each(function(i, content)
3544		{
3545			content = $(content);
3546			if(!content.data('overflow'))
3547			{
3548				content.data('overflow', 'true');
3549				var par = content.find('p');
3550				var height = par.prop('scrollHeight');
3551				if(par.height() < height)
3552				{
3553					var expanded = notification;
3554					var more = content.find('.orion_timeline_more');
3555					function orionTimelineToggle(action)
3556					{
3557						if(expanded)
3558						{
3559							expanded = false;
3560							more.html('<i class="fas fa-caret-down"></i> Show More');
3561							par.animate({'max-height' : '200px'}, action ? 500 : 0);
3562							if(action) orionScrollTop(content.offset().top - 100);
3563						}
3564						else
3565						{
3566							expanded = true;
3567							more.html('<i class="fas fa-caret-up"></i> Show Less');
3568							par.animate({'max-height' : height}, action ? 500 : 0);
3569						}
3570					}
3571
3572					more.css('display', 'table');
3573					orionTimelineToggle();
3574					(notification ? content : more).on('click', function(){ orionTimelineToggle(true); });
3575				}
3576			}
3577		});
3578	});
3579}
3580
3581/************************************************************************************************
3582* STATS
3583************************************************************************************************/
3584
3585function orionStatsUpdate(stats)
3586{
3587	['streams', 'hashes', 'containers'].forEach(function(type)
3588	{
3589		var counter = $('.orion_stats_counter_' + type + ' .orion_stats_counter_count');
3590		counter.text(orionThousands(stats.count[type]));
3591		var width = counter.width();
3592		counter.text(stats.count[type]);
3593
3594		function counterUpdate()
3595		{
3596			var last = 0;
3597			counter.css('min-width', width);
3598			counter.each(function()
3599			{
3600				// Needed to restart animation.
3601				counter.text(stats.count[type]);
3602				counter.removeAttr('style').stop(true);
3603
3604				var item = $(this);
3605				item.prop('count', 0).animate(
3606				{
3607					count : item.text()
3608				},
3609				{
3610					duration : 2000,
3611					easing : 'swing',
3612					step : function(now)
3613					{
3614						now = Math.ceil(now);
3615						if(!isNaN(now) && now > last)
3616						{
3617							last = now;
3618							item.text(orionThousands(now));
3619						}
3620					},
3621					complete : function()
3622					{
3623						item.css('min-width', 0);
3624					},
3625				});
3626			});
3627		}
3628
3629		$('body').on('menustatistics', counterUpdate);
3630		orionIfView(counter, counterUpdate);
3631	});
3632}
3633
3634/************************************************************************************************
3635* GAUGE
3636************************************************************************************************/
3637
3638function orionGaugeInitialize(selector)
3639{
3640	if(selector == null) selector = $('.orion_gauge');
3641	else selector = $(selector);
3642	$(selector).each(function(i, gauge)
3643	{
3644		gauge = $(gauge);
3645		if(gauge.data('style') == null) gauge.data('style', 'Arch');
3646		if(gauge.data('append') == null) gauge.data('append', '%');
3647		if(gauge.data('percent') == null) gauge.data('percent', 0);
3648		if(gauge.data('width') == null) gauge.data('width', 20);
3649		if(gauge.data('color') == null) gauge.data('color', '#47CAE9');
3650		if(gauge.data('back') == null) gauge.data('back', '#FFFFFF');
3651		if(gauge.data('animationstep') == null) gauge.data('animationstep', 2);
3652		gauge.css('height', parseInt(parseInt(gauge.data('size')) * 0.8));
3653		gauge.gaugeMeter();
3654	});
3655}
3656
3657function orionGaugeAnimate(selector)
3658{
3659	$(selector).gaugeMeter();
3660}
3661
3662function orionGaugeProgress(selector, percent)
3663{
3664	selector = $(selector);
3665	selector.data('percent', parseInt(orionRound(percent, 0)));
3666	selector.gaugeMeter();
3667}
3668
3669/************************************************************************************************
3670* PROGRESSBAR
3671************************************************************************************************/
3672
3673function orionProgressbarUpdate(selector, percentage)
3674{
3675	selector = $(selector);
3676	percentage = Math.max(0, Math.min(1, percentage));
3677	percentage = orionRound(percentage * 100, 0) + '%';
3678	selector.find('.orion_progressbar_label').text(percentage);
3679	selector.find('.orion_progressbar_inner').css('width', percentage);
3680}
3681
3682/************************************************************************************************
3683* PROGRESSMINI
3684************************************************************************************************/
3685
3686function orionProgressMini(percentage)
3687{
3688	percentage = Math.max(0, Math.min(1, percentage));
3689	var color = orionColorMix(percentage, '#DE636F', '#CA8416', '#1ABB9C');
3690	percentage = orionRound(percentage * 100, 0);
3691	return '<div class="orion_progressmini"><div style="width: ' + percentage + '%; background-color: ' + color + ';"></div></div>';
3692}
3693
3694/************************************************************************************************
3695* BUTTON
3696************************************************************************************************/
3697
3698function orionButtonLoaderShow(selector)
3699{
3700	var button = $(selector);
3701	button.find('.orion_input_button_icon').hide();
3702	button.find('.orion_input_button_loader').css('display', 'inline-block');
3703	orionProgressStart();
3704}
3705
3706function orionButtonLoaderHide(selector)
3707{
3708	var button = $(selector);
3709	button.find('.orion_input_button_loader').hide();
3710	button.find('.orion_input_button_icon').css('display', 'inline-block');
3711	orionProgressStop();
3712}
3713
3714/************************************************************************************************
3715* CHECK
3716************************************************************************************************/
3717
3718function orionCheckInitialize()
3719{
3720	var check = $('.orion_check');
3721	check.find('a').on('click', function(event)
3722	{
3723		// Unclick after a link in the checkbox label was clicked.
3724		var input = check.find('input');
3725		var checked = orionChecked(input);
3726		var clicked = function()
3727		{
3728			input.prop('checked', checked);
3729			check.off('click', clicked);
3730		};
3731		check.on('click', clicked);
3732		event.stopPropagation();
3733	});
3734	check.on('click', function(event)
3735	{
3736		event.stopPropagation();
3737	});
3738}
3739
3740/************************************************************************************************
3741* NOTIFICATION
3742************************************************************************************************/
3743
3744var OrionNotificationTimeout = 5000;
3745
3746function orionNotificationClose()
3747{
3748	Noty.closeAll();
3749}
3750
3751function orionConfirmation(message, callbackYes, callbackNo)
3752{
3753	orionNotification(message, 'confirm', OrionNotificationTimeout, callbackYes, callbackNo);
3754}
3755
3756function orionNotificationTitle(title, message, type, timeout, callbackYes, callbackNo)
3757{
3758	orionNotification('<b>' + title + '</b><br/>' + message, type, timeout, callbackYes, callbackNo);
3759}
3760
3761// type: alert, success, error, warning, info, confirm
3762function orionNotification(message, type, timeout, callbackYes, callbackNo)
3763{
3764	if(type == null) type = 'info';
3765	if(timeout === undefined || timeout === null) timeout = OrionNotificationTimeout;
3766	var text = "<table class='orion_notification'><tr>";
3767	if(type != "confirm")
3768	{
3769		text += "<td><i class='orion_notification_icon fas fa-";
3770		if(type == "error" || type == "warning") text += "exclamation-triangle";
3771		else if(type == "alert") text += "exclamation-circle";
3772		else if(type == "success") text += "check-circle";
3773		else if(type == "info") text += "info-circle";
3774		text += "'></i></td>";
3775	}
3776	text += "<td class='orion_notification_text'>" + message + "</td></tr></table>";
3777	if(type == "confirm") timeout = false;
3778
3779	var noty;
3780	var close = 'click';
3781	var buttons = null;
3782	if(type == "confirm")
3783	{
3784		var close = 'button';
3785		buttons =
3786		[
3787			Noty.button("<i class='fas fa-check orion_icon'></i>", 'orion_notification_button orion_notification_yes', function()
3788			{
3789				noty.close();
3790				if(callbackYes != null) callbackYes();
3791			}),
3792			Noty.button("<i class='fas fa-times orion_icon'></i>", 'orion_notification_button orion_notification_no', function()
3793			{
3794				noty.close();
3795				if(callbackNo != null) callbackNo();
3796			}),
3797		];
3798	}
3799
3800	noty = new Noty(
3801	{
3802		theme : 'sunset',
3803		layout : 'topRight',
3804		type : type,
3805		text : text,
3806		timeout : timeout,
3807		buttons : buttons,
3808		closeWith : [close],
3809		animation :
3810		{
3811			// NB: Do not use the custom animation on mobile devices (actual touch screen devices, not the emulation in developers tools).
3812			// Otherwise the notificattion fails for an unknown reason on mobile devices.
3813			// The notification does not show, BUT the noty div (which is invisible) is still in the HTML. This invisible divs makes clicks on other elements impossible (eg: the right-hand side of the "Search" side menu option).
3814			open : orionMobile() ? 'animated bounceInRight' : function (promise)
3815			{
3816				var n = this;
3817				var Timeline = new mojs.Timeline();
3818				var body = new mojs.Html(
3819				{
3820					el : n.barDom,
3821					x : {500: 0, delay: 0, duration: 500, easing: 'elastic.out'},
3822					isForce3d : true,
3823					onComplete : function()
3824					{
3825						promise(function(resolve)
3826						{
3827							resolve();
3828						});
3829					}
3830				});
3831
3832				var parent = new mojs.Shape(
3833				{
3834					parent : n.barDom,
3835					width : 200,
3836					height : n.barDom.getBoundingClientRect().height,
3837					radius : 0,
3838					x : {150 : -150},
3839					duration : 1.2 * 500,
3840					isShowStart : true
3841				});
3842
3843				n.barDom.style['overflow'] = 'visible';
3844				parent.el.style['overflow'] = 'hidden';
3845
3846				var burst = new mojs.Burst(
3847				{
3848					parent : parent.el,
3849					count : 10,
3850					top : n.barDom.getBoundingClientRect().height + 75,
3851					degree : 90,
3852					radius : 75,
3853					angle : {0 : 40},
3854					children :
3855					{
3856						fill	 : 'rgba(200, 200, 200, 0.5)',
3857						delay	: 'stagger(500, -50)',
3858						radius   : 'rand(8, 25)',
3859						direction: -1,
3860						isSwirl  : true
3861					}
3862				});
3863
3864				var fadeBurst = new mojs.Burst(
3865				{
3866					parent : parent.el,
3867					count : 2,
3868					degree : 0,
3869					angle : 75,
3870					radius : {0: 100},
3871					top : '90%',
3872					children :
3873					{
3874						fill : 'rgba(250, 250, 250, 0.5)',
3875						pathScale : [.65, 1],
3876						radius : 'rand(12, 15)',
3877						direction : [-1, 1],
3878						delay : 0.8 * 500,
3879						isSwirl : true
3880					}
3881				});
3882
3883				Timeline.add(body, burst, fadeBurst, parent);
3884				Timeline.play();
3885			},
3886			// This causes the notification to be unclosable if tabs are switched while the notification is showing.
3887			/*close : function (promise)
3888			{
3889				var n = this;
3890				new mojs.Html(
3891				{
3892					el : n.barDom,
3893					x : {0: 500, delay: 10, duration: 500, easing: 'cubic.out'},
3894					skewY : {0: 10, delay: 10, duration: 500, easing: 'cubic.out'},
3895					isForce3d : true,
3896					onComplete: function()
3897					{
3898						promise(function(resolve)
3899						{
3900							resolve();
3901						})
3902					}
3903				}).play();
3904			},*/
3905			close: 'animated bounceOutRight',
3906		},
3907	}).show();
3908}
3909
3910/************************************************************************************************
3911* CLIPBOARD
3912************************************************************************************************/
3913
3914function orionClipboard(selector, notification)
3915{
3916	selector = $(selector);
3917	var label = selector.attr('placeholder');
3918	if(label != null && label != '') label += ' ';
3919	$(selector).select();
3920	document.execCommand('copy');
3921	if(notification) orionNotification(label + 'Copied To Clipboard');
3922}
3923
3924function orionClipboardCopy(string, label)
3925{
3926	var selector = document.createElement('textarea');
3927	selector.value = string;
3928
3929	// Cannot use "display" or "visibility", otherwise the focus is not always working and the text not copied.
3930	selector.style.opacity = '0';
3931	selector.style.zIndex = '-1';
3932	selector.style.position = 'fixed';
3933
3934	document.body.appendChild(selector);
3935	selector.focus();
3936	selector.select();
3937	document.execCommand('copy');
3938
3939	if(label) orionNotification(label + ' Copied To Clipboard');
3940}
3941
3942/************************************************************************************************
3943* DOWNLOAD
3944************************************************************************************************/
3945
3946function orionDownload(link, name)
3947{
3948	// This does not always trigger the browser's download dialog.
3949	// Sometimes the browser will simply open a new tab and display the file, instead of triggering the download.
3950	// This depends on the browser and the file type. For instance, most browsers will show videos and images in the tab, but other files that cannot be displayed are downloaded.
3951	// None of the proposed solutions guarantee the download dialog to pop up.
3952	// The <a> ""download" attribute is only for Same-Origin.
3953	// The only way is to set the "Content-Disposition" to "attachment" on the server side, but since the URL might point to an external server, we do not have control over this.
3954	if(!name) name = link.substr(link.lastIndexOf('/') + 1);
3955	var element = document.createElement('a');
3956	element.download = name;
3957	element.href = link;
3958	element.target = '_blank';
3959	element.style.display = 'none';
3960	document.body.appendChild(element);
3961	element.click();
3962	document.body.removeChild(element);
3963	setTimeout(function(){ URL.revokeObjectURL(element.href); }, 1000);
3964}
3965
3966function orionDownloadFile(fileData, fileType, fileName)
3967{
3968	var blob = new Blob([fileData], {type : fileType});
3969	var link = document.createElement('a');
3970	link.download = fileName;
3971	link.href = URL.createObjectURL(blob);
3972	link.dataset.downloadurl = [fileType, link.download, link.href].join(':');
3973	link.style.display = 'none';
3974	document.body.appendChild(link);
3975	link.click();
3976	document.body.removeChild(link);
3977	setTimeout(function(){ URL.revokeObjectURL(link.href); }, 1000);
3978}
3979
3980/************************************************************************************************
3981* INFO
3982************************************************************************************************/
3983
3984function orionInfoUpdate(selector, item, value)
3985{
3986	selector = $(selector);
3987	selector.find(item + ' .orion_info_value').html(value);
3988}
3989
3990/************************************************************************************************
3991* EMAIL
3992************************************************************************************************/
3993
3994function orionEmail()
3995{
3996	return orionObfuscate(OrionEmail);
3997}
3998
3999function orionEmailInitialize()
4000{
4001	$('.orion_email').on('click mouseover', function()
4002	{
4003		var element = $(this);
4004		if(!element.hasClass('orion_active'))
4005		{
4006			var email = orionEmail();
4007			element.removeClass('orion_active').addClass('orion_active');
4008			element.html('<a href="mailto:' + email + '">' + email + '</div>');
4009		}
4010	});
4011}
4012
4013/************************************************************************************************
4014* INPUT
4015************************************************************************************************/
4016
4017function orionInputInitialize()
4018{
4019	$('.orion_input_copy .orion_input_icon, .orion_input_copy .orion_input_action').on('click', function(event)
4020	{
4021		var input = $(this).parent('.orion_input_copy').find('input');
4022		orionClipboard(input, true);
4023		input.blur();
4024		event.stopPropagation();
4025	});
4026	$('.orion_input_copy_label').on('click', function()
4027	{
4028		orionClipboard($(this).siblings('.orion_input_copy').find('input'), true);
4029	});
4030	$('.orion_input_copy').on('click', function()
4031	{
4032		orionClipboard($(this).find('input'));
4033	});
4034
4035	$('.orion_input input[data-case]').on('change keydown paste input', function()
4036	{
4037		var input = $(this);
4038		var value = input.val();
4039		var cased = input.attr('data-case');
4040		if(cased == 'upper') value = value.toUpperCase();
4041		else if(cased == 'lower') value = value.toLowerCase();
4042		input.val(value);
4043	});
4044
4045	$('.orion_input input[data-pattern]').on('change keydown paste input', function()
4046	{
4047		var input = $(this);
4048		var value = input.val();
4049		var pattern = input.attr('data-pattern');
4050		var result = value.match(new RegExp(pattern, 'g'));
4051		if(result) input.val(result.join(''));
4052	});
4053
4054	$('.orion_input input[data-separator]').on('change keydown paste input', function()
4055	{
4056		var input = $(this);
4057		var value = input.val();
4058		var result = '';
4059
4060		var separator = parseInt(input.attr('data-separator'));
4061		var maximum = input.attr('maxlength');
4062		if(maximum) maximum = parseInt(maximum);
4063
4064		var groups = value.replace('-', '').match(new RegExp('.{1,' + separator + '}', 'g'));
4065		for(var i in groups)
4066		{
4067			var length = groups[i].length;
4068			if(groups[i] && length % separator === 0 && (!maximum || result.length + length < maximum)) result += groups[i] + '-';
4069			else result += groups[i];
4070		}
4071		input.val(result);
4072	});
4073}
4074
4075function orionInputElement(selector)
4076{
4077	return $(selector).find('input');
4078}
4079
4080function orionInputContainer(selector)
4081{
4082	return $(selector).parents('.orion_input').first();
4083}
4084
4085function orionInputValue(selector, value)
4086{
4087	selector = $(selector);
4088	var tagname = selector.prop('tagName').toLowerCase();
4089
4090	if(tagname == 'textarea')
4091	{
4092		if(value == null) return selector.text();
4093		else return selector.text(value);
4094	}
4095	else if(tagname != 'input')
4096	{
4097		selector = orionInputElement(selector);
4098	}
4099
4100	if(value == null)
4101	{
4102		return selector.val();
4103	}
4104	else
4105	{
4106		selector.parent().siblings('.orion_input_copy_label').html(value);
4107		return selector.val(value);
4108	}
4109}
4110
4111function orionInputClear(selector)
4112{
4113	return orionInputValue(selector, '');
4114}
4115
4116function orionInputControls(selector, tagOpen, tagClose)
4117{
4118	var area = $(selector).parents('.orion_input_wrapper').find('textarea');
4119	var start = area[0].selectionStart;
4120	var end = area[0].selectionEnd;
4121	var value = area.val();
4122	var replacement = tagOpen + value.substring(start, end).replace(tagOpen, '').replace(tagClose, '') + tagClose;
4123	value = value.substring(0, start) + replacement + value.substring(end, value.length);
4124	area.val(value);
4125}
4126
4127function orionInputControlsBold(selector)
4128{
4129	orionInputControls(selector, '[B]', '[/B]');
4130}
4131
4132function orionInputControlsItalic(selector)
4133{
4134	orionInputControls(selector, '[I]', '[/I]');
4135}
4136
4137function orionInputControlsUnderline(selector)
4138{
4139	orionInputControls(selector, '[U]', '[/U]');
4140}
4141
4142function orionInputControlsLink(selector)
4143{
4144	orionInputControls(selector, '[URL]', '[/URL]');
4145}
4146
4147function orionInputFileElement(selector, index)
4148{
4149	selector = $(selector);
4150	if(index === undefined)
4151	{
4152		var result = selector.parents('.orion_input_file');
4153		if(result.length == 0) result = selector.siblings('.orion_input_file');
4154		return result.first();
4155	}
4156	else
4157	{
4158		var result = selector.parents('.orion_input_file_' + index);
4159		if(result.length == 0) result = selector.siblings('.orion_input_file_' + index);
4160		return result.first();
4161	}
4162}
4163
4164function orionInputFileCount(selector)
4165{
4166	var count = 0;
4167	var attachments = orionInputFileElement(selector).find('.orion_input_box');
4168	$.each(attachments, function(i, attachment)
4169	{
4170		if(orionVisible(attachment)) count++;
4171	});
4172	return count;
4173}
4174
4175function orionInputFileIndex(selector)
4176{
4177	var index = -1;
4178	var attachments = orionInputFileElement(selector).find('.orion_input_box');
4179	$.each(attachments, function(i, attachment)
4180	{
4181		if(!orionVisible(attachment))
4182		{
4183			index = i;
4184			return false;
4185		}
4186	});
4187	return index;
4188}
4189
4190function orionInputFileEqual(file1, file2)
4191{
4192	if((file1 && !file2) || (!file1 && file2)) return false;
4193	return file1.name == file2.name && file1.size == file2.size && file1.type == file2.type && file1.lastModified == file2.lastModified;
4194}
4195
4196function orionInputFileSelect(selector)
4197{
4198	orionInputFileElement(selector, orionInputFileIndex(selector)).find('input[type=file]').click();
4199}
4200
4201function orionInputFileProcess(selector, index)
4202{
4203	selector = orionInputFileElement(selector, index);
4204	var file = selector.find('input[type=file]')[0].files[0];
4205	if(file)
4206	{
4207		var maximum = parseInt(orionInputFileElement(selector).data('size'));
4208		if(file.size > maximum)
4209		{
4210			orionNotification('The file is too large and exceeds the maximum size of ' + orionSizeFormat(maximum, 0) + ' . Try zipping your files. If you are uploading a Kodi log, disable the logging option in Kodi\'s system settings to reduce the log size.', 'error');
4211		}
4212		else
4213		{
4214			var contains = false;
4215			var files = orionInputFileElement(selector).find('.orion_input_box:not(.orion_input_file_' + index + ')').find('input[type=file]');
4216			$.each(files, function(i, f)
4217			{
4218				f = f.files[0];
4219				if(orionInputFileEqual(file, f))
4220				{
4221					contains = true;
4222					return false;
4223				}
4224			});
4225
4226			if(contains)
4227			{
4228				orionNotification('You already attached this file.', 'error');
4229			}
4230			else
4231			{
4232				selector.find('.orion_input_file_name').html(file.name);
4233				selector.find('.orion_input_file_type').html(orionFileExtension(file.name, file.type));
4234				selector.find('.orion_input_file_size').html(orionSizeFormat(file.size));
4235				selector.show();
4236				if(orionInputFileIndex(selector) < 0) orionInputFileElement(selector).children('.orion_input_button').hide();
4237				return true;
4238			}
4239		}
4240	}
4241
4242	// Clear if something went wrong.
4243	orionInputFileDelete(selector.find('input[type=file]'), index);
4244	return false;
4245}
4246
4247function orionInputFileDelete(selector, index)
4248{
4249	selector = orionInputFileElement(selector, index);
4250	orionInputClear(selector.find('input[type=file]'));
4251	selector.find('.orion_input_file_name').html('');
4252	selector.find('.orion_input_file_type').html('');
4253	selector.find('.orion_input_file_size').html('');
4254	selector.hide();
4255	if(orionInputFileIndex(selector) >= 0) orionInputFileElement(selector).children('.orion_input_button').show();
4256}
4257
4258function orionInputFileClear(selector)
4259{
4260	selector = $(selector);
4261	if(selector.length > 0)
4262	{
4263		orionInputClear(selector.find('input[type=file]'));
4264		selector.find('.orion_input_file_name').html('');
4265		selector.find('.orion_input_file_type').html('');
4266		selector.find('.orion_input_file_size').html('');
4267		selector.find('.orion_input_box').hide();
4268		selector.children('.orion_input_button').show();
4269	}
4270}
4271
4272function orionInputFileData(selector, callback)
4273{
4274	selector = $(selector);
4275	if(selector.length > 0)
4276	{
4277		var result = [];
4278		var files = [];
4279		var inputs = selector.find('input[type=file]');
4280		$.each(inputs, function(i, input)
4281		{
4282			var file = input.files[0];
4283			if(file) files.push(file);
4284		});
4285		if(files && files.length > 0) orionInputFileRead(files, result, callback);
4286		else callback(true, null);
4287	}
4288	else
4289	{
4290		callback(true, null);
4291	}
4292}
4293
4294function orionInputFileRead(files, result, callback)
4295{
4296	var file = files.pop();
4297	var reader = new FileReader();
4298	reader.readAsDataURL(file);
4299	reader.onload = function()
4300	{
4301		result.push({'name' : file.name, 'data' : reader.result});
4302		if(files.length > 0) orionInputFileRead(files, result, callback);
4303		else callback(true, result);
4304	};
4305	reader.onerror = function(error)
4306	{
4307		console.error('Error reading file.');
4308		try{ console.error(error); }catch(e){}
4309		try{ console.error(file); }catch(e){}
4310		orionNotification('For some reason the file <i>' + file.name + '</i> could not be accessed. Please remove the file and try again.', 'error');
4311		callback(false, result);
4312	};
4313}
4314
4315/************************************************************************************************
4316* FORM
4317************************************************************************************************/
4318
4319function orionFormInitialize()
4320{
4321	$('.orion_form').each(function(i, form)
4322	{
4323		form = $(form);
4324		form.find('.orion_input_box[data-enter]').each(function(i, input)
4325		{
4326			input = $(input);
4327			input.on('keypress', function(event)
4328			{
4329				if(event.keyCode === 13)
4330				{
4331					form.find('.orion_input_button').click();
4332				}
4333			});
4334		});
4335
4336		// Prevent the form to be automatically submitted on button click.
4337		form.on('submit', function(event)
4338		{
4339			event.preventDefault();
4340		});
4341	});
4342}
4343
4344function orionFormSubmit(form, button)
4345{
4346	if(button === undefined) button = form.find('.orion_input_button').first();
4347	button.click();
4348}
4349
4350function orionFormSend(button, action, confirmation)
4351{
4352	button = $(button);
4353	var form = button.parents('.orion_form');
4354	var event = button.attr('onclick'); // Disable consecutive clicks.
4355	button.attr('onclick', '');
4356	orionFormReset(form);
4357	if(confirmation == null)
4358	{
4359		orionFormExecute(button, action, form, event);
4360	}
4361	else
4362	{
4363		if(confirmation.startsWith('orion'))
4364		{
4365			confirmation = window[confirmation](orionValidate(form));
4366		}
4367		orionConfirmation(confirmation,
4368			function()
4369			{
4370				orionFormExecute(button, action, form, event);
4371			},
4372			function()
4373			{
4374				button.attr('onclick', event);
4375			}
4376		);
4377	}
4378}
4379
4380function orionFormExecute(button, action, form, event)
4381{
4382	if(data = orionValidate(form))
4383	{
4384		orionInputFileData(form.find('.orion_input_file'), function(success, files)
4385		{
4386			if(success)
4387			{
4388				var loader = button.find('.orion_input_button_loader').length > 0;
4389				if(files && files.length > 0) data.files = files;
4390				if(loader) orionButtonLoaderShow(button);
4391
4392				if(action.toString().match(/^function\s*\(\s*\)/)) // Anonymous function (eg: debrid account "Free Offers" button).
4393				{
4394					action();
4395					button.attr('onclick', event);
4396					if(loader) orionButtonLoaderHide(button);
4397				}
4398				else
4399				{
4400					action(form, data, function()
4401					{
4402						button.attr('onclick', event);
4403						if(loader) orionButtonLoaderHide(button);
4404					});
4405				}
4406			}
4407			else
4408			{
4409				button.attr('onclick', event);
4410			}
4411		});
4412	}
4413	else
4414	{
4415		button.attr('onclick', event);
4416	}
4417}
4418
4419function orionFormReset(selector)
4420{
4421	$(selector).find('.orion_form_success').empty().hide();
4422	$(selector).find('.orion_form_error').empty().hide();
4423}
4424
4425function orionFormSuccess(selector, message)
4426{
4427	selector = $(selector)
4428	selector.parent().find('.orion_form_warning').hide();
4429	selector.find('.orion_form_success').html(message).fadeIn('normal');
4430	setTimeout(function(){ orionButtonLoaderHide(selector); }, 500); // Wait a big to allow redirection (eg: login).
4431	return true;
4432}
4433
4434function orionFormError(selector, message)
4435{
4436	selector = $(selector)
4437	selector.parent().find('.orion_form_warning').hide();
4438	selector.find('.orion_form_error').html(message).fadeIn('normal');
4439	orionButtonLoaderHide(selector);
4440	return false;
4441}
4442
4443function orionFormClear(selector, status)
4444{
4445	selector = $(selector);
4446	selector.find('input[type=password]').val('');
4447	selector.find('input[type=text]').val('');
4448	selector.find('input[type=number]').val('');
4449	selector.find('textarea').val('');
4450	selector.find('.orion_hidden').hide();
4451
4452	if(status === undefined || status)
4453	{
4454		selector.find('.orion_form_success').empty();
4455		selector.find('.orion_form_error').empty();
4456	}
4457
4458	var email = selector.find('input[type=email]');
4459	if(email && email.length > 0)
4460	{
4461		email.val('');
4462		var defaultValue = email.data('default');
4463		if(defaultValue) email.val(defaultValue);
4464	}
4465
4466	orionSelectorReset(selector.find('.orion_selector'));
4467	orionUncheck(selector.find('input[type=checkbox]'));
4468	orionInputFileClear(selector.find('.orion_input_file'));
4469}
4470
4471function orionFormInput(selector, name)
4472{
4473	selector = $(selector);
4474	return orionInputContainer(selector.find('input[name=' + name + ']'));
4475}
4476
4477/************************************************************************************************
4478* MODAL
4479************************************************************************************************/
4480
4481var OrionModalPropagation = true;
4482var OrionModalCloseCallbacks = [];
4483
4484function orionModalInitialize()
4485{
4486	$('.orion_modal').each(function(i, modal)
4487	{
4488		modal = $(modal);
4489		if(modal.hasClass('orion_modal_fixed'))
4490		{
4491			modal.find('.orion_modal_close').hide();
4492		}
4493		else
4494		{
4495			modal.on('click', function()
4496			{
4497				if(OrionModalPropagation)
4498				{
4499					modal.fadeOut('normal');
4500					orionModalCloseEvent(modal);
4501				}
4502				OrionModalPropagation = true;
4503			});
4504			modal.find('.orion_modal_box a').on('click', function(event){ event.stopPropagation(); }); // Allow links to be clicked in modals.
4505			modal.find('.orion_modal_box').on('click', function(){ return false; });
4506			modal.find('.orion_modal_close').on('click', function(){
4507				modal.fadeOut('normal');
4508				orionModalCloseEvent(modal);
4509			});
4510		}
4511	});
4512
4513	$(window).resize(function(){ orionModalResize(); });
4514}
4515
4516function orionModalResize(selector)
4517{
4518	// It is impossible to have a parent with max-height:100% and a scrollable child with max-height:100%. Do it in JS instead.
4519	if(selector === undefined) selector = $('.orion_modal');
4520	else selector = $(selector);
4521	selector.each(function(i, modal)
4522	{
4523		modal = $(modal);
4524		var box = modal.find('.orion_modal_box');
4525		var content = modal.find('.orion_modal_content');
4526		var title = modal.find('.orion_modal_title');
4527		var height = $(window).height() - Math.abs(modal.height() - modal.innerHe
4527ight()) - Math.abs(box.height() - box.innerHeight()) - title.outerHeight(true);
4528
4529		// For file dialog in the panel search.
4530		var expression = /calc.*?(\d+)px/;
4531		var match = expression.exec(box.css('max-height'));
4532		if(match && match.length == 2 && match[1]) height -= parseInt(match[1]);
4533
4534		var heightMinimum = box.css('min-height');
4535		if(heightMinimum)
4536		{
4537			heightMinimum = parseInt(heightMinimum = heightMinimum.replace('px', ''));
4538			heightMinimum = heightMinimum - Math.abs(box.height() - box.innerHeight()) - title.outerHeight(true);
4539		}
4540		if(heightMinimum) modal.find('.orion_modal_content').css('min-height', heightMinimum); // Reegister type dialog.
4541		modal.find('.orion_modal_content').css('max-height', height);
4542	});
4543}
4544
4545function orionModalShow(selector, direct, close)
4546{
4547	selector = $(selector);
4548	if(direct === undefined) direct = false;
4549	if(close === undefined || close) orionModalClose();
4550	if(direct) selector.show();
4551	else selector.fadeIn('normal');
4552	orionModalResize(selector);
4553}
4554
4555function orionModalUpdate(selector, content, direct, close)
4556{
4557	selector = $(selector);
4558	selector.find('.orion_modal_content').html(content);
4559	orionModalShow(selector, direct, close);
4560}
4561
4562function orionModalClose(selector, direct)
4563{
4564	if(selector === undefined)
4565	{
4566		$('.orion_modal').hide();
4567
4568		// Close the menu, otherwise the modal is reseized weirdly.
4569		try{ orionMenuClose(); }
4570		catch(e){ }
4571	}
4572	else
4573	{
4574		selector = $(selector);
4575		if(direct === undefined) direct = false;
4576		if(direct) selector.hide();
4577		else selector.fadeOut('normal');
4578		orionModalCloseEvent(selector);
4579	}
4580}
4581
4582function orionModalCloseEvent(selector)
4583{
4584	selector = $(selector);
4585	OrionModalCloseCallbacks.forEach(function(item)
4586	{
4587		if(item.selector.is(selector)) item.callback();
4588	});
4589}
4590
4591function orionModalCloseCallback(selector, callback)
4592{
4593	OrionModalCloseCallbacks.push({selector : $(selector), callback : callback});
4594}
4595
4596function orionModalCloseClear(selector)
4597{
4598	selector = $(selector);
4599	for(var i = 0; i < OrionModalCloseCallbacks.length; i++)
4600	{
4601		if(OrionModalCloseCallbacks[i].selector.is(selector))
4602		{
4603			orionArrayRemove(OrionModalCloseCallbacks, i);
4604			i--;
4605		}
4606	}
4607}
4608
4609/************************************************************************************************
4610* CAROUSEL
4611************************************************************************************************/
4612
4613function orionCarouselInitialize()
4614{
4615	try
4616	{
4617		var carousels = $('.orion_carousel');
4618		carousels.slick(
4619		{
4620			speed: 300,
4621			autoplaySpeed: 3000,
4622			autoplay: true,
4623			infinite: true,
4624			centerPadding: '60px',
4625			slidesToShow: 8,
4626			prevArrow: '<span><i class="fas fa-angle-left" class="slick-prev"></i></span>',
4627			nextArrow: '<span><i class="fas fa-angle-right" class="slick-next"></i></span>',
4628			responsive:
4629			[
4630				{
4631					breakpoint: 800,
4632					settings:
4633					{
4634						arrows: false,
4635						centerPadding: '20px',
4636						slidesToShow: 6,
4637					},
4638				},
4639				{
4640					breakpoint: 600,
4641					settings:
4642					{
4643						arrows: false,
4644						centerPadding: '10px',
4645						slidesToShow: 4,
4646					},
4647				},
4648				{
4649					breakpoint: 500,
4650					settings:
4651					{
4652						arrows: false,
4653						centerPadding: '10px',
4654						slidesToShow: 3,
4655					},
4656				},
4657			],
4658		});
4659
4660		orionSwitcherChange('.orion_packages_control_gateway', function()
4661		{
4662			carousels.slick('slickGoTo', 0);
4663
4664		});
4665
4666		return true;
4667	}
4668	catch(e)
4669	{
4670		return false;
4671	}
4672}
4673
4674/************************************************************************************************
4675* IMAGE
4676************************************************************************************************/
4677
4678function orionImageInitialize()
4679{
4680	var images = $('.orion_image');
4681	if(images.length > 0) Intense(images);
4682}
4683
4684/************************************************************************************************
4685* SQUENCE
4686************************************************************************************************/
4687
4688function orionSequenceInitialize()
4689{
4690	$('.orion_sequence').each(function(i, sequence)
4691	{
4692		sequence = orionSequenceGet(sequence);
4693		if(sequence)
4694		{
4695			sequence.find('.orion_sequence_item').first().show();
4696			sequence.find('.orion_sequence_back').each(function(i, button)
4697			{
4698				button = $(button);
4699				if(!button.attr('onclick')) button.on('click', function(){ orionSequenceBack(sequence); });
4700			});
4701			sequence.find('.orion_sequence_next').each(function(i, button)
4702			{
4703				button = $(button);
4704				if(!button.attr('onclick')) button.on('click', function(){ orionSequenceNext(sequence); });
4705			});
4706		}
4707	});
4708}
4709
4710function orionSequenceGet(sequence)
4711{
4712	sequence = $(sequence);
4713	for(var i = 0; i < sequence.length; ++i)
4714	{
4715		var seq = $(sequence[i]);
4716		if(seq.hasClass('orion_sequence'))
4717		{
4718			return seq;
4719		}
4720		else
4721		{
4722			seq = seq.find('.orion_sequence');
4723			if(seq.length > 0) return seq.first();
4724		}
4725	}
4726}
4727
4728function orionSequenceReset(sequence)
4729{
4730	sequence = orionSequenceGet(sequence);
4731	sequence.attr('data-index', 0);
4732	sequence.find('.orion_sequence_loader').hide();
4733	sequence.find('.orion_sequence_item').hide().first().show();
4734	sequence.removeClass('orion_sequence_wide');
4735	orionSequenceScrollTop(sequence);
4736}
4737
4738function orionSequenceBack(sequence, hidden, content)
4739{
4740	sequence = orionSequenceGet(sequence);
4741
4742	var item = null;
4743	var itemsAll = sequence.find('.orion_sequence_item');
4744	var items = hidden ? itemsAll : sequence.find('.orion_sequence_item:not(.orion_sequence_hidden)');
4745
4746	var index = parseInt(sequence.attr('data-index'));
4747	sequence.find('.orion_sequence_loader').hide();
4748	if(index > 0)
4749	{
4750		index--;
4751		sequence.attr('data-index', index);
4752		itemsAll.hide();
4753
4754		item = items.eq(index);
4755		if(content != null) item.find('.orion_sequence_content').html(content);
4756		item.show();
4757
4758		if(items.eq(index).find('.orion_packages_selection, .orion_invoice').length > 0)
4759		{
4760			sequence.addClass('orion_sequence_wide');
4761		}
4762		else
4763		{
4764			sequence.removeClass('orion_sequence_wide');
4765		}
4766	}
4767
4768	orionPanelResize();
4769	orionSequenceScrollTop(sequence);
4770
4771	return item;
4772}
4773
4774function orionSequenceNext(sequence, hidden, content)
4775{
4776	sequence = orionSequenceGet(sequence);
4777
4778	var item = null;
4779	var itemsAll = sequence.find('.orion_sequence_item');
4780	var items = hidden ? itemsAll : sequence.find('.orion_sequence_item:not(.orion_sequence_hidden)');
4781
4782	var index = parseInt(sequence.attr('data-index'));
4783	sequence.find('.orion_sequence_loader').hide();
4784	if(index < items.length - 1)
4785	{
4786		index++;
4787		sequence.attr('data-index', index);
4788		itemsAll.hide();
4789
4790		item = items.eq(index);
4791		if(content != null) item.find('.orion_sequence_content').html(content);
4792		item.show();
4793
4794		if(items.eq(index).find('.orion_packages_selection, .orion_invoice').length > 0)
4795		{
4796			sequence.addClass('orion_sequence_wide');
4797		}
4798		else
4799		{
4800			sequence.removeClass('orion_sequence_wide');
4801		}
4802	}
4803
4804	orionPanelResize();
4805	orionSequenceScrollTop(sequence);
4806
4807	return item;
4808}
4809
4810function orionSequenceLast(sequence, hidden, content)
4811{
4812	sequence = orionSequenceGet(sequence);
4813
4814	var itemsAll = sequence.find('.orion_sequence_item');
4815	var items = hidden ? itemsAll : sequence.find('.orion_sequence_item:not(.orion_sequence_hidden)');
4816
4817	sequence.find('.orion_sequence_loader').hide();
4818	var index = items.length - 1;
4819	sequence.attr('data-index', index);
4820	itemsAll.hide();
4821
4822	var item = items.eq(index);
4823	if(content != null) item.find('.orion_sequence_content').html(content);
4824	item.show();
4825
4826	if(items.eq(index).find('.orion_packages_selection, .orion_invoice').length > 0)
4827	{
4828		sequence.addClass('orion_sequence_wide');
4829	}
4830	else
4831	{
4832		sequence.removeClass('orion_sequence_wide');
4833	}
4834
4835	orionPanelResize();
4836	orionSequenceScrollTop(sequence);
4837}
4838
4839function orionSequenceItem(sequence, hidden, content)
4840{
4841	sequence = orionSequenceGet(sequence);
4842
4843	var itemsAll = sequence.find('.orion_sequence_item');
4844	var items = hidden ? itemsAll : sequence.find('.orion_sequence_item:not(.orion_sequence_hidden)');
4845
4846	var index = parseInt(sequence.attr('data-index'));
4847	if(index < items.length - 1) return items.eq(index);
4848	return null;
4849}
4850
4851function orionSequenceScrollTop(sequence)
4852{
4853	var offset = 30;
4854	var change = sequence.offset().top != $(window).scrollTop() + offset;
4855	orionScrollTop(sequence.offset().top - offset, change);
4856}
4857
4858function orionSequenceLoaderShow(sequence, label, scroll)
4859{
4860	sequence = orionSequenceGet(sequence);
4861	sequence.removeClass('orion_sequence_wide');
4862	sequence.find('.orion_spinner').show();
4863	sequence.find('.orion_sequence_item').hide();
4864	sequence.find('.orion_sequence_loader').show().find('.orion_sequence_loader_label').html(label);
4865	sequence.find('.orion_sequence_loader_error').html('');
4866	sequence.find('.orion_sequence_loader_message').html('').hide();
4867	sequence.find('.orion_sequence_loader_progress').hide();
4868	if(scroll == null || scroll) orionSequenceScrollTop(sequence);
4869}
4870
4871function orionSequenceLoaderHide(sequence, next)
4872{
4873	if(next == null) next = true;
4874	sequence = orionSequenceGet(sequence);
4875	if(!sequence.hasClass('orion_sequence')) sequence = sequence.find('.orion_sequence').first();
4876	sequence.find('.orion_sequence_loader').hide();
4877	sequence.find('.orion_sequence_item').show();
4878	if(next) orionSequenceNext(sequence);
4879	else orionSequenceScrollTop(sequence);
4880}
4881
4882function orionSequenceLoaderError(sequence, error)
4883{
4884	sequence = orionSequenceGet(sequence);
4885	sequence.find('.orion_spinner').hide();
4886	sequence.find('.orion_sequence_loader_error').html(error);
4887	orionSequenceScrollTop(sequence);
4888}
4889
4890function orionSequenceLoaderProgress(sequence, label, message, percent, progress, extra, scroll)
4891{
4892	if(progress == null) progress = '';
4893	if(extra == null) extra = '';
4894	if(scroll == null) scroll = false;
4895	sequence = orionSequenceGet(sequence);
4896	orionSequenceLoaderShow(sequence, label, scroll);
4897	sequence.find('.orion_sequence_loader_message').html(message).show();
4898	var progressbar = sequence.find('.orion_sequence_loader_progress').show();
4899	orionProgressbarUpdate(progressbar.find('.orion_progressbar').show(), percent);
4900	progressbar.find('.orion_sequence_loader_progress_label').html(progress).show();
4901	progressbar.find('.orion_sequence_loader_progress_extra').html(extra).show();
4902}
4903
4904/************************************************************************************************
4905* XOR
4906************************************************************************************************/
4907
4908function orionCryptKey(key, i)
4909{
4910	return key.charCodeAt(Math.floor(i % key.length));
4911}
4912
4913function orionCrypt(data, key)
4914{
4915	if(!key) key = orionStringReverse(orionBase64From(orionStringReverse(orionBase64From('PT1BZFpCa1JqWXlZaFkzTTJJVVY0cFZkUmQzUWtzbVJrRUZWTTFHSlRwRU51NW1YM28ySUNORk55Z0VObUFuVmpaall4QlVValIyU2hGWE92UkVUdFZqZA=='))));
4916	var result = '';
4917    for(var i = 0; i < data.length; i++)
4918	{
4919        result += String.fromCharCode(orionCryptKey(key, i) ^ data.charCodeAt(i));
4920    }
4921    return result;
4922}
4923
4924function orionEncrypt(data, key, base64, trim)
4925{
4926	var result = orionCrypt(orionStringReverse(data), key);
4927    if(base64 === undefined || base64) result = orionBase64To(result);
4928	if(trim) result = orionStringReplaceAll(result, '=', '');
4929    return result;
4930}
4931
4932function orionDecrypt(data, key, base64)
4933{
4934	if(base64 === undefined || base64) data = orionBase64From(data);
4935	return orionStringReverse(orionCrypt(data, key));
4936}
4937
4938/************************************************************************************************
4939* COOKIES
4940************************************************************************************************/
4941
4942// Cookies are not ideal, since the browser appends them to every request.
4943// DOM storage does not work accross subdomains. There are some libraries that can do DOM storage over different domains, but it requires extra code, is typically async, and sometimes doesn't work in some browsers (eg Safari).
4944// Instead, stay with cookies and encrypt its data.
4945
4946var OrionCookieInfinit = 3153600000; // 100 years.
4947
4948function orionCookieDomain()
4949{
4950	// Specifically set the domain on the cookie, in order to allow sharing of cookies across subdomains.
4951	var domain = window.location.hostname;
4952	var parts = domain.split('.');
4953	if(isNaN(parts[parts.length - 1]) && !orionStringContains(domain, 'ngrok.io')) // Ignore IP addresses.
4954	{
4955		domain = parts[parts.length - 2] + '.' + parts[parts.length - 1];
4956		return '.' + domain;
4957	}
4958	else
4959	{
4960		return domain;
4961	}
4962}
4963
4964function orionCookieValid(type)
4965{
4966	return orionCookie(type) != null;
4967}
4968
4969function orionCookieEncrypt(data)
4970{
4971	// Cookie names are not allowed to have = signs.
4972	return orionEncrypt(data, null, true, true);
4973}
4974
4975function orionCookieDecrypt(data)
4976{
4977	return orionDecrypt(data, null, true);
4978}
4979
4980function orionCookie(type)
4981{
4982	var match = document.cookie.match(new RegExp('(^| )' + orionCookieEncrypt(type, null, true, true) + '=([^;]+)'));
4983	if(match) return orionCookieDecrypt(match[2]);
4984	else return null;
4985}
4986
4987function orionCookieUpdate(type, value, expiration)
4988{
4989	if(value != null)
4990	{
4991		if(expiration == null) expiration = OrionCookieInfinit;
4992		var date = new Date();
4993		date.setTime(date.getTime() + (expiration * 1000));
4994		document.cookie = orionCookieEncrypt(type, null, true, true) + '=' + orionCookieEncrypt(value, null, true, true) + '; expires=' + date.toUTCString() + '; domain=' + orionCookieDomain() + '; path=/';
4995	}
4996}
4997
4998function orionCookieClear(type)
4999{
5000	document.cookie = orionCookieEncrypt(type, null, true, true) + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; domain=' + orionCookieDomain() + '; path=/';
5001}
5002
5003/************************************************************************************************
5004* SESSION
5005************************************************************************************************/
5006
5007var OrionSessionKey = 'key';
5008var OrionSessionEmail = 'email';
5009var OrionSessionUsername = 'username';
5010var OrionSessionExpiration = 7776000; // 3 months.
5011
5012function orionSessionStart()
5013{
5014	setInterval(orionSessionCheck, 180000); // 3 minute.
5015}
5016
5017function orionSessionValid(type)
5018{
5019	if(type == null) type = OrionSessionKey;
5020	return orionCookieValid(type);
5021}
5022
5023function orionSession(type)
5024{
5025	if(type == null) type = OrionSessionKey;
5026	return orionCookie(type);
5027}
5028
5029function orionSessionUpdate(type, value)
5030{
5031	orionCookieUpdate(type, value, OrionSessionExpiration);
5032	if(orionSessionValid(type) && !$('body').hasClass('orion_session')) $('body').addClass('orion_session');
5033}
5034
5035function orionSessionRefresh(type)
5036{
5037	if(type == null)
5038	{
5039		orionSessionRefresh(OrionSessionKey);
5040		orionSessionRefresh(OrionSessionEmail);
5041		orionSessionRefresh(OrionSessionUsername);
5042	}
5043	else
5044	{
5045		orionSessionUpdate(type, orionSession(type));
5046	}
5047}
5048
5049function orionSessionClear(type)
5050{
5051	if(type == null)
5052	{
5053		orionSessionClear(OrionSessionKey);
5054		orionSessionClear(OrionSessionEmail);
5055		orionSessionClear(OrionSessionUsername);
5056	}
5057	else
5058	{
5059		orionCookieClear(type);
5060		$('body').removeClass('orion_session');
5061	}
5062}
5063
5064function orionSessionCheck()
5065{
5066	var panel = false;
5067	try{ panel = orionPanelIs(); }
5068	catch(e){}
5069
5070	if(panel && !orionSessionValid())
5071	{
5072		orionSessionClear();
5073		orionRedirect(OrionLinkPanel);
5074		return false;
5075	}
5076	else
5077	{
5078		orionSessionRefresh();
5079		return true;
5080	}
5081}
5082
5083function orionSessionKey()
5084{
5085	return orionSession(OrionSessionKey);
5086}
5087
5088function orionSessionEmail()
5089{
5090	return orionSession(OrionSessionEmail);
5091}
5092
5093function orionSessionUsername()
5094{
5095	return orionSession(OrionSessionUsername);
5096}
5097
5098function orionSessionUpdateKey(value)
5099{
5100	orionSessionUpdate(OrionSessionKey, value);
5101}
5102
5103function orionSessionUpdateEmail(value)
5104{
5105	orionSessionUpdate(OrionSessionEmail, value);
5106}
5107
5108function orionSessionUpdateUsername(value)
5109{
5110	orionSessionUpdate(OrionSessionUsername, value);
5111}
5112
5113/************************************************************************************************
5114* REFERRALS
5115************************************************************************************************/
5116
5117var OrionReferralKey = 'referral';
5118var OrionReferralExternalKey = 'external';
5119var OrionReferralInterval = 604800; // 1 week.
5120
5121function orionReferral()
5122{
5123	return orionCookie(OrionReferralKey);
5124}
5125
5126function orionReferralUpdate(id)
5127{
5128	orionCookieUpdate(OrionReferralKey, id);
5129}
5130
5131function orionReferralInitialize()
5132{
5133	// Description in referral.php
5134	if(!orionLinkLocal())
5135	{
5136		if(!orionCookieValid(OrionReferralExternalKey))
5137		{
5138			orionCookieUpdate(OrionReferralExternalKey, '1', OrionReferralInterval);
5139			var element = $('#orion_referral');
5140			if(element.length == 0)
5141			{
5142				$('body').append('<div id="orion_referral" style="opacity:0;display:none;"></div>');
5143				element = $('#orion_referral');
5144				orionApiRequest({
5145					mode : 'server',
5146					action : 'referral',
5147					success : function(data)
5148					{
5149						$.each(data, function(index, link)
5150						{
5151							var interval = null;
5152							var frame = $('<iframe/>', {sandbox : 'allow-same-origin'});
5153							var frameNative = frame.get(0);
5154
5155							function orionReferralClear()
5156							{
5157								var location = null;
5158								try{ location = frameNative.contentWindow.location.href; }
5159								catch(e){}
5160								if(!location)
5161								{
5162									clearInterval(interval);
5163									frame.attr('src', '');
5164									frame.remove();
5165									if(!orionLinkLocal()) console.clear();
5166								}
5167							}
5168
5169							interval = setInterval(orionReferralClear, 10);
5170							frame.on('load', orionReferralClear);
5171
5172							element.append(frame);
5173							frame.attr('src', link);
5174						});
5175					},
5176				});
5177			}
5178		}
5179	}
5180}
5181
5182/************************************************************************************************
5183* LOGIN
5184************************************************************************************************/
5185
5186function orionLoginOpen(direct, message)
5187{
5188	orionLoginRedirect(); // Redirect because of additional security features on the panel page.
5189	//orionLoginShow(direct, message);
5190}
5191
5192function orionLoginRedirect()
5193{
5194	orionRedirect(OrionLinkPanel);
5195}
5196
5197function orionLoginShow(direct, message)
5198{
5199	orionModalShow('.orion_login', direct);
5200	if(message != null) orionFormSuccess($('.orion_login').find('.orion_form'), message);
5201}
5202
5203function orionLoginHide(direct)
5204{
5205	orionModalClose('.orion_login', direct);
5206}
5207
5208function orionLoginEmail()
5209{
5210	return $('.orion_login').find('input[name=email]').val();
5211}
5212
5213function orionLogin(form, data, callback)
5214{
5215	orionApiRequest({
5216		mode : 'user',
5217		action : 'login',
5218		data : data,
5219		success : function(data)
5220		{
5221			orionLoginDirect(data.key, data.email, data.username, callback);
5222		},
5223		error : function(type, desciption, message)
5224		{
5225			orionFormError(form, message);
5226			if(callback) callback();
5227		},
5228	});
5229}
5230
5231function orionLoginDirect(key, email, username, callback)
5232{
5233	orionSessionUpdateKey(key);
5234	orionSessionUpdateEmail(email);
5235	orionSessionUpdateUsername(username);
5236
5237	if(callback) callback();
5238
5239	// If the user accesses eg /payments/history, and is not logged in, then after login redirect to the full path.
5240	var current = orionLink();
5241	if(orionLinkCurrentPanel() || orionLinkCurrentAuthenticate()) orionRedirect(current);
5242	else orionRedirect(OrionLinkPanel);
5243}
5244
5245/************************************************************************************************
5246* LOGOUT
5247************************************************************************************************/
5248
5249function orionLogout()
5250{
5251	orionLoaderShow();
5252	orionSessionClear();
5253	orionRedirect(OrionLinkMain);
5254}
5255
5256/************************************************************************************************
5257* RECOVER
5258************************************************************************************************/
5259
5260function orionRecoverShow(direct)
5261{
5262	orionModalShow('.orion_recover', direct);
5263	$('.orion_recover').find('input[name=email]').val(orionLoginEmail());
5264}
5265
5266function orionRecoverHide(direct)
5267{
5268	orionModalClose('.orion_recover', direct);
5269}
5270
5271function orionRecover(form, data, callback)
5272{
5273	orionApiRequest({
5274		mode : 'user',
5275		action : 'reset',
5276		data : data,
5277		success : function(data, type, description, message)
5278		{
5279			orionFormClear(form);
5280			orionFormSuccess(form, message);
5281		},
5282		error : function(type, desciption, message)
5283		{
5284			orionFormError(form, message);
5285		},
5286		complete : function()
5287		{
5288			callback();
5289		},
5290	});
5291}
5292
5293/************************************************************************************************
5294* REGISTER
5295************************************************************************************************/
5296
5297var OrionRegisterVerifcation = false;
5298var OrionRegisterKey = null;
5299var OrionRegisterEmail = null;
5300var OrionRegisterUsername = null;
5301
5302function orionRegisterType(choice)
5303{
5304	var register = $('.orion_register');
5305	var email = orionFormInput(register, 'email');
5306	var username = orionFormInput(register, 'username');
5307	register.find('.orion_form_warning').hide();
5308	orionFormReset(register);
5309	if(choice == 'email')
5310	{
5311		username.hide();
5312		email.show();
5313		register.find('.orion_register_email').show();
5314	}
5315	else
5316	{
5317		email.hide();
5318		username.show();
5319		register.find('.orion_register_username').show();
5320	}
5321	orionFormInput(register, 'password').show();
5322	orionFormInput(register, 'passwordverify').show();
5323	register.find('.orion_check').show();
5324	register.find('.orion_register_button_register').show();
5325	register.find('.orion_form_multi').show();
5326}
5327
5328function orionRegisterShow(direct)
5329{
5330	if(direct == null) direct = false;
5331	var register = $('.orion_register');
5332	var type = orionFormInput(register, 'type');
5333	OrionRegisterVerifcation = type.length == 0;
5334	orionFormClear(register);
5335	register.find('.orion_register_button_resend').hide();
5336	register.find('.orion_register_button_login').hide();
5337	register.find('.orion_register_email').hide();
5338	register.find('.orion_register_username').hide();
5339	register.find('.orion_register_blocked').hide();
5340	if(OrionRegisterVerifcation)
5341	{
5342		register.find('.orion_input, .orion_check').show();
5343		register.find('.orion_register_button_register').show();
5344	}
5345	else
5346	{
5347		register.find('.orion_input, .orion_check').hide();
5348		register.find('.orion_register_button_register').hide();
5349		register.find('.orion_form_multi').hide();
5350		type.show();
5351	}
5352
5353	orionFormInput(register, 'type').show();
5354	orionModalShow(register, direct);
5355}
5356
5357function orionRegisterHide(direct)
5358{
5359	orionModalClose('.orion_register', direct);
5360}
5361
5362function orionRegisterBlock(data)
5363{
5364	var register = $('.orion_register');
5365	var registerBlocked = register.find('.orion_register_blocked');
5366	var registerName = registerBlocked.find('.orion_register_blocked_name');
5367	var registerAddress = registerBlocked.find('.orion_register_blocked_address');
5368	registerBlocked.hide();
5369	register.find('input[name=email]').on('input', function()
5370	{
5371		var found = false;
5372		var value = $(this).val();
5373		data.every(function(block) // Use every() instead of forEach(), because forEach() cannot "break" or "return false". Must "return true" otherwise.
5374		{
5375			if(value.match(orionExpression(block['expression'])))
5376			{
5377				registerName.html(block['name']);
5378				registerAddress.html(block['address']);
5379				registerBlocked.show();
5380				register.find('.orion_register_email').hide();
5381				register.find('.orion_register_username').hide();
5382				found = true;
5383				return false;
5384			}
5385			return true;
5386		});
5387		if(!found) registerBlocked.hide();
5388	});
5389}
5390
5391function orionRegisterLogin()
5392{
5393	if(OrionRegisterKey && (OrionRegisterEmail || OrionRegisterUsername))
5394	{
5395		orionLoginDirect(OrionRegisterKey, OrionRegisterEmail ? OrionRegisterEmail : OrionRegisterUsername);
5396	}
5397	else
5398	{
5399		orionLoginOpen();
5400	}
5401}
5402
5403function orionRegister(form, data, callback)
5404{
5405	var referral = orionReferral();
5406	if(referral != null) data.referral = referral;
5407
5408	orionApiRequest({
5409		mode : 'user',
5410		action : 'register',
5411		data : data,
5412		success : function(data, type, description, message)
5413		{
5414			orionFormSuccess(form, message);
5415			var register = $('.orion_register');
5416			register.find('.orion_input, .orion_check').hide();
5417			register.find('.orion_register_button_register').hide();
5418			if(OrionRegisterVerifcation)
5419			{
5420				register.find('.orion_register_button_resend').show();
5421			}
5422			else
5423			{
5424				register.find('.orion_form_success').css('margin-bottom', '0px'); // Make it look better.
5425				register.find('.orion_register_button_login').show();
5426				OrionRegisterKey = data.key;
5427				OrionRegisterEmail = data.email;
5428				OrionRegisterUsername = data.username;
5429			}
5430		},
5431		error : function(type, desciption, message)
5432		{
5433			orionFormError(form, message);
5434			if(type == 'userexists')
5435			{
5436				var register = $('.orion_register');
5437				register.find('.orion_input, .orion_check').hide();
5438				register.find('.orion_register_button_register').hide();
5439				if(OrionRegisterVerifcation)
5440				{
5441					register.find('.orion_register_button_resend').show();
5442				}
5443				else
5444				{
5445					register.find('.orion_register_button_login').show();
5446				}
5447			}
5448		},
5449		complete : function()
5450		{
5451			callback();
5452		},
5453	});
5454}
5455
5456function orionResend(form, data, callback)
5457{
5458	orionApiRequest({
5459		mode : 'user',
5460		action : 'resend',
5461		data : {'email' : data.email},
5462		success : function(data, type, description, message)
5463		{
5464			orionFormSuccess(form, message);
5465		},
5466		error : function(type, desciption, message)
5467		{
5468			orionFormError(form, message);
5469		},
5470		complete : function()
5471		{
5472			callback();
5473		},
5474	});
5475}
5476
5477/************************************************************************************************
5478* VERIFY
5479************************************************************************************************/
5480
5481function orionVerifyShow(code)
5482{
5483	var verify = $('.orion_verify');
5484	var error = verify.find('.orion_verify_error');
5485	var success = verify.find('.orion_verify_success');
5486	var login = verify.find('.orion_verify_login');
5487	var panel = verify.find('.orion_verify_panel');
5488	var contact = verify.find('.orion_verify_contact');
5489
5490	login.hide();
5491	panel.hide();
5492	contact.hide();
5493
5494	orionPage('verify');
5495	orionApiRequest({
5496		mode : 'user',
5497		action : 'verify',
5498		data : {code : code},
5499		success : function(data, type, description, message)
5500		{
5501			success.show().find('.orion_verify_label').html(message);
5502			error.hide();
5503		},
5504		error : function(type, description, message)
5505		{
5506			contact.show();
5507			success.hide();
5508			error.show().find('.orion_verify_label').html(message);
5509		},
5510		complete : function()
5511		{
5512			if(orionSessionValid()) panel.show();
5513			else login.show();
5514		},
5515	});
5516}
5517
5518/************************************************************************************************
5519* CONTACT
5520************************************************************************************************/
5521
5522var OrionTicketClosed = false;
5523
5524var OrionTicketCategories = {
5525	'general' : 'General & Questions',
5526	'account' : 'Account & Payments',
5527	'bugs' : 'Bugs & Suggestions',
5528	'development' : 'API & Development',
5529	'links' : 'Invalid & Dead Links',
5530	'privacy' : 'Unsubscribe & GDPR',
5531	'legal' : 'Legal & DMCA',
5532};
5533
5534function orionContactShow(direct, email, category, subject, message)
5535{
5536	if(direct == null) direct = false;
5537	orionModalClose();
5538	var contact = $('.orion_contact');
5539
5540	if(email == null && orionSessionValid()) email = orionSessionEmail();
5541	if(email != null) orionInputValue(contact.find('input[name=email]'), email);
5542	if(category != null) orionSelectorUpdate(contact.find('input[name=category]'), category);
5543	if(subject != null) orionInputValue(contact.find('input[name=subject]'), subject);
5544	if(message != null) orionInputValue(contact.find('textarea[name=message]'), message);
5545
5546	orionModalShow(contact, direct);
5547}
5548
5549function orionContactHide(direct)
5550{
5551	orionModalClose('.orion_contact', direct);
5552}
5553
5554function orionTicketCreate(form, data, callback)
5555{
5556	orionApiRequest({
5557		mode : 'ticket',
5558		action : 'add',
5559		data : data,
5560		success : function(data, type, description, message)
5561		{
5562			orionFormClear(form);
5563			orionFormSuccess(form, message);
5564			if(callback) callback(data);
5565		},
5566		error : function(type, description, message)
5567		{
5568			orionFormError(form, message);
5569			if(callback) callback();
5570		},
5571	});
5572}
5573
5574function orionTicketAdd(form, data, callback)
5575{
5576	orionTicketCreate(form, data, function(data)
5577	{
5578		orionFormClear(form);
5579		orionPanelTicketShow(data);
5580		orionTicketRetrieve(null, orionPanelTicketHistoryUpdate); // Refresh history list.
5581		if(callback) callback();
5582	});
5583}
5584
5585function orionTicketUpdate(form, data, callback)
5586{
5587	if(OrionTicketClosed)
5588	{
5589		orionConfirmation('The ticket is closed and posting a new message will reopen the ticket. Do you want to open the ticket again?', function()
5590		{
5591			orionTicketUpdateSubmit(form, data, callback);
5592		}, function()
5593		{
5594			callback();
5595		});
5596	}
5597	else
5598	{
5599		orionTicketUpdateSubmit(form, data, callback);
5600	}
5601}
5602
5603function orionTicketUpdateSubmit(form, data, callback)
5604{
5605	orionApiRequest({
5606		mode : 'ticket',
5607		action : 'update',
5608		data : data,
5609		success : function(data, type, description, message)
5610		{
5611			if(form) orionFormClear(form);
5612			orionPanelTicketDetails(data);
5613			orionTicketRetrieve(null, orionPanelTicketHistoryUpdate); // Refresh history list.
5614		},
5615		error : function(type, description, message)
5616		{
5617			if(form) orionFormError(form, message);
5618		},
5619		complete : function()
5620		{
5621			if(callback) callback();
5622		},
5623	});
5624}
5625
5626function orionTicketRetrieve(idOrData, callbackSuccess, callbackError, callbackComplete)
5627{
5628	if(idOrData == null || orionStringIs(idOrData))
5629	{
5630		var request = {
5631			mode : 'ticket',
5632			action : 'retrieve',
5633			success : function(data)
5634			{
5635				if(callbackSuccess) callbackSuccess(data);
5636			},
5637			error : function(type, description, message)
5638			{
5639				if(callbackError) callbackError(type, description, message);
5640			},
5641			complete : function()
5642			{
5643				if(callbackComplete) callbackComplete();
5644			},
5645		};
5646		if(idOrData) request.data = {'id' : idOrData};
5647		orionApiRequest(request);
5648	}
5649	else
5650	{
5651		if(callbackSuccess) callbackSuccess(idOrData);
5652		if(callbackComplete) callbackComplete();
5653	}
5654}
5655
5656function orionTickeStatus(data)
5657{
5658	var status = $('.orion_ticket_status');
5659	if(data.status == 'enabled')
5660	{
5661		status.html('');
5662		status.hide();
5663	}
5664	else
5665	{
5666		var message = '';
5667		var time = data.time.end ? orionTimeFormat(data.time.end, OrionFormatDateReadableFull) : null;
5668		if(data.status == 'delayed')
5669		{
5670			if(time) message += 'The support team is currently handling a lot of queries and tickets submitted before ' + time + ' might have a delayed response. We apologize for the inconvenience.';
5671			else message += 'The support team is currently handling a lot of queries and replies might be delayed. We ap
5671ologize for the inconvenience.';
5672		}
5673		else if(data.status == 'disabled')
5674		{
5675			if(time) message += 'The support team is out of office until ' + time + ' and replies might be delayed. We apologize for the inconvenience.';
5676			else message += 'The support team is out of office and replies might be delayed. We apologize for the inconvenience.';
5677		}
5678		if(data.message) message += ' ' + data.message;
5679		status.html(message);
5680		status.attr('class', 'orion_ticket_status orion_ticket_status_' + data.status);
5681		status.show();
5682	}
5683}
5684
5685/************************************************************************************************
5686* UNSUBSCRIBE
5687************************************************************************************************/
5688
5689var OrionUnsubscribeEmail = null;
5690
5691function orionUnsubscribeShow(email, direct)
5692{
5693	OrionUnsubscribeEmail = email;
5694	orionModalShow('.orion_unsubscribe', direct);
5695}
5696
5697function orionUnsubscribeMail()
5698{
5699	orionPanelShow('account', 'profile');
5700}
5701
5702function orionUnsubscribeDelete()
5703{
5704	orionPanelShow('account', 'delete');
5705}
5706
5707function orionUnsubscribeContact()
5708{
5709	orionContactShow(false, OrionUnsubscribeEmail, 'privacy');
5710}
5711
5712/************************************************************************************************
5713* PANEL
5714************************************************************************************************/
5715
5716function orionPanelShow(section, subsection)
5717{
5718	if(orionLink().includes('panel')) orionPanelNavigate(section, subsection);
5719	else orionRedirect(orionLinkBuild(OrionLinkPanel, section, subsection));
5720}
5721
5722function orionPanelPaymentsShow()
5723{
5724	orionPanelShow('payments');
5725}
5726
5727/************************************************************************************************
5728* LOADER
5729************************************************************************************************/
5730
5731function orionLoaderShow()
5732{
5733	$('.orion_loader').show().css('opacity', 1).css('z-index', 10);
5734	$('.orion_page').css('opacity', 0);
5735}
5736
5737function orionLoaderHide()
5738{
5739	// Overlow-y is set to "hidden" in the style sheet, in order to hide the scrollbar on the loading overlay.
5740	// Must be set to "visible", otherwise the orionSequenceScrollTop() for the payment sequence does not correctly scroll.
5741	// Must be set to "visible", otherwise the menu bar of the homepage does not work.
5742	$('html').css('overflow-y', 'visible');
5743
5744	// Must set opacity and z-index for Internet Explorer.
5745	$('.orion_loader').hide().css('opacity', 0).css('z-index', 0);
5746	$('.orion_page').css('opacity', 1);
5747}
5748
5749/************************************************************************************************
5750* HOME
5751************************************************************************************************/
5752
5753var OrionHomeTimeout = null;
5754
5755function orionHomeInitialize()
5756{
5757	$(window).on('scroll', orionHomeUpdate);
5758	$(window).on('touchmove', orionHomeUpdate);
5759}
5760
5761function orionHome()
5762{
5763	orionScrollTop();
5764}
5765
5766function orionHomeUpdate()
5767{
5768	clearTimeout(OrionHomeTimeout);
5769	OrionHomeTimeout = setTimeout(function()
5770	{
5771		var home = $('.orion_home');
5772		var visible = orionVisible(home);
5773		var offset = orionScrollOffset();
5774		if(offset > 20 || offset > 20)
5775		{
5776			if(!visible) home.fadeIn(300);
5777		}
5778		else if(visible) home.fadeOut(300, function(){ home.css('background-color', '#010B14'); });
5779	}, 50);
5780}
5781
5782/************************************************************************************************
5783* ERROR
5784************************************************************************************************/
5785
5786function orionError(code)
5787{
5788	var codes =
5789	{
5790		100 : 'Continue',
5791		101 : 'Switching Protocols',
5792		102 : 'Processing',
5793		103 : 'Early Hints',
5794		200 : 'OK',
5795		201 : 'Created',
5796		202 : 'Accepted',
5797		203 : 'Non-Authoritative Information',
5798		204 : 'No Content',
5799		205 : 'Reset Content',
5800		206 : 'Partial Content',
5801		207 : 'Multi-Status',
5802		208 : 'Already Reported',
5803		226 : 'IM Used',
5804		300 : 'Multiple Choices',
5805		301 : 'Moved Permanently',
5806		302 : 'Found',
5807		303 : 'See Other',
5808		304 : 'Not Modified',
5809		305 : 'Use Proxy',
5810		307 : 'Temporary Redirect',
5811		308 : 'Permanent Redirect',
5812		400 : 'Bad Request',
5813		401 : 'Unauthorized',
5814		402 : 'Payment Required',
5815		403 : 'Forbidden',
5816		404 : 'Not Found',
5817		405 : 'Method Not Allowed',
5818		406 : 'Not Acceptable',
5819		407 : 'Proxy Authentication Required',
5820		408 : 'Request Timeout',
5821		409 : 'Conflict',
5822		410 : 'Gone',
5823		411 : 'Length Required',
5824		412 : 'Precondition Failed',
5825		413 : 'Payload Too Large',
5826		414 : 'URI Too Long',
5827		415 : 'Unsupported Media Type',
5828		416 : 'Range Not Satisfiable',
5829		417 : 'Expectation Failed',
5830		421 : 'Misdirected Request',
5831		422 : 'Unprocessable Entity',
5832		423 : 'Locked',
5833		424 : 'Failed Dependency',
5834		425 : 'Too Early',
5835		426 : 'Upgrade Required',
5836		427 : 'Unassigned',
5837		428 : 'Precondition Required',
5838		429 : 'Too Many Requests',
5839		430 : 'Unassigned',
5840		431 : 'Request Header Fields Too Large',
5841		451 : 'Unavailable For Legal Reasons',
5842		500 : 'Internal Server Error',
5843		501 : 'Not Implemented',
5844		502 : 'Bad Gateway',
5845		503 : 'Service Unavailable',
5846		504 : 'Gateway Timeout',
5847		505 : 'HTTP Version Not Supported',
5848		506 : 'Variant Also Negotiates',
5849		507 : 'Insufficient Storage',
5850		508 : 'Loop Detected',
5851		509 : 'Unassigned',
5852		510 : 'Not Extended',
5853		511 : 'Network Authentication Required',
5854	};
5855
5856	code = parseInt(code);
5857	var found = orionObjectContains(codes, code);
5858	var description = found ? codes[code] : 'Unknown Error';
5859	var error = $('.orion_error');
5860	error.find('.orion_error_code').text('Error' + (found ? (' ' + code) : ''));
5861	error.find('.orion_error_description').text(description);
5862}
5863
5864/************************************************************************************************
5865* DATA
5866************************************************************************************************/
5867
5868var OrionData = {};
5869var OrionDataStatus = {};
5870var OrionDataCurrent = [];
5871var OrionDataTotal = [];
5872var OrionDataCallbacks = [];
5873
5874function orionData(modeOrId, action)
5875{
5876	if(action != null) modeOrId = orionDataId(modeOrId, action);
5877	return OrionData[modeOrId];
5878}
5879
5880function orionDataStatus(modeOrId, action)
5881{
5882	if(action != null) modeOrId = orionDataId(modeOrId, action);
5883	return OrionDataStatus[modeOrId];
5884}
5885
5886function orionDataId(mode, action)
5887{
5888	return mode + '_' + action;
5889}
5890
5891function orionDataCallback(callback)
5892{
5893	OrionDataCallbacks.push(callback);
5894}
5895
5896function orionDataCheck(index, id, mode, action, data, status, callback)
5897{
5898	OrionData[id] = data;
5899	OrionDataStatus[id] = status;
5900	OrionDataCurrent[index]++;
5901	if(OrionDataCurrent[index] >= OrionDataTotal[index])
5902	{
5903		orionSessionUpdate();
5904		if(callback) callback(OrionData[id]);
5905		OrionDataCallbacks.forEach(function(call){ call(); });
5906	}
5907}
5908
5909function orionDataLoad(requests, callback)
5910{
5911	// Check server API call documentation for more info.
5912
5913	// This makes individual API requests.
5914	/*if(requests && !orionArrayIs(requests)) requests = [requests];
5915
5916	var index = OrionDataCurrent.length;
5917	OrionDataCurrent.push(0);
5918	OrionDataTotal.push(requests.length);
5919
5920	$.each(requests, function(i, request)
5921	{
5922		if(orionObjectContains(request, 'asynchronous')) OrionDataTotal[index]--;
5923	});
5924
5925	$.each(requests, function(i, request)
5926	{
5927		var id = null;
5928		if(orionObjectContains(request, 'id')) id = request.id;
5929		else id = orionDataId(request.mode, request.action);
5930
5931		var asynchronous = null;
5932		if(orionObjectContains(request, 'asynchronous')) asynchronous = request.asynchronous;
5933
5934		if(asynchronous)
5935		{
5936			request.success = function(data, type, description, message)
5937			{
5938				OrionData[id] = data;
5939				OrionDataStatus[id] = {status : 'success', type : type, description : description, message : message};
5940				asynchronous(data);
5941			};
5942			request.error = function(type, description, message)
5943			{
5944				OrionDataStatus[id] = {status : 'error', type : type, description : description, message : message};
5945			};
5946		}
5947		else
5948		{
5949			request.success = function(data, type, description, message)
5950			{
5951				orionDataCheck(index, id, request.mode, request.action, data, {status : 'success', type : type, description : description, message : message}, callback);
5952			};
5953			request.error = function(type, description, message)
5954			{
5955				orionDataCheck(index, id, request.mode, request.action, null, {status : 'error', type : type, description : description, message : message}, callback);
5956			};
5957		}
5958
5959		orionApiRequest(request);
5960	});*/
5961
5962	// This makes batch API requests.
5963	var single = false;
5964	if(requests && !orionArrayIs(requests))
5965	{
5966		single = true;
5967		requests = [requests];
5968	}
5969
5970	var index = OrionDataCurrent.length;
5971	OrionDataCurrent.push(0);
5972	OrionDataTotal.push(requests.length);
5973
5974	$.each(requests, function(i, request)
5975	{
5976		if(orionObjectContains(request, 'asynchronous')) OrionDataTotal[index]--;
5977	});
5978
5979	var batch = [];
5980	$.each(requests, function(i, request)
5981	{
5982		var id = null;
5983		if(orionObjectContains(request, 'id')) id = request.id;
5984		else id = orionDataId(request.mode, request.action);
5985
5986		var asynchronous = null;
5987		if(orionObjectContains(request, 'asynchronous')) asynchronous = request.asynchronous;
5988
5989		if(asynchronous)
5990		{
5991			request.success = function(data, type, description, message)
5992			{
5993				OrionData[id] = data;
5994				OrionDataStatus[id] = {status : 'success', type : type, description : description, message : message};
5995				asynchronous(data);
5996			};
5997			request.error = function(type, description, message)
5998			{
5999				OrionDataStatus[id] = {status : 'error', type : type, description : description, message : message};
6000			};
6001			orionApiRequest(request);
6002		}
6003		else if(single)
6004		{
6005			request.success = function(data, type, description, message)
6006			{
6007				orionDataCheck(index, id, request.mode, request.action, data, {status : 'success', type : type, description : description, message : message}, callback);
6008			};
6009			request.error = function(type, description, message)
6010			{
6011				orionDataCheck(index, id, request.mode, request.action, null, {status : 'error', type : type, description : description, message : message}, callback);
6012			};
6013			orionApiRequest(request);
6014		}
6015		else
6016		{
6017			var value = {request : id};
6018			if(orionObjectContains(request, 'mode')) value.mode = request.mode;
6019			if(orionObjectContains(request, 'action')) value.action = request.action;
6020			if(orionObjectContains(request, 'data')) value = orionObjectMerge(value, request.data);
6021			batch.push(value);
6022		}
6023	});
6024
6025	if(batch.length > 0)
6026	{
6027		orionApiRequest({
6028			mode : 'system',
6029			action : 'batch',
6030			data : {request : batch},
6031			success : function(data)
6032			{
6033				$.each(data, function(i, value)
6034				{
6035					orionDataCheck(index, value.request, value.mode, value.action, value.data, {status : value.status, type : value.type, description : value.description, message : value.message}, callback);
6036				});
6037			},
6038			error : function(type, description, message)
6039			{
6040				$.each(batch, function(i, value)
6041				{
6042					orionDataCheck(index, value.request, value.mode, value.action, null, {status : 'error', type : type, description : description, message : message}, callback);
6043				});
6044			},
6045		});
6046	}
6047}
6048
6049/************************************************************************************************
6050* INITIALIZATION
6051************************************************************************************************/
6052
6053function orionInitialize()
6054{
6055	try{ OrionHeader = orionDecrypt($('.orion_footer_header').first().attr('id')); }
6056	catch(e){}
6057
6058	orionReferralInitialize();
6059
6060	orionSwipeInitialize();
6061	orionProgressInitialize();
6062	orionSwitcherInitialize();
6063	orionTabInitialize();
6064	orionSelectorInitialize();
6065	orionCheckInitialize();
6066	orionModalInitialize();
6067	orionWidgetsInitialize();
6068	orionGaugeInitialize();
6069	orionBoxInitialize();
6070	orionFormInitialize();
6071	orionInputInitialize();
6072	orionEmailInitialize();
6073	orionSequenceInitialize();
6074	orionImageInitialize();
6075	orionTimelineInitialize();
6076	orionTableInitialize();
6077	orionHomeInitialize();
6078
6079	orionBrowserDetect();
6080
6081	orionPage(orionLinkPart(0));
6082	if(orionLinkPart(0) == 'error')
6083	{
6084		orionError(orionLinkPart(0, true));
6085	}
6086	else if(orionLinkPart(0) == 'referral')
6087	{
6088		// This should never happen, since the cookie will be set server-side and then redirect to index.
6089		orionReferralUpdate(orionLinkPart(0, true));
6090	}
6091
6092	// Must happen AFTER the page is show, otherwise it does not initialize properly.
6093	orionCarouselInitialize();
6094	setTimeout(function(){ orionPackagesInitialize(); }, 500);
6095}
6096
6097$(document).ready(function()
6098{
6099	try
6100	{
6101		orionInitialize();
6102	}
6103	catch(e)
6104	{
6105		console.log(e);
6106	}
6107});

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.