PageSourceSearch

https://julianorah.com/js/custom.260701080142.js

js julianorah.com collected 2026-10-07 16:03:47 UTC 12,955 bytes, 485 lines download raw bytes

1/* JS for preset "Menu V2" */
2(function() {
3	$(function() {
4		$('.menu-wrapper').each(function() {
5			initMenu($(this))
6		});
7	});
8
9	// Make :active pseudo classes work on iOS
10	document.addEventListener("touchstart", function() {}, false);
11
12	const initMenu = function($menuWrapper) {
13		const $body = $('body');
14		const $menu = $('.ed-menu', $menuWrapper);
15		const $menuLinks = $('a', $menu);
16		const $menuTrigger = $('.menu-trigger', $menuWrapper);
17		const $banner = $('.banner').first();
18
19		const smoothScrollOffset = 20;
20		
21		// Set aria attributes
22		$menuTrigger.attr({
23				'aria-expanded': 'false',
24				'aria-controls': $menu.attr('id'),
25		});
26
27		toggleClassOnClick($body.add($menu), $menuTrigger, null, 'open open-menu'); // Keep open on $menu for backward compatibility
28		activateSmoothScroll($menuLinks.add($('.scroll a')), smoothScrollOffset);
29		addClassOnVisibleLinkTargets($menuLinks, 'active', 2 / 3);
30		handleSticky($menuWrapper, 'sticky', $banner);
31	};
32
33	/**
34	 * Observe element's height changes and reload the initMenu() function
35	 *
36	 * @param {HTMLElement} elm Element to observe
37	 * @param {function} callback to call when elmement's height changed
38	 */
39	const observeHeightChange = function(elm, callback) {
40		if (!('ResizeObserver' in window) || elm == null) return;
41
42		const ro = new ResizeObserver(callback);
43		ro.observe(elm);
44	}
45
46	/**
47	 * Toggles class on a target when a trigger is clicked
48	 * 
49	 * @param {jQuery} $target The target to apply the CSS class to
50	 * @param {jQuery} $trigger The Trigger
51	 * @param {jQuery} $closeTrigger Optional close trigger
52	 * @param {string} cssClass CSS Class to toggle on the target
53	 */
54	const toggleClassOnClick = function($target, $trigger, $closeTrigger, cssClass) {
55
56		// Reset in case class "open" was saved accidentally
57		$target.removeClass(cssClass);
58		$trigger.removeClass(cssClass).attr('aria-expanded', 'false');
59
60		// Click on trigger toggles class "open"
61		$trigger.off('.toggle').on('click.toggle', function() {
62			const isExpanded = $(this).attr('aria-expanded') === 'true';
63			const $menu = $target.filter('.ed-menu');
64			$(this).toggleClass(cssClass).attr('aria-expanded', !isExpanded);
65			$target.toggleClass(cssClass);
66
67			// Set focus to menu on open
68			if (!isExpanded) {
69				$menu.attr('tabindex', '0');
70				const focusMenu = () => {
71					$menu.focus();
72					$menu.off('transitionend', focusMenu);
73				};
74				$menu.on('transitionend', focusMenu);
75			}
76
77			$menu[0].addEventListener(
78				'blur',
79				function() {
80					$menu[0].removeAttribute('tabindex');
81				}, {
82					once: true
83				}
84			);
85		});
86
87		// Close target when link inside is clicked
88		$target.find('a').click(function() {
89			$target.removeClass(cssClass);
90			$trigger.removeClass(cssClass).attr('aria-expanded', 'false');
91		});
92
93		if (!$closeTrigger || !$closeTrigger.length) {
94			return;
95		}
96
97		$closeTrigger.click(function() {
98			$target.removeClass(cssClass);
99			$trigger.removeClass(cssClass).attr('aria-expanded', 'false');
100		});
101	};
102
103	/**
104	 * Smooth scroll to link targets
105	 * 
106	 * @param {jQuery} $scrollLinks The links
107	 * @param {jQuery} scrollOffset Offset to subtract from the scroll target position (e.g. for fixed positioned elements like a menu)
108	 */
109	const activateSmoothScroll = function($scrollLinks, scrollOffset) {
110		if (typeof scrollOffset === 'undefined') {
111			scrollOffset = 0;
112		}
113
114		const determineTarget = function($trigger, hash) {
115			if (hash == '#!next') {
116				return $trigger.closest('.ed-element').next();
117			}
118			// Prevent invalid selectors
119			try {
120				return $(hash);
121			} catch (e) {
122				return $();
123			}
124		}
125
126		$scrollLinks.click(function(e) {
127			const $target = determineTarget($(this), this.hash);
128			if (!$target.length) return;
129			e.preventDefault();
130
131			viewport.scrollTo($target, 'top', 500, 0);
132
133		});
134	};
135
136	/**
137	 * We are using the fill property on an element to pass user's choices from CSS to JavaScript
138	 * 
139	 * @param {jQuery} $element
140	 */
141	const getStickyMode = function($element) {
142		const fillValue = getComputedStyle($element[0]).fill;
143
144		return fillValue === 'rgb(255, 0, 0)' ?
145			'sticky_banner' :
146			fillValue === 'rgb(0, 255, 0)' ?
147			'sticky_menu' :
148			fillValue === 'rgb(0, 0, 255)' ?
149			'sticky_instant' :
150			fillValue === 'rgb(255, 255, 255)' ?
151			'sticky_reverse' :
152			'sticky_none';
153	};
154
155	/**
156	 * Adds a class to an element when not currently visible
157	 * 
158	 * @param {jQuery} $element The element to handle stickyness for
159	 * @param {string} cssClass The actual CSS class to be applied to the element when it's above a certain scroll position
160	 * @param {jQuery} $banner A banner to reference the scroll position to
161	 */
162	const handleSticky = function($element, cssClass, $banner) {
163		let triggerPos = 0,
164			offset = 0;
165		let menuWrapperHeight = $element.outerHeight();
166		let mode;
167		let prevScroll = 0;
168		$element.removeClass(cssClass);
169		
170		const toggleSpacer = function(toggle) {
171			document.body.style.setProperty('--spacer-height', toggle ? menuWrapperHeight + 'px' : '');
172		};
173
174		const handleScroll = function() {
175			if (!$element.length || mode === 'sticky_none') return;
176			//if (!$element.length || mode === 'sticky_none' || mode === 'sticky_instant') return;
177
178			const isReverse = mode === 'sticky_reverse',
179				curScroll = viewport.getScrollTop();
180
181			if (triggerPos <= curScroll && (!isReverse || prevScroll > curScroll)) {
182				$element.addClass(cssClass);
183				toggleSpacer(true);
184			} else {
185				$element.removeClass(cssClass);
186				toggleSpacer(false);
187			}
188
189			prevScroll = curScroll;
190		};
191		
192		const updateOffset = function() {
193			mode = getStickyMode($element);
194			menuWrapperHeight = $element.outerHeight();
195			if (!$element.hasClass(cssClass)) {
196				offset = $element.offset().top;
197			}
198			if (mode === 'sticky_banner' && !$banner.length) {
199				mode = 'sticky_menu';
200			}
201			if (mode === 'sticky_banner') {
202				triggerPos = $banner.offset().top + ($banner.length ? $banner.outerHeight() : $element.outerHeight());
203			}
204			if (mode === 'sticky_menu' || mode === 'sticky_reverse') {
205				triggerPos = offset + $element.outerHeight();
206			}
207			if (mode === 'sticky_instant') {
208				triggerPos = offset;
209			}
210			
211			handleScroll();
212		}
213		
214		viewport.observe('resize', updateOffset);
215		viewport.observe('animation.end', updateOffset);
216		observeHeightChange($element[0], updateOffset);
217		updateOffset();
218		
219		viewport.observe('scroll', handleScroll);
220		handleScroll();
221	};
222
223	/**
224	 * Adds a class to links whose target is currently inside the viewport
225	 * 
226	 * @param {jQuery} $links Link(s) to be observed
227	 * @param {string} cssClass CSS Class to be applied
228	 * @param {float} sectionViewportRatio Ratio by which the target should be within the viewport
229	 */
230	const addClassOnVisibleLinkTargets = function($links, cssClass, sectionViewportRatio) {
231		if (typeof sectionViewportRatio === 'undefined') {
232			sectionViewportRatio = 1 / 2;
233		}
234
235		const menuTargets = [];
236		const activeLink = $links.filter('.active');
237
238		const links = $links.filter(function() {
239			// Prevent invalid selectors
240			let $target;
241			try {
242				$target = $(this.hash);
243			} catch (e) {
244				return false;
245			}
246			if (!this.hash || !$target.length) {
247				return false;
248			}
249
250			// Cache offset position to improve performance (update on resize)		
251			const updateOffset = function() {
252				$target.data('offset', $target.offset().top);
253			};
254
255			viewport.observe('resize', updateOffset);
256			viewport.observe('animation.end', updateOffset);
257			updateOffset();
258
259			menuTargets.push($target);
260			return true;
261		});
262
263		// No hash links found, so don't handle it at all
264		if (!links.length) {
265			return;
266		}
267
268		const checkVisibility = function() {
269			$links.removeClass('active');
270
271			// Check section position reversely
272			for (let i = menuTargets.length - 1; i >= 0; i--) {
273				const desiredScrollPosition = menuTargets[i].data('offset') - viewport.getHeight() * (1 - sectionViewportRatio);
274				if (viewport.getScrollTop() >= desiredScrollPosition && menuTargets[i][0].offsetParent !== null) {
275					links.eq(i).addClass(cssClass);
276					return;
277				}
278			}
279
280			// Fallback to originally active item
281			activeLink.addClass(cssClass);
282		};
283
284		viewport.observe('scroll', checkVisibility);
285		checkVisibility();
286	};
287})();
288/* End JS for preset "Menu V2" */
289
290/* JS for preset "Content Slider" */
291$(function() {
292	if (document.body.classList.contains('edit') || document.body.classList.contains('preview')) {
293		return;
294	}
295
296	viewport.promise('api.slick.ready', function() {
297		viewport.jQuery('.content-slider-wrap > .inner').slick({
298			autoplay: true,
299			arrows: true,
300			prevArrow: '<button type="button" data-role="none" class="slick-prev" tabindex="0" role="button"></button>',
301			nextArrow: '<button type="button" data-role="none" class="slick-next" tabindex="0" role="button"></button>',
302			speed: 300,
303			autoplaySpeed: 3000,
304			dots: true,
305			adaptiveHeight: true,
306			fade: false
307		});
308	}).requireSlick();
309});
310
311/* End JS for preset "Content Slider" */
312
313/* JS for preset "Countdown V3" */
314$(function() {
315	var slice = Array.prototype.slice;
316	var valid = true;
317
318	var ready = function(callback) {
319		var fn = function() {
320			if (document.body.classList.contains('edit')) {
321				return;
322			}
323			callback();
324		};
325
326		if (window.readyState !== 'loading') {
327			fn();
328			return;
329		}
330
331		document.addEventListener('DOMContentLoaded', fn);
332	};
333
334	var countdown = function(date, tick) {
335		var now = new Date().getTime(),
336			running = false,
337			days = 0,
338			hours = 0,
339			minutes = 0,
340			seconds,
341			interval;
342
343		var updateCounter = function() {
344			if (!running) return;
345
346			now = new Date().getTime();
347			seconds = Math.round((date - now) / 1000);
348
349			if (seconds > 86400) {
350				days = Math.floor(seconds / 86400);
351				seconds %= 86400;
352			}
353
354			if (seconds > 3600) {
355				hours = Math.floor(seconds / 3600);
356				seconds %= 3600;
357			}
358
359			if (seconds > 60) {
360				minutes = Math.floor(seconds / 60);
361				seconds %= 60;
362			}
363
364			tick(days, hours, minutes, seconds);
365		};
366
367		if (isNaN(Date.parse(date))) {
368			date = date.replace(/\-/g, '/');
369		}
370
371		if (isNaN(Date.parse(date))) {
372			date = date.replace(/\s/, 'T');
373		}
374
375		date = new Date(date).getTime();
376
377		if (now >= date) {
378			valid = false;
379			return;
380		}
381
382		tick = tick || (function() {});
383
384		return {
385			start: function() {
386				interval = window.setInterval(updateCounter, 1000);
387				running = true;
388				updateCounter();
389			},
390			stop: function() {
391				if (interval) window.clearInterval(interval);
392				interval = undefined;
393				running = false;
394			}
395		};
396	};
397
398	var writeCountdown = function(element, days, hours, minutes, seconds) {
399		var daysElm = element.querySelector(".countdown-days"),
400			hoursElm = element.querySelector('.countdown-hours'),
401			minutesElm = element.querySelector('.countdown-minutes'),
402			secondsElm = element.querySelector('.countdown-seconds');
403
404		if (daysElm) daysElm.innerHTML = days;
405		if (hoursElm) hoursElm.innerHTML = hours;
406		if (minutesElm) minutesElm.innerHTML = minutes;
407		if (secondsElm) secondsElm.innerHTML = seconds;
408	};
409
410	var buildCountdown = function(element) {
411		var dataContent = window.getComputedStyle(element, ':before').content.slice(1, -1);
412		var date = dataContent;
413
414
415		element.countdown = countdown(date, function(days, hours, minutes, seconds) {
416			writeCountdown(
417				element, parseInt(days),
418				("0" + parseInt(hours)).slice(-2),
419				("0" + parseInt(minutes)).slice(-2),
420				("0" + parseInt(seconds)).slice(-2)
421			);
422		});
423
424		if (valid) {
425			element.countdown.start();
426		}
427	};
428
429	var destroyCountdown = function() {
430		var instances = slice.call(document.querySelectorAll('.countdown-instance')),
431			len = instances.length,
432			i = 0,
433			element;
434		for (; i < len; i++) {
435			element = instances[i];
436			writeCountdown(element, "0", "0", "0", "0");
437			if (element.countdown) {
438				element.countdown.stop();
439			}
440		}
441	};
442
443	var observeInstances = function() {
444		const observer = new MutationObserver((mutations) => {
445			mutations.forEach((mutation) => {
446				mutation.addedNodes.forEach((node) => {
447					if (node.nodeType === 1 && node.classList.contains('countdown-instance')) {
448						if (document.body.classList.contains('preview') || !document.body.classList.contains('edit-full')) {
449							buildCountdown(node);
450						} else {
451							destroyCountdown();
452						}
453					}
454				});
455			});
456		});
457
458		observer.observe(document.body, {
459			childList: true,
460			subtree: true
461		});
462	};
463
464	var preview = false;
465	var listener = function() {
466		if (valid) {
467			if (!preview && document.body.classList.contains('preview') || !document.body.classList.contains('edit-full')) {
468				var instances = slice.call(document.querySelectorAll('.countdown-instance'));
469				instances.forEach(buildCountdown);
470				preview = true;
471			} else if (preview && !document.body.classList.contains('preview')) {
472				destroyCountdown();
473				preview = false;
474			}
475		}
476
477		requestAnimationFrame(listener);
478	};
479
480	requestAnimationFrame(listener);
481	ready(function() {
482		observeInstances();
483	});
484});
485/* End JS for preset "Countdown V3" */

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.