@@ -18,6 +18,8 @@ | |||
"babel-eslint": "^10.1.0", | |||
"eslint": "^6.7.2", | |||
"eslint-plugin-vue": "^6.2.2", | |||
"fs": "^0.0.1-security", | |||
"jsmediatags": "^3.9.3", | |||
"vue-template-compiler": "^2.6.11" | |||
}, | |||
"eslintConfig": { | |||
@@ -0,0 +1,98 @@ | |||
const jsmediatags = require('jsmediatags'); | |||
const fs = require('fs'); | |||
const INPUT = './public/audio/'; | |||
const OUTPUT = 'src/data.json'; | |||
const data = {"albums" : []}; | |||
const promises = []; | |||
try { | |||
if (fs.existsSync(INPUT)) { | |||
//file exists | |||
} | |||
else { | |||
console.log("\n============== Weband Error ====================\n\n"); | |||
console.log("Audio files are missing. Please run first : \n\n$ ./scripts/weband-init.sh <input_directory> \n"); | |||
console.log("\n================================================\n\n"); | |||
process.exit(1); | |||
} | |||
} catch(err) { | |||
console.log(err); | |||
process.exit(1); | |||
} | |||
function is_dir(path) { | |||
try { | |||
var stat = fs.lstatSync(path); | |||
return stat.isDirectory(); | |||
} catch (e) { | |||
// lstatSync throws an error if path doesn't exist | |||
return false; | |||
} | |||
} | |||
function id3ToJSON(path){ | |||
fs.readdirSync(path).forEach(file => { | |||
let isDir = is_dir(path + file); | |||
if(isDir) | |||
id3ToJSON(path + file + '/'); | |||
else { | |||
let filePath = path + file; | |||
promises.push(new Promise((resolve, reject) => { | |||
new jsmediatags.Reader(filePath) | |||
.read({ | |||
onSuccess: (tag) => { | |||
var tags = tag.tags; | |||
if(!data.artist) | |||
data.artist = tags.artist; | |||
let album = getAlbum(tags.album); | |||
if(!album){ | |||
album = {"title": tags.album, "tracks": [], "year": tags.year} | |||
data.albums.push(album); | |||
} | |||
let track = {"title": tags.title, "file" : filePath.replace(INPUT,'audio/')}; | |||
album.tracks.push(track); | |||
resolve(tag); | |||
}, | |||
onError: (error) => { | |||
console.log('Error'); | |||
console.log(error); | |||
reject(error); | |||
} | |||
}); | |||
})); | |||
} | |||
}); | |||
} | |||
function getAlbum(albumTitle){ | |||
for(let i = 0 ; i < data.albums.length; i++){ | |||
if(data.albums[i].title === albumTitle) | |||
return data.albums[i]; | |||
} | |||
return null; | |||
} | |||
id3ToJSON(INPUT); | |||
Promise.all(promises).then((values) => { | |||
var jsonContent = JSON.stringify(data, null, 2); | |||
console.log(jsonContent); | |||
fs.writeFile(OUTPUT, jsonContent, 'utf8', function (err) { | |||
if (err) { | |||
console.log("An error occured while writing JSON Object to File."); | |||
return console.log(err); | |||
} | |||
console.log("JSON file has been saved : " + OUTPUT); | |||
}); | |||
}); |
@@ -0,0 +1,85 @@ | |||
#!/bin/bash | |||
DIRECTORY=$(cd `dirname $0` && pwd) | |||
IMAGES_DIRECTORY=$DIRECTORY'/../src/assets/images/' | |||
AUDIOS_DIRECTORY=$DIRECTORY'/../public/audio/' | |||
ICONO_HEADER='icono_1.png' | |||
ICONO_FOOTER='icono_2.png' | |||
display_usage(){ | |||
echo "" | |||
echo " webbandd script" | |||
echo "" | |||
echo " usage : webbandd.sh <input_directory>" | |||
echo "" | |||
echo " <input_directory> must contains :" | |||
echo " - two image files : icono_1.png & icono_2.png" | |||
echo " - one folder per album with mp3 and/or wav audio files" | |||
echo "" | |||
} | |||
error(){ | |||
echo "" | |||
echo " Weband ERROR :" | |||
echo "" | |||
echo " "$1 | |||
echo "" | |||
} | |||
# 1 argument required | |||
if [ $# -ne 1 ] | |||
then | |||
display_usage | |||
exit 1 | |||
fi | |||
# check_image(){ | |||
# if [[ -e $1'.svg' || -e $1'.png' || -e $1'.jpg' ]] | |||
# then | |||
# return 0 | |||
# else | |||
# error $1' image file is missing !' | |||
# exit -1 | |||
# fi | |||
# } | |||
check_image(){ | |||
if [ -e $1 ] | |||
then | |||
return 0 | |||
else | |||
error $1' image file is missing !' | |||
exit -1 | |||
fi | |||
} | |||
check_image $1'/'$ICONO_HEADER | |||
echo " => "$1'/'$ICONO_HEADER" image exists - OK " | |||
check_image $1'/'$ICONO_FOOTER | |||
echo " => "$1'/'$ICONO_FOOTER" image exists - OK " | |||
#copy images (cover and contact) | |||
cp $1'/'$ICONO_HEADER $IMAGES_DIRECTORY | |||
cp $1'/'$ICONO_FOOTER $IMAGES_DIRECTORY | |||
if [ -d $AUDIOS_DIRECTORY ] | |||
then | |||
rm -rf $AUDIOS_DIRECTORY | |||
fi | |||
mkdir $AUDIOS_DIRECTORY | |||
for d in `find $1/* -type d` | |||
do | |||
echo ' copying audio directory "'$d'"' | |||
cp -r $d $AUDIOS_DIRECTORY | |||
done | |||
echo " => audio files copy - OK " | |||
node $DIRECTORY'/weband-id3-to-json.js' |
@@ -1,28 +1,38 @@ | |||
<template> | |||
<div id="app"> | |||
<img alt="Vue logo" src="./assets/logo.png"> | |||
<HelloWorld msg="Welcome to Your Vue.js App"/> | |||
<Weband/> | |||
</div> | |||
</template> | |||
<script> | |||
import HelloWorld from './components/HelloWorld.vue' | |||
import Weband from './components/Weband.vue' | |||
export default { | |||
name: 'App', | |||
components: { | |||
HelloWorld | |||
Weband | |||
} | |||
} | |||
</script> | |||
<style> | |||
#app { | |||
font-family: Avenir, Helvetica, Arial, sans-serif; | |||
-webkit-font-smoothing: antialiased; | |||
-moz-osx-font-smoothing: grayscale; | |||
text-align: center; | |||
color: #2c3e50; | |||
margin-top: 60px; | |||
@import './assets/css/_variables.css'; | |||
*{ | |||
margin :0; | |||
padding:0; | |||
} | |||
body{ | |||
font-family: var(--font-family); | |||
font-size: 2rem; | |||
letter-spacing: 0.1rem; | |||
} | |||
body, html, #app { | |||
margin :0; | |||
padding:0; | |||
} | |||
</style> |
@@ -0,0 +1,10 @@ | |||
@font-face { | |||
font-family: "work-sans"; | |||
src: url('../fonts/work-sans/WorkSans-Regular.otf'); | |||
} | |||
:root{ | |||
--font-family : 'work-sans'; | |||
--pretty-margin: 0.5rem; | |||
--main-color: black; | |||
--secondary-color: black; | |||
} |
@@ -1,58 +0,0 @@ | |||
<template> | |||
<div class="hello"> | |||
<h1>{{ msg }}</h1> | |||
<p> | |||
For a guide and recipes on how to configure / customize this project,<br> | |||
check out the | |||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. | |||
</p> | |||
<h3>Installed CLI Plugins</h3> | |||
<ul> | |||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li> | |||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li> | |||
</ul> | |||
<h3>Essential Links</h3> | |||
<ul> | |||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li> | |||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li> | |||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li> | |||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li> | |||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li> | |||
</ul> | |||
<h3>Ecosystem</h3> | |||
<ul> | |||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li> | |||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li> | |||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li> | |||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li> | |||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li> | |||
</ul> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'HelloWorld', | |||
props: { | |||
msg: String | |||
} | |||
} | |||
</script> | |||
<!-- Add "scoped" attribute to limit CSS to this component only --> | |||
<style scoped> | |||
h3 { | |||
margin: 40px 0 0; | |||
} | |||
ul { | |||
list-style-type: none; | |||
padding: 0; | |||
} | |||
li { | |||
display: inline-block; | |||
margin: 0 10px; | |||
} | |||
a { | |||
color: #42b983; | |||
} | |||
</style> |
@@ -0,0 +1,374 @@ | |||
<template> | |||
<div v-if="!collapsed" class="player"> | |||
<div class="progress-wrap"> | |||
<div class="time-info"> | |||
{{audio ? formatDuration(audio.currentTime) : "00:00"}} | |||
</div> | |||
<progress :value="audioPosition" :max="audio ? Math.floor(audio.duration): 0"></progress> | |||
<div class="time-info"> | |||
{{audio ? formatDuration(audio.duration) : "00:00"}} | |||
</div> | |||
</div> | |||
<h3>{{album.title}}</h3> | |||
<ul> | |||
<li | |||
v-for="(track, index) in album.tracks" | |||
v-on:click="trackClicked('track-'+index)" | |||
v-bind:key="index+'-'+track.title" | |||
v-bind:class="{ active: audio && audio.getAttribute('id') === 'track-'+index }"> | |||
<span class="track-number">{{index + 1}}</span> | |||
<span class="track-title">{{track.title.trim()}}</span> | |||
<audio v-on:timeupdate="updateAudioPosition()" v-bind:src="track.file" v-bind:id="'track-'+ index"></audio> | |||
</li> | |||
</ul> | |||
<div class="button-wrap"> | |||
<img v-if="playing" v-on:click="pause()" src="../assets/images/pause.png" id="play-button"> | |||
<img v-else v-on:click="play()" src="../assets/images/play.png" id="play-button"> | |||
</div> | |||
<div class="player-switch opened" v-on:click="toggle()"></div> | |||
</div> | |||
<div v-else class="player-switch" v-on:click="toggle()"> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'Player', | |||
props: { | |||
album: Object, | |||
}, | |||
data: function () { | |||
return { | |||
switcher : null, | |||
collapsed: true, | |||
playing: false, | |||
audioPosition: 0, | |||
audio : null, | |||
startTimeInfo: "00:00", | |||
endTimeInfo: "00:00", | |||
playerImage: '../assets/images/play.png' | |||
} | |||
}, | |||
updated() { | |||
//this.bindAudioTags(); | |||
}, | |||
mounted(){ | |||
this.switcher = document.getElementsByClassName('player-switch')[0]; | |||
document.body.append(this.switcher); | |||
// this.currentAudio = null; | |||
// this.playing = false; | |||
// this.playerContainer = document.createElement("div"); | |||
// this.container = document.querySelector('#player') | |||
// this.playerContainer.classList.add('dd-player'); | |||
// this.switcher = document.createElement("div"); | |||
// this.switcher.classList.add('dd-player-switch'); | |||
// this.playerContainer.classList.add('collapsed'); | |||
// document.body.appendChild(this.switcher); | |||
// | |||
// this.switcher.addEventListener('click', () => { | |||
// this.playerContainer.classList.toggle('collapsed'); | |||
// this.switcher.classList.toggle('opened'); | |||
// }); | |||
// | |||
// | |||
// this.addProgressBar(); | |||
// this.bindAudioTags(); | |||
// this.addPlayButton(); | |||
// | |||
// let wrap = document.createElement("div"); | |||
// wrap.setAttribute('id', 'wrap-playlist') | |||
// this.container.parentElement.appendChild(this.playerContainer); | |||
// this.playerContainer.appendChild(wrap); | |||
// wrap.appendChild(this.container); | |||
}, | |||
methods: { | |||
toggle : function(){ | |||
// this.playerContainer.classList.toggle('collapsed'); | |||
this.collapsed = !this.collapsed; | |||
this.switcher.classList.toggle('opened'); | |||
}, | |||
play: function(){ | |||
if(!this.audio) | |||
this.audio = document.querySelectorAll('audio')[0]; | |||
this.playing = true; | |||
this.audio.play(); | |||
// if(this.playing){ | |||
// this.audio.play(); | |||
// // this.currentAudio.parentElement.classList.add('playing'); | |||
// // self.progressBar.setAttribute('max', Math.floor(self.currentAudio.duration)); | |||
// // self.endTimeInfo.innerHTML = self.formatDuration(self.currentAudio.duration); | |||
// // this.playButton.setAttribute('src','./images/pause.png'); | |||
// } | |||
// else{ | |||
// this.audio.pause(); | |||
// // self.currentAudio.parentElement.classList.remove('playing'); | |||
// // this.playButton.setAttribute('src','./images/play.png'); | |||
// } | |||
}, | |||
pause: function(){ | |||
this.playing = false; | |||
this.audio.pause(); | |||
}, | |||
trackClicked: function(audioId){ | |||
//let track = document.getElementById(audioId); | |||
if(this.playing){ | |||
this.pause(); | |||
this.audio = document.getElementById(audioId); | |||
} | |||
else{ | |||
this.audio = document.getElementById(audioId); | |||
} | |||
this.playing = true; | |||
this.audio.play(); | |||
}, | |||
updateAudioPosition: function(){ | |||
this.audioPosition = Math.floor(this.audio.currentTime); | |||
}, | |||
// addProgressBar: function(){ | |||
// this.progressBar = document.createElement("progress"); | |||
// this.progressBar.setAttribute('value', 0); | |||
// this.startTimeInfo = document.createElement('div'); | |||
// this.endTimeInfo = document.createElement('div'); | |||
// let progressWrap = document.createElement("div"); | |||
// this.startTimeInfo.innerHTML = "00:00"; | |||
// this.endTimeInfo.innerHTML = "00:00"; | |||
// progressWrap.appendChild(this.startTimeInfo); | |||
// progressWrap.appendChild(this.progressBar); | |||
// progressWrap.appendChild(this.endTimeInfo); | |||
// this.startTimeInfo.classList.add('time-info'); | |||
// this.endTimeInfo.classList.add('time-info'); | |||
// progressWrap.classList.add('progress-wrap'); | |||
// this.playerContainer.append(progressWrap); | |||
// let self = this; | |||
// this.progressBar.addEventListener('click', (e) => { | |||
// if(self.currentAudio){ | |||
// self.currentAudio.currentTime = Math.floor(((e.layerX - self.progressBar.offsetLeft) * self.currentAudio.duration) / self.progressBar.offsetWidth); | |||
// self.progressBar.value = Math.floor(self.currentAudio.currentTime ); | |||
// } | |||
// }); | |||
// | |||
// }, | |||
// addPlayButton: function(){ | |||
// this.playButton = document.createElement("img"); | |||
// this.playButton.setAttribute('src','./images/play.png'); | |||
// this.playButton.setAttribute('id','play-button'); | |||
// let wrap = document.createElement("div"); | |||
// wrap.append(this.playButton); | |||
// this.container.append(wrap); | |||
// let self = this; | |||
// this.playButton.addEventListener('click', () => { | |||
// self.playing = !self.playing; | |||
// if(self.playing){ | |||
// self.currentAudio.play(); | |||
// self.currentAudio.parentElement.classList.add('playing'); | |||
// self.progressBar.setAttribute('max', Math.floor(self.currentAudio.duration)); | |||
// self.endTimeInfo.innerHTML = self.formatDuration(self.currentAudio.duration); | |||
// this.playButton.setAttribute('src','./images/pause.png'); | |||
// } | |||
// else{ | |||
// self.currentAudio.pause(); | |||
// self.currentAudio.parentElement.classList.remove('playing'); | |||
// this.playButton.setAttribute('src','./images/play.png'); | |||
// } | |||
// }) | |||
// }, | |||
// bindAudioTags: function(){ | |||
// let liTags = this.container.querySelectorAll('li') | |||
// let self = this; | |||
// let i = 0; | |||
// liTags.forEach(function(itemElement){ | |||
// let audioTag = itemElement.querySelector('audio') | |||
// if(audioTag){ | |||
// audioTag.addEventListener('timeupdate', () => { | |||
// self.audioTimeUpdate(audioTag); | |||
// }); | |||
// audioTag.addEventListener('ended', () => { | |||
// self.audioEnded(i); | |||
// }) | |||
// | |||
// //itemElement.appendChild(audioElement); | |||
// audioTag.setAttribute('id','track-'+i); | |||
// if(i === 0) | |||
// self.currentAudio = audioTag; | |||
// itemElement.addEventListener('click', () => { | |||
// let items = self.container.getElementsByTagName('li'); | |||
// for(let i = 0; i < items.length; i++){ | |||
// items[i].classList.remove('playing'); | |||
// } | |||
// if(self.currentAudio) | |||
// self.currentAudio.pause(); | |||
// audioTag.play(); | |||
// self.playButton.setAttribute('src','./images/pause.png'); | |||
// self.playing = true; | |||
// itemElement.classList.add('playing'); | |||
// self.currentAudio = audioTag; | |||
// self.progressBar.setAttribute('max', Math.floor(self.currentAudio.duration)); | |||
// self.endTimeInfo.innerHTML = self.formatDuration(self.currentAudio.duration); | |||
// self.progressBar.setAttribute('value', 0); | |||
// }); | |||
// | |||
// i++; | |||
// } | |||
// }); | |||
// }, | |||
// audioTimeUpdate: function(audioElt){ | |||
// this.progressBar.value = Math.floor(audioElt.currentTime); | |||
// this.startTimeInfo.innerHTML = this.formatDuration(audioElt.currentTime); | |||
// }, | |||
// | |||
// audioEnded: function(n){ | |||
// let audios = document.getElementsByTagName('audio'); | |||
// let next = n == audios.length - 1 ? 0 : n + 1; | |||
// this.currentAudio.pause(); | |||
// this.currentAudio.currentTime = 0; | |||
// this.currentAudio = document.getElementsByTagName('audio')[next]; | |||
// this.currentAudio.parentElement.click(); | |||
// }, | |||
formatDuration: function(time){ | |||
let s = parseInt(time% 60); | |||
let m = parseInt((time / 60) % 60); | |||
return (m < 10 ? ('0'+m) : m) + ':' + (s < 10 ? ('0'+s) : s); | |||
} | |||
} | |||
} | |||
</script> | |||
<style> | |||
*{ | |||
--one-dpi: 72px; | |||
--dd-blue: rgb(0,42,255); | |||
} | |||
.player{ | |||
width: 25vw; | |||
height: 100vh; | |||
position:fixed; | |||
top: 0px; | |||
background-color: white; | |||
right:0px; | |||
color: black; | |||
padding-top: var(--one-dpi); | |||
padding-bottom: var(--one-dpi); | |||
display: flex; | |||
flex-direction: column; | |||
/* justify-content: space-between; */ | |||
} | |||
.collapsed{ | |||
display: none; | |||
} | |||
.player-switch{ | |||
position :fixed; | |||
bottom: 0; | |||
right: 0; | |||
width: var(--one-dpi); | |||
height:var(--one-dpi); | |||
background-color: white; | |||
cursor: pointer; | |||
z-index:10; | |||
} | |||
.player-switch.opened{ | |||
background-color: var(--dd-blue); | |||
} | |||
li.active{ | |||
font-weight: bold; | |||
} | |||
.player h3 { | |||
font-size: 1rem; | |||
margin: calc(var(--one-dpi) / 2); | |||
} | |||
.player ul { | |||
margin-left : calc(var(--one-dpi) / 2); | |||
color: var(--dd-blue); | |||
font-size: 1.5rem; | |||
list-style-type : none; | |||
} | |||
/* | |||
.dd-player ul li img{ | |||
width:24px; | |||
height:24px; | |||
margin-right: 12px; | |||
} | |||
.playing{ | |||
font-weight: bold; | |||
color : black; | |||
} | |||
*/ | |||
.progress-wrap{ | |||
display:flex; | |||
width: 100%; | |||
justify-content: space-around; | |||
} | |||
.progress-wrap .time-info{ | |||
font-size:0.66rem; | |||
display: flex; | |||
justify-content: center; | |||
} | |||
progress{ | |||
display: inline; | |||
height: 10px; | |||
width:66%; | |||
} | |||
/* | |||
#play-button{ | |||
max-width: calc(var(--one-dpi) / 2); | |||
max-height: calc(var(--one-dpi) / 2); | |||
margin: calc(var(--one-dpi) / 2); | |||
} | |||
*/ | |||
progress::-moz-progress-bar{ | |||
background-color: black; | |||
} | |||
progress{ | |||
background-color: #rgba(200,200,200,0.5); | |||
} | |||
.button-wrap{ | |||
margin-top: 100px; | |||
} | |||
/* | |||
.dd-player ul li, #play-button{ | |||
cursor : pointer; | |||
} | |||
#wrap-playlist{ | |||
flex: 1; | |||
} | |||
*/ | |||
span.track-number{ | |||
width: 25px; | |||
display: inline-block; | |||
} | |||
span.track-title:before{ | |||
content: " - "; | |||
} | |||
</style> |
@@ -0,0 +1,149 @@ | |||
<template> | |||
<div class="weband"> | |||
<section class="cover"> | |||
</section> | |||
<section class="albums"> | |||
<h2>{{this.artist}} | Albums</h2> | |||
<ul> | |||
<li v-for="album in albums" v-bind:key="album.title" v-on:click="albumSelected(album)"> | |||
{{album.title}} | {{album.year}} | |||
</li> | |||
</ul> | |||
</section> | |||
<section class="icono-2"> | |||
</section> | |||
<section class="contact"> | |||
<span>contact<span class="point">@</span>{{this.artist.toLowerCase()}}<span class="point">.</span>space</span> | |||
<footer class="flex-center"> | |||
dede<span class='point'>.</span>space<span class='pipe'>|</span>{{new Date().getFullYear()}} | |||
</footer> | |||
</section> | |||
<Player v-bind:album="currentAlbum"/> | |||
</div> | |||
</template> | |||
<script> | |||
import Player from './Player.vue' | |||
import * as data from '../data.json'; | |||
export default { | |||
name: 'Weband', | |||
props: { | |||
msg: String | |||
}, | |||
components: { | |||
Player | |||
}, | |||
data: function () { | |||
return { | |||
artist: data.artist, | |||
albums: data.albums, | |||
currentAlbum: data.albums[0] | |||
} | |||
}, | |||
methods: { | |||
albumSelected: function(album){ | |||
this.currentAlbum = album; | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
@import '../assets/css/_variables.css'; | |||
.point { | |||
margin: 0px var(--pretty-margin); | |||
} | |||
.pipe { | |||
margin: 0px calc(3 * var(--pretty-margin)); | |||
} | |||
section{ | |||
height: 100vh; | |||
} | |||
section h2 { | |||
font-size: 2rem; | |||
} | |||
section:nth-of-type(odd){ | |||
background-color: white; | |||
color: var(--secondary-color); | |||
} | |||
section:nth-of-type(even){ | |||
background-color: var(--secondary-color); | |||
color: white; | |||
} | |||
section.cover{ | |||
background : url(../assets/images/icono_1.png) no-repeat center center; | |||
background-color: var(--main-color); | |||
} | |||
section.albums{ | |||
background-color: black; | |||
color:white; | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
line-height: 200%; | |||
} | |||
section.albums > * { | |||
padding-left: 10vw; | |||
} | |||
section.albums h2 { | |||
padding-left: 20vw; | |||
font-weight: normal; | |||
} | |||
section.albums ul { | |||
list-style-type: none; | |||
} | |||
section.icono-2{ | |||
background : url(../assets/images/icono_2.png) no-repeat center center; | |||
background-color: var(--secondary-color); | |||
} | |||
section.contact{ | |||
display : flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
align-items: center; | |||
position :relative; | |||
/* background : url(../assets/images/contact.png) no-repeat center center; */ | |||
background-color: var(--main-color); | |||
/*color:white; */ | |||
} | |||
footer{ | |||
font-size: 0.92rem; | |||
position: absolute; | |||
width: 100%; | |||
bottom: 0; | |||
text-align: center; | |||
} | |||
.hidden{ | |||
display: none; | |||
} | |||
</style> |
@@ -0,0 +1,36 @@ | |||
{ | |||
"albums" : [ | |||
{ | |||
"title" : "Album 1", | |||
"date" : "2019", | |||
"tracks" : [ | |||
{ | |||
"file" : "audio/album_1/01.mp3", | |||
"title" : "Track 1" | |||
}, | |||
{ | |||
"file" : "audio/album_1/02.mp3", | |||
"title" : "Track 2" | |||
}, | |||
{ | |||
"file" : "audio/album_1/03.mp3", | |||
"title" : "Track 3" | |||
} | |||
] | |||
}, | |||
{ | |||
"title" : "Album 2", | |||
"date" : "2020", | |||
"tracks" : [ | |||
{ | |||
"file" : "audio/album_2/04.mp3", | |||
"title" : "Track X" | |||
}, | |||
{ | |||
"file" : "audio/album_2/05.mp3", | |||
"title" : "Track Y" | |||
} | |||
] | |||
} | |||
] | |||
} |
@@ -3729,6 +3729,11 @@ fs.realpath@^1.0.0: | |||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" | |||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= | |||
fs@^0.0.1-security: | |||
version "0.0.1-security" | |||
resolved "https://registry.yarnpkg.com/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4" | |||
integrity sha1-invTcYa23d84E/I4WLV+yq9eQdQ= | |||
fsevents@^1.2.7: | |||
version "1.2.13" | |||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" | |||
@@ -4699,6 +4704,13 @@ jsesc@~0.5.0: | |||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" | |||
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= | |||
jsmediatags@^3.9.3: | |||
version "3.9.3" | |||
resolved "https://registry.yarnpkg.com/jsmediatags/-/jsmediatags-3.9.3.tgz#309632d221d701bd385df65c9c6840cb399e11ff" | |||
integrity sha512-h53yFnPYF1Y5jwr2ebcVzIIsvRpSalm0jhNiJDUztoPPHGpuHxi9YHUzdDgiw+ykiinXHd1s6HSIbudHw79zQw== | |||
dependencies: | |||
xhr2 "^0.1.4" | |||
json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: | |||
version "1.0.2" | |||
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" | |||
@@ -8190,6 +8202,11 @@ ws@^6.0.0, ws@^6.2.1: | |||
dependencies: | |||
async-limiter "~1.0.0" | |||
xhr2@^0.1.4: | |||
version "0.1.4" | |||
resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" | |||
integrity sha1-f4dliEdxbbUCYyOBL4GMras4el8= | |||
xtend@^4.0.0, xtend@~4.0.1: | |||
version "4.0.2" | |||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" | |||