Add song player with multiple songs and options page.
This commit is contained in:
parent
14a554639d
commit
dcc31cc85a
40 changed files with 940 additions and 70 deletions
36
static/images/gears.svg
Normal file
36
static/images/gears.svg
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="800px" height="800px" viewBox="0 0 30.998 30.998"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M18.73,16.686l-1.713-0.205c-0.176-0.654-0.433-1.271-0.763-1.844l1.063-1.354c0.164-0.209,0.145-0.531-0.043-0.718
|
||||
l-1.766-1.767c-0.187-0.187-0.509-0.206-0.717-0.044l-1.356,1.067c-0.571-0.33-1.188-0.587-1.841-0.761L11.39,9.345
|
||||
c-0.031-0.262-0.273-0.477-0.537-0.477H8.354c-0.264,0-0.506,0.215-0.537,0.477l-0.206,1.714
|
||||
c-0.653,0.174-1.271,0.432-1.842,0.762l-1.357-1.065c-0.207-0.163-0.53-0.145-0.716,0.042l-1.767,1.769
|
||||
c-0.187,0.187-0.206,0.509-0.043,0.717l1.065,1.354c-0.331,0.572-0.586,1.19-0.761,1.844l-1.713,0.205
|
||||
C0.215,16.718,0,16.959,0,17.225v2.498c0,0.265,0.215,0.506,0.477,0.536l1.714,0.207c0.175,0.651,0.431,1.269,0.761,1.841
|
||||
l-1.064,1.354c-0.163,0.21-0.144,0.532,0.043,0.719l1.765,1.767c0.186,0.188,0.509,0.207,0.716,0.045l1.357-1.068
|
||||
c0.571,0.33,1.189,0.588,1.842,0.762L7.817,27.6c0.031,0.262,0.273,0.477,0.537,0.477h2.499c0.264,0,0.506-0.215,0.537-0.477
|
||||
l0.206-1.715c0.653-0.174,1.271-0.432,1.843-0.762l1.356,1.064c0.208,0.162,0.53,0.145,0.716-0.041l1.767-1.77
|
||||
c0.187-0.188,0.207-0.51,0.043-0.717l-1.065-1.354c0.331-0.572,0.586-1.19,0.761-1.844l1.715-0.205
|
||||
c0.263-0.031,0.477-0.271,0.477-0.537v-2.498C19.209,16.957,18.994,16.718,18.73,16.686z M9.605,23.271
|
||||
c-2.651,0-4.801-2.148-4.801-4.801c0-2.652,2.15-4.802,4.801-4.802c2.652,0,4.801,2.149,4.801,4.802
|
||||
C14.407,21.123,12.257,23.271,9.605,23.271z"/>
|
||||
<path d="M30.641,8.804L29.35,8.651c-0.132-0.492-0.324-0.959-0.574-1.39l0.803-1.02c0.123-0.155,0.107-0.399-0.033-0.54
|
||||
l-1.33-1.329c-0.14-0.142-0.383-0.156-0.54-0.033l-1.021,0.803c-0.43-0.249-0.896-0.441-1.385-0.571l-0.156-1.29
|
||||
c-0.022-0.198-0.205-0.359-0.402-0.359H22.83c-0.199,0-0.381,0.161-0.404,0.359l-0.154,1.29c-0.492,0.13-0.957,0.323-1.388,0.572
|
||||
l-1.021-0.802c-0.156-0.122-0.399-0.107-0.539,0.031l-1.331,1.332c-0.142,0.141-0.155,0.383-0.032,0.539l0.803,1.021
|
||||
c-0.25,0.43-0.441,0.896-0.574,1.388L16.9,8.806c-0.198,0.023-0.359,0.206-0.359,0.405v1.881c0,0.197,0.162,0.381,0.359,0.402
|
||||
l1.289,0.157c0.133,0.49,0.326,0.955,0.574,1.386l-0.803,1.02c-0.122,0.156-0.107,0.4,0.033,0.54l1.328,1.329
|
||||
c0.141,0.143,0.383,0.156,0.539,0.033l1.021-0.804c0.43,0.249,0.896,0.442,1.387,0.572l0.155,1.292
|
||||
c0.022,0.195,0.206,0.356,0.404,0.356h1.881c0.198,0,0.38-0.161,0.403-0.356l0.154-1.292c0.492-0.13,0.957-0.323,1.387-0.572
|
||||
l1.021,0.802c0.157,0.123,0.399,0.109,0.54-0.031l1.33-1.332c0.141-0.139,0.155-0.382,0.032-0.538l-0.802-1.021
|
||||
c0.25-0.429,0.44-0.895,0.572-1.386l1.291-0.154c0.198-0.025,0.359-0.205,0.359-0.405V9.21
|
||||
C30.999,9.009,30.837,8.829,30.641,8.804z M23.771,13.764c-1.998,0-3.615-1.618-3.615-3.614c0-1.997,1.619-3.614,3.615-3.614
|
||||
c1.994,0,3.613,1.617,3.613,3.614C27.384,12.145,25.766,13.764,23.771,13.764z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
BIN
static/images/songs/pg.jpg
Normal file
BIN
static/images/songs/pg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
BIN
static/images/songs/velkommen.jpg
Normal file
BIN
static/images/songs/velkommen.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 360 KiB |
BIN
static/images/songs/winds.png
Normal file
BIN
static/images/songs/winds.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 910 KiB |
201
static/main.css
201
static/main.css
|
|
@ -31,6 +31,22 @@ li {
|
|||
list-style-type: none;
|
||||
}
|
||||
|
||||
select {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
border: none;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
#music {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
gap: .4em;
|
||||
inline-size: fit-content;
|
||||
height: 1.4em;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
border-bottom: thick solid rgba(255, 255, 255, 0.1);
|
||||
|
|
@ -64,6 +80,7 @@ header div {
|
|||
mask-repeat: no-repeat;
|
||||
mask-size: 100% 100%;
|
||||
user-select: none;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
header ul {
|
||||
|
|
@ -96,7 +113,7 @@ header ul:hover {
|
|||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 4em;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
|
|
@ -143,7 +160,6 @@ textarea, input, button {
|
|||
}
|
||||
|
||||
#sound {
|
||||
height: 1.5em;
|
||||
filter: invert();
|
||||
}
|
||||
|
||||
|
|
@ -153,20 +169,9 @@ textarea, input, button {
|
|||
|
||||
#linksHelper {
|
||||
margin: auto 1em auto auto;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
#soundDiv {
|
||||
margin-left: auto;
|
||||
inline-size: fit-content;
|
||||
padding: 0;
|
||||
padding-top: 1rem;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
#soundDiv p {
|
||||
margin: 0 auto 0 auto;
|
||||
flex-direction: column;
|
||||
gap: .2em;
|
||||
}
|
||||
|
||||
#headerLinks {
|
||||
|
|
@ -456,6 +461,163 @@ div.hs.selected {
|
|||
-webkit-box-orient: vertical
|
||||
}
|
||||
|
||||
aside.metromenu {
|
||||
z-index: 2;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 30vw;
|
||||
background-color: black;
|
||||
height: 100vh;
|
||||
transition: .2s;
|
||||
transition-timing-function: cubic-bezier(0.1, 0.2, 0.3, 0.955);
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
aside.metromenu.closed {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
aside.metromenu h2 {
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
aside.metromenu p {
|
||||
margin-bottom: .4em;
|
||||
}
|
||||
|
||||
aside.metromenu .optionsToggle {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.optionsToggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
aside.metromenu .optionsToggle {
|
||||
opacity: .6;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
aside.metromenu #content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.checkbox p {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
border: thick solid white;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb, input[type="range"]::-moz-range-thumb {
|
||||
background-color: black;
|
||||
transition: .2s;
|
||||
border-radius: 0;
|
||||
border: medium solid white;
|
||||
height: 1.2em;
|
||||
}
|
||||
|
||||
input[type="range"]:hover::-webkit-slider-thumb, input[type="range"]:hover::-moz-range-thumb {
|
||||
height: 2em;
|
||||
background-color: white;
|
||||
border-width: thin;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-runnable-track, input[type="range"]::-moz-range-track {
|
||||
background-color: white;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
#songDrawer {
|
||||
display: flex;
|
||||
height: 10em;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
gap: .2em;
|
||||
}
|
||||
|
||||
.drawerSong {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.drawerSong img {
|
||||
transition: .4s;
|
||||
width: 5em;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
opacity: .4;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.drawerSong p {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.drawerSong:hover img, .drawerSong.selected img {
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
.drawerSong.selected img {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.playlistTitle {
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#playlist {
|
||||
transition: .2s;
|
||||
max-height: 10em;
|
||||
overflow: auto;
|
||||
border: medium solid white;
|
||||
}
|
||||
|
||||
#playlist p {
|
||||
cursor: pointer;
|
||||
padding: 0 .4em;
|
||||
}
|
||||
|
||||
#playlist p:first-child {
|
||||
padding-bottom: .2em;
|
||||
}
|
||||
|
||||
.playingSong {
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.bg.invisible {
|
||||
opacity: 0!important;
|
||||
}
|
||||
|
||||
@keyframes ellipsis-loader {
|
||||
0%, 25% {
|
||||
transform: translateX(0);
|
||||
|
|
@ -508,6 +670,7 @@ div.hs.selected {
|
|||
|
||||
#headerLinks {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#homeTitle {
|
||||
|
|
@ -571,12 +734,20 @@ div.hs.selected {
|
|||
.hsProjectHeaderIcon {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
aside.metromenu {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
#homeSquares {
|
||||
width: 60vw;
|
||||
}
|
||||
|
||||
aside.metromenu {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 720px) {
|
||||
|
|
|
|||
BIN
static/music/PG2.mp3
Normal file
BIN
static/music/PG2.mp3
Normal file
Binary file not shown.
BIN
static/music/dreamscape.mp3
Normal file
BIN
static/music/dreamscape.mp3
Normal file
Binary file not shown.
BIN
static/music/skychat.mp3
Normal file
BIN
static/music/skychat.mp3
Normal file
Binary file not shown.
|
|
@ -1,25 +1,156 @@
|
|||
const toggle = document.querySelector('#soundDiv')
|
||||
toggle.innerHTML = `<img src="/static/images/sound-on.png" id="sound" onclick="toggleAudio()"><p>${toggle.getAttribute("data-title")}</p>`
|
||||
// This script handles the playback of music in the header's miniplayer ;)
|
||||
const body = document.querySelector("body");
|
||||
|
||||
document.getElementById("music").innerHTML = `
|
||||
<img src="/static/images/gears.svg" class="optionsToggle invertedc">
|
||||
<img src="/static/images/sound-on.png" id="sound">
|
||||
<select name="song" id="songSelection"></select>
|
||||
`
|
||||
|
||||
const songs = [
|
||||
{ file: "Velkommen.mp3", name: 'Velkommen', artwork: "velkommen.jpg" },
|
||||
{ file: "PG2.mp3", name: 'Frugal APE', artwork: "pg.jpg" },
|
||||
{ file: "dreamscape.mp3", name: 'Dreamscape', artwork: "winds.png" },
|
||||
{ file: "skychat.mp3", name: 'Skychat', artwork: "winds.png" }
|
||||
];
|
||||
|
||||
// Options page
|
||||
const optionsAside = document.createElement("aside");
|
||||
optionsAside.classList.add("closed");
|
||||
optionsAside.classList.add("metromenu");
|
||||
{
|
||||
const back = document.createElement("p");
|
||||
back.textContent = headeri18n.back;
|
||||
back.classList.add("optionsToggle");
|
||||
|
||||
const title = document.createElement("h2");
|
||||
title.textContent = headeri18n.options;
|
||||
optionsAside.appendChild(title);
|
||||
optionsAside.appendChild(back);
|
||||
|
||||
const content = document.createElement("div");
|
||||
content.innerHTML = `
|
||||
<div id="content">
|
||||
<div id="songDrawer"></div>
|
||||
<div>
|
||||
<p class="playingSong"></p>
|
||||
<p>${headeri18n.by} tenkuma</p>
|
||||
</div>
|
||||
<div id="playlist"></div>
|
||||
<div>
|
||||
<p>Volume</p>
|
||||
<input id="volume" oninput="setVolume(this.value / 100)" type="range" min="0" max="100"></input>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<p>${headeri18n.hideBackground}</p>
|
||||
<input id="background" type="checkbox"></input>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
optionsAside.appendChild(content);
|
||||
}
|
||||
body.appendChild(optionsAside);
|
||||
|
||||
|
||||
const audio = new Audio(`/static/${toggle.getAttribute("data-source")}`);
|
||||
const toggleIMG = document.querySelector('#sound');
|
||||
toggleIMG.addEventListener('click', () => {
|
||||
toggleAudio();
|
||||
})
|
||||
|
||||
const savedTime = localStorage.getItem("audioTime");
|
||||
const wasPlaying = localStorage.getItem("audioPlaying") === 'true'
|
||||
const hideBG = document.querySelector("input#background");
|
||||
if (localStorage.getItem("bgHidden") === "true") hideBG.checked = true, toggleBG();
|
||||
hideBG.addEventListener("click", () => {
|
||||
toggleBG();
|
||||
})
|
||||
|
||||
if (savedTime) audio.currentTime = parseFloat(savedTime);
|
||||
|
||||
if (wasPlaying) {
|
||||
play();
|
||||
} else {
|
||||
stop();
|
||||
function toggleBG() {
|
||||
const bg = document.querySelector(".bg");
|
||||
bg.classList.toggle("invisible");
|
||||
localStorage.setItem("bgHidden", bg.classList.contains("invisible"))
|
||||
}
|
||||
|
||||
const songsDrawer = document.querySelector("#songDrawer");
|
||||
const drawerSongs = [];
|
||||
const playlist = document.querySelector("#playlist");
|
||||
const expandButton = document.createElement('p');
|
||||
expandButton.textContent = "Playlist";
|
||||
expandButton.classList.add("playlistTitle");
|
||||
playlist.appendChild(expandButton);
|
||||
|
||||
songs.forEach(song => {
|
||||
const songElement = document.createElement("div");
|
||||
songElement.classList.add("drawerSong");
|
||||
songElement.dataset.song = song.file;
|
||||
const songImage = document.createElement("img");
|
||||
songImage.src = `/static/images/songs/${song.artwork}`;
|
||||
songElement.appendChild(songImage);
|
||||
songElement.addEventListener('click', () => {
|
||||
changeSong(song.file);
|
||||
});
|
||||
drawerSongs.push(songElement);
|
||||
songsDrawer.appendChild(songElement);
|
||||
|
||||
// Playlist
|
||||
const playlistEntry = document.createElement("p");
|
||||
playlistEntry.textContent = song.name;
|
||||
playlistEntry.addEventListener('click', () => {
|
||||
changeSong(song.file);
|
||||
})
|
||||
playlist.appendChild(playlistEntry);
|
||||
})
|
||||
|
||||
const audioSelect = document.getElementById("songSelection");
|
||||
songs.forEach(song => {
|
||||
const songOption = document.createElement("option");
|
||||
songOption.value = song.file;
|
||||
songOption.textContent = song.name;
|
||||
audioSelect.appendChild(songOption);
|
||||
});
|
||||
|
||||
const playingSongLabel = document.querySelector(".playingSong");
|
||||
|
||||
function updatePlayingLabel(label) {
|
||||
drawerSongs.forEach(sng => {
|
||||
sng.classList.remove("selected");
|
||||
if (sng.dataset.song == label) {
|
||||
sng.classList.add("selected");
|
||||
}
|
||||
});
|
||||
|
||||
const songString = songs.find(item => item.file === label).name;
|
||||
playingSongLabel.textContent = songString;
|
||||
}
|
||||
|
||||
const savedSong = localStorage.getItem("song");
|
||||
|
||||
if (savedSong) {
|
||||
audioSelect.value = savedSong;
|
||||
updatePlayingLabel(savedSong);
|
||||
} else {
|
||||
audioSelect.value = songs[0].file;
|
||||
updatePlayingLabel(songs[0].file);
|
||||
}
|
||||
|
||||
const optionsButton = document.querySelectorAll(".optionsToggle");
|
||||
optionsButton.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
optionsAside.classList.toggle('closed');
|
||||
});
|
||||
});
|
||||
|
||||
// Create the audio object using the current select value
|
||||
let audio = new Audio(`/static/music/${audioSelect.value}`);
|
||||
|
||||
const savedTime = localStorage.getItem("audioTime");
|
||||
const savedVolume = localStorage.getItem("volume");
|
||||
const wasPlaying = localStorage.getItem("audioPlaying") === 'true';
|
||||
|
||||
function play() {
|
||||
audio.volume = 0.8;
|
||||
audio.volume = localStorage.getItem("volume");
|
||||
audio.play();
|
||||
localStorage.setItem("audioPlaying", "true")
|
||||
toggleIMG.src = "/static/images/sound-on.png"
|
||||
console.log(`[Music Player] playing ${audioSelect.value}`)
|
||||
}
|
||||
|
||||
function stop() {
|
||||
|
|
@ -28,6 +159,11 @@ function stop() {
|
|||
toggleIMG.src = "/static/images/sound-off.png"
|
||||
}
|
||||
|
||||
function setVolume(volume) {
|
||||
audio.volume = volume;
|
||||
localStorage.setItem("volume", volume);
|
||||
}
|
||||
|
||||
function toggleAudio() {
|
||||
if (!audio.paused) {
|
||||
stop();
|
||||
|
|
@ -37,6 +173,34 @@ function toggleAudio() {
|
|||
}
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
localStorage.setItem("audioTime", audio.currentTime);
|
||||
localStorage.setItem("audioPlaying", !audio.paused);
|
||||
});
|
||||
localStorage.setItem("audioTime", audio.currentTime);
|
||||
localStorage.setItem("audioPlaying", !audio.paused);
|
||||
});
|
||||
|
||||
function changeSong(song) {
|
||||
const wasPlaying = !audio.paused;
|
||||
stop();
|
||||
localStorage.removeItem("audioTime");
|
||||
audio = new Audio(`/static/music/${song}`);
|
||||
if (savedVolume) setVolume(savedVolume);
|
||||
console.log(`[Music Player] changing song to ${song}`)
|
||||
localStorage.setItem("song", song);
|
||||
updatePlayingLabel(song);
|
||||
if (wasPlaying) play();
|
||||
}
|
||||
|
||||
// hooking into the options menu 'change' event to update the song
|
||||
audioSelect.addEventListener('change', () => {
|
||||
changeSong(audioSelect.value);
|
||||
})
|
||||
|
||||
// Set initial playback state and volume based on saved preferences
|
||||
if (savedTime) audio.currentTime = parseFloat(savedTime);
|
||||
|
||||
if (savedVolume) document.getElementById("volume").value = savedVolume * 100;
|
||||
|
||||
if (wasPlaying) {
|
||||
play();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue