diff --git a/assets/img/equaliser-animated.gif b/assets/img/equaliser-animated.gif
new file mode 100644
index 0000000..27285ed
Binary files /dev/null and b/assets/img/equaliser-animated.gif differ
diff --git a/assets/js/player/data.js b/assets/js/player/data.js
new file mode 100644
index 0000000..a8f68b1
--- /dev/null
+++ b/assets/js/player/data.js
@@ -0,0 +1 @@
+export default Array.from(document.querySelectorAll(".listitem")).map(t=>({title:t.getAttribute("data-title"),artist:t.getAttribute("data-artist"),cover:t.getAttribute("data-cover"),file:t.getAttribute("data-file")}));
\ No newline at end of file
diff --git a/assets/js/player/elements.js b/assets/js/player/elements.js
new file mode 100644
index 0000000..db9e72c
--- /dev/null
+++ b/assets/js/player/elements.js
@@ -0,0 +1 @@
+import*as utils from"./utils.js";export default{get(){this.cover=document.querySelector("#track-cover"),this.title=document.querySelector("#track-title"),this.artist=document.querySelector("#track-artist"),this.playPauseButton=document.querySelector("#play-pause"),this.nextButton=document.querySelector("#next"),this.previousButton=document.querySelector("#previous"),this.volumeButton=document.querySelector("#volume"),this.volumeControl=document.querySelector("#volume-control"),this.seekbar=document.querySelector("#seekbar"),this.currentDuration=document.querySelector("#current-duration"),this.totalDuration=document.querySelector("#total-duration"),this.downloadButton=document.querySelector("#download"),this.collection=document.querySelectorAll(".listitem"),this.playIcon='',this.pauseIcon='',this.mutedIcon='',this.soundIcon=''},createAudioElement(t){this.audio=new Audio(t)},actions(){this.playPauseButton.onclick=(()=>this.togglePlayPause()),this.audio.onended=(()=>this.next()),this.volumeButton.onclick=(()=>this.toggleMute()),this.volumeControl.onchange=(()=>this.setVolume(this.volumeControl.value)),this.seekbar.onchange=(()=>this.setSeekbar(this.seekbar.value)),this.seekbar.max=this.audio.duration,this.totalDuration.innerText=utils.convertTo12HourFormat(this.audio.duration),this.audio.ontimeupdate=(()=>this.timeUpdate()),this.nextButton.onclick=(()=>this.next()),this.previousButton.onclick=(()=>this.back()),this.collection.forEach((t,e)=>t.onclick=(()=>this.swap(e))),"mediaSession"in navigator&&(navigator.mediaSession.setActionHandler("play",()=>this.togglePlayPause()),navigator.mediaSession.setActionHandler("pause",()=>this.togglePlayPause()),navigator.mediaSession.setActionHandler("nexttrack",()=>this.next()),navigator.mediaSession.setActionHandler("previoustrack",()=>this.back()),navigator.mediaSession.setActionHandler("seekto",t=>{t.seekTime&&"number"==typeof t.seekTime&&this.setSeekbar(t.seekTime)}))}};
\ No newline at end of file
diff --git a/assets/js/player/index.js b/assets/js/player/index.js
new file mode 100644
index 0000000..27d8e72
--- /dev/null
+++ b/assets/js/player/index.js
@@ -0,0 +1 @@
+import player from"./player.js";window.addEventListener("load",()=>{player.start()});
\ No newline at end of file
diff --git a/assets/js/player/player.js b/assets/js/player/player.js
new file mode 100644
index 0000000..c1f8909
--- /dev/null
+++ b/assets/js/player/player.js
@@ -0,0 +1 @@
+import audios from"./data.js";import elements from"./elements.js";import*as utils from"./utils.js";export default{audioData:audios,currentPlaying:utils.currentIndex(),currentAudio:{},isPlaying:!1,savedMuted:!1,savedVolume:1,start(){elements.get.call(this),this.update(),this.volumeControl.value=100},play(){this.isPlaying=!0,this.audio.play(),this.playPauseButton.innerHTML=this.pauseIcon},pause(){this.isPlaying=!1,this.audio.pause(),this.playPauseButton.innerHTML=this.playIcon},togglePlayPause(){this.isPlaying?this.pause():this.play()},swap(t){this.currentPlaying=t,this.pause(),this.update(),this.play()},next(){let t=this.currentPlaying+1;t===this.audioData.length&&(t=0),this.swap(t)},back(){let t=this.currentPlaying-1;-1===t&&(t=this.audioData.length-1),this.swap(t)},toggleMute(){this.audio.muted=!this.audio.muted,this.savedMuted=this.audio.muted,this.volumeButton.innerHTML=this.audio.muted?this.mutedIcon:this.soundIcon},setVolume(t){this.savedVolume=t/100,this.audio.volume=this.savedVolume},setSeekbar(t){this.audio.muted=!0,this.audio.currentTime=t},timeUpdate(){const t=this.audio.currentTime,i=utils.convertTo12HourFormat(t);Math.ceil(this.seekbar.value)!=Math.ceil(t)&&(this.seekbar.value=t),this.currentDuration.innerText!==i&&(this.currentDuration.innerText=i),this.savedMuted||this.seekbar.dragging||(this.audio.muted=!1),"mediaSession"in navigator&&"setPositionState"in navigator.mediaSession&&navigator.mediaSession.setPositionState({duration:this.audio.duration||0,playbackRate:this.audio.playbackRate||1,position:this.audio.currentTime||0})},update(){(this.currentPlaying<0||this.currentPlaying>this.audioData.length)&&(this.currentPlaying=0),this.currentAudio=this.audioData[this.currentPlaying],this.cover.src=utils.imageURL(this.currentAudio.cover),this.title.innerText=this.currentAudio.title,this.artist.innerText=this.currentAudio.artist,"mediaSession"in navigator&&(navigator.mediaSession.metadata=new MediaMetadata({title:this.currentAudio.title,artist:this.currentAudio.artist,artwork:[{src:this.cover.src,sizes:"512x512",type:"image/webp"}]}));const t=utils.audioURL(this.currentAudio.file);elements.createAudioElement.call(this,t),this.downloadButton.href=t,this.downloadButton.download=utils.generateFilename(this.currentAudio.file,this.currentAudio.artist,this.currentAudio.title),window.location.hash=this.currentPlaying+1,document.title=this.currentAudio.title+" | xmp3",this.audio.onloadeddata=(()=>{elements.actions.call(this),this.audio.muted=this.savedMuted,this.audio.volume=this.savedVolume})}};
\ No newline at end of file
diff --git a/assets/js/player/utils.js b/assets/js/player/utils.js
new file mode 100644
index 0000000..26a1332
--- /dev/null
+++ b/assets/js/player/utils.js
@@ -0,0 +1 @@
+export function URL(t="/"){return"https://xmp3.github.io"+t};export function imageURL(t){return t.startsWith("https://")?t:URL("/image/"+t)};export function audioURL(t){return t.startsWith("https://")?t:URL("/audio/"+t)};export function currentIndex(){return window.location.hash.slice(1)-1||0};export function convertTo12HourFormat(t){const n=t=>("0"+Math.floor(t)).slice(-2);return n(t/60)+":"+n(t%60)};export function generateFilename(t,...n){const o=t=>t.split(".").pop();return n.join(" - ")+"."+o(t)};
\ No newline at end of file
diff --git a/assets/js/progressbar/index.js b/assets/js/progressbar/index.js
new file mode 100644
index 0000000..470e8f7
--- /dev/null
+++ b/assets/js/progressbar/index.js
@@ -0,0 +1 @@
+import ProgressBar from"./progressbar.js";document.addEventListener("DOMContentLoaded",()=>{document.querySelectorAll(".progressbar-container").forEach(r=>new ProgressBar(r))});
\ No newline at end of file
diff --git a/assets/js/progressbar/progressbar.js b/assets/js/progressbar/progressbar.js
new file mode 100644
index 0000000..489706a
--- /dev/null
+++ b/assets/js/progressbar/progressbar.js
@@ -0,0 +1 @@
+export default class{constructor(t){this.progressbar=t,this.properties={},this.dragging=!1,this.defineAttributes(),this.defineProperties(),this.addEventListeners()}defineAttributes(){this.attributes={min:0,max:1,step:.1,value:0},Object.keys(this.attributes).forEach(t=>this[t]=+this.progressbar.getAttribute("data-"+t)||this.attributes[t])}defineProperties(){this.properties.dragging={get:()=>this.dragging},this.properties.min={get:()=>this.min,set:t=>this.min=+t},this.properties.max={get:()=>this.max,set:t=>this.max=+t},this.properties.step={get:()=>this.step,set:t=>this.step=+t},this.properties.value={get:()=>this.value,set:t=>this.update(t)},this.properties.onchange={set:t=>this.onchange=t},Object.keys(this.properties).forEach(t=>Object.defineProperty(this.progressbar,t,this.properties[t]))}addEventListeners(){this.progressbar.addEventListener("mousedown",this.onMouseDown.bind(this)),document.addEventListener("mousemove",this.onMouseMove.bind(this)),document.addEventListener("mouseup",this.onMouseUp.bind(this)),document.addEventListener("mouseleave",this.onMouseUp.bind(this)),this.progressbar.addEventListener("touchstart",this.onTouchStart.bind(this)),document.addEventListener("touchmove",this.onTouchMove.bind(this)),document.addEventListener("touchend",this.onTouchEnd.bind(this)),document.addEventListener("touchcancel",this.onTouchEnd.bind(this))}eventHandler(t){"function"==typeof this[t]&&this[t]()}eventChange(t){const e=this.progressbar.getBoundingClientRect();let s=t-e.left,i=(s=Math.max(0,Math.min(s,e.width)))/e.width*(this.max-this.min)+this.min;this.progressbar.value=Math.round(i/this.step)*this.step,this.eventHandler("onchange")}onMouseDown(t){this.dragging=!0,this.eventChange(t.clientX)}onMouseMove(t){this.dragging&&this.eventChange(t.clientX)}onMouseUp(){this.dragging=!1}onTouchStart(t){this.dragging=!0,this.eventChange(t.touches[0].clientX)}onTouchMove(t){this.dragging&&this.eventChange(t.touches[0].clientX)}onTouchEnd(){this.dragging=!1}update(t){this.value=+t;const e=this.value/this.max*100;this.progressbar.style.setProperty("--progressbar-transform",e+"%")}};
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..f627fca
--- /dev/null
+++ b/index.html
@@ -0,0 +1 @@
+