// Check if client is using a mobile phone
const getMobileOS = ( ) => {
const ua = navigator . userAgent
if ( /android/i . test ( ua ) ) {
return "Android"
}
else if ( /iPad|iPhone|iPod/ . test ( ua ) || ( navigator . platform === 'MacIntel' && navigator . maxTouchPoints > 1 ) ) {
return "iOS"
}
return "Other"
}
const isRunningOn = getMobileOS ( ) ;
if ( isRunningOn != 'Other' ) {
alert ( 'Whoop, it seems that you attempt to access this website on a ' + isRunningOn + ' mobile/tablet, which might not work properly. You should better use a computer instead.' ) ;
}
// Loads the Youtube IFrame Player API code asynchronously.
let tag = document . createElement ( 'script' ) ;
tag . src = 'https://www.youtube.com/iframe_api' ;
let firstScriptTag = document . getElementsByTagName ( 'script' ) [ 0 ] ;
firstScriptTag . parentNode . insertBefore ( tag , firstScriptTag ) ;
//THE DJ CROSSFADER!!!
let crossfader = document . querySelector ( '#crossfader' ) ;
class player {
trackID ;
player ;
crossfaderCoef ;
constructor ( id ) {
this . id = id ;
this . iframe = document . querySelector ( '#' + this . id ) ;
this . node = this . iframe . parentNode ;
this . volume = this . node . querySelector ( '.volume-slider' ) . value ;
this . volSliderVal = this . node . querySelector ( '.volume-slider-value' ) ;
this . volVal = this . node . querySelector ( '.volume-value' )
this . volSliderVal . innerHTML = this . volume + '%' ;
this . loadButton = this . node . querySelector ( '.load-button' ) ;
this . speedSlider = this . node . querySelector ( '.speed-slider' ) ;
this . speedSlider . nextElementSibling . querySelector ( '.speed-value' ) . innerHTML = this . speedSlider . value ;
}
init ( trackID ) {
this . trackID = trackID ;
let src = ` https://www.youtube-nocookie.com/embed/ ${ trackID } ?disablekb=1&enablejsapi=1&playsinline=1&autoplay=0&rel=0&modestbranding=1&iv_load_policy=3&version=3 ` ;
this . iframe . setAttribute ( 'src' , src ) ;
this . player = new YT . Player ( this . id , {
events : {
'onReady' : this . onPlayerReady . bind ( this ) ,
'onStateChange' : this . onPlayerStateChange . bind ( this ) ,
'onError' : this . onPlayerError . bind ( this )
}
} ) ;
}
onPlayerReady ( e ) {
this . player . setVolume ( this . volume ) ;
this . node . querySelector ( '.volume-slider' ) . addEventListener ( 'input' , ( e ) => {
this . volume = e . target . value ;
this . volSliderVal . innerHTML = this . volume + '%' ;
this . volumeUpdate ( ) ;
} ) ;
this . loadButton . addEventListener ( 'click' , ( ) => {
let url = prompt ( 'Enter a YouTube URL (track or playlist):' , 'https://www.youtube.com/watch?v=' + this . trackID ) ;
if ( url ) {
let resp = check _url ( url ) ;
if ( resp < 0 ) {
alert ( 'Wrong URL...' ) ;
console . log ( 'Nothing to load!' ) ;
} else if ( resp == 0 ) {
let newTrackID = get _video _id ( url ) ;
let newPlaylistID = get _playlist _id ( url ) ;
if ( newPlaylistID [ 0 ] != 0 ) {
console . log ( 'playlistid to load: ' + newPlaylistID ) ;
this . loadPlaylist ( newPlaylistID ) ;
} else if ( newTrackID != 0 ) {
console . log ( 'track to load: ' + newTrackID ) ;
this . load ( newTrackID ) ;
} else {
alert ( 'Wrong URL...' ) ;
console . log ( 'Nothing to load!' ) ;
}
} else if ( resp == 1 ) {
// fetch_bandcamp_url(url);
alert ( 'Wrong URL...' ) ;
console . log ( 'Nothing to load!' ) ;
}
}
} ) ;
this . speedSlider . addEventListener ( 'input' , ( e ) => {
this . speedSlider . nextElementSibling . querySelector ( '.speed-value' ) . innerHTML = e . target . value ;
this . player . setPlaybackRate ( Number ( e . target . value ) ) ;
} ) ;
this . volumeUpdate ( ) ;
}
onPlayerStateChange ( e ) {
console . log ( this . id + ' state change: ' + e . data ) ;
}
onPlayerError ( e ) {
console . log ( this . id + ' - Error: ' + e . data ) ;
}
load ( trackID ) {
this . player . loadVideoById ( trackID ) ;
}
loadPlaylist ( playlistID ) {
//First element should be the playlist ID, second element the index of the video to play
let list = playlistID [ 0 ] ;
let index = playlistID [ 1 ] ;
this . player . loadPlaylist ( {
list : list ,
listType : 'playlist' ,
index : index
} ) ;
}
volumeUpdate ( ) {
let outputVolume = Math . round ( this . volume * this . crossfaderCoef ) ;
this . player . setVolume ( outputVolume ) ;
this . volVal . innerHTML = outputVolume + '%' ;
let boxShadowStyle = '0 0 3em #ddbddd'
this . iframe . style . boxShadow = boxShadowStyle + componentToHex ( Math . round ( this . crossfaderCoef * 255 ) ) ;
}
}
let playerA = new player ( 'playerA' ) ;
let playerB = new player ( 'playerB' ) ;
function onYouTubeIframeAPIReady ( ) {
playerA . init ( 'anWaxGNDRJ4' )
playerB . init ( 'PhEuAuhH418' ) ;
}
calcCrossfaderCoefs ( crossfader . value ) ;
crossfader . addEventListener ( 'input' , ( e ) => {
//Makes the slider stick on the middle
if ( Math . abs ( crossfader . value - 500 ) < 20 ) {
crossfader . value = 500 ;
}
calcCrossfaderCoefs ( crossfader . value ) ;
playerA . volumeUpdate ( ) ;
playerB . volumeUpdate ( ) ;
// updateCrossfader(crossfader.value);
} ) ;
function calcCrossfaderCoefs ( input ) {
let normVal = input / 1000 ;
//Classic 'transition' curve
let coefB = Math . min ( normVal * 2 , 1 ) ;
let coefA = Math . min ( ( 1 - normVal ) * 2 , 1 ) ;
//Cheap smooth curve
// let coefB = normVal ** 2;
// let coefA = (1 - normVal) ** 2;
//Amplitude-preserving curve (but not power)
// let coefB = (normVal ** 2) * (3 - 2 * normVal);
// let coefA = 1 - coefB;
playerA . crossfaderCoef = coefA ;
playerB . crossfaderCoef = coefB ;
}
function check _url ( url ) {
let test = url . indexOf ( 'youtu' ) ;
if ( test > - 1 && test < 13 ) {
return 0 ; //It's most likely a youtube link
} else if ( url . indexOf ( '.bandcamp' ) > - 1 || url . indexOf ( '/album/' ) > - 1 || url . indexOf ( '/track/' ) > - 1 ) {
return 1 ; //It's most likely a bandcamp link
} else {
return - 1 ; //Wrong link
}
}
// From https://stackoverflow.com/a/55616161/14182148
function get _playlist _id ( url ) {
let VID _REGEX = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
let regPlaylist = /[?&]list=([^#\&\?]+)/ ;
let regIndex = /[?&]index=([^#]+)/ ;
let match = url . match ( regPlaylist ) ;
console . log ( 'match ' + match ) ;
let matchIndex = url . match ( regIndex ) ;
console . log ( 'matchindex ' + matchIndex ) ;
// console.log('regexp ' + match.match(/=LL([^#\&\?]*)/));
let index = 0 ;
if ( matchIndex != null ) {
index = matchIndex [ 1 ] - 1 ;
}
if ( match != null && match [ 1 ] . lenght > 2 ) { // lenght > 2 prevents from trying to load a "list=LL" (liked videos of signed-in user)
return [ match [ 1 ] , index ] ;
} else {
return [ 0 , 0 ] ;
}
}
function get _video _id ( url ) { //originally 'video_id_from_playlist'
let VID _REGEX = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
let video _id = url . match ( VID _REGEX ) ;
if ( video _id != null ) {
return video _id [ 2 ] ;
} else {
return 0 ;
}
}
function fetch _bandcamp _url ( url ) {
let iframe = document . createElement ( 'iframe' ) ;
iframe . setAttribute ( 'class' , 'hidden' ) ;
iframe . setAttribute ( 'src' , url ) ;
playerB . node . parentNode . appendChild ( iframe ) ;
console . log ( iframe ) ;
let scrap = iframe . contentWindow . document . querySelector ( 'script[data-tralbum]' ) ;
console . log ( 'scrap: ' + scrap ) ;
}
// Thumbnails: https://i1.ytimg.com/vi/{trackID}/mqdefault.jpg
// Converts values from 0-255 to 00-ff
function componentToHex ( c ) {
var hex = c . toString ( 16 ) ;
return hex . length == 1 ? "0" + hex : hex ;
}
// -----------------------------------
// BANDCAMP Tests
// -----------------------------------
// let surl = 'https://bandcamp.com/api/bcsearch_public_api/1/autocomplete_elastic';
// let req = {"search_text": "techno",
// "search_filter": "",
// "full_page": false,
// "fan_id": null
// };
// let bcURL = 'https://clett.bandcamp.com/track/placenta';
// let request = new Request(bcURL);
// let headers = new Headers({
// 'X-Requested-With': 'com.bandcamp.android',
// 'Content-Type': 'application/json',
// 'User-Agent': 'Dalvik/2.1.0 (Linux; U; Android 9; Unknown Device)',
// 'Host': 'bandcamp.com',
// });
//
// let data;
// fetch(bcURL, {
// method: 'GET',
// headers: headers
// })
// .then(response => response.json())
// .then(data => console.log('data: ' + data));
// console.log('response: ' + data);
// console.log('data: ' + data);
// let html = async await fetch(bcURL).text(); // html as text
// let doc = new DOMParser().parseFromString(html, 'text/html');
// doc.title; doc.body;
// let Http = new XMLHttpRequest();
// Http.open("GET", bcURL);
// Http.send();
//
// Http.onreadystatechange = (e) => {
// console.log(Http.responseText)
// }
// -----------------------------------
// BACKGROUND
// -----------------------------------
let areStarsShooting = true ;
let shootingStarWrapper = document . getElementsByClassName ( 'shooting-stars-wrapper' ) [ 0 ] ;
let shootingStar ;
function newShootingStar ( ) {
shootingStar = document . createElement ( 'div' ) ;
let topStart = Math . floor ( 0.6 * window . innerHeight * Math . random ( ) ) ;
let leftStart = Math . floor ( window . innerWidth * ( 0.1 + 0.8 * Math . random ( ) ) ) ;
let duration = 1000 + Math . floor ( Math . random ( ) * 5000 ) ;
let distance = 200 + Math . floor ( Math . random ( ) * 500 ) ;
let angle = Math . floor ( Math . random ( ) * 30 ) - 15 ;
let direction = Math . random ( ) < 0.5 ? - 1 : 1 ;
shootingStar . style . top = topStart + 'px' ;
shootingStar . style . left = leftStart + 'px' ;
// root.style.setProperty('--shooting-star-duration', duration + "px");
// shootingStarCSS.insertRule(':root{--shooting-star-duration: '+ duration + 'ms}');
document . documentElement . style . setProperty ( '--shooting-star-duration' , duration + "ms" ) ;
document . documentElement . style . setProperty ( '--shooting-star-translate' , distance + "px" ) ;
document . documentElement . style . setProperty ( '--shooting-star-angle' , angle + "deg" ) ;
document . documentElement . style . setProperty ( '--shooting-star-direction' , direction ) ;
shootingStarWrapper . appendChild ( shootingStar ) ;
shootingStar . classList . add ( 'shooting-star' ) ;
setTimeout ( ( ) => {
shootingStar . remove ( ) ;
// let nextIn = 10;
let nextIn = 7000 + Math . floor ( Math . random ( ) * 5000 ) ;
if ( areStarsShooting ) {
setTimeout ( ( ) => {
newShootingStar ( ) ;
} , nextIn ) ;
}
} , duration ) ;
// setTimeout('newShootingStar()', duration + nextIn);
}
newShootingStar ( ) ;
document . getElementById ( 'shooting-stars-enable' ) . innerHTML = '⭐️ Disable shooting stars⭐️ ' ;
document . getElementById ( 'shooting-stars-enable' ) . addEventListener ( 'click' , ( ) => {
if ( areStarsShooting ) {
document . getElementById ( 'shooting-stars-enable' ) . innerHTML = '⭐️ Enable shooting stars⭐️ ' ;
areStarsShooting = false ;
} else {
document . getElementById ( 'shooting-stars-enable' ) . innerHTML = '⭐️ Disable shooting stars⭐️ ' ;
areStarsShooting = true ;
newShootingStar ( ) ;
}
} ) ;
// Enable/disable shooting stars (with P5JS background)
// if (typeof processingSketch !== 'undefined') {
// let separatorSpan = document.createElement('span');
// separatorSpan.innerHTML = ' - ';
// document.getElementById('shooting-stars-enable').after(separatorSpan);
// document.getElementById('shooting-stars-enable').innerHTML = 'Disable shooting stars⭐️ ';
// }
// document.getElementById('shooting-stars-enable').addEventListener('click', () => {
// if (processingIsOn) {
// processingSketch.frameRate(0);
// document.getElementById('shooting-stars-enable').innerHTML = 'Enable shooting stars⭐️ ';
// processingIsOn = false;
// } else {
// processingSketch.frameRate(24);
// document.getElementById('shooting-stars-enable').innerHTML = 'Disable shooting stars⭐️ ';
// processingIsOn = true;
// }
// });
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- EMAIL OBFUSCATION - found here http : //www.grall.name/posts/1/antiSpam-emailAddressObfuscation.html
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /
document . getElementById ( 'contact-me' ) . addEventListener ( 'click' , ( e ) => {
console . log ( 'okay' ) ;
var y = decode ( "pbagnpg@gsypy.klm" ) ; //https://rot13.com/
e . target . setAttribute ( "href" , "mailto:" + y ) ;
} )
function decode ( a ) {
// ROT13 : a Caesar cipher
// letter -> letter' such that code(letter') = (code(letter) + 13) modulo 26
return a . replace ( /[a-zA-Z]/g , function ( c ) {
return String . fromCharCode ( ( c <= "Z" ? 90 : 122 ) >= ( c = c . charCodeAt ( 0 ) + 13 ) ? c : c - 26 ) ;
} )
} ;