var About_Pop=About_Pop||{};
About_Pop.Context=About_Pop.Context||{};
About_Pop.Context.General=class General {
static isInitialized=false;
static NAMESPACE=About_Pop.NAMESPACE;
static CONFIG=About_Pop.CONFIG;
constructor(){
this.constructor.initiator();
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Context=About_Pop.Context||{};
About_Pop.Context.Frontend=class Frontend {
static isInitialized=false;
constructor(){
this.constructor.initiator();
this._linkClickListener=event=> {
const href=event.target.closest('[href]' )?.getAttribute('href' )?.trim();
if(! href||event.target.closest('.lang-item')||event.target.closest('[target="_blank"]') ) return;
if(! this.doLink(href) ) return;
event.preventDefault();
}
this._overlayLinkClickListener=event=> {
const href=event.target.closest('[href]' )?.getAttribute('href' )?.trim();
if(! href) return;
event.preventDefault();
this.doOverlay(href);
}
this._popstateListener=event=> {
if(event.state?.isOverlay){
this.doOverlay(window.location.href, false);
}else{
this.doLink(window.location.href, false);
}}
this.build()
.then(()=> {
About_Pop.whenIsReady
.then(()=> {
this.in()
.then(()=> setTimeout(()=> this.scrollTo(window.location.href, false, false), 50) );
});
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
const body=document.querySelector('body');
this.main=document.querySelector('main');
this.introElement=document.querySelector('#front-page__intro');
this.marquees=[];
for(const marqueeElement of document.querySelectorAll('marquee-wrap') ) this.marquees.push(new MarqueeJS(marqueeElement, {
isScrollable: true,
pixelsPerSecond: 100
}) );
About_Pop.whenIsLoaded.then(()=> {
for(const Marquee of this.marquees){
Marquee.calculateDimensions();
Marquee.buildLoop();
}});
if(About_Pop.Component?.Header) this.Header=new About_Pop.Component.Header();
if(About_Pop.Component?.Accessibility) this.Accessibility=new About_Pop.Component.Accessibility();
if(About_Pop.View?.Program) this.Program=new About_Pop.View.Program();
if(About_Pop.View?.Archive) this.Archive=new About_Pop.View.Archive();
if(About_Pop.View?.Timetable) this.Timetable=new About_Pop.View.Timetable();
if(About_Pop.View?.Venues) this.Venues=new About_Pop.View.Venues();
this.buildLinks();
window.addEventListener('popstate', this._popstateListener);
body.whenMediaIsLoaded()
.then(()=> body.classList.add('media-is-loaded') );
return resolve();
});
return promise;
}
buildLinks(){
this.setExternalLinks();
this.internalLinkElements=document.querySelectorAll('a[href^="' + About_Pop.HOME_URL + '"]:not([data-fetch="overlay"])');
for(const internalLinkElement of this.internalLinkElements) internalLinkElement.addEventListener('click', this._linkClickListener);
this.overlayLinkElements=document.querySelectorAll('a[href^="' + About_Pop.HOME_URL + '"][data-fetch="overlay"]');
for(const overlayLinkElement of this.overlayLinkElements) overlayLinkElement.addEventListener('click', this._overlayLinkClickListener);
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
this.destroyLinks();
for(const Marquee of this.marquees) Marquee.destroy();
if(this.Header) this.Header.destroy();
if(this.Accessibility) this.Accessibility.destroy();
if(this.Program) this.Program.destroy();
if(this.Archive) this.Archive.destroy();
if(this.Timetable) this.Timetable.destroy();
if(this.Venues) this.Venues.destroy();
window.removeEventListener('popstate', this._popstateListener);
return resolve();
});
return promise;
}
destroyLinks(){
for(const internalLinkElement of this.internalLinkElements) internalLinkElement.removeEventListener('click', this._linkClickListener);
for(const overlayLinkElement of this.overlayLinkElements) overlayLinkElement.removeEventListener('click', this._overlayLinkClickListener);
}
setExternalLinks(){
for(const externalLinkElement of document.querySelectorAll('a[href^="http"]:not([href=""]):not([href^="' + About_Pop.HOME_URL + '"])') ){
externalLinkElement.setAttribute('target', '_blank');
}}
doLink(link, doesPush=true){
if(this.Overlay){
this.Overlay.close();
this.Overlay=null;
return true;
}
if(this.scrollTo(link, doesPush)!==null) return true;
window.location.href=link;
return true;
if(link.indexOf(About_Pop.HOME_URL)!==-1){
if(link.slice(-1)!=='/') link +='/';
let waitFors=[]
if(this.Header) waitFors.push(this.Header.close());
this.out();
About_Pop.Extension.JavaScript.Fetch.fetchJSONEndpoint(link)
.then(data=> {
if(! data||! data.View||! About_Pop?.Helper?.Template) return reject();
const body=document.querySelector('body');
const title=document.querySelector('head title');
const Template=new About_Pop.Helper.Template(data.View._template_name);
const templateFragment=Template.get(data.View);
Promise.all(waitFors)
.then(()=> {
this.destroy();
body.setAttribute('class', data.class + ' is-ready is-loaded');
document.querySelector('html').removeAttribute('data-program-type');
title.innerHTML=data.title;
if(this.introElement) this.introElement.remove();
if(this.main){
this.main.replaceWith(templateFragment);
}else{
const headerElement=document.querySelector('header#header');
if(! headerElement) return reject();
headerElement.after(templateFragment);
}
if(data.language_links){
for(const language of data.language_links){
const languageLinkElements=document.querySelectorAll('.lang-item-' + language.slug + ' a');
if(! languageLinkElements||languageLinkElements.length===0) continue;
for(const languageLinkElement of languageLinkElements) languageLinkElement.setAttribute('href', language.link);
}}
this.build();
document.scrollingElement.scrollTop=0;
if(doesPush) history.pushState({}, '', link);
setTimeout(()=> {
this.in()
setTimeout(()=> this.scrollTo(link, false, false), 250);
}, 150);
});
})
.catch(error=> {
console.log(error);
history.replaceState({}, '', window.location.href);
window.location.href=link;
});
return true;
}
return false;
}
doOverlay(link, doesPush=true){
const promise=new Promise(( resolve, reject)=> {
if(link.slice(-1)!=='/') link +='/';
let waitFors=[]
if(this.Header) waitFors.push(this.Header.close());
if(this.Overlay) waitFors.push(this.Overlay.close());
const existingOverlayElement=document.querySelector('.overlay[data-link="' + link + '"]');
if(existingOverlayElement){
this.Overlay=new About_Pop.Component.Overlay(existingOverlayElement);
if(doesPush) history.pushState({ isOverlay: true }, '', link);
setTimeout(()=> this.Overlay.open(), 100);
return resolve();
}
About_Pop.Extension.JavaScript.Fetch.fetchJSONEndpoint(link)
.then(data=> {
if(! data||! data.View||! About_Pop?.Helper?.Template) return reject();
const body=document.querySelector('body');
const title=document.querySelector('head title');
const Template=new About_Pop.Helper.Template(data.View._template_name);
data.View.back_link='javascript:history.back();';
const templateFragment=Template.get(data.View);
const mainElement=templateFragment?.querySelector('main');
if(! mainElement) return reject();
const overlayTemplate=new About_Pop.Helper.Template('Component/Overlay/');
const overlayTemplateFragment=overlayTemplate.get({
link: link,
content: mainElement.innerHTML,
back_link: 'javascript:history.back();'
});
const overlayElement=overlayTemplateFragment?.querySelector('.overlay');
if(! overlayElement) return reject();
body.appendChild(overlayElement);
this.Overlay=new About_Pop.Component.Overlay(overlayElement);
this.destroyLinks();
this.buildLinks();
if(doesPush) history.pushState({ isOverlay: true }, '', link);
setTimeout(()=> this.Overlay.open(), 100);
return resolve();
})
.catch(error=> {
console.log(error);
history.replaceState({}, '', window.location.href);
window.location.href=link;
});
});
return promise;
}
scrollTo(link, doesPush=true, doesCloseHeader=true){
const targetSection=document.querySelector('.page[data-link="' + link + '"], .event[data-link="' + link + '"], .venue[data-link="' + link + '"]');
if(! targetSection) return null
const bodyElement=document.querySelector('body');
const paddingVertical=parseFloat(getComputedStyle(bodyElement).getPropertyValue('--grid-wrap-padding-vertical')||0);
const scrollPromise=document.documentElement.animateScrollTo(targetSection, {
duration: 1500,
offset:
(document.querySelector('#header' )?.clientHeight||0)
+(window.innerWidth >=700 
?(document.querySelector('#venues__switch-wrap' )?.clientHeight||paddingVertical)
: paddingVertical)
});
if(this.Header&&doesCloseHeader) this.Header.close();
if(doesPush) window.history.pushState({}, '', link);
return scrollPromise;
}
in(){
const promise=new Promise(( resolve, reject)=> {
const body=document.querySelector('body');
if(body.classList.contains('in') ) return resolve();
body.classList.add('in');
if(this.main&&this.main.hasTransitionEvents()){
this.main.addEventListener('transitionend', event=> resolve(), { once: true });
}else{
return resolve();
}});
return promise;
}
out(){
const promise=new Promise(( resolve, reject)=> {
const body=document.querySelector('body');
if(! body.classList.contains('in') ) return resolve();
body.classList.remove('in');
if(this.main&&this.main.hasTransitionEvents()){
this.main.addEventListener('transitionend', event=> resolve(), { once: true });
}else{
return resolve();
}});
return promise;
}};
class ScrollJS {
static scrollableElements=[];
static interaction=null;
static offset={ top: 0, left: 0 };
static overflow={ top: 0, left: 0 };
static _touchstart=event=> {
this.interaction={
start: {
x: event.touches[0].clientX,
y: event.touches[0].clientY
}}
}
static _touchmove=event=> {
if(! this.interaction) return;
this.interaction.distance={
x: this.interaction.start.x - event.touches[0].clientX,
y: this.interaction.start.y - event.touches[0].clientY
}
this.interaction.delta={
x: Math.abs(this.interaction.distance.x),
y: Math.abs(this.interaction.distance.y)
}
this._preventScroll(event, { x: this.interaction.distance.x, y: this.interaction.distance.y }, event.touches[0].target);
}
static _touchend=event=> {
this.interaction=null;
}
static _wheel=event=> {
this._preventScroll(event, { x: event.deltaX, y: event.deltaY }, event.target);
}
static animate(args={}){
let {
unit,
unitsPerSecond,
from,
to: to=0,
duration: duration=500,
animation
}=args;
const {
element: element=this,
property,
easing: easing=x => x < 0.5 ? 8 * x * x * x * x:1 - Math.pow(-2 * x + 2, 4) / 2
}=args;
if(! animation){
let distance, fromUnit, toUnit;
[ , from, fromUnit ]=typeof from==='string'&&from.match(/(?<from>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, from, '' ];
[ , to, toUnit ]=typeof to==='string'&&to.match(/(?<to>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, to, '' ];
from=parseFloat(from);
to=parseFloat(to);
unit=unit||toUnit||fromUnit||null;
switch(property){
case 'scrollLeft':
case 'scrollTop':
unit=null;
from=element[ property ];
distance=to - from;
break;
default:
let computedStyle, computedStyleFrom, computedFrom, computedUnit;
computedStyle=getComputedStyle(element);
computedStyleFrom=computedStyle[ property ]!==undefined ? computedStyle[ property ]:0;
[ , computedFrom, computedUnit ]=typeof computedStyleFrom==='string'&&computedStyleFrom.match(/(?<from>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, 0, '' ];
unit=unit||computedUnit||null;
from=from||(parseFloat(computedFrom)===parseFloat(computedFrom) ? parseFloat(computedFrom):0);
distance=to - from;
break;
}
if(unitsPerSecond){
unitsPerSecond=parseFloat(unitsPerSecond)===parseFloat(unitsPerSecond) ? parseFloat(unitsPerSecond):100;
duration=Math.abs(distance) / Math.abs(unitsPerSecond) * 1000;
}
animation={
property,
unit,
from,
to,
distance,
duration: duration,
starttime: Date.now(),
runtime: 0,
progress: 0,
value: 0,
request: null,
isCanceled: false,
whenCanceled: null,
_resolveCanceled: null,
_rejectCanceled: null,
isCompleted: false,
whenCompleted: null,
_resolveCompleted: null,
_rejectCompleted: null,
hasEnded: false,
whenEnded: null,
_resolveEnded: null,
_rejectEnded: null,
cancel: ()=> {
animation._rejectCompleted();
animation._resolveCanceled();
animation._resolveEnded();
},
complete: ()=> {
animation._rejectCanceled();
animation._resolveCompleted();
animation._resolveEnded();
}}
animation.whenCanceled=new Promise(( resolve, reject)=> { animation._resolveCanceled=resolve; animation._rejectCanceled=reject; });
animation.whenCompleted=new Promise(( resolve, reject)=> { animation._resolveCompleted=resolve; animation._rejectCompleted=reject; });
animation.whenEnded=new Promise(( resolve, reject)=> { animation._resolveEnded=resolve; animation._rejectEnded=reject; });
animation.whenCanceled.then(()=> animation.isCanceled=true).catch(()=> {});
animation.whenCompleted.then(()=> animation.isCompleted=true).catch(()=> {});
animation.whenEnded
.then(()=> {
animation.hasEnded=true;
cancelAnimationFrame(animation.request);
})
.catch(()=> {});
}
animation.runtime=Date.now() - animation.starttime;
animation.progress=animation.duration <=0||animation.isCompleted ? 1:Math.min(1, Math.max(0, animation.runtime / animation.duration) );
animation.value=animation.from + easing(animation.progress) * animation.distance;
switch(animation.property){
case 'scrollLeft':
case 'scrollTop':
element[ animation.property ]=animation.value;
break;
default:
element.style.setProperty(animation.property, animation.value +(animation.unit||'') );
break;
}
if(animation.progress >=1) animation.complete();
if(! animation.hasEnded) animation.request=requestAnimationFrame(timestamp=> this.animate({ element, easing, animation }) );
return animation;
}
static _preventScroll(event, distance, targetElement){
let closestScrollableElement=null;
const direction={
x: distance.x===0 ? false:(distance.x < 0 ? 'left':'right'),
y: distance.y===0 ? false:(distance.y < 0 ? 'up':'down')
};
for(const scrollableElement of this.scrollableElements){
if(scrollableElement!==targetElement&&! scrollableElement.contains(targetElement) ) continue;
closestScrollableElement=scrollableElement;
break;
}
if(! closestScrollableElement){
event.preventDefault();
return;
}
const scrollTop=closestScrollableElement.scrollTop;
const scrollLeft=closestScrollableElement.scrollLeft;
const scrollOverflow={
x: closestScrollableElement.scrollWidth - closestScrollableElement.clientWidth,
y: closestScrollableElement.scrollHeight - closestScrollableElement.clientHeight
};
if(scrollOverflow.x <=0) direction.x=null;
if(scrollOverflow.y <=0) direction.y=null;
const scrollProgress={
x: scrollLeft / scrollOverflow.x,
y: scrollTop / scrollOverflow.y
};
const hasReachedLeft=scrollLeft <=0||scrollProgress.x <=0;
const hasReachedRight=scrollOverflow.x <=0||scrollProgress.x >=0;
const hasReachedTop=scrollTop <=0||scrollProgress.y <=0;
const hasReachedBottom=scrollOverflow.y <=0||scrollProgress.y >=1;
if((direction.x==='right'&&hasReachedRight)
||(direction.x==='left'&&hasReachedLeft)
||(direction.y==='down'&&hasReachedBottom)
||(direction.y==='up'&&hasReachedTop)
||(direction.x===null&&direction.y===null)
){
event.preventDefault();
return;
}}
static getOffset(){
return this.offset;
}
static setOffset(offset){
switch(typeof offset){
case 'number':
case 'string':
this.offset.top=this.offset.left=parseFloat(offset)===parseFloat(offset) ? parseFloat(offset):0;
break;
case 'object':
if(offset.left) this.offset.left=offset.left;
if(offset.top) this.offset.top=offset.top;
break;
}
offset=this.getOffset();
return offset;
}
static resetOffset(){
return this.setOffset(0);
}
static getOverflow(){
return this.overflow;
}
static setOverflow(overflow){
switch(typeof overflow){
case 'number':
case 'string':
this.overflow.top=this.overflow.left=parseFloat(overflow)===parseFloat(overflow) ? parseFloat(overflow):0;
break;
case 'object':
if(overflow.left) this.overflow.left=overflow.left;
if(overflow.top) this.overflow.top=overflow.top;
break;
}
overflow=this.getOverflow();
return overflow;
}
static resetOverflow(){
return this.setOverflow(0);
}
static enable(scrollableElements=[]){
this.scrollableElements=[];
document.querySelector('body').classList.remove('do-prevent-scroll');
document.documentElement.removeEventListener('touchstart', this._touchstart);
document.documentElement.removeEventListener('touchmove', this._touchmove, { passive: false });
document.documentElement.removeEventListener('touchend', this._touchend);
document.documentElement.removeEventListener('wheel', this._wheel, { passive: false });
}
static disable(scrollableElements=[]){
this.enable();
this.scrollableElements = ! scrollableElements||! scrollableElements.length ? []:scrollableElements;
document.querySelector('body').classList.add('do-prevent-scroll');
document.documentElement.addEventListener('touchstart', this._touchstart);
document.documentElement.addEventListener('touchmove', this._touchmove, { passive: false });
document.documentElement.addEventListener('touchend', this._touchend);
document.documentElement.addEventListener('wheel', this._wheel, { passive: false });
}}
HTMLElement.prototype.animateScrollTo=function(input, args={}){
const {
duration: duration=500,
unitsPerSecond,
isCancelable: isCancelable=true,
doesAddOffset: doesAddOffset=true,
}=args;
let { offset, overflow }=args;
const current={ left: this.scrollLeft, top: this.scrollTop };
let to={ left: this.scrollLeft, top: this.scrollTop };
switch(typeof input){
case 'number':
case 'string':
to.top=parseFloat(input)===parseFloat(input) ? parseFloat(input):0;
break;
case 'object':
if(typeof input.getBoundingClientRect==='function'){
const bounds=input.getBoundingClientRect();
to.left=current.left + bounds.left;
to.top=current.top + bounds.top;
}else{
if(input.left!==undefined) to.left=input.left;
if(input.top!==undefined) to.top=input.top;
}
break;
default:
return;
break;
}
switch(typeof offset){
case 'number':
case 'string':
offset={
left: parseFloat(offset)===parseFloat(offset) ? parseFloat(offset):0,
top: parseFloat(offset)===parseFloat(offset) ? parseFloat(offset):0
}
break;
case 'object':
offset.left=offset.left ?? ScrollJS.offset.left ?? 0;
offset.top=offset.top ?? ScrollJS.offset.top ?? 0;
break;
default:
offset={
left: ScrollJS.offset.left ?? 0,
top: ScrollJS.offset.top ?? 0
}
break;
}
switch(typeof overflow){
case 'number':
case 'string':
overflow={
left: parseFloat(overflow)===parseFloat(overflow) ? parseFloat(overflow):0,
top: parseFloat(overflow)===parseFloat(overflow) ? parseFloat(overflow):0
}
break;
case 'object':
overflow.left=overflow.left ?? ScrollJS.overflow.left ?? 0;
overflow.top=overflow.top ?? ScrollJS.overflow.top ?? 0;
break;
default:
overflow={
left: ScrollJS.overflow.left ?? 0,
top: ScrollJS.overflow.top ?? 0
}
break;
}
const scrollOverflow={
left: this.scrollWidth - this.clientWidth - overflow.left,
top: this.scrollHeight - this.clientHeight - overflow.top
}
for(const t in to){
let value=to[ t ];
switch(typeof value){
case 'number':
break;
case 'string':
const operator=value.charAt(0);
value=parseFloat(value);
switch(operator){
case '+':
case '-':
to[ t ]=current[ t ] + value;
break;
default:
to[ t ]=value;
break;
}
break;
default:
to[ t ]=current[ t ];
break;
}
to[ t ]=Math.max(0, Math.min(scrollOverflow[ t ], to[ t ]) );
}
if(doesAddOffset){
to.left -=offset.left;
to.top -=offset.top;
}
const promise=new Promise(( resolve, reject)=> {
const leftAnimation=ScrollJS.animate({ element: this, property: 'scrollLeft', to: to.left, duration, unitsPerSecond });
const topAnimation=ScrollJS.animate({ element: this, property: 'scrollTop', to: to.top, duration, unitsPerSecond });
if(isCancelable){
const cancelAnimations=()=> {
leftAnimation.cancel();
topAnimation.cancel();
this.removeEventListener('wheel', cancelAnimations, { once: true });
this.removeEventListener('mousedown', cancelAnimations, { once: true });
this.removeEventListener('touchstart', cancelAnimations, { once: true });
}
this.addEventListener('wheel', cancelAnimations, { once: true });
this.addEventListener('mousedown', cancelAnimations, { once: true });
this.addEventListener('touchstart', cancelAnimations, { once: true });
}
Promise.all([ leftAnimation.whenEnded, topAnimation.whenEnded ])
.then(()=> resolve());
});
return promise;
};
class SliderJS {
static textdomain='sjs';
constructor(element=document.querySelector('slider-wrap'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.SliderJS) this.element.SliderJS.destroy();
this.element.SliderJS=this;
args=this.constructor.parseArgs(args,
{
doesAutoplay: this.constructor.getAttributeDefault(this.element, 'data-does-autoplay', false),
doesStartRandom: false,
doesLoop: this.constructor.getAttributeDefault(this.element, 'data-does-loop', true),
loopBufferCount: 2,
doesAlternate: this.constructor.getAttributeDefault(this.element, 'data-does-alternate', false),
doesSnap: true,
isSkippable: true,
doesOverflow: false,
doesPreload: true,
preloadBufferCount: 2,
alignTo: 'center',
transition: this.constructor.getAttributeDefault(this.element, 'data-transition', 'horizontal'),
transitionType: 'transform',
transitionDuration: 250,
snapTransitionDuration: 250,
swipeTransitionDuration: 250,
revertTransitionDuration: 250,
easingFunction: x=> 1 - Math.pow(1 - x, 4),
snapEasingFunction: x=> 1 - Math.pow(1 - x, 4),
swipeEasingFunction: x=> 1 - Math.pow(1 - x, 4),
revertEasingFunction: x=> 1 - Math.pow(1 - x, 4),
doesAutosize: false,
sizeTransitionDuration: 250,
sizeEasingFunction: x=> 1 - Math.pow(1 - x, 4),
stepSize: 1,
playDirection: 'ltr',
pauseDuration: 5000,
doesPauseOnHover: false,
doesListenTo: {
drag: true,
swipe: true,
mouse: false,
wheel: false,
keyboard: true
},
threshold: {
dragDistance: 0.025,
swipeDuration: 200,
swipeDistance: 0.1,
swipeTolerance: 0.1,
wheelDuration: 300,
hasReachedStart: 0.05,
hasReachedEnd: 0.05
},
doesDispatchEvents: true,
debug: false
}
);
this.isPlaying=false;
this.wasPlaying=false;
this.animation=null;
this.heightAnimation=null;
this.isTransitioning=false;
this.interaction=null;
this.wheelTimeout=null;
this.playInterval=null;
this.doesAutoplay=args.doesAutoplay;
this.doesStartRandom=args.doesStartRandom;
this.doesLoop=args.doesLoop;
this.loopBufferCount=parseInt(args.loopBufferCount);
this.loopElementBefore=null;
this.loopElementAfter=null;
this.loopObserver=null;
this.bufferElementBefore=null;
this.bufferElementAfter=null;
this.doesAlternate=args.doesAlternate;
this.doesSnap=args.doesSnap;
this.isSkippable=args.isSkippable;
this.doesOverflow=args.doesOverflow;
this.minScroll;
this.maxScroll;
this.doesPreload=args.doesPreload;
this.preloadBufferCount=parseInt(args.preloadBufferCount);
this.alignTo=args.alignTo;
this.transitionDuration=args.transitionDuration;
this.snapTransitionDuration=args.snapTransitionDuration;
this.swipeTransitionDuration=args.swipeTransitionDuration;
this.revertTransitionDuration=args.revertTransitionDuration;
this.easingFunction=args.easingFunction;
this.snapEasingFunction=args.snapEasingFunction;
this.swipeEasingFunction=args.swipeEasingFunction;
this.revertEasingFunction=args.revertEasingFunction;
this.doesAutosize=args.doesAutosize;
this.sizeTransitionDuration=args.sizeTransitionDuration;
this.sizeEasingFunction=args.sizeEasingFunction;
this.stepSize=parseInt(args.stepSize);
this.playDirection=args.playDirection;
this.pauseDuration=args.pauseDuration;
this.doesPauseOnHover=args.doesPauseOnHover;
this.doesListenTo=args.doesListenTo;
this.threshold=args.threshold;
this.property={};
this.transitionType=this.doesListenTo.wheel ? 'scroll':args.transitionType;
this.setTransition(args.transition);
this.slidesElement=this.element.querySelector('slider-slides, .slider-slides');
this.slideElements=this.slidesElement.querySelectorAll(':scope > *:not([data-clone])');
this.slideCount=this.slideElements.length;
this.slidesSize=null;
this.slide={
former: null,
active: null,
target: null
};
this.controlsElement=this.element.querySelector('slider-controls, .slider-controls');
this.indicatorsElement=this.element.querySelector('slider-indicators, .slider-indicators');
this.displayElements=this.element.querySelectorAll('slider-display, .slider-display');
this.availableWrapClasses=[ 'has-reached-start', 'has-reached-end' ];
this.availableSlideClasses=[ 'is-former', 'is-active', 'is-target', 'might-become-visible' ];
this.doesDispatchEvents=args.doesDispatchEvents;
this.debug=args.debug;
this._resizeListener=event=> {
this.calculateDimensions();
this.setSlide(this.getActiveSlideIndex(), 0);
}
this._loopListener=event=> {
const scrollPosition=this.getScrollPosition();
if(this.loopElementBefore){
let value=this.loopElementBefore[ this.property.offset ] + this.loopElementBefore[ this.property.size ] - scrollPosition;
if(value >=this.threshold.loop){
if(this.transitionType==='scroll') this.setScrollPosition(scrollPosition + this.slidesSize);
if(this.animation&&this.animation.from&&! this.animation.hasEnded) this.animation.from +=this.slidesSize;
if(this.interaction&&this.interaction.start&&this.interaction.start.scroll) this.interaction.start.scroll +=this.slidesSize;
}}
if(this.loopElementAfter){
let value=this.loopElementAfter[ this.property.offset ] - scrollPosition;
if(value <=this.threshold.loop){
if(this.transitionType==='scroll') this.setScrollPosition(scrollPosition - this.slidesSize);
if(this.animation&&this.animation.from&&! this.animation.hasEnded) this.animation.from -=this.slidesSize;
if(this.interaction&&this.interaction.start&&this.interaction.start.scroll) this.interaction.start.scroll -=this.slidesSize;
}}
}
this._playClick=event=> {
event.preventDefault();
this.play();
}
this._pauseClick=event=> {
event.preventDefault();
this.pause();
}
this._togglePlayClick=event=> {
if(! this.isPlaying){
this._playClick(event);
}else{
this._pauseClick(event);
}}
this._touchstartListener=event=> {
this._dragSwipeStart(event.touches[0].clientX, event.touches[0].clientY, event);
}
this._touchmoveListener=event=> {
this._dragSwipeMove(event.touches[0].clientX, event.touches[0].clientY, event);
}
this._touchendListener=event=> {
this._dragSwipeEnd();
}
this._mousedownListener=event=> {
this._dragSwipeStart(event.clientX, event.clientY, event);
}
this._mousemoveListener=event=> {
this._dragSwipeMove(event.clientX, event.clientY, event);
}
this._mouseupListener=event=> {
this._dragSwipeEnd();
}
this._mouseenterListener=event=> {
this.wasPlaying=this.isPlaying;
this.pause();
}
this._mouseleaveListener=event=> {
if(! this.wasPlaying) return;
this.play();
}
this._wheelListener=event=> {
clearTimeout(this.wheelTimeout);
this.endSlide()
.then(()=> {
if(! this.doesSnap) return;
this.wheelTimeout=setTimeout(()=> {
const duration=this.getSlideInViewIndex()===this.getActiveSlideIndex() ? this.revertTransitionDuration:this.snapTransitionDuration;
const easingFunction=this.getSlideInViewIndex()===this.getActiveSlideIndex() ? this.revertEasingFunction:this.snapEasingFunction;
this.setSlide(this.getSlideInViewIndex(), duration, { easingFunction });
}, this.threshold.wheelDuration);
});
}
this._triggerClickListener=event=> {
const triggerElement=event.target.closest('[data-trigger]');
if(! triggerElement) return;
const trigger=triggerElement.getAttribute('data-trigger').split(':');
const textdomain=trigger[0];
const func=trigger[1]||null;
const param=trigger[2]||null;
if(textdomain!==this.constructor.textdomain||typeof this[ func ]!=='function') return;
event.preventDefault();
if(! param){
this[ func ]();
}else{
this[ func ](param);
}}
this._keyupListener=event=> {
switch(event.which){
case 39:
this.next();
break;
case 37:
this.prev();
break;
}}
this.whenIsReady=new Promise(( resolve, reject)=> {
this.build()
.then(()=> {
if(this.doesAutoplay) this.play();
return resolve(this);
});
});
}
static dispatchCustomEvent(element, name, detail={}, log=false){
if(! name||typeof name!=='string'||name.trim()==='') return false;
const Event=new CustomEvent(this.textdomain + ':' + name, { bubbles: true, detail });
if(log) console.log(Event);
return element.dispatchEvent(Event);
}
static parseArgs(values, ...defaults){
if(typeof values!=='object'||Array.isArray(values)||! Object.keys(values).length) values={};
if(! defaults.length) return values;
defaults.reverse();
for(const defaultObject of defaults){
if(typeof defaultObject!=='object'||! Object.keys(defaultObject).length) continue;
for(const property in defaultObject){
const defaultValue=defaultObject[ property ];
const value=values[ property ];
if(value===null) continue;
if(value===undefined){
values[ property ]=defaultValue;
}else if(typeof value==='object'&&! Array.isArray(value)
&&	typeof defaultValue==='object'&&! Array.isArray(defaultValue)
){
values[ property ]=this.parseArgs(value, defaultValue);
}}
}
return values;
}
static getAttributeDefault(element, attribute, defaultValue=null, validateBoolean=true){
const value=element.getAttribute(attribute);
if([ undefined, null, 'undefined', 'null', '' ].indexOf(value)!==-1)	return defaultValue;
if(validateBoolean){
if([ false, 0, 'false', '0' ].indexOf(value)!==-1) return false;
if([ true, 1, 'true', '1' ].indexOf(value)!==-1) return true;
}
return value;
}
static animate(element, args={}){
let {
unit: unit=null,
unitsPerSecond,
from,
to: to=0,
duration: duration=500,
animationObject: animationObject=null
}=args;
const {
property,
easingFunction: easingFunction=x => x < 0.5 ? 8 * x * x * x * x:1 - Math.pow(-2 * x + 2, 4) / 2
}=args;
if(! animationObject){
let distance, fromUnit, toUnit;
[ , from, fromUnit ]=typeof from==='string'&&from.match(/(?<from>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, from, '' ];
[ , to, toUnit ]=typeof to==='string'&&to.match(/(?<to>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, to, '' ];
from=parseFloat(from)===parseFloat(from) ? parseFloat(from):undefined;
to=parseFloat(to)===parseFloat(to) ? parseFloat(to):0;
unit=unit ?? toUnit ?? fromUnit ?? null;
switch(property){
case 'scrollLeft':
case 'scrollTop':
unit=null;
from=element[ property ];
distance=to - from;
break;
default:
let computedStyle, computedStyleFrom, computedFrom, computedUnit;
computedStyle=getComputedStyle(element);
computedStyleFrom=computedStyle.getPropertyValue(property)!=='' ? computedStyle.getPropertyValue(property):0;
[ , computedFrom, computedUnit ]=typeof computedStyleFrom==='string'&&computedStyleFrom.match(/(?<from>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, 0, '' ];
unit=unit ?? computedUnit ?? null;
from=from ??(parseFloat(computedFrom)===parseFloat(computedFrom) ? parseFloat(computedFrom):0);
distance=to - from;
break;
}
if(unitsPerSecond){
unitsPerSecond=parseFloat(unitsPerSecond)===parseFloat(unitsPerSecond) ? parseFloat(unitsPerSecond):100;
duration=Math.abs(distance) / Math.abs(unitsPerSecond) * 1000;
}
animationObject={
property,
unit,
from,
to,
distance,
duration: duration,
starttime: Date.now(),
runtime: 0,
progress: 0,
value: 0,
request: null,
isCanceled: false,
whenCanceled: null,
_resolveCanceled: null,
_rejectCanceled: null,
isCompleted: false,
whenCompleted: null,
_resolveCompleted: null,
_rejectCompleted: null,
hasEnded: false,
whenEnded: null,
_resolveEnded: null,
_rejectEnded: null,
cancel: ()=> {
animationObject._rejectCompleted();
animationObject._resolveCanceled();
animationObject._resolveEnded();
},
complete: ()=> {
animationObject._rejectCanceled();
animationObject._resolveCompleted();
animationObject._resolveEnded();
}}
animationObject.whenCanceled=new Promise(( resolve, reject)=> { animationObject._resolveCanceled=resolve; animationObject._rejectCanceled=reject; });
animationObject.whenCompleted=new Promise(( resolve, reject)=> { animationObject._resolveCompleted=resolve; animationObject._rejectCompleted=reject; });
animationObject.whenEnded=new Promise(( resolve, reject)=> { animationObject._resolveEnded=resolve; animationObject._rejectEnded=reject; });
animationObject.whenCanceled.then(()=> animationObject.isCanceled=true).catch(()=> {});
animationObject.whenCompleted.then(()=> animationObject.isCompleted=true).catch(()=> {});
animationObject.whenEnded
.then(()=> {
animationObject.hasEnded=true;
cancelAnimationFrame(animationObject.request);
})
.catch(()=> {});
}
animationObject.runtime=Date.now() - animationObject.starttime;
animationObject.progress=animationObject.duration <=0||animationObject.isCompleted ? 1:Math.min(1, Math.max(0, animationObject.runtime / animationObject.duration) );
animationObject.value=animationObject.from + easingFunction(animationObject.progress) * animationObject.distance;
switch(animationObject.property){
case 'scrollLeft':
case 'scrollTop':
element[ animationObject.property ]=animationObject.value;
break;
default:
element.style.setProperty(animationObject.property, animationObject.value +(animationObject.unit||'') );
break;
}
if(animationObject.progress >=1) animationObject.complete();
if(! animationObject.hasEnded) animationObject.request=requestAnimationFrame(timestamp=> this.animate(element, { easingFunction, animationObject }) );
return animationObject;
}
_dragSwipeStart(x, y, event){
x=parseFloat(x);
y=parseFloat(y);
if(! x||x!==x||! y||y!==y
||(this.isTransitioning&&! this.isSkippable)
) return;
this.endSlide();
this.wasPlaying=this.isPlaying;
this.pause();
this.interaction={
starttime: Date.now(),
hasStarted: false,
isDragging: false,
start: {
x,
y,
scroll: this.getScrollPosition()
}}
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'interactionrun', {
SliderJS: this,
interaction: this.interaction
}, this.debug);
}
_dragSwipeMove(x, y, event){
x=parseFloat(x);
y=parseFloat(y);
if(! x||x!==x||! y||y!==y
||	! this.interaction
) return;
this.interaction.distance={
x: x - this.interaction.start.x,
y: y - this.interaction.start.y
}
this.interaction.delta={
x: Math.abs(this.interaction.distance.x),
y: Math.abs(this.interaction.distance.y)
}
this.interaction.progress=this.interaction.delta[ this.property.axis.scroll ] / this.element[ this.property.size ];
this.interaction.target=this.interaction.distance[ this.property.axis.scroll ] > 0 ? 'previous':'next';
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'interactionprogress', {
SliderJS: this,
interaction: this.interaction,
progress: this.interaction.progress,
distance: this.interaction.distance,
delta: this.interaction.delta
}, this.debug);
const dragDistanceAbsolute=this.element[ this.property.size ] * this.threshold.dragDistance;
const swipeToleranceAbsolute=this.element[ this.property.size ] * this.threshold.swipeTolerance;
const swipeDistanceAbsolute=this.element[ this.property.size ] * this.threshold.swipeDistance;
if(! this.interaction.hasStarted
&& 	this.interaction.delta[ this.property.axis.fixed ] > swipeToleranceAbsolute
){
this.interaction=null;
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'interactioncancel', {
SliderJS: this,
interaction: this.interaction
}, this.debug);
return;
}
if(this.interaction.delta[ this.property.axis.scroll ] >=dragDistanceAbsolute
|| 	this.interaction.delta[ this.property.axis.scroll ] >=swipeDistanceAbsolute
){
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
const wasStarted=this.interaction.hasStarted;
this.interaction.hasStarted=true;
if(this.doesDispatchEvents&&! wasStarted){
this.constructor.dispatchCustomEvent(this.element, 'interactionstart', {
SliderJS: this,
interaction: this.interaction
}, this.debug);
}}
if((this.doesListenTo.drag&&this.interaction.isDragging)
||(this.doesListenTo.drag&&this.interaction.progress >=this.threshold.dragDistance)
){
const interactionValue=this.interaction.start.scroll - this.interaction.distance[ this.property.axis.scroll ];
this.interaction.isDragging=true;
this.setScrollPosition(interactionValue);
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'drag', { SliderJS: this }, this.debug);
}}
_dragSwipeEnd(){
let targetIndex=this.getActiveSlideIndex();
const swipeDistanceAbsolute=this.element[ this.property.size ] * this.threshold.swipeDistance;
if(this.wasPlaying) this.play();
switch(this.transition){
case 'horizontal':
case 'vertical':
targetIndex=this.getSlideInViewIndex();
break;
}
if(this.doesListenTo.swipe
&& 	this.interaction&&this.interaction.hasStarted
&& 	Date.now() - this.interaction.starttime < this.threshold.swipeDuration
&& 	this.interaction.delta[ this.property.axis.scroll ] >=swipeDistanceAbsolute
){
if(targetIndex===this.getActiveSlideIndex()){
if(this.interaction.target==='previous') targetIndex -=1;
if(this.interaction.target==='next') targetIndex +=1;
}
this.interaction=null;
if(! this.doesLoop) targetIndex=Math.max(0, Math.min(this.slideCount - 1, targetIndex) );
this.setSlide(targetIndex, this.swipeTransitionDuration, { easingFunction: this.swipeEasingFunction });
if(this.doesDispatchEvents){
this.constructor.dispatchCustomEvent(this.element, 'swipe', { SliderJS: this }, this.debug);
this.constructor.dispatchCustomEvent(this.element, 'interactionend', { SliderJS: this }, this.debug);
}
return;
}
if(this.doesSnap
&&	this.interaction&&this.interaction.hasStarted
){
const duration=targetIndex===this.getActiveSlideIndex() ? this.revertTransitionDuration:this.snapTransitionDuration;
const easingFunction=targetIndex===this.getActiveSlideIndex() ? this.revertEasingFunction:this.snapEasingFunction;
this.setSlide(targetIndex, duration, { easingFunction });
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'snap', { SliderJS: this }, this.debug);
}
this.interaction=null;
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'interactionend', { SliderJS: this }, this.debug);
}
build(){
const promise=new Promise(( resolve, reject)=> {
this.element.setAttribute('data-slide-count', this.slideCount);
this.element.style.setProperty('--sjs-slide-count', this.slideCount);
this.element.setAttribute('data-does-loop', this.doesLoop);
this.element.setAttribute('data-does-alternate', this.doesAlternate);
this.element.setAttribute('data-does-autoplay', this.doesAutoplay);
if(this.doesAutosize) this.element.setAttribute('data-does-autosize', true);
let doesListenToString='';
for(const [ doesListenTo, value ] of Object.entries(this.doesListenTo) ){
if(value===true) doesListenToString +=doesListenTo + ' ';
}
this.element.setAttribute('data-does-listen-to', doesListenToString.trim());
Promise.all([
this.buildSlides(),
this.buildBuffer(),
this.buildPreload(),
this.buildLoop(),
this.buildIndicators()
])
.then(()=> {
this.buildResizeEvents();
this.buildTouchEvents();
this.buildMouseEvents();
this.buildWheelEvents();
this.buildClickEvents();
this.buildKeyboardEvents();
return resolve();
});
})
.then(()=> {
this.calculateDimensions();
this.setSlide(this.getActiveSlideIndex(), 0);
if(this.doesAutoplay) this.play();
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'buildend', { SliderJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
clearTimeout(this.wheelTimeout);
clearInterval(this.playInterval);
this.stop();
Promise.all([
this.destroySlides(),
this.destroyBuffer(),
this.destroyPreload(),
this.destroyLoop(),
this.destroyIndicators()
])
.then(()=> {
this.element.removeAttribute('data-slide-count');
this.element.style.removeProperty('--sjs-slide-count');
this.element.removeAttribute('data-does-listen-to');
this.element.removeAttribute('data-does-autosize');
this.destroyResizeEvents();
this.destroyTouchEvents();
this.destroyMouseEvents();
this.destroyWheelEvents();
this.destroyClickEvents();
this.destroyKeyboardEvents();
return resolve();
});
})
.then(()=> {
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'destroyend', { SliderJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
buildSlides(){
const promise=new Promise(( resolve, reject)=> {
let activeSlide=this.slidesElement.querySelector(':scope > .is-active')||this.slidesElement.querySelector(':scope > :first-child');
if(this.doesStartRandom) activeSlide=this.slideElements[ Math.floor(Math.random() * this.slideCount) ];
this.destroySlides()
.then(()=> {
activeSlide.classList.add('is-active');
this.slide={
former: null,
active: activeSlide,
target: null
}
return resolve();
});
});
return promise;
}
destroySlides(){
const promise=new Promise(( resolve, reject)=> {
this.cleanSlideClasses();
this.slidesElement.style.webkitTransform='';
this.slidesElement.style.mozTransform='';
this.slidesElement.style.transform='';
this.slidesElement[ this.property.scroll ]='';
this.element.removeAttribute('data-active-slide-index');
this.element.style.removeProperty('--sjs-active-slide-index');
return resolve();
});
return promise;
}
cleanSlideClasses(){
const promise=new Promise(( resolve, reject)=> {
for(const slideElement of this.slidesElement.children) slideElement.classList.remove(...this.availableSlideClasses);
if(this.indicatorElements) for(const indicatorElement of this.indicatorElements) indicatorElement.classList.remove(...this.availableSlideClasses);
this.element.classList.remove(...this.availableWrapClasses);
return resolve();
});
return promise;
}
buildBuffer(){
const promise=new Promise(( resolve, reject)=> {
this.destroyBuffer()
.then(()=> {
if(this.doesLoop) return resolve();
let bufferElement=this.slideElements[ 0 ].cloneNode(true);
bufferElement.classList.remove(...this.availableSlideClasses);
bufferElement.removeAttribute('id');
bufferElement.setAttribute('data-clone', 'buffer');
bufferElement.innerHTML='';
this.bufferElementBefore=bufferElement;
this.bufferElementAfter=bufferElement.cloneNode(true);
this.slidesElement.prepend(this.bufferElementBefore);
this.slidesElement.append(this.bufferElementAfter);
return resolve();
})
});
return promise;
}
destroyBuffer(){
const promise=new Promise(( resolve, reject)=> {
const cloneElements=this.slidesElement.querySelectorAll(':scope > [data-clone]');
for(const cloneElement of cloneElements) cloneElement.remove();
return resolve();
});
return promise;
}
buildPreload(){
const promise=new Promise(( resolve, reject)=> {
this.destroyPreload()
.then(()=> {
if(! this.doesPreload) return resolve();
const imageElements=this.element.querySelectorAll('img');
for(const imageElement of imageElements){
imageElement.setAttribute('decoding', 'async');
imageElement.setAttribute('loading', 'lazy');
}
const mediaElements=this.element.querySelectorAll('video, audio');
for(const mediaElement of mediaElements){
mediaElement.setAttribute('preload', 'none');
mediaElement.setAttribute('data-does-autoplay', mediaElement.autoplay);
mediaElement.autoplay=false;
}
return resolve();
});
});
return promise;
}
destroyPreload(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
buildLoop(){
const promise=new Promise(( resolve, reject)=> {
this.destroyLoop()
.then(()=> {
if(! this.doesLoop) return resolve();
switch(this.transitionType){
case 'transform':
this.loopObserver=new MutationObserver(this._loopListener);
this.loopObserver.observe(this.slidesElement, {
attributes: true,
attributeFilter: ['style']
});
break;
case 'scroll':
default:
this.slidesElement.addEventListener('scroll', this._loopListener);
break;
}
for(let s=this.slideCount - 1; s >=Math.max(0, this.slideCount - this.loopBufferCount); s--){
let slideElement=this.slideElements[ s ].cloneNode(true);
if(s===this.slideCount - 1) this.loopElementBefore=slideElement;
slideElement.classList.remove(...this.availableSlideClasses);
slideElement.setAttribute('data-clone', 'before');
this.slidesElement.prepend(slideElement);
}
for(let s=0; s < Math.min(this.slideCount, this.loopBufferCount); s++){
let slideElement=this.slideElements[ s ].cloneNode(true);
if(s===0) this.loopElementAfter=slideElement;
slideElement.classList.remove(...this.availableSlideClasses);
slideElement.setAttribute('data-clone', 'after');
this.slidesElement.append(slideElement);
}
return resolve();
});
});
return promise;
}
destroyLoop(){
const promise=new Promise(( resolve, reject)=> {
switch(this.transitionType){
case 'transform':
if(this.loopObserver) this.loopObserver.disconnect();
this.loopObserver=null;
break;
case 'scroll':
default:
this.slidesElement.removeEventListener('scroll', this._loopListener);
break;
}
const cloneElements=this.slidesElement.querySelectorAll(':scope > [data-clone]');
for(const cloneElement of cloneElements) cloneElement.remove();
return resolve();
});
return promise;
}
buildIndicators(){
const promise=new Promise(( resolve, reject)=> {
this.destroyIndicators()
.then(()=> {
if(! this.indicatorsElement) return resolve();
if(this.indicatorsElement.children.length===1){
const indicatorElement=this.indicatorsElement.children[0];
indicatorElement.setAttribute('data-trigger', this.constructor.textdomain + ':setSlide:0');
indicatorElement.setAttribute('data-index', 0);
for(let i=1; i < this.slideCount; i++){
const newIndicatorElement=indicatorElement.cloneNode(true);
newIndicatorElement.setAttribute('data-trigger', this.constructor.textdomain + ':setSlide:' + i);
newIndicatorElement.setAttribute('data-index', i);
this.indicatorsElement.appendChild(newIndicatorElement);
}}
this.indicatorElements=this.indicatorsElement.querySelectorAll(':scope > *');
return resolve();
});
});
return promise;
}
destroyIndicators(){
const promise=new Promise(( resolve, reject)=> {
this.indicatorElements=null;
return resolve();
});
return promise;
}
buildResizeEvents(){
this.destroyResizeEvents();
window.addEventListener('resize', this._resizeListener);
}
destroyResizeEvents(){
window.removeEventListener('resize', this._resizeListener);
}
buildTouchEvents(){
this.destroyTouchEvents();
if(! this.doesListenTo.drag&&! this.doesListenTo.swipe) return;
this.element.addEventListener('touchstart', this._touchstartListener, { passive: false });
window.addEventListener('touchmove', this._touchmoveListener, { passive: false });
window.addEventListener('touchend', this._touchendListener, { passive: false });
}
destroyTouchEvents(){
this.element.removeEventListener('touchstart', this._touchstartListener);
window.removeEventListener('touchmove', this._touchmoveListener);
window.removeEventListener('touchend', this._touchendListener);
}
buildMouseEvents(){
this.destroyMouseEvents();
if(this.doesListenTo.mouse){
this.element.addEventListener('mousedown', this._mousedownListener, { passive: false });
window.addEventListener('mousemove', this._mousemoveListener, { passive: false });
window.addEventListener('mouseup', this._mouseupListener, { passive: false });
}
if(this.doesPauseOnHover){
this.element.addEventListener('mouseenter', this._mouseenterListener);
this.element.addEventListener('mouseleave', this._mouseleaveListener);
}}
destroyMouseEvents(){
this.element.removeEventListener('mousedown', this._mousedownListener);
window.removeEventListener('mousemove', this._mousemoveListener);
window.removeEventListener('mouseup', this._mouseupListener);
this.element.removeEventListener('mouseenter', this._mouseenterListener);
this.element.removeEventListener('mouseleave', this._mouseleaveListener);
}
buildWheelEvents(){
this.destroyWheelEvents();
if(! this.doesListenTo.wheel) return;
this.element.addEventListener('wheel', this._wheelListener);
}
destroyWheelEvents(){
this.element.removeEventListener('wheel', this._wheelListener);
}
buildClickEvents(){
this.destroyClickEvents();
const triggerElements=this.element.querySelectorAll('[data-trigger^="' + this.constructor.textdomain + ':"]');
for(const triggerElement of triggerElements){
triggerElement.addEventListener('click', this._triggerClickListener);
}
const toggleElements=this.element.querySelectorAll('[data-toggle^="' + this.constructor.textdomain + ':"]');
for(const toggleElement of toggleElements){
const toggle=toggleElement.getAttribute('data-toggle').split(':');
const textdomain=toggle[0];
const action=toggle[1]||null;
if(textdomain!==this.constructor.textdomain||! action) return;
switch(action){
case 'play':
case 'start':
case 'pause':
case 'stop':
toggleElement.addEventListener('click', this._togglePlayClick);
break;
}}
}
destroyClickEvents(){
const triggerElements=this.element.querySelectorAll('[data-trigger^="' + this.constructor.textdomain + ':"]');
for(const triggerElement of triggerElements){
triggerElement.removeEventListener('click', this._triggerClickListener);
}
const toggleElements=this.element.querySelectorAll('[data-toggle^="' + this.constructor.textdomain + ':"]');
for(const toggleElement of toggleElements){
toggleElement.removeEventListener('click', this._togglePlayClick);
}}
buildKeyboardEvents(){
this.destroyKeyboardEvents();
if(! this.doesListenTo.keyboard) return;
document.addEventListener('keyup', this._keyupListener);
}
destroyKeyboardEvents(){
document.removeEventListener('keyup', this._keyupListener);
}
setTransition(transition='horizontal'){
switch(transition){
case 'fade':
this.transition='fade';
this.property={
scroll: 'scrollLeft',
offset: 'offsetLeft',
size: 'clientWidth',
axis: {
scroll: 'x',
fixed: 'y'
}}
break;
case 'vertical':
this.transition='vertical';
this.property={
scroll: 'scrollTop',
offset: 'offsetTop',
size: 'clientHeight',
axis: {
scroll: 'y',
fixed: 'x'
}}
break;
case 'horizontal':
default:
this.transition='horizontal';
this.property={
scroll: 'scrollLeft',
offset: 'offsetLeft',
size: 'clientWidth',
axis: {
scroll: 'x',
fixed: 'y'
}}
break;
}
switch(this.transitionType){
case 'transform':
this.property.scroll='--sjs-scroll';
break;
case 'scroll':
default:
this.transitionType='scroll';
break;
}
this.calculateDimensions();
this.element.setAttribute('data-transition', this.transition);
this.element.setAttribute('data-transition-type', this.transitionType);
}
setScrollPosition(offset=0){
offset=parseFloat(offset);
if(! offset) return;
switch(this.transitionType){
case 'transform':
this.slidesElement.style.setProperty(this.property.scroll, offset);
break;
case 'scroll':
default:
this.slidesElement[ this.property.scroll ]=offset;
break;
}
return this.getScrollPosition();
}
getScrollPosition(){
let scrollPosition;
switch(this.transitionType){
case 'transform':
scrollPosition=parseFloat(this.slidesElement.style.getPropertyValue(this.property.scroll) );
break;
case 'scroll':
default:
scrollPosition=this.slidesElement[ this.property.scroll ];
break;
}
return scrollPosition;
}
setDisplays(){
for(const displayElement of this.displayElements){
const info=displayElement.getAttribute('data-info');
switch(info){
case 'total':
case 'count':
displayElement.innerHTML=this.slideCount;
break;
case 'current':
case 'active':
displayElement.innerHTML=this.getActiveSlideIndex() + 1;
break;
}}
}
publishData(){
const activeSlide=this.getActiveSlide();
const activeSlideIndex=this.getSlideIndex(activeSlide);
this.element.setAttribute('data-active-slide-index', activeSlideIndex);
this.element.style.setProperty('--sjs-active-slide-index', activeSlideIndex);
}
calculateDimensions(){
if(! this.slideElements) return;
this.slidesSize=0;
for(const slideElement of [ ...this.slideElements ]){
this.slidesSize +=slideElement[ this.property.size ];
const slideStyles=getComputedStyle(slideElement);
switch(this.transition){
case 'horizontal':
this.slidesSize +=parseFloat(slideStyles.getPropertyValue('margin-left') );
this.slidesSize +=parseFloat(slideStyles.getPropertyValue('margin-right') );
break;
case 'vertical':
this.slidesSize +=parseFloat(slideStyles.getPropertyValue('margin-top') );
this.slidesSize +=parseFloat(slideStyles.getPropertyValue('margin-bottom') );
break;
}}
const scrollOverflow=this.slidesSize - this.element[ this.property.size ];
switch(this.alignTo){
case 'end':
case 'right':
case 'bottom':
this.threshold.loop=this.slidesElement[ this.property.size ] * 0.9;
this.minScroll=(this.bufferElementBefore ? this.bufferElementBefore[ this.property.size ]:0) +(scrollOverflow < 0 ? scrollOverflow:0);
break;
case 'center':
case 'middle':
this.threshold.loop=this.slidesElement[ this.property.size ] * 0.5;
this.minScroll=(this.bufferElementBefore ? this.bufferElementBefore[ this.property.size ]:0) +(scrollOverflow < 0 ? scrollOverflow * 0.5:0);
break;
case 'start':
case 'left':
case 'top':
default:
this.threshold.loop=this.slidesElement[ this.property.size ] * 0.1;
this.minScroll=this.bufferElementBefore ? this.bufferElementBefore[ this.property.size ]:0;
break;
}
this.maxScroll=Math.max(this.minScroll, this.minScroll + scrollOverflow);
}
getSlideIndex(slideElement){
const tempSlideElements=this.slidesElement.children;
let index=[ ...tempSlideElements ].indexOf(slideElement);
if(index!==-1){
index -=(this.doesLoop ? this.loopBufferCount:0);
}else{
index=this.getActiveSlideIndex();
}
return index;
}
getFormerSlide(){
return this.slide.former||null;
}
getFormerSlideIndex(){
const slide=this.getFormerSlide();
const index=slide ? parseInt([ ...this.slideElements ].indexOf(slide) ):null;
return index;
}
getActiveSlide(){
return this.slide.active||this.slide.target||null;
}
getActiveSlideIndex(){
const slide=this.getActiveSlide();
const index=slide ? parseInt([ ...this.slideElements ].indexOf(slide) ):0;
return index;
}
getTargetSlide(){
return this.slide.target||null;
}
getTargetSlideIndex(){
const slide=this.getTargetSlide();
const index=slide ? parseInt([ ...this.slideElements ].indexOf(slide) ):null;
return index;
}
getSlideInView(){
let minOffset=null;
let slideInView=null;
const scrollPosition=this.getScrollPosition();
switch(this.alignTo){
case 'end':
case 'right':
case 'bottom':
for(const slideElement of this.slideElements){
let currentOffset=Math.abs(slideElement[ this.property.offset ] + slideElement[ this.property.size ] - this.slidesElement[ this.property.size ] - scrollPosition);
if(minOffset!==null&&currentOffset >=minOffset) continue;
minOffset=currentOffset;
slideInView=slideElement;
}
break;
case 'center':
case 'middle':
for(const slideElement of this.slideElements){
let currentOffset=Math.abs(slideElement[ this.property.offset ] + slideElement[ this.property.size ] * 0.5 - this.slidesElement[ this.property.size ] * 0.5 - scrollPosition);
if(minOffset!==null&&currentOffset >=minOffset) continue;
minOffset=currentOffset;
slideInView=slideElement;
}
break;
case 'start':
case 'left':
case 'top':
default:
for(const slideElement of this.slideElements){
const currentOffset=Math.abs(slideElement[ this.property.offset ] - scrollPosition);
if(minOffset!==null&&currentOffset >=minOffset) continue;
minOffset=currentOffset;
slideInView=slideElement;
}
break;
}
return slideInView;
}
getSlideInViewIndex(){
const slide=this.getSlideInView();
const index=slide ? parseInt([ ...this.slideElements ].indexOf(slide) ):0;
return index;
}
setSlide(index=this.getActiveSlideIndex(), duration=this.transitionDuration, args={}){
let tempIndex, targetIndex;
const activeIndex=this.getActiveSlideIndex();
const {
easingFunction: easingFunction=this.easingFunction,
sizeTransitionDuration: sizeTransitionDuration=this.sizeTransitionDuration,
sizeEasingFunction: sizeEasingFunction=this.sizeEasingFunction
}=args;
switch(typeof index){
case 'object':
index=this.getSlideIndex(index);
break;
case 'number':
case 'string':
default:
break;
}
index=parseInt(index);
targetIndex=parseInt(index);
const promise=new Promise(( resolve, reject)=> {
if(this.isTransitioning&&! this.isSkippable) return reject('setSlide: isTransitioning&&! isSkippable');
this.isTransitioning=true;
if(this.doesAlternate||this.doesLoop){
targetIndex=index % this.slideCount;
if(targetIndex < 0) targetIndex=this.slideCount + targetIndex;
}
targetIndex=Math.max(0, Math.min(this.slideCount - 1, targetIndex) );
this.endSlide()
.then(()=> this.cleanSlideClasses())
.then(()=> {
this.slide={
former: this.slideElements[ activeIndex ],
active: null,
target: this.slideElements[ targetIndex ]
}
if(! this.slide.target) return reject('setSlide: undefined target slide');
this.setDisplays();
this.publishData();
this.slide.former.classList.add('is-former');
this.slide.target.classList.add('is-target');
if(this.indicatorElements) for(const indicatorElement of this.indicatorElements){
if(indicatorElement.matches('[data-index="' + activeIndex + '"]') ) indicatorElement.classList.add('is-former');
if(indicatorElement.matches('[data-index="' + targetIndex + '"]') ) indicatorElement.classList.add('is-target');
}
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'transitionstart', { SliderJS: this }, this.debug);
let scrollTo, widthTo, heightTo;
let animationTarget=this.slide.target;
if(this.doesLoop){
const tempSlideElements=this.slidesElement.children;
const animationTargetIndex=Math.max(0, Math.min(tempSlideElements.length, this.loopBufferCount + index) );
animationTarget=tempSlideElements[ animationTargetIndex ];
}
switch(this.alignTo){
case 'end':
case 'right':
case 'bottom':
scrollTo=animationTarget[ this.property.offset ] + animationTarget[ this.property.size ] - this.slidesElement[ this.property.size ];
break;
case 'center':
case 'middle':
scrollTo=animationTarget[ this.property.offset ] + animationTarget[ this.property.size ] * 0.5 - this.slidesElement[ this.property.size ] * 0.5;
break;
case 'start':
case 'left':
case 'top':
default:
scrollTo=animationTarget[ this.property.offset ];
break;
}
if(! this.doesLoop&&! this.doesOverflow){
scrollTo=Math.max(this.minScroll, scrollTo);
scrollTo=Math.min(this.maxScroll, scrollTo);
}
if(this.doesPreload){
const tempSlideElements=this.slidesElement.children;
const animationTargetIndex=[ ...tempSlideElements ].indexOf(animationTarget);
let p=1;
let preloadElements=[ animationTarget ];
for(p; p <=this.preloadBufferCount; p++){
const preloadElementBefore=tempSlideElements[ animationTargetIndex - p ];
const preloadElementAfter=tempSlideElements[ animationTargetIndex + p ];
if(preloadElementBefore) preloadElements.push(preloadElementBefore);
if(preloadElementAfter) preloadElements.push(preloadElementAfter);
}
for(const preloadElement of preloadElements) this.preloadSlide(preloadElement);
}
let whenAnimationsEnded=[];
switch(this.transition){
case 'horizontal':
case 'vertical':
this.animation=this.constructor.animate(this.slidesElement, {
property: this.property.scroll,
unit: '',
to: scrollTo,
duration,
easingFunction
});
whenAnimationsEnded.push(this.animation.whenEnded);
break;
case 'fade':
this.animation=this.constructor.animate(this.slide.target, {
property: 'opacity',
unit: '',
from: this.slide.target===this.slide.former ? 1:0,
to: 1,
duration: this.slide.target===this.slide.former ? 0:duration,
easingFunction
});
whenAnimationsEnded.push(this.animation.whenEnded);
break;
}
if(this.doesAutosize){
this.heightAnimation=this.constructor.animate(this.element, {
property: '--sjs-height',
unit: 'px',
to: this.slide.target.clientHeight,
duration: this.slide.target===this.slide.former ? 0:sizeTransitionDuration,
easingFunction: sizeEasingFunction
});
whenAnimationsEnded.push(this.heightAnimation.whenEnded);
}
Promise.all(whenAnimationsEnded)
.then(()=> this.endSlide())
.then(()=> {
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'transitionend', { SliderJS: this }, this.debug);
return resolve();
})
.catch(error=> {
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'transitioncancel', { SliderJS: this }, this.debug);
if(this.debug) console.log(error);
return reject(error);
});
});
});
return promise;
}
endSlide(){
const promise=new Promise(( resolve, reject)=> {
if(! this.animation||! this.animation.cancel||this.animation.isCanceled){
return resolve();
}else{
this.animation.cancel();
if(this.heightAnimation) this.heightAnimation.cancel();
Promise.all([
this.cleanSlideClasses(),
this.animation.whenEnded
])
.then(()=> {
const formerSlideIndex=this.getFormerSlideIndex();
const targetSlideIndex=this.getTargetSlideIndex();
this.animation=null;
this.heightAnimation=null;
this.slide={
former: this.slideElements[ formerSlideIndex ],
active: this.slideElements[ targetSlideIndex ],
target: null
}
if(! this.slide.active) return reject('endSlide: undefined active slide');
const previousSlideElement=this.slide.active.previousElementSibling||this.slidesElement.lastElementChild;
const nextSlideElement=this.slide.active.nextElementSibling||this.slidesElement.firstElementChild;
this.slide.active.classList.add('is-active');
if(this.slide.former) this.slide.former.classList.add('is-former');
if(previousSlideElement) previousSlideElement.classList.add('might-become-visible');
if(nextSlideElement) nextSlideElement.classList.add('might-become-visible');
if(this.indicatorElements) for(const indicatorElement of this.indicatorElements){
if(indicatorElement.matches('[data-index="' + targetSlideIndex + '"]') ) indicatorElement.classList.add('is-active');
if(indicatorElement.matches('[data-index="' + formerSlideIndex + '"]') ) indicatorElement.classList.add('is-former');
}
for(const slideElement of this.slideElements) slideElement.style.removeProperty('opacity');
const scrollPosition=this.getScrollPosition();
const firstSlideElement=this.slideElements[0];
const lastSlideElement=this.slideElements[ this.slideElements.length - 1 ];
if(firstSlideElement[ this.property.offset ] - scrollPosition >=this.element[ this.property.size ] * this.threshold.hasReachedStart * -1) this.element.classList.add('has-reached-start');
if(lastSlideElement[ this.property.offset ] + lastSlideElement[ this.property.size ] - scrollPosition <=this.element[ this.property.size ] + this.element[ this.property.size ] * this.threshold.hasReachedEnd) this.element.classList.add('has-reached-end');
return resolve();
});
}})
.catch(error=> this.debug&&console.log(error) )
.finally(()=> this.isTransitioning=false);
return promise;
}
preloadSlide(element){
if(this.constructor.getAttributeDefault(element, 'data-is-preloaded', false)===true) return;
const imageElements=element.querySelectorAll('img');
for(const imageElement of imageElements){
imageElement.setAttribute('decoding', 'auto');
imageElement.setAttribute('loading', 'eager');
}
const mediaElements=element.querySelectorAll('video, audio');
for(const mediaElement of mediaElements){
mediaElement.setAttribute('preload', 'auto');
mediaElement.autoplay=this.constructor.getAttributeDefault(mediaElement, 'data-does-autoplay', false);
if(mediaElement.autoplay) mediaElement.play().catch(error=> this.debug&&console.log(error) );
}
element.setAttribute('data-is-preloaded', true);
}
prev(duration=this.transitionDuration){
return this.setSlide(this.getActiveSlideIndex() - this.stepSize, duration);
}
previous(duration){
return this.prev(duration);
}
back(duration){
return this.prev(duration);
}
backward(duration){
return this.prev(duration);
}
backwards(duration){
return this.prev(duration);
}
first(duration=this.transitionDuration){
return this.setSlide(0, duration);
}
next(duration=this.transitionDuration){
return this.setSlide(this.getActiveSlideIndex() + this.stepSize, duration);
}
forward(duration){
return this.next(duration);
}
forwards(duration){
return this.next(duration);
}
last(duration=this.transitionDuration){
return this.setSlide(this.slideCount - 1, duration);
}
play(){
if(this.isPlaying) return;
this.playInterval=setInterval(()=> {
switch(this.playDirection){
case 'rtl':
this.prev();
break;
case 'ltr':
default:
this.next();
break;
}}, this.pauseDuration + this.transitionDuration);
this.isPlaying=true;
}
start(){
return this.play();
}
pause(){
if(! this.isPlaying) return;
clearInterval(this.playInterval);
this.isPlaying=false;
}
stop(){
return this.pause();
}};
class VideoJS {
constructor(element=document.querySelector('video-wrap'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.VideoJS) this.element.VideoJS.destroy();
this.element.VideoJS=this;
args=this.constructor.parseArgs(args,
{
type: this.constructor.getAttributeDefault(this.element, 'data-type', 'vimeo'),
key: this.element.getAttribute('data-key'),
src: this.element.getAttribute('data-src'),
title: '',
author: '',
description: '',
isLive: this.constructor.getAttributeDefault(this.element, 'data-is-live', false),
doesLoop: this.constructor.getAttributeDefault(this.element, 'data-does-loop', false),
doesAutoplay: this.constructor.getAttributeDefault(this.element, 'data-does-autoplay', false),
doesPlayOnSeek: true,
doesForcePlayOnSeek: false,
doesAutoresume: false,
doesAutopause: {
onOtherPlay: false,
onIntersection: false,
onVisibilitychange: false
},
doesUnmuteOnPlay: true,
doesForceUnmuteOnPlay: false,
doesAutounmute: false,
doesAutomute: {
onOtherPlay: false,
onIntersection: false,
onVisibilitychange: false
},
ratio: 1.7777777777,
doesAutosize: true,
fullscreenElement: this.element,
activeDuration: 2500,
timeFormat: 'H:i:s',
vimeoConfig: {
autopause: false,
autoplay: false,
background: false,
byline: false,
color: '000000',
controls: false,
dnt: true,
keyboard: false,
loop: false,
muted: true,
pip: false,
playsinline: true,
portrait: false,
quality: 'auto',
responsive: false,
speed: false,
title: false,
transparent: false
},
youtubeConfig: {
host: 'https://www.youtube-nocookie.com',
apiKey: window.YOUTUBE_APIKEY||this.constructor.YOTUBE_APIKEY||'',
dataHost: 'https://youtube.googleapis.com/youtube/v3/videos?part=player&id={{ src }}&maxWidth=4000&key={{ api_key }}',
autohide: 0,
cc_load_policy: 0,
controls: 0,
disablekb: 1,
iv_load_policy: 3,
loop: false,
modestbranding: 1,
playsinline: 1,
rel: 0,
showinfo: 0
},
doesDispatchEvents: true,
debug: false
}
);
this.type=args.type;
this.src=(args.src||args.key||'').trim();
this.state='';
let srcSplit;
switch(this.type){
case 'youtube':
srcSplit=this.src.split('/');
this.src=srcSplit.pop();
srcSplit=this.src.split('?v=');
this.src=srcSplit.pop();
break;
}
this.setTitle(args.title);
this.setAuthor(args.author);
this.setDescription(args.description);
this.isLive=args.isLive;
this.doesLoop=args.vimeoConfig.loop=args.doesLoop;
this.doesAutoplay=args.doesAutoplay||this.isLive;
this.doesPlayOnSeek=args.doesPlayOnSeek;
this.doesForcePlayOnSeek=args.doesForcePlayOnSeek;
this.isPlaying=false;
this.currentTime=0;
this.remainingTime=0;
this.isForcePaused=false;
this.doesAutoresume=args.doesAutoresume;
this.doesAutopause=args.doesAutopause;
this.autopauseIntersectionObserver=null;
this.isAutopaused=false;
this.autopausedBy=null;
this.doesUnmuteOnPlay=args.doesUnmuteOnPlay;
this.doesForceUnmuteOnPlay=args.doesForceUnmuteOnPlay;
this.isMuted=true;
this.isForceMuted=false;
this.doesAutounmute=args.doesAutounmute;
this.doesAutomute=args.doesAutomute;
this.automuteIntersectionObserver=null;
this.isAutomuted=false;
this.automutedBy=null;
this.ratio=args.ratio;
this.doesAutosize=args.doesAutosize;
this.activityTimeout=null;
this.isActive=true;
this.activeDuration=args.activeDuration;
this.timeFormat=args.timeFormat;
this.viewerElement=this.element.querySelector('video-viewer')||this.element.querySelector('.video-viewer');
this.blockElement=this.element.querySelector('video-viewer__block')||this.element.querySelector('.video-viewer__block');
this.frameElement=this.element.querySelector('video-viewer__frame')||this.element.querySelector('.video-viewer__frame');
this.controlsElement=this.element.querySelector('video-controls')||this.element.querySelector('.video-controls');
this.displayElements=this.element.querySelectorAll('video-display')||this.element.querySelectorAll('.video-display');
this.duration=0;
this.progressBarElements=this.element.querySelectorAll('video-progress-bar')||this.element.querySelectorAll('.video-progress-bar');
this.progressInterval=null;
this.progress=0;
this.fullscreenElement=args.fullscreenElement;
this.isFullscreen=false;
this.vimeoConfig=args.vimeoConfig;
this.youtubeConfig=args.youtubeConfig;
this.doesDispatchEvents=args.doesDispatchEvents;
this.debug=args.debug;
this._playClick=event=> {
event.preventDefault();
this.isForcePaused=false;
if(this.doesUnmuteOnPlay&&! this.isForceMuted||this.doesForceUnmuteOnPlay){
this.unmute()
.finally(()=> this.play());
}else{
this.play();
}}
this._pauseClick=event=> {
event.preventDefault();
this.isForcePaused=true;
this.pause();
}
this._togglePlayClick=event=> {
if(! this.isPlaying){
this._playClick(event);
}else{
this._pauseClick(event);
}}
this._muteClick=event=> {
event.preventDefault();
this.isForceMuted=true;
this.mute();
}
this._unmuteClick=event=> {
event.preventDefault();
this.isForceMuted=false;
this.unmute();
}
this._toggleMuteClick=event=> {
if(! this.isMuted){
this._muteClick(event);
}else{
this._unmuteClick(event);
}}
this._maximizeClick=event=> {
event.preventDefault();
this.maximize();
}
this._minimizeClick=event=> {
event.preventDefault();
this.minimize();
}
this._toggleFullscreenClick=event=> {
if(! this.isFullscreen){
this._maximizeClick(event);
}else{
this._minimizeClick(event);
}}
this._fullscreenListener=event=> {
const element=document.fullscreenElement||document.mozFullscreenElement||document.webkitFullscreenElement;
if(! element){
this.isFullscreen=false;
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:minimize', { VideoJS: this }, this.debug);
}else{
this.isFullscreen=true;
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:maximize', { VideoJS: this, element }, this.debug);
}
this.element.setAttribute('data-is-fullscreen', this.isFullscreen);
}
this._barClick=event=> {
if(! this.duration) return;
const bar=event.target;
const dimensions=bar.getBoundingClientRect();
const x=event.clientX;
const percent=Math.max(0, Math.min(1,(x - dimensions.left) / dimensions.width) );
const time=this.duration * percent;
this.seekTo(time);
if(this.doesUnmuteOnPlay&&! this.isForceMuted||this.doesForceUnmuteOnPlay) this.unmute();
}
this._mousemoveListener=event=> {
const wasActive=this.isActive;
clearTimeout(this.activityTimeout);
this.isActive=true;
this.element.setAttribute('data-is-active', this.isActive);
this.activityTimeout=setTimeout(()=> this._mouseleaveListener(), this.activeDuration);
if(this.doesDispatchEvents&&! wasActive) this.constructor.dispatchCustomEvent(this.element, 'vjs:activitychange', { VideoJS: this }, this.debug);
}
this._mouseleaveListener=event=> {
const wasActive=this.isActive;
clearTimeout(this.activityTimeout);
this.isActive=false;
this.element.setAttribute('data-is-active', this.isActive);
if(this.doesDispatchEvents&&wasActive) this.constructor.dispatchCustomEvent(this.element, 'vjs:activitychange', { VideoJS: this }, this.debug);
}
this._autopauseIntersectionObserverCallback=entries=> {
for(const entry of entries){
if(! entry.isIntersecting&&this.getState()!=='paused'){
this.pause()
.then(()=> {
this.autopausedBy='intersection';
this.isAutopaused=true;
});
}else if(this.isAutopaused&&this.autopausedBy==='intersection'&&this.doesAutoresume){
this.play();
}}
}
this._autopauseVisibilitychangeListener=event=> {
if(document.visibilityState==='hidden'&&this.getState()!=='paused'){
this.pause()
.then(()=> {
this.autopausedBy='visibilitychange';
this.isAutopaused=true;
});
}else if(this.isAutopaused&&this.autopausedBy==='visibilitychange'&&this.doesAutoresume){
this.play();
}}
this._automuteIntersectionObserverCallback=entries=> {
for(const entry of entries){
if(! entry.isIntersecting&&! this.isMuted){
this.mute()
.then(()=> {
this.automutedBy='intersection';
this.isAutomuted=true;
});
}else if(this.isAutomuted&&this.automutedBy==='intersection'&&this.doesAutounmute){
this.unmute();
}}
}
this._automuteVisibilitychangeListener=event=> {
if(document.visibilityState==='hidden'&&! this.isMuted){
this.mute()
.then(()=> {
this.automutedBy='visibilitychange';
this.isAutomuted=true;
});
}else if(this.isAutomuted&&this.automutedBy==='visibilitychange'&&this.doesAutounmute){
this.unmute();
}}
this.player=null;
this.playerResolve;
this.whenPlayerIsReady=new Promise(( resolve, reject)=> {
this.playerResolve=resolve;
});
this.isReady=new Promise(( resolve, reject)=> {
this.build()
.then(()=> {
return resolve(this);
});
});
}
static dispatchCustomEvent=function(element, name, detail={}, log=false){
if(! name||typeof name!=='string'||name.trim()==='') return false;
const Event=new CustomEvent(name, { bubbles: true, detail });
if(log) console.log(Event);
return element.dispatchEvent(Event);
}
static parseArgs(values, ...defaults){
if(typeof values!=='object'||Array.isArray(values)||! Object.keys(values).length) values={};
if(! defaults.length) return values;
defaults.reverse();
for(const defaultObject of defaults){
if(typeof defaultObject!=='object'||! Object.keys(defaultObject).length) continue;
for(const property in defaultObject){
const defaultValue=defaultObject[ property ];
const value=values[ property ];
if(value===null) continue;
if(value===undefined){
values[ property ]=defaultValue;
}else if(typeof value==='object'&&! Array.isArray(value)
&&	typeof defaultValue==='object'&&! Array.isArray(defaultValue)
){
values[ property ]=this.parseArgs(value, defaultValue);
}}
}
return values;
}
static getAttributeDefault=function(element, attribute, defaultValue=null, validateBoolean=true){
const value=element.getAttribute(attribute);
if([ undefined, null, 'undefined', 'null', '' ].indexOf(value)!==-1)	return defaultValue;
if(validateBoolean){
if([ false, 0, 'false', '0' ].indexOf(value)!==-1) return false;
if([ true, 1, 'true', '1' ].indexOf(value)!==-1) return true;
}
return value;
}
static doesSupportFullscreen=function(element){
return !! (
element.requestFullscreen
|| 	element.mozRequestFullscreen
|| 	element.webkitEnterFullscreen
|| 	element.webkitRequestFullscreen
);
}
static toTimeFormat=function(value, format='H:i:s', removeNullValues=true, total=null){
if(! total||typeof total!=='number') total=value;
let ms=Math.floor(( value % 1000) );
let s=Math.floor(( value / 1000) % 60);
let i=Math.floor(( value /(1000 * 60) ) % 60);
let G=Math.floor(( value /(1000 * 60 * 60) ) % 24), H=G;
let tms=Math.floor(( total % 1000) );
let ts=Math.floor(( total / 1000) % 60);
let ti=Math.floor(( total /(1000 * 60) ) % 60);
let tG=Math.floor(( total /(1000 * 60 * 60) ) % 24), tH=tG;
if(ms < 100) ms=(ms < 10 ? '00':'0') + ms;
if(s < 10) s='0' + s;
if(i < 10) i='0' + i;
if(H < 10) H='0' + H;
format=format.replace(/ms/g, ms);
format=format.replace(/s/g, s);
format=format.replace(/i/g, i);
format=format.replace(/G/g, G <=0&&tG <=0&&removeNullValues ? '':G);
format=format.replace(/H/g, G <=0&&tG <=0&&removeNullValues ? '':H);
format=format.replace(/^\D*/, '');
return format;
}
build(){
const promise=new Promise(( resolve, reject)=> {
this.element.setAttribute('data-type', this.type);
this.element.setAttribute('data-src', this.src);
this.element.setAttribute('data-is-live', this.isLive);
this.element.setAttribute('data-does-loop', this.doesLoop);
this.element.setAttribute('data-does-autoplay', this.doesAutoplay);
this.element.setAttribute('data-state', this.state);
this.element.setAttribute('data-is-muted', this.isMuted);
this.element.setAttribute('data-is-active', this.isActive);
this.element.setAttribute('data-is-fullscreen', this.isFullscreen);
this.buildPlayer()
.then(()=> {
this.buildHoverEvents();
this.buildAutopauseEvents();
this.buildAutomuteEvents();
this.buildClickEvents();
this.buildFullscreenEvents();
return resolve();
});
})
.then(()=> {
if(this.isLive||this.doesAutoplay) this.play();
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:buildend', { VideoJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
clearTimeout(this.activityTimeout);
clearInterval(this.progressInterval);
this.mute();
this.stop();
this.destroyPlayer()
.then(()=> {
this.element.style.removeProperty('--ratio');
this.element.style.removeProperty('--padding-top');
this.element.style.removeProperty('--progress');
this.destroyHoverEvents();
this.destroyAutopauseEvents();
this.destroyAutomuteEvents();
this.destroyClickEvents();
this.destroyFullscreenEvents();
return resolve();
});
})
.then(()=> {
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:destroyend', { VideoJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
buildPlayer(){
const promise=new Promise(( resolve, reject)=> {
this.destroyPlayer()
.then(()=> {
switch(this.type){
case 'vimeo':
this._buildVimeoPlayer()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'youtube':
this._buildYoutubePlayer()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'html':
this._buildHtmlPlayer()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
}});
})
.then(()=> this.playerResolve())
.catch(error=> this.debug&&console.log(error) );
return promise;
}
_buildVimeoPlayer(){
const promise=new Promise(( resolve, reject)=> {
this.player=new Vimeo.Player(this.frameElement,
{
...this.vimeoConfig,
...{
id: this.src,
width: window.innerWidth,
height: window.innerHeight
}}
);
this.player.on('bufferstart', data=> {
this.setState('buffering');
});
this.player.on('bufferend', data=> {
if(this.isPlaying) this.play();
});
this.player.on('ended', data=> {
this.setState('ended');
});
this.player.on('pause', data=> {
this.setState('paused');
});
this.player.on('play', data=> {
this.setState('playing');
});
this.player.on('timeupdate', data=> {
this.setProgress(data.seconds * 1000, data.duration * 1000);
});
this.player.on('loaded', data=> {
Promise.all([ this.player.getCurrentTime(), this.player.getDuration() ])
.then(values=> this.setProgress(values[0] * 1000, values[1] * 1000) );
this.player.getVideoTitle()
.then(title=> this.setTitle(title) );
this.setState('loaded');
return resolve();
});
if(this.doesAutosize){
Promise.all([ this.player.getVideoWidth(), this.player.getVideoHeight() ])
.then(dimensions=> {
const width=dimensions[0];
const height=dimensions[1];
this.ratio=width / height;
this.element.style.setProperty('--ratio', this.ratio);
this.element.style.setProperty('--padding-top',(100 / this.ratio) + '%');
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:autosize', { VideoJS: this, ratio: this.ratio }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
}});
return promise;
}
_buildYoutubePlayer(){
const promise=new Promise(( resolve, reject)=> {
this.constructor.whenYoutubeIsReady
.then(()=> {
const youtubeFrameElement=document.createElement('div');
this.frameElement.append(youtubeFrameElement);
this.player=new YT.Player(youtubeFrameElement, {
height: '100%',
width: '100%',
videoId: this.src,
host: this.youtubeConfig.host,
playerVars: this.youtubeConfig,
events: {
onReady: ()=> {
this.setState('loaded');
return resolve();
},
onStateChange: args=> {
switch(args.data){
case YT.PlayerState.PLAYING:
this.setState('playing');
break;
case YT.PlayerState.BUFFERING:
this.setState('buffering');
break;
case YT.PlayerState.PAUSED:
this.setState('paused');
break;
case YT.PlayerState.ENDED:
this.setState('ended');
break;
}
clearInterval(this.progressInterval);
if(! this.isPlaying) return;
this.progressInterval=setInterval(()=> {
this.setProgress(this.player.getCurrentTime() * 1000, this.player.getDuration() * 1000);
}, 250);
}}
});
})
.catch(error=> this.debug&&console.log(error) );
if(this.doesAutosize&&this.youtubeConfig.apiKey){
let youtubeDataURL=this.youtubeConfig.dataHost.replace('{{ src }}', this.src);
youtubeDataURL=youtubeDataURL.replace('{{ api_key }}', this.youtubeConfig.apiKey);
fetch(youtubeDataURL)
.then(response=> response.json())
.then(data=> {
if(! data.items[0]||! data.items[0].player||! data.items[0].player.embedWidth) return;
const width=parseInt(data.items[0].player.embedWidth);
const height=parseInt(data.items[0].player.embedHeight);
this.ratio=width / height;
this.element.style.setProperty('--ratio', this.ratio);
this.element.style.setProperty('--padding-top',(100 / this.ratio) + '%');
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:autosize', { VideoJS: this, ratio: this.ratio }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
}});
return promise;
}
_buildHtmlPlayer(){
const promise=new Promise(( resolve, reject)=> {
this.player=document.createElement('video');
this.player.src=this.src;
this.player.setAttribute('preload', 'auto');
this.player.setAttribute('playsinline', '');
if(this.doesLoop) this.player.setAttribute('loop', '');
this.frameElement.innerHTML='';
this.frameElement.appendChild(this.player);
this.player.addEventListener('ended', event=> this.setState('ended') );
this.player.addEventListener('pause', event=> this.setState('paused') );
this.player.addEventListener('play', event=> this.setState('playing') );
this.player.addEventListener('timeupdate', event=> this.setProgress());
this.player.addEventListener('canplay', event=> {
this.setState('loaded');
this.setProgress();
return resolve();
});
this.player.addEventListener('loadedmetadata', event=> {
this.setState('loaded');
this.setProgress();
if(this.doesAutosize){
const width=this.player.videoWidth;
const height=this.player.videoHeight;
this.ratio=width / height;
this.element.style.setProperty('--ratio', this.ratio);
this.element.style.setProperty('--padding-top',(100 / this.ratio)+ '%');
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:autosize', { VideoJS: this, ratio: this.ratio }, this.debug);
}
return resolve();
});
});
return promise;
}
destroyPlayer(){
const promise=new Promise(( resolve, reject)=> {
switch(this.type){
case 'vimeo':
this._destroyVimeoPlayer()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'youtube':
this._destroyYoutubePlayer()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'html':
this._destroyHtmlPlayer()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
}})
.then(()=> {
this.player=null;
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
_destroyVimeoPlayer(){
const promise=new Promise(( resolve, reject)=> {
if(! this.player) return resolve();
this.player.destroy()
.then(()=> resolve())
.catch(error=> reject(error) );
});
return promise;
}
_destroyYoutubePlayer(){
const promise=new Promise(( resolve, reject)=> {
if(! this.player) return resolve();
this.frameElement.innerHTML='';
return resolve();
});
return promise;
}
_destroyHtmlPlayer(){
const promise=new Promise(( resolve, reject)=> {
if(! this.player) return resolve();
this.frameElement.innerHTML='';
return resolve();
});
return promise;
}
buildHoverEvents(){
this.destroyHoverEvents();
document.addEventListener('mousemove', this._mousemoveListener);
document.documentElement.addEventListener('mouseleave', this._mouseleaveListener);
}
destroyHoverEvents(){
document.removeEventListener('mousemove', this._mousemoveListener);
document.documentElement.removeEventListener('mouseleave', this._mouseleaveListener);
}
buildAutopauseEvents(){
this.destroyAutopauseEvents();
if(! this.doesAutopause||this.isLive) return;
if(this.doesAutopause===true||this.doesAutopause.onOtherPlay===true){
}
if(this.doesAutopause===true||this.doesAutopause.onIntersection===true){
this.autopauseIntersectionObserver=new IntersectionObserver(this._autopauseIntersectionObserverCallback);
this.autopauseIntersectionObserver.observe(this.element);
}
if(this.doesAutopause===true||this.doesAutopause.onVisibilitychange===true){
document.addEventListener('visibilitychange', this._autopauseVisibilitychangeListener);
}}
destroyAutopauseEvents(){
if(this.autopauseIntersectionObserver) this.autopauseIntersectionObserver.disconnect();
document.removeEventListener('visibilitychange', this._autopauseVisibilitychangeListener);
}
buildAutomuteEvents(){
this.destroyAutomuteEvents();
if(! this.doesAutomute) return;
if(this.doesAutomute===true||this.doesAutomute.onOtherPlay===true){
}
if(this.doesAutomute===true||this.doesAutomute.onIntersection===true){
this.automuteIntersectionObserver=new IntersectionObserver(this._automuteIntersectionObserverCallback);
this.automuteIntersectionObserver.observe(this.element);
}
if(this.doesAutomute===true||this.doesAutomute.onVisibilitychange===true){
document.addEventListener('visibilitychange', this._automuteVisibilitychangeListener);
}}
destroyAutomuteEvents(){
if(this.automuteIntersectionObserver) this.automuteIntersectionObserver.disconnect();
document.removeEventListener('visibilitychange', this._automuteVisibilitychangeListener);
}
buildClickEvents(){
this.destroyClickEvents();
if(this.blockElement) this.blockElement.addEventListener('click', this._togglePlayClick);
const triggerElements=this.element.querySelectorAll('[data-trigger]');
for(const triggerElement of triggerElements){
const action=triggerElement.getAttribute('data-trigger');
switch(action){
case 'play':
case 'start':
triggerElement.addEventListener('click', this._playClick);
break;
case 'pause':
case 'stop':
triggerElement.addEventListener('click', this._pauseClick);
break;
case 'unmute':
triggerElement.addEventListener('click', this._unmuteClick);
break;
case 'mute':
triggerElement.addEventListener('click', this._muteClick);
break;
case 'maximize':
case 'max':
if(! this.constructor.doesSupportFullscreen(this.fullscreenElement) ){
triggerElement.remove();
break;
}
triggerElement.addEventListener('click', this._maximizeClick);
break;
case 'minimize':
case 'min':
if(! this.constructor.doesSupportFullscreen(this.fullscreenElement) ){
triggerElement.remove();
break;
}
triggerElement.addEventListener('click', this._minimizeClick);
break;
}}
const toggleElements=this.element.querySelectorAll('[data-toggle]');
for(const toggleElement of toggleElements){
const action=toggleElement.getAttribute('data-toggle');
switch(action){
case 'play':
case 'start':
case 'pause':
case 'stop':
toggleElement.addEventListener('click', this._togglePlayClick);
break;
case 'unmute':
case 'mute':
toggleElement.addEventListener('click', this._toggleMuteClick);
break;
case 'fullscreen':
case 'maximize':
case 'max':
case 'minimize':
case 'min':
if(! this.constructor.doesSupportFullscreen(this.fullscreenElement) ){
toggleElement.remove();
break;
}
toggleElement.addEventListener('click', this._toggleFullscreenClick);
break;
}}
for(const progressBarElement of this.progressBarElements){
progressBarElement.addEventListener('click', this._barClick);
}}
destroyClickEvents(){
if(this.blockElement) this.blockElement.removeEventListener('click', this._togglePlayClick);
const triggerElements=this.element.querySelectorAll('[data-trigger]');
for(const triggerElement of triggerElements){
const action=triggerElement.getAttribute('data-trigger');
switch(action){
case 'play':
case 'start':
triggerElement.removeEventListener('click', this._playClick);
break;
case 'pause':
case 'stop':
triggerElement.removeEventListener('click', this._pauseClick);
break;
case 'unmute':
triggerElement.removeEventListener('click', this._unmuteClick);
break;
case 'mute':
triggerElement.removeEventListener('click', this._muteClick);
break;
case 'maximize':
case 'max':
triggerElement.removeEventListener('click', this._maximizeClick);
break;
case 'minimize':
case 'min':
triggerElement.removeEventListener('click', this._minimizeClick);
break;
}}
const toggleElements=this.element.querySelectorAll('[data-toggle]');
for(const toggleElement of toggleElements){
const action=toggleElement.getAttribute('data-toggle');
switch(action){
case 'play':
case 'start':
case 'pause':
case 'stop':
toggleElement.removeEventListener('click', this._togglePlayClick);
break;
case 'unmute':
case 'mute':
toggleElement.removeEventListener('click', this._toggleMuteClick);
break;
case 'fullscreen':
case 'maximize':
case 'max':
case 'minimize':
case 'min':
toggleElement.removeEventListener('click', this._toggleFullscreenClick);
break;
}}
for(const progressBarElement of this.progressBarElements){
progressBarElement.removeEventListener('click', this._barClick);
}}
buildFullscreenEvents(){
this.destroyFullscreenEvents();
if(! this.constructor.doesSupportFullscreen(this.fullscreenElement) ) return;
document.addEventListener('fullscreenchange', this._fullscreenListener);
document.addEventListener('mozfullscreenchange', this._fullscreenListener);
document.addEventListener('webkitfullscreenchange', this._fullscreenListener);
}
destroyFullscreenEvents(){
document.removeEventListener('fullscreenchange', this._fullscreenListener);
document.removeEventListener('mozfullscreenchange', this._fullscreenListener);
document.removeEventListener('webkitfullscreenchange', this._fullscreenListener);
}
getState(){
return this.state;
}
setState(state){
const lastState=this.getState();
this.isPlaying=false;
switch(state){
case 'loaded':
case 'load':
this.state='loaded';
break;
case 'ended':
case 'end':
this.state='ended';
if(this.doesLoop){
this.seekTo(0)
.then(()=> this.play());
}
break;
case 'paused':
case 'pause':
case 'stopped':
case 'stop':
this.state='paused';
if(this.isLive) this.play();
break;
case 'buffering':
case 'buffer':
this.state='buffering';
break;
case 'playing':
case 'play':
this.state='playing';
this.isPlaying=true;
break;
}
state=this.getState();
this.element.setAttribute('data-state', state);
if(this.doesDispatchEvents&&lastState!==state) this.constructor.dispatchCustomEvent(this.element, 'vjs:statechange', { VideoJS: this, state }, this.debug);
return state;
}
getProgress(){
return this.progress;
}
setProgress(currentTime, duration=this.duration){
switch(this.type){
case 'vimeo':
case 'youtube':
this.duration=duration||this.duration;
this.currentTime=currentTime;
break;
case 'html':
this.duration=this.player.duration * 1000;
this.currentTime=this.player.currentTime * 1000;
break;
}
this.remainingTime=Math.max(0, this.duration - this.currentTime);
this.progress=Math.min(1, this.currentTime / this.duration);
this.element.style.setProperty('--progress', this.progress);
this.setDisplays();
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:progress', {
VideoJS: this,
duration: this.duration,
currentTime: this.currentTime,
progress: this.progress,
remainingTime: this.remainingTime
}, this.debug);
return this.progress;
}
setDisplays(){
for(const displayElement of this.displayElements){
const info=displayElement.getAttribute('data-info');
switch(info){
case 'duration':
displayElement.innerHTML=this.constructor.toTimeFormat(this.duration, this.timeFormat, true, this.duration);
break;
case 'currentTime':
displayElement.innerHTML=this.constructor.toTimeFormat(this.currentTime, this.timeFormat, true, this.duration);
break;
case 'remainingTime':
displayElement.innerHTML=this.constructor.toTimeFormat(this.remainingTime, this.timeFormat, true, this.duration);
break;
}}
}
getTitle(){
return this.title;
}
setTitle(title=''){
const lastTitle=this.getTitle();
this.title=title;
title=this.getTitle();
if(this.doesDispatchEvents&&lastTitle!==title) this.constructor.dispatchCustomEvent(this.element, 'vjs:titlechange', { VideoJS: this, title }, this.debug);
return title;
}
getAuthor(){
return this.author;
}
setAuthor(author=''){
const lastAuthor=this.getAuthor();
this.author=author;
author=this.getAuthor();
if(this.doesDispatchEvents&&lastAuthor!==author) this.constructor.dispatchCustomEvent(this.element, 'vjs:authorchange', { VideoJS: this, author }, this.debug);
return author;
}
getDescription(){
return this.description;
}
setDescription(description=''){
const lastDescription=this.getDescription();
this.description=description;
description=this.getDescription();
if(this.doesDispatchEvents&&lastDescription!==description) this.constructor.dispatchCustomEvent(this.element, 'vjs:descriptionchange', { VideoJS: this, description }, this.debug);
return description;
}
play(){
const promise=new Promise(( resolve, reject)=> {
this.whenPlayerIsReady
.then(()=> {
const whenPlayerisMuted=new Promise(( resolve, reject)=> {
if(! this.isMuted) return resolve();
this.mute()
.then(()=> resolve())
.catch(error=> resolve(error) );
});
return whenPlayerisMuted;
})
.then(()=> {
if(this.isPlaying) return resolve();
switch(this.type){
case 'vimeo':
this.player.play()
.then(()=> resolve())
.catch(error=> resolve(error) );
break;
case 'youtube':
this.player.setPlaybackQuality('default');
this.player.playVideo();
return resolve();
break;
case 'html':
this.player.play()
.then(()=> resolve())
.catch(error=> resolve(error) );
break;
}})
})
.then(()=> {
this.setState('playing');
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:play', { VideoJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
start(){
return this.play();
}
pause(){
this.isAutopaused=false;
const promise=new Promise(( resolve, reject)=> {
if(this.isLive) return reject('pause(): Cannot pause live video');
this.whenPlayerIsReady
.then(()=> {
switch(this.type){
case 'vimeo':
this.player.pause()
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'youtube':
this.player.pauseVideo();
return resolve();
break;
case 'html':
this.player.pause()
return resolve();
break;
}})
})
.then(()=> {
this.setState('paused');
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:pause', { VideoJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
stop(){
return this.pause();
}
seekTo(ms, doPlay=this.doesPlayOnSeek&&! this.isForcePaused||this.doesForcePlayOnSeek){
const lastState=this.getState();
const s=ms / 1000;
const promise=new Promise(( resolve, reject)=> {
this.whenPlayerIsReady
.then(()=> {
this.setState('buffering');
switch(this.type){
case 'vimeo':
this.player.setCurrentTime(s)
.then(s=> resolve(s * 1000) )
.catch(error=> reject(error) );
break;
case 'youtube':
this.player.seekTo(s, true);
return resolve(s * 1000);
break;
case 'html':
this.player.currentTime=s;
return resolve(s * 1000);
break;
}});
})
.then(ms=> {
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:seekto', { VideoJS: this, ms }, this.debug);
clearInterval(this.progressInterval);
this.setProgress(ms);
this.setState(lastState);
const whenActionDoesFinish=new Promise(( resolve, reject)=> {
if(doPlay){
this.play()
.then(()=> resolve(ms) );
}else{
return resolve(ms);
}});
return whenActionDoesFinish;
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
unmute(){
const promise=new Promise(( resolve, reject)=> {
this.whenPlayerIsReady
.then(()=> {
switch(this.type){
case 'vimeo':
Promise.all([ this.player.setMuted(false), this.player.setVolume(1) ])
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'youtube':
this.player.unMute();
this.player.setVolume(100);
return resolve();
break;
case 'html':
this.player.muted=false;
this.player.volume=1;
return resolve();
break;
}});
})
.then(()=> {
this.isMuted=false;
this.element.setAttribute('data-is-muted', this.isMuted);
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:unmute', { VideoJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
mute(){
this.isAutomuted=false;
const promise=new Promise(( resolve, reject)=> {
this.whenPlayerIsReady
.then(()=> {
switch(this.type){
case 'vimeo':
this.player.setMuted(true)
.then(()=> resolve())
.catch(error=> reject(error) );
break;
case 'youtube':
this.player.mute();
return resolve();
break;
case 'html':
this.player.volume=0;
this.player.muted=true;
return resolve();
break;
}});
})
.then(()=> {
this.isMuted=true;
this.element.setAttribute('data-is-muted', this.isMuted);
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'vjs:mute', { VideoJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
maximize(){
const promise=new Promise(( resolve, reject)=> {
if(this.fullscreenElement.requestFullscreen){ this.fullscreenElement.requestFullscreen(); }
else if(this.fullscreenElement.mozRequestFullscreen){ this.fullscreenElement.mozRequestFullscreen(); }
else if(this.fullscreenElement.webkitEnterFullscreen){ this.fullscreenElement.webkitEnterFullscreen(); }
else if(this.fullscreenElement.webkitRequestFullscreen){ this.fullscreenElement.webkitRequestFullscreen(); }else{
return reject('maximize(): No fullscreen support');
}
return resolve();
});
return promise;
}
minimize(){
const promise=new Promise(( resolve, reject)=> {
if(document.exitFullscreen){ document.exitFullscreen(); }
else if(document.mozExitFullscreen){ document.mozExitFullscreen(); }
else if(document.webkitExitFullscreen){ document.webkitExitFullscreen(); }else{
return reject('minimize(): No full screen support');
}
return resolve();
});
return promise;
}}
VideoJS.youtubeResolve;
VideoJS.whenYoutubeIsReady=new Promise(( resolve, reject)=> {
VideoJS.youtubeResolve=resolve;
});
function onYouTubeIframeAPIReady(){
VideoJS.youtubeResolve();
};
class MarqueeJS {
constructor(element=document.querySelector('marquee-wrap, .marquee-wrap'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.MarqueeJS) this.element.MarqueeJS.destroy();
this.element.MarqueeJS=this;
args=this.constructor.parseArgs(args,
{
direction: this.constructor.getAttributeDefault(this.element, 'data-direction', 'ltr'),
doesAutoplay: this.constructor.getAttributeDefault(this.element, 'data-does-autoplay', true),
isScrollable: this.constructor.getAttributeDefault(this.element, 'data-is-scrollable', false),
doesPauseOnHover: this.constructor.getAttributeDefault(this.element, 'data-is-scrollable', false),
transitionDuration: 0,
pixelsPerSecond: 50,
threshold: {
wheelDuration: 300
},
doesDispatchEvents: true,
debug: false
}
);
this.isPlaying=false;
this.wasPlaying=false;
this.animation=null;
this.interaction=null;
this.wheelTimeout=null;
this.loopObserver=null;
this.setDirection(args.direction);
this.doesAutoplay=args.doesAutoplay;
this.isScrollable=args.isScrollable;
this.doesPauseOnHover=args.doesPauseOnHover;
this.transitionDuration=parseFloat(args.transitionDuration);
this.pixelsPerSecond=this.transitionDuration > 0 ? 0:parseFloat(args.pixelsPerSecond);
this.threshold=args.threshold;
this.partsElement=this.element.querySelector('marquee-parts, .marquee-parts');
this.partElements=this.partsElement.querySelectorAll(':scope > *:not([data-clone])');
this.partsSize=null;
this.doesDispatchEvents=args.doesDispatchEvents;
this.debug=args.debug;
this._resizeListener=event=> {
this.calculateDimensions();
this.buildLoop();
}
this._loopListener=event=> {
const scrollPosition=this.getScrollPosition();
if(scrollPosition > this.partsSize){
this.setScrollPosition(scrollPosition - this.partsSize);
if(this.animation) this.animation.from -=this.partsSize;
}
if(scrollPosition < 0){
this.setScrollPosition(scrollPosition + this.partsSize);
if(this.animation) this.animation.from +=this.partsSize;
}}
this._touchstartListener=event=> {
this.wasPlaying=this.isPlaying;
this.pause();
this.interaction={
starttime: Date.now(),
start: {
x: event.touches[0].clientX,
y: event.touches[0].clientY,
scroll: this.getScrollPosition()
}}
}
this._touchmoveListener=event=> {
if(! this.interaction) return;
event.preventDefault();
this.interaction.distance={
x: event.touches[0].clientX - this.interaction.start.x,
y: event.touches[0].clientY - this.interaction.start.y
}
this.interaction.delta={
x: Math.abs(this.interaction.distance.x),
y: Math.abs(this.interaction.distance.y)
}
const interactionValue=this.interaction.start.scroll - this.interaction.distance.x;
this.setScrollPosition(interactionValue);
}
this._touchendListener=event=> {
if(this.wasPlaying) this.play();
this.interaction=null;
}
this._mouseenterListener=event=> {
this.wasPlaying=this.isPlaying;
if(this.doesPauseOnHover) this.pause();
}
this._mouseleaveListener=event=> {
if(this.wasPlaying) this.play();
}
this._wheelListener=event=> {
event.preventDefault();
clearTimeout(this.wheelTimeout);
this.pause();
this.setScrollPosition(this.getScrollPosition() + event.deltaX);
this.wheelTimeout=setTimeout(()=> {
if(this.wasPlaying&&(! this.doesPauseOnHover||! this.element.matches(':hover') )) this.play();
}, this.threshold.wheelDuration);
}
this.whenIsReady=new Promise(( resolve, reject)=> {
this.build()
.then(()=> {
return resolve(this);
});
});
}
static dispatchCustomEvent(element, name, detail={}, log=false){
if(! name||typeof name!=='string'||name.trim()==='') return false;
const Event=new CustomEvent(name, { bubbles: true, detail });
if(log) console.log(Event);
return element.dispatchEvent(Event);
}
static parseArgs(values, ...defaults){
if(typeof values!=='object'||Array.isArray(values)||! Object.keys(values).length) values={};
if(! defaults.length) return values;
defaults.reverse();
for(const defaultObject of defaults){
if(typeof defaultObject!=='object'||! Object.keys(defaultObject).length) continue;
for(const property in defaultObject){
const defaultValue=defaultObject[ property ];
const value=values[ property ];
if(value===null) continue;
if(value===undefined){
values[ property ]=defaultValue;
}else if(typeof value==='object'&&! Array.isArray(value)
&&	typeof defaultValue==='object'&&! Array.isArray(defaultValue)
){
values[ property ]=parseArgs(value, defaultValue);
}}
}
return values;
}
static getAttributeDefault(element, attribute, defaultValue=null, validateBoolean=true){
const value=element.getAttribute(attribute);
if([ undefined, null, 'undefined', 'null', '' ].indexOf(value)!==-1)	return defaultValue;
if(validateBoolean){
if([ false, 0, 'false', '0' ].indexOf(value)!==-1) return false;
if([ true, 1, 'true', '1' ].indexOf(value)!==-1) return true;
}
return value;
}
static animate(args={}){
let {
unit,
unitsPerSecond,
from,
to: to=0,
duration: duration=500,
animation
}=args;
const {
element: element=this,
property,
easing: easing=x => x < 0.5 ? 8 * x * x * x * x:1 - Math.pow(-2 * x + 2, 4) / 2
}=args;
if(! animation){
let distance, fromUnit, toUnit;
[ , from, fromUnit ]=typeof from==='string'&&from.match(/(?<from>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, from, '' ];
[ , to, toUnit ]=typeof to==='string'&&to.match(/(?<to>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, to, '' ];
from=parseFloat(from)===parseFloat(from) ? parseFloat(from):undefined;
to=parseFloat(to)===parseFloat(to) ? parseFloat(to):0;
unit=unit||toUnit||fromUnit||null;
switch(property){
case 'scrollLeft':
case 'scrollTop':
unit=null;
from=element[ property ];
distance=to - from;
break;
default:
let computedStyle, computedStyleFrom, computedFrom, computedUnit;
computedStyle=getComputedStyle(element);
computedStyleFrom=computedStyle.getPropertyValue(property)!=='' ? computedStyle.getPropertyValue(property):0;
[ , computedFrom, computedUnit ]=typeof computedStyleFrom==='string'&&computedStyleFrom.match(/(?<from>-?\d+\.?\d*)(?<unit>\D*)/)||[ 0, 0, '' ];
unit=unit||computedUnit||null;
from=from ??(parseFloat(computedFrom)===parseFloat(computedFrom) ? parseFloat(computedFrom):0);
distance=to - from;
break;
}
if(unitsPerSecond){
unitsPerSecond=parseFloat(unitsPerSecond)===parseFloat(unitsPerSecond) ? parseFloat(unitsPerSecond):100;
duration=Math.abs(distance) / Math.abs(unitsPerSecond) * 1000;
}
animation={
property,
unit,
from,
to,
distance,
duration: duration,
starttime: Date.now(),
runtime: 0,
progress: 0,
value: 0,
request: null,
isCanceled: false,
whenCanceled: null,
_resolveCanceled: null,
_rejectCanceled: null,
isCompleted: false,
whenCompleted: null,
_resolveCompleted: null,
_rejectCompleted: null,
hasEnded: false,
whenEnded: null,
_resolveEnded: null,
_rejectEnded: null,
cancel: ()=> {
animation._rejectCompleted();
animation._resolveCanceled();
animation._resolveEnded();
},
complete: ()=> {
animation._rejectCanceled();
animation._resolveCompleted();
animation._resolveEnded();
}}
animation.whenCanceled=new Promise(( resolve, reject)=> { animation._resolveCanceled=resolve; animation._rejectCanceled=reject; });
animation.whenCompleted=new Promise(( resolve, reject)=> { animation._resolveCompleted=resolve; animation._rejectCompleted=reject; });
animation.whenEnded=new Promise(( resolve, reject)=> { animation._resolveEnded=resolve; animation._rejectEnded=reject; });
animation.whenCanceled.then(()=> animation.isCanceled=true).catch(()=> {});
animation.whenCompleted.then(()=> animation.isCompleted=true).catch(()=> {});
animation.whenEnded
.then(()=> {
animation.hasEnded=true;
cancelAnimationFrame(animation.request);
})
.catch(()=> {});
}
animation.runtime=Date.now() - animation.starttime;
animation.progress=animation.duration <=0||animation.isCompleted ? 1:Math.min(1, Math.max(0, animation.runtime / animation.duration) );
animation.value=animation.from + easing(animation.progress) * animation.distance;
switch(animation.property){
case 'scrollLeft':
case 'scrollTop':
element[ animation.property ]=animation.value;
break;
default:
element.style.setProperty(animation.property, animation.value +(animation.unit||'') );
break;
}
if(animation.progress >=1) animation.complete();
if(! animation.hasEnded) animation.request=requestAnimationFrame(timestamp=> this.animate({ element, easing, animation }) );
return animation;
}
build(){
const promise=new Promise(( resolve, reject)=> {
this.element.setAttribute('data-direction', this.direction);
this.element.setAttribute('data-does-autoplay', this.doesAutoplay);
this.element.setAttribute('data-is-scrollable', this.isScrollable);
Promise.all([
this.buildLoop()
])
.then(()=> {
this.buildResizeEvents();
this.buildTouchEvents();
this.buildMouseEvents();
this.buildWheelEvents();
return resolve();
});
})
.then(()=> {
this.calculateDimensions();
if(this.doesAutoplay) this.play();
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'mjs:buildend', { MarqueeJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
clearTimeout(this.wheelTimeout);
this.stop();
Promise.all([
this.destroyLoop()
])
.then(()=> {
this.destroyResizeEvents();
this.destroyTouchEvents();
this.destroyMouseEvents();
this.destroyWheelEvents();
return resolve();
});
})
.then(()=> {
if(this.doesDispatchEvents) this.constructor.dispatchCustomEvent(this.element, 'mjs:destroyend', { MarqueeJS: this }, this.debug);
})
.catch(error=> this.debug&&console.log(error) );
return promise;
}
buildLoop(){
const promise=new Promise(( resolve, reject)=> {
this.destroyLoop()
.then(()=> {
this.loopObserver=new MutationObserver(this._loopListener);
this.loopObserver.observe(this.partsElement, {
attributes: true,
attributeFilter: ['style']
});
const elementWidth=this.element.clientWidth;
const partsWidth=this.partsElement.clientWidth;
const multiplicator=Math.ceil(elementWidth / partsWidth);
for(let m=0; m <=multiplicator; m++){
for(let p=0; p < this.partElements.length; p++){
const partElement=this.partElements[ p ].cloneNode(true);
if(m===0&&p===0) this.firstLoopElement=partElement;
partElement.setAttribute('data-clone', '');
this.partsElement.append(partElement);
}}
return resolve();
});
});
return promise;
}
destroyLoop(){
const promise=new Promise(( resolve, reject)=> {
if(this.loopObserver) this.loopObserver.disconnect();
this.loopObserver=null;
const cloneElements=this.partsElement.querySelectorAll(':scope > [data-clone]');
for(const cloneElement of cloneElements) cloneElement.remove();
return resolve();
});
return promise;
}
buildResizeEvents(){
this.destroyResizeEvents();
window.addEventListener('resize', this._resizeListener);
}
destroyResizeEvents(){
window.removeEventListener('resize', this._resizeListener);
}
buildTouchEvents(){
this.destroyTouchEvents();
if(this.doesPauseOnHover||this.isScrollable){
this.element.addEventListener('touchstart', this._touchstartListener, { passive: false });
window.addEventListener('touchend', this._touchendListener, { passive: false });
}
if(this.isScrollable){
window.addEventListener('touchmove', this._touchmoveListener, { passive: false });
}}
destroyTouchEvents(){
this.element.removeEventListener('touchstart', this._touchstartListener);
window.removeEventListener('touchend', this._touchendListener);
window.removeEventListener('touchmove', this._touchmoveListener);
}
buildMouseEvents(){
this.destroyMouseEvents();
if(! this.doesPauseOnHover&&! this.isScrollable) return;
this.element.addEventListener('mouseenter', this._mouseenterListener);
this.element.addEventListener('mouseleave', this._mouseleaveListener);
}
destroyMouseEvents(){
this.element.removeEventListener('mouseenter', this._mouseenterListener);
this.element.removeEventListener('mouseleave', this._mouseleaveListener);
}
buildWheelEvents(){
this.destroyWheelEvents();
if(! this.isScrollable) return;
this.element.addEventListener('wheel', this._wheelListener);
}
destroyWheelEvents(){
this.element.removeEventListener('wheel', this._wheelListener);
}
setDirection(direction='ltr'){
this.wasPlaying=this.isPlaying;
this.pause();
switch(direction){
case 'rtl':
this.direction='rtl';
break;
case 'ltr':
default:
this.direction='ltr';
break;
}
if(this.wasPlaying) this.play();
}
switchDirection(){
switch(this.direction){
case 'rtl':
return this.setDirection('ltr');
break;
case 'ltr':
default:
return this.setDirection('rtl');
break;
}}
setScrollPosition(offset=0){
offset=parseFloat(offset);
if(! offset) return;
this.partsElement.style.setProperty('--transform', offset);
return this.getScrollPosition();
}
getScrollPosition(){
let scrollPosition=parseFloat(this.partsElement.style.getPropertyValue('--transform') );
if(scrollPosition!==scrollPosition) scrollPosition=0;
return scrollPosition;
}
calculateDimensions(){
this.partsSize=0;
for(const partElement of [ ...this.partElements ]){
this.partsSize +=partElement.clientWidth;
const partStyles=getComputedStyle(partElement);
this.partsSize +=parseFloat(partStyles.getPropertyValue('margin-left') );
this.partsSize +=parseFloat(partStyles.getPropertyValue('margin-right') );
}}
play(){
if(this.isPlaying) return;
let to;
switch(this.direction){
case 'rtl':
to=this.getScrollPosition() - this.partsSize;
break;
case 'ltr':
default:
to=this.getScrollPosition() + this.partsSize
break;
}
this.animation=this.constructor.animate({
element: this.partsElement,
property: '--transform',
to,
duration: this.transitionDuration,
unitsPerSecond: this.pixelsPerSecond,
unit: null,
easing: x=> x
});
this.isPlaying=true;
this.animation.whenCompleted
.then(()=> {
this.pause();
this.play();
})
.catch(error=> {});
}
start(){
return this.play();
}
pause(){
if(this.animation&&this.animation.cancel) this.animation.cancel();
this.animation=null;
this.isPlaying=false;
}
stop(){
return this.pause();
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Overlay=class Overlay {
static isInitialized=false;
constructor(element=document.querySelector('.overlay'), args={}){
this.element=element;
if(! this.element) return;
args=About_Pop.Helper.parseArgs(args,
{}
);
this._clickListener=event=> {
if(event.target.parentElement?.closest('.overlay-content-wrap') ) return;
history.back();
}
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
this.element.addEventListener('click', this._clickListener);
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
this.element.removeEventListener('click', this._clickListener);
this.element=null;
return resolve();
});
return promise;
}
open(){
const promise=new Promise(( resolve, reject)=> {
if(this.element.classList.contains('is-active') ) return resolve();
this.element.classList.add('is-active');
ScrollJS.disable([ this.element.querySelector('.overlay-inner') ]);
return resolve();
});
return promise;
}
close(doRemove=false){
const promise=new Promise(( resolve, reject)=> {
if(! this.element.classList.contains('is-active') ) return resolve();
this.element.classList.remove('is-active');
ScrollJS.enable();
new Promise(( transitionResolve, transitionReject)=> {
if(! this.element.hasTransitionEvents()) return transitionResolve();
this.element.whenTransitionEnded()
.then(()=> transitionResolve());
})
.finally(()=> {
if(doRemove){
this.element.remove();
this.element=null;
}
return resolve();
});
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.View=About_Pop.View||{};
About_Pop.View.Venues=class Venues {
static isInitialized=false;
static mapMaxZoom=18;
static mapMinZoom=10;
static mapTargetZoom=16;
static mapZoom=12;
static mapZIPRange=[ 70173, 70629 ];
constructor(args={}){
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this.venues=[];
this.routes=[];
this._transitionStartListener=event=> {
if(! event?.detail?.SliderJS?.slide?.target) return;
const hasPins=event.detail.SliderJS.slide.target.getAttributeDefault('data-has-pins', false);
if(! hasPins){
for(const Venue of this.venues){
if(! Venue.Marker) continue;
Venue.Marker.map=null;
}}else if(this.map){
for(const Venue of this.venues){
if(! Venue.Marker) continue;
Venue.Marker.map=this.map;
}}
}
this._switchClickListener=event=> {
const buttonElement=event.target.closest('.button');
if(! buttonElement||! this.Tab_Slider?.slideElements) return;
const index=parseInt(buttonElement.getAttribute('data-index') );
const slideElement=this.Tab_Slider?.slideElements[ index ];
if(! slideElement) return;
const link=slideElement.getAttribute('data-link');
if(link&&link!==window.location.href) window.history.replaceState({}, '', link);
for(const Venue of this.venues) Venue.close();
for(const Route of this.routes) Route.close();
this.setMapBounds();
}
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
this.tabSwitchElements=document.querySelectorAll('#venues__switch .button');
for(const tabSwitchElement of this.tabSwitchElements) tabSwitchElement.addEventListener('click', this._switchClickListener);
const tabSliderElement=document.querySelector('#venues__tabs');
if(tabSliderElement){
this.Tab_Slider=new SliderJS(tabSliderElement, {
doesLoop: false,
doesListenTo: {
drag: false,
swipe: false,
mouse: false,
wheel: false,
keyboard: false
},
transitionDuration: 250,
transition: 'fade'
});
tabSliderElement.addEventListener('sjs:transitionstart', this._transitionStartListener);
}
this.buildMap(document.querySelector('#venues__map-inner') )
.then(map=> {
this.map=map;
this.map.defaultBounds=new google.maps.LatLngBounds();
Promise.all([
google.maps.importLibrary('marker'),
google.maps.importLibrary('geometry')
])
.then(libraries=> {
const markerLibrary=libraries[0];
const geometryLibrary=libraries[1];
if(About_Pop?.Component?.Venue){
const venueElements=document.querySelectorAll('article.venue:not(.route)');
for(const venueElement of venueElements){
const Venue=new About_Pop.Component.Venue(venueElement, {
map,
markerLibrary,
geometryLibrary
});
if(Venue.lat&&Venue.lng
&&	Venue.zip&&Venue.zip >=this.constructor.mapZIPRange[0]&&Venue.zip <=this.constructor.mapZIPRange[1]
){
this.map.defaultBounds.extend({ lat: Venue.lat, lng: Venue.lng });
}
this.venues.push(Venue);
}}
if(About_Pop?.Component?.Route){
const routeElements=document.querySelectorAll('article.route');
for(const routeElement of routeElements){
const Route=new About_Pop.Component.Route(routeElement, {
map,
markerLibrary
});
this.routes.push(Route);
}}
})
.then(()=> {
this.setMapBounds();
this.Tab_Slider.setSlide();
return resolve();
});
});
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
for(const Venue of this.venues) Venue.destroy();
for(const Route of this.routes) Route.destroy();
if(this.tabSwitchElements) for(const tabSwitchElement of this.tabSwitchElements) tabSwitchElement.removeEventListener('click', this._switchClickListener);
if(this.Tab_Slider?.element) this.Tab_Slider.element.removeEventListener('sjs:transitionstart', this._transitionStartListener);
if(this.Tab_Slider) this.Tab_Slider.destroy();
return resolve();
});
return promise;
}
buildMap(element){
const promise=new Promise(( resolve, reject)=> {
if(! element) return reject();
google.maps.importLibrary('maps')
.then(mapsLibrary=> {
const map=new mapsLibrary.Map(element, {
mapId: '6c1f8e6cf57d5493f0aede17',
zoomControl: true,
mapTypeControl: false,
scaleControl: false,
streetViewControl: false,
rotateControl: false,
fullscreenControl: false,
gestureHandling: 'greedy',
center: { lat: 48.8041, lng: 9.2080 },
maxZoom: this.constructor.mapMaxZoom,
minZoom: this.constructor.mapMinZoom,
zoom: this.constructor.mapZoom
});
return resolve(map);
});
});
return promise;
}
setMapBounds(bounds=null){
if(! this.map) return;
if(! bounds) bounds=this.map.defaultBounds;
this.map.fitBounds(bounds);
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Header=class Header {
static isInitialized=false;
constructor(element=document.querySelector('header'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.Header) this.element.Header.destroy();
this.element.Header=this;
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this._resizeListener=event=> {
this.publishDimensions();
}
this._toggleClickListener=event=> {
event.preventDefault();
this.toggle();
}
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
this.innerElement=this.element.querySelector('.header-inner');
this.mainNavigationToggles=[];
for(const mainNavigationToggleElement of document.querySelectorAll('[data-toggle="main-navigation"]') ){
this.mainNavigationToggles.push(new About_Pop.Component.Button(mainNavigationToggleElement) );
mainNavigationToggleElement.addEventListener('click', this._toggleClickListener);
}
this.mainNavigationElement=this.element.querySelector('.header__main-navigation');
this.switchElement=document.querySelector('.switch');
About_Pop.whenIsReady.then(()=> this.publishDimensions());
About_Pop.whenIsLoaded.then(()=> this.publishDimensions());
window.addEventListener('resize', this._resizeListener);
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
if(this.mainNavigationToggles){
for(const mainNavigationToggle of this.mainNavigationToggles){
mainNavigationToggle.element.removeEventListener('click', this._toggleClickListener);
mainNavigationToggle.destroy();
}}
window.removeEventListener('resize', this._resizeListener);
return resolve();
});
return promise;
}
publishDimensions(){
const body=document.querySelector('body');
if(this.element&&! body.classList.contains('navigation-is-active') ){
const headerHeight=this.element.clientHeight;
body.style.setProperty('--header-height', headerHeight + 'px');
}
if(this.mainNavigationElement) body.style.setProperty('--main-navigation-height', this.mainNavigationElement.clientHeight + 'px');
if(this.switchElement) body.style.setProperty('--switch-height', this.switchElement.clientHeight + 'px');
}
open(){
const promise=new Promise(( resolve, reject)=> {
const body=document.querySelector('body');
if(body.classList.contains('navigation-is-active') ) return resolve();
body.classList.add('navigation-is-active');
if(this.mainNavigationToggles){
for(const mainNavigationToggle of this.mainNavigationToggles) mainNavigationToggle.setState('active');
}
About_Pop.Frontend?.Accessibility?.close();
About_Pop.Frontend?.Program?.closeFilterOverlay();
if(! this.innerElement||! this.innerElement.hasTransitionEvents()){
return resolve();
}else{
this.innerElement.addEventListener('transitionend', event=> resolve(), { once: true });
}});
return promise;
}
close(){
const promise=new Promise(( resolve, reject)=> {
const body=document.querySelector('body');
if(! body.classList.contains('navigation-is-active') ) return resolve();
body.classList.remove('navigation-is-active');
if(this.mainNavigationToggles){
for(const mainNavigationToggle of this.mainNavigationToggles) mainNavigationToggle.setState('default');
}
About_Pop.Frontend?.Accessibility?.close();
if(! this.innerElement||! this.innerElement.hasTransitionEvents()){
return resolve();
}else{
this.innerElement.addEventListener('transitionend', event=> resolve(), { once: true });
}});
return promise;
}
toggle(){
const body=document.querySelector('body');
if(! body.classList.contains('navigation-is-active') ){
return this.open();
}else{
return this.close();
}}
};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Festival=class Festival {
static isInitialized=false;
constructor(element, args={}){
this.element=element;
if(! this.element) return;
if(this.element.Festival) this.element.Festival.destroy();
this.element.Festival=this;
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Button=class Button {
static isInitialized=false;
constructor(element=document.querySelector('.button'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.Button) this.element.Button.destroy();
this.element.Button=this;
args=About_Pop.Helper.parseArgs(args,
{
state: this.element.getAttribute('data-state')
}
);
this.constructor.initiator();
const State_Handler_Trait=(About_Pop.CONFIG?.Component?.State?.Handler?.Class ?? 'About_Pop.Component.State.Handler').toScope();
if(! State_Handler_Trait) return;
Object.assign(this, State_Handler_Trait);
this.constructStates();
this.setState(args.state);
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.State=class State {
static isInitialized=false;
constructor(element=document.querySelector('.state'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.State) this.element.State.destroy();
this.element.State=this;
args=About_Pop.Helper.parseArgs(args,
{
slug: this.element.getAttribute('data-slug')
}
);
this.constructor.initiator();
this.slug=args.slug.trim();
this.isActive=false;
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.State=About_Pop.Component.State||{};
About_Pop.Component.State.Handler={
states: [],
State: null,
constructStates(stateElements){
this.states=[];
if(! this.element) return;
stateElements=stateElements||this.element.querySelectorAll('.state');
if(! stateElements) return;
const State_Class=(About_Pop.CONFIG?.Component?.State?.Class ?? 'About_Pop.Component.State').toScope();
if(! State_Class) return;
for(const element of stateElements){
this.states.push(new State_Class(element) );
}},
setState(slug='default'){
if(! this.states) return;
for(const State of this.states) State.isActive=false;
const targetState=this.getState(slug);
if(! targetState) return;
targetState.isActive=true;
this.State=targetState;
if(this.element) this.element.setAttribute('data-state', this.State.slug);
return this.getCurrentState();
},
getState(slug){
let targetState=null;
slug=slug.trim();
for(const State of this.states){
if(State.slug==='default') targetState=State;
if(State.slug===slug) targetState=State;
}
if(! targetState) targetState=this.states[0];
return targetState;
},
getCurrentState(){
if(! this.State&&this.element) this.setState(this.element.getAttribute('data-state') );
return this.State;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Accessibility=class Accessibility {
static isInitialized=false;
constructor(element=document.querySelector('.accessibility'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.Accessibility) this.element.Accessibility.destroy();
this.element.Accessibility=this;
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this._resizeListener=event=> {
this.publishDimensions();
}
this._toggleClickListener=event=> {
const toggleElement=event.target.closest('a[href^="#accessibility:toggle:"], [data-trigger^="accessibility:toggle:"]');
if(! toggleElement) return;
let trigger=toggleElement.getAttribute('data-trigger') ?? toggleElement.getAttribute('href');
if(! trigger) return;
event.preventDefault();
trigger=trigger.split('accessibility:toggle:');
this.toggle(trigger[1]);
}
this._resetClickListener=event=> {
event.preventDefault();
this.reset();
}
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
About_Pop.whenIsReady.then(()=> this.publishDimensions());
About_Pop.whenIsLoaded.then(()=> this.publishDimensions());
window.addEventListener('resize', this._resizeListener);
this.toggleElements=document.querySelectorAll('a[href^="#accessibility:toggle:"], [data-trigger^="accessibility:toggle:"]');
for(const toggleElement of this.toggleElements) toggleElement.addEventListener('click', this._toggleClickListener);
this.resetElements=document.querySelectorAll('a[href="#accessibility:reset"], [data-trigger="accessibility:reset"]');
for(const resetElement of this.resetElements) resetElement.addEventListener('click', this._resetClickListener);
this.options=JSON.parse(localStorage.getItem('aboutpop-accessibility') ) ?? [];
this.set();
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
window.removeEventListener('resize', this._resizeListener);
if(this.toggleElements) for(const toggleElement of this.toggleElements) toggleElement.removeEventListener('click', this._toggleClickListener);
if(this.resetElements) for(const resetElement of this.resetElements) resetElement.removeEventListener('click', this._resetClickListener);
return resolve();
});
return promise;
}
publishDimensions(){
if(! this.element) return;
const body=document.querySelector('body');
body.style.setProperty('--accessibility-width', this.element.clientWidth + 'px');
body.style.setProperty('--accessibility-height', this.element.clientHeight + 'px');
}
toggle(todo=null){
const body=document.querySelector('body');
switch(todo){
case 'self':
if(this.element) this.element.classList.toggle('is-active');
body.classList.toggle('accessibility-is-active');
break;
default:
this._toggleOption(todo);
break;
}
this.set();
}
_toggleOption(option){
if(! option) return;
const index=this.options.indexOf(option);
if(index===-1){
this.options.push(option);
}else{
this.options.splice(index, 1);
}}
close(){
const body=document.querySelector('body');
if(this.element) this.element.classList.remove('is-active');
body.classList.remove('accessibility-is-active');
}
set(){
const body=document.querySelector('body');
localStorage.setItem('aboutpop-accessibility', JSON.stringify(this.options) );
body.setAttribute('data-accessibility', this.options);
this.publishDimensions();
if(About_Pop?.Frontend?.Program?.events) for(const Event of About_Pop.Frontend.Program.events) Event.publishDimensions();
}
reset(){
this.options=[];
this.set();
}};
var About_Pop=About_Pop||{};
About_Pop.View=About_Pop.View||{};
About_Pop.View.Page=class Page {
static isInitialized=false;
constructor(args={}){
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Image=class Image {
static isInitialized=false;
constructor(element=document.querySelector('.image'), args={}){
this.element=element;
if(! this.element) return;
if(this.element.Image) this.element.Image.destroy();
this.element.Image=this;
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Section=class Section {
static isInitialized=false;
constructor(element, args={}){
this.element=element;
if(! this.element) return;
if(this.element.Section) this.element.Section.destroy();
this.element.Section=this;
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};
var About_Pop=About_Pop||{};
About_Pop.Component=About_Pop.Component||{};
About_Pop.Component.Footer=class Footer {
static isInitialized=false;
constructor(element, args={}){
this.element=element;
if(! this.element) return;
if(this.element.Footer) this.element.Footer.destroy();
this.element.Footer=this;
args=About_Pop.Helper.parseArgs(args,
{}
);
this.constructor.initiator();
this.build()
.then(()=> {
});
}
static initiator(){
if(this.isInitialized) return;
this.isInitialized=true;
}
build(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}
destroy(){
const promise=new Promise(( resolve, reject)=> {
return resolve();
});
return promise;
}};