From 98c5434a32cc546937550726aebf3cbc1b1b6b9e Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Thu, 14 Feb 2019 22:45:27 +0800 Subject: Pineapple Cloud Music. --- libs/music/pcm | 73 +++++++ libs/music/player.d/cgi-bin/pcm.cgi | 31 +++ libs/music/player.d/pcm.js | 365 ++++++++++++++++++++++++++++++++++ libs/music/player.d/w3.css | 384 ++++++++++++++++++++++++++++++++++++ 4 files changed, 853 insertions(+) create mode 100644 libs/music/pcm create mode 100755 libs/music/player.d/cgi-bin/pcm.cgi create mode 100644 libs/music/player.d/pcm.js create mode 100644 libs/music/player.d/w3.css (limited to 'libs') diff --git a/libs/music/pcm b/libs/music/pcm new file mode 100644 index 0000000..99185f4 --- /dev/null +++ b/libs/music/pcm @@ -0,0 +1,73 @@ + + + +Private Playlist + + + + + + + +
+
+

 Pineapple Cloud Music

+
+

You can't see me

+ + +
+
+
+
+
+

0:00

+
+
+

Not playing at all.

+ +
+
+

0:00

+
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+ + + + diff --git a/libs/music/player.d/cgi-bin/pcm.cgi b/libs/music/player.d/cgi-bin/pcm.cgi new file mode 100755 index 0000000..65aa0a6 --- /dev/null +++ b/libs/music/player.d/cgi-bin/pcm.cgi @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +import sys,os,cgi,json +from urllib.parse import quote +d=cgi.parse(fp=sys.stdin) + +print('Status: 200 OK',end='\r\n') +print('Content-type: application/json',end='\r\n') +print(end='\r\n') + +ro={'status':200,'message':'OK'} +if 'folder' not in d or d['folder']=='': + plp=os.environ['DOCUMENT_ROOT']+'/libs/music/player.d/playlists/playlists' + alblist=list() + with open(plp,mode='r',encoding='utf-8') as f: + for line in f: + line=line.strip() + if len(line)>0:alblist.append(quote(line)) + rro={'type':'fileList','data':{'subFolderList':alblist}} + ro['result']=rro + print(json.dumps(ro)) +else: + alp=os.environ['DOCUMENT_ROOT']+'/libs/music/player.d/playlists/'+d['folder'][0].strip('/')+'.playlist' + alblist=list() + with open(alp,mode='r',encoding='utf-8') as f: + for line in f: + line=line.strip() + if len(line)>0: + alblist.append({'fileName':quote(line),'fileSize':0,'modifiedTime':0}) + rro={'type':'fileList','data':{'musicList':alblist[1:],'subFolderList':list()}} + ro['result']=rro + print(json.dumps(ro)) diff --git a/libs/music/player.d/pcm.js b/libs/music/player.d/pcm.js new file mode 100644 index 0000000..575b7f7 --- /dev/null +++ b/libs/music/player.d/pcm.js @@ -0,0 +1,365 @@ +; // Private Cloud Music - player.js +; // Licence: WTFPL +; // BLumia - 2016/11/11 +; // szO Chris && 2jjy && jxpxxzj Orz +; // ↑ Moe ↑ Moe ↑ Moe +; // Modified to use on chrisoft.org by Chris Xiong +; // szO BLumia Orz +; // ↑ Moe +// formatTime,getCookie by Chrissssss +function formatTime(t) { + if(isNaN(t))return '--:--'; + let m=Math.floor(t/60),s=Math.round(t-Math.floor(t/60)*60); + if(s<10)return `${m}:0${s}`; + else if(s==60)return `${m+1}:00`; + else return `${m}:${s}`; +} +function getCookie(key) { + if (!navigator.cookieEnabled) return ""; + return document.cookie.replace(new RegExp('(?:(?:^|.*;\\s*)'+key+'\\s*\\=\\s*([^;]*).*$)|^.*$'),'$1'); +} +function setCookie(cookieName, cookieValue, maxAge = 0) { + if (!navigator.cookieEnabled) return; + var cookieStr = cookieName + "=" + cookieValue; + if (maxAge > 0) cookieStr += ";max-age=" + maxAge; + document.cookie = cookieStr; +} + +const PCMAPI_URL='/libs/music/player.d/cgi-bin/pcm.cgi'; +const AUDIO_URL='//filestorage.chrisoft.org/music/ogg/'; + +(function() { + var Helper = function() { + this.el = null; + this.entry = function(selector) { + if (typeof selector == 'string') { + if (selector[0] == '<') { + var singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; + if (singleTagRE.test(selector)) this.el = document.createElement(RegExp.$1); + } else { + this.el = document.getElementById(selector); + } + } + else this.el = selector; + return this; + } + } + Helper.prototype = { + css: function(property, value) { + if(this.el) this.el.style.cssText += ';' + property + ":" + value; + return this; + }, + attr: function(property, value) { + if(this.el) this.el.setAttribute(property, value); + return this; + }, + append: function(node) { + if(this.el) this.el.appendChild(node); + return this; + }, + text: function(content) { + if(this.el) this.el.textContent = content; + return this; + }, + click: function(handler) { + if(!this.el) return this; + if (typeof(handler) == "function") this.el.onclick = handler; + else this.el.click(); + return this; + }, + innerHTML: function(text) { + if(this.el) this.el.innerHTML = text; + return this; + } + } + var H = function(selector) { + var f = new Helper(); + return f.entry(selector); + } + var Player = { + path: null, // sample: 'Test/' + data: null, + audio: document.getElementsByTagName('audio')[0], + currentIndex: -1, + loop: 0, + order: 0, + playlist: H("playlist").el, + folderlist: H("folderlist").el, + nowPlaying: H("nowPlaying").el, + + updateMetadata: function() { + if ('mediaSession' in navigator) { + window.navigator.mediaSession.metadata = new MediaMetadata({ + title: nowPlaying.innerHTML, + album: decodeURIComponent(this.path) + }); + } + }, + + playAtIndex: function(i) { + // FIXME: trigger this when audio doesn't finished load will cause play promise error. + this.audio.pause(); + this.currentIndex = i; + this.audio.src = AUDIO_URL + this.data[i].fileName + '.ogg'; + this.audio.load(); + this.audio.play(); + window.history.replaceState("","Useless Title","#/"+this.path+this.data[i].fileName+"/"); // title seems be fucked. + H(this.nowPlaying).innerHTML(decodeURIComponent(this.data[i].fileName)); + }, + + freshFolderlist: function(callback) { + var xhr = new XMLHttpRequest(); + xhr.open("POST", PCMAPI_URL, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + var that = this; + xhr.onreadystatechange = function () { + if (xhr.readyState != 4 || xhr.status != 200) return; + var data = JSON.parse(xhr.responseText); + if (data.status != 200) { + console.error("Fetch error. Reason: " + data.message + " Url: ./api.php"); + return; + } + data.result.data.subFolderList.forEach(function(item, i) { + var decodedFolderName = decodeURIComponent(item); + if (that.path == null) that.path = item + '/'; + // attr aim data as uriencoded path. + H(that.folderlist).append( + H("").attr('aim', item).append( + H("
  • ").text(decodedFolderName + '/').el + ).el + ); + }); + }; + xhr.onerror = function() { + console.error("Ajax load folders failed. Status: " + xhr.status + " Url: ./api.php"); + }; + xhr.onloadend = function() { + var nodeList = document.querySelectorAll('#folderlist a'); + for(var i = 0; i < nodeList.length; i++) { + var el = nodeList[i]; + el.onclick = function() { + that.path = this.getAttribute('aim') + '/'; + that.fetchData(); + }; + } + typeof callback === 'function' && callback(); + } + xhr.send("do=getfilelist"); + }, + + fetchData: function() { + var that = this; + var xhr = new XMLHttpRequest(); + xhr.open("POST", PCMAPI_URL, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.onreadystatechange = function () { + if (xhr.readyState != 4 || xhr.status != 200) return; + var data = JSON.parse(xhr.responseText); + if (data.status != 200) { + console.error("Fetch error. Reason: " + data.message + " Url: ./api.php"); + return; + } + that.data = data.result.data.musicList; + that.freshPlaylist(); + that.freshSubFolderList(data.result.data.subFolderList); + }; + xhr.onerror = function() { + console.error("Ajax load playlist failed. Status: " + xhr.status + " Url: ./api.php"); + that.data = []; + }; + xhr.send("do=getfilelist&folder="+that.path); + }, + + freshPlaylist : function() { + var that = this; + var data = this.data; + var songTitle = ''; + this.playlist.innerHTML = ''; + data.forEach(function(item, i) { + songTitle = decodeURIComponent(item.fileName); + H(that.playlist).append( + H("").attr('index', i).append( + H("
  • ").text(songTitle).el + ).el + ); + }); + // everytime after update playlist dom, do this. + var nodeList = document.querySelectorAll('#playlist a'); + for(var i = 0; i < nodeList.length; i++) { + var el = nodeList[i]; + el.onclick = function() { + that.playAtIndex(this.getAttribute('index')); + }; + } + }, + + freshSubFolderList : function(list) { + var that = this; + H("subfolderlist").innerHTML(""); + list.forEach(function(item, i) { + var decodedFolderName = decodeURIComponent(item); + // attr aim data as uriencoded path. + H("subfolderlist").append( + H("").attr('aim', item).append( + H("
  • ").text(decodedFolderName + '/').el + ).el + ); + }); + var nodeList = document.querySelectorAll('#subfolderlist a'); + for(var i = 0; i < nodeList.length; i++) { + var el = nodeList[i]; + el.onclick = function() { + that.path = this.getAttribute('aim') + '/'; + that.fetchData(); + }; + } + }, + + urlMatch : function() { + var isUrlMatched = false; + // Match folder name and song title. + var re = new RegExp("[#][/](.*[/])(.*)[/]$"); + var urlMatch = re.exec(location.href); + if (urlMatch != null) { + isUrlMatched = true; + this.path = urlMatch[1]; + this.audio.src = AUDIO_URL + urlMatch[2] + '.ogg'; + this.audio.play(); + H(this.nowPlaying).innerHTML(decodeURIComponent(urlMatch[2])); + } + // Only match folder name. + if (!isUrlMatched) { + re = new RegExp("[#][/](.*[/])"); + urlMatch = re.exec(location.href); + if (urlMatch != null) { + isUrlMatched = true; + this.path = urlMatch[1]; + } + } + }, + + applyLoop : function() { + if (this.loop == 1) { + this.audio.loop = true; + H("btn-loop").innerHTML("Loop: √"); + } else { + this.audio.loop = false; + H("btn-loop").innerHTML("Loop: ×"); + } + }, + + applyOrder : function() { + if (this.order == 1) { + this.audio.onended = function() { + if (this.loop == 0) { + H("btn-next").click(); + } + }; + H("btn-order").innerHTML("Order: √"); + } else { + this.audio.onended = undefined; + H("btn-order").innerHTML("Order: ×"); + } + }, + + init : function() { + var that = this; + this.freshFolderlist(function() { + that.urlMatch(); + that.fetchData(); + }); + this.loop = getCookie("pcm-loop") == "1" ? 1 : 0; + this.order = getCookie("pcm-order") == "1" ? 1 : 0; + this.applyLoop(); + this.applyOrder(); + }, + + ready : function() { + var that = this; + + this.audio.ontimeupdate = function() { + H("curTime").innerHTML(formatTime(Player.audio.currentTime)); + H("totalTime").innerHTML(formatTime(Player.audio.duration)); + H("timebar").css("width", Player.audio.currentTime / Player.audio.duration*100+"%"); + var r = 0; + for(var i=0; i.w3-half,.w3-row-padding>.w3-third,.w3-row-padding>.w3-twothird,.w3-row-padding>.w3-threequarter,.w3-row-padding>.w3-quarter,.w3-row-padding>.w3-col{padding:0 8px} +.w3-spin{animation:w3-spin 2s infinite linear;-webkit-animation:w3-spin 2s infinite linear} +@-webkit-keyframes w3-spin{ +0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)} +100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} +@keyframes w3-spin{ +0%{-webkit-transform:rotate(0deg);transform: rotate(0deg)} +100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} +.w3-container-4{padding:0.01em 4px}.w3-container-8{padding:0.01em 8px}.w3-container-12{padding:0.01em 12px}.w3-container,.w3-container-16{padding:0.01em 16px} +.w3-container-24{padding:0.01em 24px}.w3-container-32{padding:0.01em 32px}.w3-container-48{padding:0.01em 48px}.w3-container-64{padding:0.01em 64px} +.w3-example{background-color:#f1f1f1;padding:0.01em 16px} +.w3-code{font-family:Consolas,"courier new";font-size:16px;line-height:1.4;width:auto;background-color:#fff;padding:8px 12px;border-left:4px solid #009688;word-wrap:break-word} +.w3-example,.w3-code,.w3-reference{margin:20px 0} +.w3-card{border:1px solid #ccc} +.w3-card-2,.w3-example{box-shadow:0 2px 4px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)!important} +.w3-card-4,.w3-hover-shadow:hover{box-shadow:0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)!important} +.w3-card-8{box-shadow:0 8px 16px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)!important} +.w3-card-12{box-shadow:0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19)!important} +.w3-card-16{box-shadow:0 16px 24px 0 rgba(0,0,0,0.22),0 25px 55px 0 rgba(0,0,0,0.21)!important} +.w3-card-24{box-shadow:0 24px 24px 0 rgba(0,0,0,0.2),0 40px 77px 0 rgba(0,0,0,0.22)!important} +.w3-animate-fading{-webkit-animation:fading 10s infinite;animation:fading 10s infinite} +@-webkit-keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +@keyframes fading{0%{opacity:0}50%{opacity:1}100%{opacity:0}} +.w3-animate-opacity{-webkit-animation:opac 1.5s;animation:opac 1.5s} +@-webkit-keyframes opac{from{opacity:0} to{opacity:1}} +@keyframes opac{from{opacity:0} to{opacity:1}} +.w3-animate-top{position:relative;-webkit-animation:animatetop 0.4s;animation:animatetop 0.4s} +@-webkit-keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +@keyframes animatetop{from{top:-300px;opacity:0} to{top:0;opacity:1}} +.w3-animate-left{position:relative;-webkit-animation:animateleft 0.4s;animation:animateleft 0.4s} +@-webkit-keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +@keyframes animateleft{from{left:-300px;opacity:0} to{left:0;opacity:1}} +.w3-animate-right{position:relative;-webkit-animation:animateright 0.4s;animation:animateright 0.4s} +@-webkit-keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +@keyframes animateright{from{right:-300px;opacity:0} to{right:0;opacity:1}} +.w3-animate-bottom{position:relative;-webkit-animation:animatebottom 0.4s;animation:animatebottom 0.4s} +@-webkit-keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0px;opacity:1}} +@keyframes animatebottom{from{bottom:-300px;opacity:0} to{bottom:0;opacity:1}} +.w3-animate-zoom {-webkit-animation:animatezoom 0.6s;animation:animatezoom 0.6s} +@-webkit-keyframes animatezoom{from{-webkit-transform:scale(0)} to{-webkit-transform:scale(1)}} +@keyframes animatezoom{from{transform:scale(0)} to{transform:scale(1)}} +.w3-animate-input{-webkit-transition:width 0.4s ease-in-out;transition:width 0.4s ease-in-out}.w3-animate-input:focus{width:100%!important} +.w3-transparent{background-color:transparent!important} +.w3-hover-none:hover{box-shadow:none!important;background-color:transparent!important} +.w3-amber,.w3-hover-amber:hover{color:#000!important;background-color:#ffc107!important} +.w3-aqua,.w3-hover-aqua:hover{color:#000!important;background-color:#00ffff!important} +.w3-blue,.w3-hover-blue:hover{color:#fff!important;background-color:#2196F3!important} +.w3-light-blue,.w3-hover-light-blue:hover{color:#000!important;background-color:#87CEEB!important} +.w3-brown,.w3-hover-brown:hover{color:#fff!important;background-color:#795548!important} +.w3-cyan,.w3-hover-cyan:hover{color:#000!important;background-color:#00bcd4!important} +.w3-blue-grey,.w3-hover-blue-grey:hover{color:#fff!important;background-color:#607d8b!important} +.w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important} +.w3-light-green,.w3-hover-light-green:hover{color:#000!important;background-color:#8bc34a!important} +.w3-indigo,.w3-hover-indigo:hover{color:#fff!important;background-color:#3f51b5!important} +.w3-khaki,.w3-hover-khaki:hover{color:#000!important;background-color:#f0e68c!important} +.w3-lime,.w3-hover-lime:hover{color:#000!important;background-color:#cddc39!important} +.w3-orange,.w3-hover-orange:hover{color:#000!important;background-color:#ff9800!important} +.w3-deep-orange,.w3-hover-deep-orange:hover{color:#fff!important;background-color:#ff5722!important} +.w3-pink,.w3-hover-pink:hover{color:#fff!important;background-color:#e91e63!important} +.w3-purple,.w3-hover-purple:hover{color:#fff!important;background-color:#9c27b0!important} +.w3-deep-purple,.w3-hover-deep-purple:hover{color:#fff!important;background-color:#673ab7!important} +.w3-red,.w3-hover-red:hover{color:#fff!important;background-color:#f44336!important} +.w3-sand,.w3-hover-sand:hover{color:#000!important;background-color:#fdf5e6!important} +.w3-teal,.w3-hover-teal:hover{color:#fff!important;background-color:#009688!important} +.w3-yellow,.w3-hover-yellow:hover{color:#000!important;background-color:#ffeb3b!important} +.w3-white,.w3-hover-white:hover{color:#000!important;background-color:#fff!important} +.w3-black,.w3-hover-black:hover{color:#fff!important;background-color:#000!important} +.w3-grey,.w3-hover-grey:hover{color:#000!important;background-color:#9e9e9e!important} +.w3-light-grey,.w3-hover-light-grey:hover{color:#000!important;background-color:#f1f1f1!important} +.w3-dark-grey,.w3-hover-dark-grey:hover{color:#fff!important;background-color:#616161!important} +.w3-pale-red,.w3-hover-pale-red:hover{color:#000!important;background-color:#ffe7e7!important}.w3-pale-green,.w3-hover-pale-green:hover{color:#000!important;background-color:#e7ffe7!important} +.w3-pale-yellow,.w3-hover-pale-yellow:hover{color:#000!important;background-color:#ffffd7!important}.w3-pale-blue,.w3-hover-pale-blue:hover{color:#000!important;background-color:#e7ffff!important} +.w3-text-amber,.w3-hover-text-amber:hover{color:#ffc107!important} +.w3-text-aqua,.w3-hover-text-aqua:hover{color:#00ffff!important} +.w3-text-blue,.w3-hover-text-blue:hover{color:#2196F3!important} +.w3-text-light-blue,.w3-hover-text-light-blue:hover{color:#87CEEB!important} +.w3-text-brown,.w3-hover-text-brown:hover{color:#795548!important} +.w3-text-cyan,.w3-hover-text-cyan:hover{color:#00bcd4!important} +.w3-text-blue-grey,.w3-hover-text-blue-grey:hover{color:#607d8b!important} +.w3-text-green,.w3-hover-text-green:hover{color:#4CAF50!important} +.w3-text-light-green,.w3-hover-text-light-green:hover{color:#8bc34a!important} +.w3-text-indigo,.w3-hover-text-indigo:hover{color:#3f51b5!important} +.w3-text-khaki,.w3-hover-text-khaki:hover{color:#b4aa50!important} +.w3-text-lime,.w3-hover-text-lime:hover{color:#cddc39!important} +.w3-text-orange,.w3-hover-text-orange:hover{color:#ff9800!important} +.w3-text-deep-orange,.w3-hover-text-deep-orange:hover{color:#ff5722!important} +.w3-text-pink,.w3-hover-text-pink:hover{color:#e91e63!important} +.w3-text-purple,.w3-hover-text-purple:hover{color:#9c27b0!important} +.w3-text-deep-purple,.w3-hover-text-deep-purple:hover{color:#673ab7!important} +.w3-text-red,.w3-hover-text-red:hover{color:#f44336!important} +.w3-text-sand,.w3-hover-text-sand:hover{color:#fdf5e6!important} +.w3-text-teal,.w3-hover-text-teal:hover{color:#009688!important} +.w3-text-yellow,.w3-hover-text-yellow:hover{color:#d2be0e!important} +.w3-text-white,.w3-hover-text-white:hover{color:#fff!important} +.w3-text-black,.w3-hover-text-black:hover{color:#000!important} +.w3-text-grey,.w3-hover-text-grey:hover{color:#757575!important} +.w3-text-light-grey,.w3-hover-text-light-grey:hover{color:#f1f1f1!important} +.w3-text-dark-grey,.w3-hover-text-dark-grey:hover{color:#3a3a3a!important} +.w3-border-amber,.w3-hover-border-amber:hover{border-color:#ffc107!important} +.w3-border-aqua,.w3-hover-border-aqua:hover{border-color:#00ffff!important} +.w3-border-blue,.w3-hover-border-blue:hover{border-color:#2196F3!important} +.w3-border-light-blue,.w3-hover-border-light-blue:hover{border-color:#87CEEB!important} +.w3-border-brown,.w3-hover-border-brown:hover{border-color:#795548!important} +.w3-border-cyan,.w3-hover-border-cyan:hover{border-color:#00bcd4!important} +.w3-border-blue-grey,.w3-hover-blue-grey:hover{border-color:#607d8b!important} +.w3-border-green,.w3-hover-border-green:hover{border-color:#4CAF50!important} +.w3-border-light-green,.w3-hover-border-light-green:hover{border-color:#8bc34a!important} +.w3-border-indigo,.w3-hover-border-indigo:hover{border-color:#3f51b5!important} +.w3-border-khaki,.w3-hover-border-khaki:hover{border-color:#f0e68c!important} +.w3-border-lime,.w3-hover-border-lime:hover{border-color:#cddc39!important} +.w3-border-orange,.w3-hover-border-orange:hover{border-color:#ff9800!important} +.w3-border-deep-orange,.w3-hover-border-deep-orange:hover{border-color:#ff5722!important} +.w3-border-pink,.w3-hover-border-pink:hover{border-color:#e91e63!important} +.w3-border-purple,.w3-hover-border-purple:hover{border-color:#9c27b0!important} +.w3-border-deep-purple,.w3-hover-border-deep-purple:hover{border-color:#673ab7!important} +.w3-border-red,.w3-hover-border-red:hover{border-color:#f44336!important} +.w3-border-sand,.w3-hover-border-sand:hover{border-color:#fdf5e6!important} +.w3-border-teal,.w3-hover-border-teal:hover{border-color:#009688!important} +.w3-border-yellow,.w3-hover-border-yellow:hover{border-color:#ffeb3b!important} +.w3-border-white,.w3-hover-border-white:hover{border-color:#fff!important} +.w3-border-black,.w3-hover-border-black:hover{border-color:#000!important} +.w3-border-grey,.w3-hover-border-grey:hover{border-color:#9e9e9e!important} +.w3-border-light-grey,.w3-hover-border-light-grey:hover{border-color:#f1f1f1!important} +.w3-border-dark-grey,.w3-hover-border-dark-grey:hover{border-color:#616161!important} +.w3-border-pale-red,.w3-hover-border-pale-red:hover{border-color:#ffe7e7!important}.w3-border-pale-green,.w3-hover-border-pale-green:hover{border-color:#e7ffe7!important} +.w3-border-pale-yellow,.w3-hover-border-pale-yellow:hover{border-color:#ffffd7!important}.w3-border-pale-blue,.w3-hover-border-pale-blue:hover{border-color:#e7ffff!important} +.w3-opacity,.w3-hover-opacity:hover{opacity:0.60} +.w3-text-shadow{text-shadow:1px 1px 0 #444}.w3-text-shadow-white{text-shadow:1px 1px 0 #ddd} \ No newline at end of file -- cgit v1.2.3