
Uh Oh
+ ++ This ain't it. Go back! +
+diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ed58b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +/.phpunit.cache +/node_modules +/vendor +.env +.env.backup +.env.production +.phpunit.result.cache +npm-debug.log +yarn-error.log +/.fleet +/.idea +/.vscode + +/.parcel-cache + +.env.local +/.env.local.php +/.env.*.local +/.nova + +/assets/audio/tapes + +*.DS_Store +*.codekit3 \ No newline at end of file diff --git a/assets/css/front/index.css b/assets/css/front/index.css new file mode 100644 index 0000000..e2be788 --- /dev/null +++ b/assets/css/front/index.css @@ -0,0 +1,182 @@ +main { + display: flex; + justify-content: center; + align-items: center; + padding: 15px; + transform-origin: 0 0; + perspective: 500px; + top: 50px; + position: relative; +} + +header { + padding-top: 5px; + background: rgba(6, 9, 41, 0.5); + position: fixed; + z-index: 1000; +} + +div.audio-progress { + width: 0; + height: 1px; + background: var(--primary); + position: relative; + margin: 0 auto; +} + +article.tape-select { + transition: all 1s cubic-bezier(0.83, 0.05, 0.28, 1); + max-width: 800px; + margin: 0 auto; + padding: 10px; + backface-visibility: hidden; + position: absolute; + top: 1px; +} + +article.tape-tracklist { + transition: all 1s cubic-bezier(0.83, 0.05, 0.28, 1); + max-width: 800px; + width: 100%; + margin: 0 auto; + padding: 10px; + position: absolute; + backface-visibility: hidden; + top: 5px; + padding-bottom: 60px; +} + +img.the-logo { + width: 50px; + margin: 0 auto; + display: block; +} + +img.covers { + padding-bottom: 2px; + border-radius: 3px; + width: 92px; + cursor: pointer; +} + +img#tape-cover { + width: 350px; + margin: 0 auto; + display: block; + border-radius: 3px; +} + +h1#tape-title { + width: 300px; + margin: 15px auto; +} + +section#playlist { + width: 300px; + margin: 0 auto; +} + +section#playlist div { + width: 100%; + display: inline-block; + font-size: 1em; + position: relative; + margin-bottom: 10px; + transition: all 0.5s cubic-bezier(0.83, 0.05, 0.28, 1); +} + +section#playlist div label:nth-child(2) { + font-weight: bold; + color: var(--highlight); +} + +section#playlist div label { + width: 100%; + display: inline-block; + font-size: 0.9em; +} + +button.selectBtn { + height: 100%; + position: absolute; + width: 100%; + opacity: 0; +} + +div#tape-controls { + background: var(--white); + width: 300px; + position: fixed; + z-index: 500; + height: 35px; + left: 50%; + transform: translate(-50%, -50%); + transition: all 1s cubic-bezier(0.83, 0.05, 0.28, 1); + border-radius: 20px; + display: flex; + border: 3px solid var(--white); +} + +img.tape-control-button { + width: 35px; + display: flex; +} + +label.control-text { + width: 73.6%; + color: var(--secondary); + position: relative; + text-transform: uppercase; + font-size: 0.65em; + font-weight: bold; + height: 100%; + line-height: 100%; + align-items: center; + display: flex; + background: var(--highlight); + border-radius: 3px; + padding: 0 5px; + margin: 0 5px; +} + +.front { + transform: rotateY(0deg); +} + +.back-right { + transform: rotateY(-180deg); +} + +.back-left { + transform: rotateY(180deg); +} + +.playing { + background: var(--white); + padding: 5px; + border-radius: 3px; +} + +.not-playing { + background: none; + padding: 0; + border-radius: 0; +} + +.label-playing { + color: var(--secondary); +} + +.label-notplaying { + color: var(--white); +} + +.control-open { + bottom: -17.5px; + opacity: 1; +} + +.control-closed { + bottom: -55px; + opacity: 0; +} diff --git a/assets/css/front/start.css b/assets/css/front/start.css new file mode 100644 index 0000000..ebc0f93 --- /dev/null +++ b/assets/css/front/start.css @@ -0,0 +1,5 @@ +@import "../global/colors.css"; +@import "../global/forms.css"; +@import "../global/typography.css"; +@import "../global/frame.css"; +@import "index.css"; diff --git a/assets/css/global/colors.css b/assets/css/global/colors.css new file mode 100644 index 0000000..331f79b --- /dev/null +++ b/assets/css/global/colors.css @@ -0,0 +1,13 @@ +:root { + /* BASE COLORS */ + --primary: #ff0505; + --secondary: #03061e; + --highlight: #7ac1df; + --white: #efebe3; + --grey: #abb7b7; + --black: #32302f; + --error: #b62520; + --silence: #ea6010; + --suspend: #fb263a; + --primary-rgb: 20 13 13; +} diff --git a/assets/css/global/fonts/Rubik/rubik-bold.ttf b/assets/css/global/fonts/Rubik/rubik-bold.ttf new file mode 100644 index 0000000..10e508c Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-bold.ttf differ diff --git a/assets/css/global/fonts/Rubik/rubik-bold.woff b/assets/css/global/fonts/Rubik/rubik-bold.woff new file mode 100644 index 0000000..6bbd130 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-bold.woff differ diff --git a/assets/css/global/fonts/Rubik/rubik-bold.woff2 b/assets/css/global/fonts/Rubik/rubik-bold.woff2 new file mode 100644 index 0000000..5b2301d Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-bold.woff2 differ diff --git a/assets/css/global/fonts/Rubik/rubik-light.ttf b/assets/css/global/fonts/Rubik/rubik-light.ttf new file mode 100644 index 0000000..d231ee9 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-light.ttf differ diff --git a/assets/css/global/fonts/Rubik/rubik-light.woff b/assets/css/global/fonts/Rubik/rubik-light.woff new file mode 100644 index 0000000..118c490 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-light.woff differ diff --git a/assets/css/global/fonts/Rubik/rubik-light.woff2 b/assets/css/global/fonts/Rubik/rubik-light.woff2 new file mode 100644 index 0000000..7aca0ad Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-light.woff2 differ diff --git a/assets/css/global/fonts/Rubik/rubik-medium.ttf b/assets/css/global/fonts/Rubik/rubik-medium.ttf new file mode 100644 index 0000000..00f9211 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-medium.ttf differ diff --git a/assets/css/global/fonts/Rubik/rubik-medium.woff b/assets/css/global/fonts/Rubik/rubik-medium.woff new file mode 100644 index 0000000..e2209d9 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-medium.woff differ diff --git a/assets/css/global/fonts/Rubik/rubik-medium.woff2 b/assets/css/global/fonts/Rubik/rubik-medium.woff2 new file mode 100644 index 0000000..d924774 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-medium.woff2 differ diff --git a/assets/css/global/fonts/Rubik/rubik-regular.ttf b/assets/css/global/fonts/Rubik/rubik-regular.ttf new file mode 100644 index 0000000..aad7a38 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-regular.ttf differ diff --git a/assets/css/global/fonts/Rubik/rubik-regular.woff b/assets/css/global/fonts/Rubik/rubik-regular.woff new file mode 100644 index 0000000..64304d5 Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-regular.woff differ diff --git a/assets/css/global/fonts/Rubik/rubik-regular.woff2 b/assets/css/global/fonts/Rubik/rubik-regular.woff2 new file mode 100644 index 0000000..fe294fd Binary files /dev/null and b/assets/css/global/fonts/Rubik/rubik-regular.woff2 differ diff --git a/assets/css/global/fonts/ahg/AlteHaasGroteskBold.ttf b/assets/css/global/fonts/ahg/AlteHaasGroteskBold.ttf new file mode 100644 index 0000000..d310391 Binary files /dev/null and b/assets/css/global/fonts/ahg/AlteHaasGroteskBold.ttf differ diff --git a/assets/css/global/fonts/ahg/AlteHaasGroteskBold.woff b/assets/css/global/fonts/ahg/AlteHaasGroteskBold.woff new file mode 100644 index 0000000..ebc8cd1 Binary files /dev/null and b/assets/css/global/fonts/ahg/AlteHaasGroteskBold.woff differ diff --git a/assets/css/global/fonts/ahg/AlteHaasGroteskBold.woff2 b/assets/css/global/fonts/ahg/AlteHaasGroteskBold.woff2 new file mode 100644 index 0000000..30070e7 Binary files /dev/null and b/assets/css/global/fonts/ahg/AlteHaasGroteskBold.woff2 differ diff --git a/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.ttf b/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.ttf new file mode 100644 index 0000000..ff5edb9 Binary files /dev/null and b/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.ttf differ diff --git a/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.woff b/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.woff new file mode 100644 index 0000000..c9e89e7 Binary files /dev/null and b/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.woff differ diff --git a/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.woff2 b/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.woff2 new file mode 100644 index 0000000..dec30d7 Binary files /dev/null and b/assets/css/global/fonts/ahg/AlteHaasGroteskRegular.woff2 differ diff --git a/assets/css/global/forms.css b/assets/css/global/forms.css new file mode 100644 index 0000000..737cce8 --- /dev/null +++ b/assets/css/global/forms.css @@ -0,0 +1,47 @@ +input[type="email"], +input[type="password"], +input[type="text"] { + border: 0; + padding: 5px; + border-radius: 5px; + font: 18px var(--base-type); + display: inline-block; + background: var(--white); + color: var(--primary); + transition: all 0.2s linear; +} + +input[type="text"]:focus, +input[type="password"]:focus { + outline: solid var(--highlight); + background-color: var(--highlight); +} + +textarea { + border: 0; + border-radius: 3px; + color: var(--primary); + background: var(--white); +} + +button, +input[type="submit"] { + color: var(--white); + font: 1em var(--base-type); + border-radius: 5px; + position: relative; + cursor: pointer; + border: 0; + transition: all 0.3s linear; + padding: 0 0 10px; + text-align: left; +} + +select { + font: 20px var(--base-type); + border-radius: 5px; + border: 1px solid var(--primary); + appearance: none; + color: var(--primary); + background: var(--secondary); +} diff --git a/assets/css/global/frame.css b/assets/css/global/frame.css new file mode 100644 index 0000000..5ce49ea --- /dev/null +++ b/assets/css/global/frame.css @@ -0,0 +1,226 @@ +html { + width: 100%; + height: 100%; + overflow: hidden; + font: 400 1em/1em var(--base-type); +} + +html body { + background: var(--secondary); + color: var(--white); + margin: 0; + padding: 0; + height: 100%; + width: 100%; + overflow-y: scroll; + overflow-x: hidden; +} + +a { + color: var(--highlight); + text-decoration: none; + transition: all 0.2s linear; + /* + border-bottom: 1px solid var(--white); + + */ +} + +strong { + color: var(--primary); +} + +header { + width: 100%; + color: var(--primary); +} + +header > div:nth-child(1) { + display: grid; + grid-template-columns: 200px 1fr 40px; + padding: 10px; + gap: 10px; + height: 200px; + width: 80%; + margin: 0 auto; + max-width: 1000px; + position: relative; +} + +header > div span { + font-size: 3em; + font-weight: bold; + position: absolute; + bottom: 25px; + width: 50%; + line-height: 0.8em; +} + +header > div img { + width: 100%; +} + +header > div a { + color: var(--primary); +} + +header > div i { + font-size: 1.3em; +} + +header > div nav { + background: var(--black); + position: relative; +} + +.header-right { + text-align: right; +} + +div.system-notice-error { + background: var(--error); + color: var(--white); + padding: 10px; +} + +div.system-notice-message { + background: var(--highlight); + color: var(--black); + padding: 10px; +} + +main > section > article { + width: 80%; + max-width: 1000px; + margin: 0 auto; + min-height: 400px; +} + +/* NAV */ + +#main-nav { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: var(--black); + z-index: 1; + display: flex; + align-items: center; + justify-content: center; +} + +label[for="element-toggle"] { + cursor: pointer; +} + +#element-toggle { + display: none; +} + +#element-toggle:not(:checked) ~ #main-nav { + display: none; +} + +a.nav-links { + border-bottom: none; + font-size: 40px; + color: var(--highlight); + text-decoration: none; +} + +/* GLOBALS */ + +a:hover { + /* + border-bottom: 1px solid var(--secondary); + */ + color: var(--white); +} + +pre { + white-space: pre; + background: var(--secondary); +} + +code { + background: var(--secondary); + color: var(--primary); + padding: 3px; +} + +sup { + background: var(--black); + color: var(--white); + padding: 2px; + border-radius: 3px; + vertical-align: baseline; + font-family: var(--mono-type); +} + +.button-icon { + height: 90%; + padding-top: 3px; +} + +.menu-icon { + width: 40px; +} + +.location-title { + display: none; +} + +.location-image { + height: 200px; + width: 200px; + display: inline-block; + border-radius: 3px; +} + +footer { + width: 100%; + color: var(--primary); + background: var(--secondary); + height: 200px; +} + +footer > div:nth-child(1) { + display: grid; + grid-template-columns: 50% 50%; + padding: 10px; + gap: 10px; + height: 200px; + width: 80%; + margin: 0 auto; + max-width: 1000px; + position: relative; +} + +/* + responsive + */ + +@media only screen and (max-width: 960px) { + header > div:nth-child(1) { + } + + header > div nav { + bottom: 17px; + } +} + +@media only screen and (max-width: 960px) { + header > div:nth-child(1) { + grid-template-columns: 150px 65% 1fr; + height: 150px; + } + + header > div nav { + bottom: 17px; + } +} + +@media only screen and (max-width: 800px) { +} diff --git a/assets/css/global/typography.css b/assets/css/global/typography.css new file mode 100644 index 0000000..ce25919 --- /dev/null +++ b/assets/css/global/typography.css @@ -0,0 +1,54 @@ +/* RUBIK */ +@font-face { + font-family: AlteHaasGrotesk; + src: url("fonts/ahg/AlteHaasGroteskRegular.ttf") format("truetype"), + url("fonts/ahg/AlteHaasGroteskRegular.woff") format("woff"), + url("fonts/ahg/AlteHaasGroteskRegular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} + +@font-face { + font-family: AlteHaasGrotesk; + src: url("fonts/ahg/AlteHaasGroteskBold.ttf") format("truetype"), + url("fonts/ahg/AlteHaasGroteskBold.woff") format("woff"), + url("fonts/ahg/AlteHaasGroteskBold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} + +:root { + --base-type: AlteHaasGrotesk, helvetica, arial, sans-serif; + --mono-type: "Lucida Console", monaco, monospace; +} + +p { + font: 400 1.2em/1.1em var(--base-type); + color: var(--white); +} + +h1 { + font-size: 2em; + font-weight: 600; + font-kerning: normal; + letter-spacing: -3px; + text-transform: uppercase; + line-height: 0.75em; + margin: 0; + overflow-wrap: break-word; + color: var(--highlight); +} + +h2 { + text-transform: lowercase; + font-size: 1.5em; + font-weight: 500; + line-height: 0.8em; + color: var(--primary); + margin: 15px 0; +} + +h3 { + font-size: 1.5em; + font-weight: 300; +} diff --git a/assets/images/global/btn_back.png b/assets/images/global/btn_back.png new file mode 100644 index 0000000..53a3595 Binary files /dev/null and b/assets/images/global/btn_back.png differ diff --git a/assets/images/global/btn_forward.png b/assets/images/global/btn_forward.png new file mode 100644 index 0000000..f7631e1 Binary files /dev/null and b/assets/images/global/btn_forward.png differ diff --git a/assets/images/global/btn_play.png b/assets/images/global/btn_play.png new file mode 100644 index 0000000..6053dc0 Binary files /dev/null and b/assets/images/global/btn_play.png differ diff --git a/assets/images/global/close.svg b/assets/images/global/close.svg new file mode 100644 index 0000000..ac1c0e0 --- /dev/null +++ b/assets/images/global/close.svg @@ -0,0 +1,7 @@ + + + diff --git a/assets/images/global/icon-search.svg b/assets/images/global/icon-search.svg new file mode 100644 index 0000000..47a3125 --- /dev/null +++ b/assets/images/global/icon-search.svg @@ -0,0 +1,8 @@ + + + diff --git a/assets/images/global/logo-dark.svg b/assets/images/global/logo-dark.svg new file mode 100644 index 0000000..157f499 --- /dev/null +++ b/assets/images/global/logo-dark.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/images/global/logo-light.svg b/assets/images/global/logo-light.svg new file mode 100644 index 0000000..d41c38a --- /dev/null +++ b/assets/images/global/logo-light.svg @@ -0,0 +1,5 @@ + + + diff --git a/assets/images/global/logo-primary.png b/assets/images/global/logo-primary.png new file mode 100644 index 0000000..97d0bf2 Binary files /dev/null and b/assets/images/global/logo-primary.png differ diff --git a/assets/images/global/logo-secondary.png b/assets/images/global/logo-secondary.png new file mode 100644 index 0000000..a95a670 Binary files /dev/null and b/assets/images/global/logo-secondary.png differ diff --git a/assets/images/global/menu.svg b/assets/images/global/menu.svg new file mode 100644 index 0000000..c3bc8b0 --- /dev/null +++ b/assets/images/global/menu.svg @@ -0,0 +1,8 @@ + + + diff --git a/assets/images/global/special-trash.jpg b/assets/images/global/special-trash.jpg new file mode 100644 index 0000000..6ee082a Binary files /dev/null and b/assets/images/global/special-trash.jpg differ diff --git a/assets/images/global/status-silence.svg b/assets/images/global/status-silence.svg new file mode 100644 index 0000000..13fdd48 --- /dev/null +++ b/assets/images/global/status-silence.svg @@ -0,0 +1,6 @@ + + + diff --git a/assets/images/global/status-suspend.svg b/assets/images/global/status-suspend.svg new file mode 100644 index 0000000..49a4cf3 --- /dev/null +++ b/assets/images/global/status-suspend.svg @@ -0,0 +1,6 @@ + + + diff --git a/assets/images/global/tape-icons.png b/assets/images/global/tape-icons.png new file mode 100644 index 0000000..f7631e1 Binary files /dev/null and b/assets/images/global/tape-icons.png differ diff --git a/assets/script/RecordPlayer.js b/assets/script/RecordPlayer.js new file mode 100644 index 0000000..23f3b9e --- /dev/null +++ b/assets/script/RecordPlayer.js @@ -0,0 +1,171 @@ +export default class RecordPlayer { + //-------------------------- + // constructor + //-------------------------- + constructor() { + this.audio = null; + this.tracklist = ""; + this.currentTrackIndex = -1; + this.trackListCount = 0; + this.playing = false; + this.controlText = document.querySelector(".control-text"); + this.tapePlaylist = document.getElementById("playlist"); + this.nextBtn = document.getElementById("control-next"); + this.backBtn = document.getElementById("control-back"); + this.progress = document.querySelector(".audio-progress"); + + this.setUpAudio(); + } + + setUpAudio() { + let self = this; + //create element + this.audio = document.createElement("audio"); + this.audio.controls = false; + //set event listners + this.audio.addEventListener( + "timeupdate", + (e) => this.handleTimeUpdate(e), + false + ); + + this.audio.addEventListener("ended", () => self.control("next"), false); + + this.nextBtn.addEventListener("click", () => { + self.control("next"); + }); + + this.backBtn.addEventListener("click", () => { + self.control("back"); + }); + } + + setTrackList(tracklist) { + this.tracklist = tracklist; + this.trackListCount = this.tracklist.length; + this.buildPlaylist(this.tracklist); + } + + buildPlaylist(tracklist) { + //clear playlist + this.tapePlaylist.innerHTML = ""; + //build playlist ui + for (var i = 0, length = tracklist.length; i < length; i++) { + let sleeve = document.createElement("div"); + let select = document.createElement("button"); + sleeve.className = "sleeve"; + sleeve.id = "sleeve_" + i; + select.id = tracklist[i].file; + select.className = "selectBtn"; + let recordTitle = document.createElement("label"); + recordTitle.id = "title_" + i; + recordTitle.innerText = tracklist[i].title; + let recordAlbum = document.createElement("label"); + recordAlbum.id = "album_" + i; + recordAlbum.innerText = tracklist[i].album; + let recordArtist = document.createElement("label"); + recordArtist.id = "artist_" + i; + recordArtist.innerText = tracklist[i].artist; + + sleeve.appendChild(select); + sleeve.appendChild(recordTitle); + sleeve.appendChild(recordAlbum); + sleeve.appendChild(recordArtist); + + this.tapePlaylist.appendChild(sleeve); + + select.addEventListener("click", (e) => this.handleButton(e), false); + } + } + + playRecord(file) { + for (var i = 0, length = this.tracklist.length; i < length; i++) { + let sleeve = document.getElementById("sleeve_" + i); + let album = document.getElementById("album_" + i); + let artist = document.getElementById("artist_" + i); + //handle pause state + if (file == this.tracklist[i].file) { + //if index is same, play our pause + if (i === this.currentTrackIndex) { + if (this.playing) { + this.audio.pause(); + sleeve.style.opacity = 0.5; + this.playing = false; + return; + } else { + console.log("play"); + this.audio.play(); + sleeve.style.opacity = 1; + this.playing = true; + return; + } + } else { + //if index is different play next track + this.currentTrackIndex = i; + this.controlText.innerText = this.tracklist[i].title; + sleeve.classList.add("playing"); + sleeve.classList.remove("not-playing"); + album.classList.add("label-playing"); + album.classList.remove("label-notplaying"); + artist.classList.add("label-playing"); + artist.classList.remove("label-notplaying"); + } + } else { + sleeve.classList.add("not-playing"); + sleeve.classList.remove("playing"); + album.classList.add("label-notplaying"); + artist.classList.add("label-notplaying"); + } + } + + let record = + "assets/audio/tapes/" + + this.tracklist[this.currentTrackIndex].tape + + "/" + + this.tracklist[this.currentTrackIndex].file; + + this.audio.src = record; + this.audio.volume = 0.5; + this.playing = true; + this.audio.play(); + } + + control(task) { + this.playing = false; + let fresh = ""; + switch (task) { + case "next": + fresh = this.currentTrackIndex + 1; + if (fresh > this.tracklist.length - 1) fresh = 0; + + break; + case "back": + fresh = this.currentTrackIndex - 1; + if (fresh < 0) fresh = this.tracklist.length - 1; + break; + } + this.playRecord(this.tracklist[fresh].file); + } + + //event handlers + handleButton(e) { + e.stopPropagation(); + e.preventDefault(); + this.playRecord(e.target.id); + } + + handleTimeUpdate(e) { + var s = parseInt(this.audio.currentTime % 60); + if (s < 10) { + s = "0" + s; + } + var m = parseInt((this.audio.currentTime / 60) % 60); + if (m < 10) { + m = "0" + m; + } + + var currentProgress = this.audio.currentTime / this.audio.duration; + var visProgress = 350 * currentProgress; + this.progress.style.width = visProgress + "px"; + } +} diff --git a/assets/script/Start.js b/assets/script/Start.js new file mode 100644 index 0000000..7b2b0be --- /dev/null +++ b/assets/script/Start.js @@ -0,0 +1,9 @@ +import TapeDeck from "./TapeDeck.js"; + +document.addEventListener( + "DOMContentLoaded", + function () { + new TapeDeck(); + }, + false +); diff --git a/assets/script/TapeDeck.js b/assets/script/TapeDeck.js new file mode 100644 index 0000000..d81b53d --- /dev/null +++ b/assets/script/TapeDeck.js @@ -0,0 +1,74 @@ +import RecordPlayer from "./RecordPlayer.js"; + +export default class TapeDeck { + //-------------------------- + // constructor + //-------------------------- + constructor() { + let self = this; + this.rp = new RecordPlayer(); + this.tapeSelect = document.querySelector(".tape-select"); + this.tapeTracklist = document.querySelector(".tape-tracklist"); + this.tapeCover = document.getElementById("tape-cover"); + this.tapeTitle = document.getElementById("tape-title"); + this.tapeControls = document.getElementById("tape-controls"); + this.logo = document.querySelector(".the-logo"); + this.logo.addEventListener("click", (e) => { + self.tapeSelect.classList.remove("back-left"); + self.tapeSelect.classList.add("front"); + self.tapeTracklist.classList.remove("front"); + self.tapeTracklist.classList.add("back-right"); + self.tapeControls.classList.add("control-closed"); + self.tapeControls.classList.remove("control-open"); + }); + + var request = new XMLHttpRequest(); + request.open("GET", "api/tapes/list", true); + request.onload = () => { + if (request.status == 200) { + let response = JSON.parse(request["response"]); + //console.log(response); + } else { + let error = JSON.parse(request["response"]); + consolel.log(); + } + }; + request.send(); + //make images buttons for playlist retrieval + var covers = document.querySelectorAll(".covers"); + for (var i = 0, length = covers.length; i < length; i++) { + covers[i].addEventListener("click", (e) => this.handleCovers(e), false); + } + } + + //event handlers + + handleCovers(e) { + e.stopPropagation(); + e.preventDefault(); + + var request = new XMLHttpRequest(); + let self = this; + request.open("GET", "api/tapes/tracklist/" + e.target.id, true); + request.onload = () => { + if (request.status == 200) { + let response = JSON.parse(request["response"]); + //once playlist is loaded, flip to list mode + self.tapeSelect.classList.remove("front"); + self.tapeSelect.classList.add("back-left"); + self.tapeTracklist.classList.remove("back-right"); + self.tapeTracklist.classList.add("front"); + self.tapeCover.src = e.target.src; + self.tapeTitle.innerText = "VOL. " + response[0].tape; + //open tape controls + self.tapeControls.classList.remove("control-closed"); + self.tapeControls.classList.add("control-open"); + //set up player + self.rp.setTrackList(response); + } else { + let error = JSON.parse(request["response"]); + } + }; + request.send(); + } +} diff --git a/assets/script/third/anime.es.js b/assets/script/third/anime.es.js new file mode 100644 index 0000000..0352381 --- /dev/null +++ b/assets/script/third/anime.es.js @@ -0,0 +1,1311 @@ +/* + * anime.js v3.2.2 + * (c) 2023 Julian Garnier + * Released under the MIT license + * animejs.com + */ + +// Defaults + +var defaultInstanceSettings = { + update: null, + begin: null, + loopBegin: null, + changeBegin: null, + change: null, + changeComplete: null, + loopComplete: null, + complete: null, + loop: 1, + direction: 'normal', + autoplay: true, + timelineOffset: 0 +}; + +var defaultTweenSettings = { + duration: 1000, + delay: 0, + endDelay: 0, + easing: 'easeOutElastic(1, .5)', + round: 0 +}; + +var validTransforms = ['translateX', 'translateY', 'translateZ', 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'perspective', 'matrix', 'matrix3d']; + +// Caching + +var cache = { + CSS: {}, + springs: {} +}; + +// Utils + +function minMax(val, min, max) { + return Math.min(Math.max(val, min), max); +} + +function stringContains(str, text) { + return str.indexOf(text) > -1; +} + +function applyArguments(func, args) { + return func.apply(null, args); +} + +var is = { + arr: function (a) { return Array.isArray(a); }, + obj: function (a) { return stringContains(Object.prototype.toString.call(a), 'Object'); }, + pth: function (a) { return is.obj(a) && a.hasOwnProperty('totalLength'); }, + svg: function (a) { return a instanceof SVGElement; }, + inp: function (a) { return a instanceof HTMLInputElement; }, + dom: function (a) { return a.nodeType || is.svg(a); }, + str: function (a) { return typeof a === 'string'; }, + fnc: function (a) { return typeof a === 'function'; }, + und: function (a) { return typeof a === 'undefined'; }, + nil: function (a) { return is.und(a) || a === null; }, + hex: function (a) { return /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(a); }, + rgb: function (a) { return /^rgb/.test(a); }, + hsl: function (a) { return /^hsl/.test(a); }, + col: function (a) { return (is.hex(a) || is.rgb(a) || is.hsl(a)); }, + key: function (a) { return !defaultInstanceSettings.hasOwnProperty(a) && !defaultTweenSettings.hasOwnProperty(a) && a !== 'targets' && a !== 'keyframes'; }, +}; + +// Easings + +function parseEasingParameters(string) { + var match = /\(([^)]+)\)/.exec(string); + return match ? match[1].split(',').map(function (p) { return parseFloat(p); }) : []; +} + +// Spring solver inspired by Webkit Copyright © 2016 Apple Inc. All rights reserved. https://webkit.org/demos/spring/spring.js + +function spring(string, duration) { + + var params = parseEasingParameters(string); + var mass = minMax(is.und(params[0]) ? 1 : params[0], .1, 100); + var stiffness = minMax(is.und(params[1]) ? 100 : params[1], .1, 100); + var damping = minMax(is.und(params[2]) ? 10 : params[2], .1, 100); + var velocity = minMax(is.und(params[3]) ? 0 : params[3], .1, 100); + var w0 = Math.sqrt(stiffness / mass); + var zeta = damping / (2 * Math.sqrt(stiffness * mass)); + var wd = zeta < 1 ? w0 * Math.sqrt(1 - zeta * zeta) : 0; + var a = 1; + var b = zeta < 1 ? (zeta * w0 + -velocity) / wd : -velocity + w0; + + function solver(t) { + var progress = duration ? (duration * t) / 1000 : t; + if (zeta < 1) { + progress = Math.exp(-progress * zeta * w0) * (a * Math.cos(wd * progress) + b * Math.sin(wd * progress)); + } else { + progress = (a + b * progress) * Math.exp(-progress * w0); + } + if (t === 0 || t === 1) { return t; } + return 1 - progress; + } + + function getDuration() { + var cached = cache.springs[string]; + if (cached) { return cached; } + var frame = 1/6; + var elapsed = 0; + var rest = 0; + while(true) { + elapsed += frame; + if (solver(elapsed) === 1) { + rest++; + if (rest >= 16) { break; } + } else { + rest = 0; + } + } + var duration = elapsed * frame * 1000; + cache.springs[string] = duration; + return duration; + } + + return duration ? solver : getDuration; + +} + +// Basic steps easing implementation https://developer.mozilla.org/fr/docs/Web/CSS/transition-timing-function + +function steps(steps) { + if ( steps === void 0 ) steps = 10; + + return function (t) { return Math.ceil((minMax(t, 0.000001, 1)) * steps) * (1 / steps); }; +} + +// BezierEasing https://github.com/gre/bezier-easing + +var bezier = (function () { + + var kSplineTableSize = 11; + var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); + + function A(aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1 } + function B(aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1 } + function C(aA1) { return 3.0 * aA1 } + + function calcBezier(aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT } + function getSlope(aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1) } + + function binarySubdivide(aX, aA, aB, mX1, mX2) { + var currentX, currentT, i = 0; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { aB = currentT; } else { aA = currentT; } + } while (Math.abs(currentX) > 0.0000001 && ++i < 10); + return currentT; + } + + function newtonRaphsonIterate(aX, aGuessT, mX1, mX2) { + for (var i = 0; i < 4; ++i) { + var currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) { return aGuessT; } + var currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; + } + + function bezier(mX1, mY1, mX2, mY2) { + + if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { return; } + var sampleValues = new Float32Array(kSplineTableSize); + + if (mX1 !== mY1 || mX2 !== mY2) { + for (var i = 0; i < kSplineTableSize; ++i) { + sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + } + + function getTForX(aX) { + + var intervalStart = 0; + var currentSample = 1; + var lastSample = kSplineTableSize - 1; + + for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { + intervalStart += kSampleStepSize; + } + + --currentSample; + + var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); + var guessForT = intervalStart + dist * kSampleStepSize; + var initialSlope = getSlope(guessForT, mX1, mX2); + + if (initialSlope >= 0.001) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } else if (initialSlope === 0.0) { + return guessForT; + } else { + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); + } + + } + + return function (x) { + if (mX1 === mY1 && mX2 === mY2) { return x; } + if (x === 0 || x === 1) { return x; } + return calcBezier(getTForX(x), mY1, mY2); + } + + } + + return bezier; + +})(); + +var penner = (function () { + + // Based on jQuery UI's implemenation of easing equations from Robert Penner (http://www.robertpenner.com/easing) + + var eases = { linear: function () { return function (t) { return t; }; } }; + + var functionEasings = { + Sine: function () { return function (t) { return 1 - Math.cos(t * Math.PI / 2); }; }, + Expo: function () { return function (t) { return t ? Math.pow(2, 10 * t - 10) : 0; }; }, + Circ: function () { return function (t) { return 1 - Math.sqrt(1 - t * t); }; }, + Back: function () { return function (t) { return t * t * (3 * t - 2); }; }, + Bounce: function () { return function (t) { + var pow2, b = 4; + while (t < (( pow2 = Math.pow(2, --b)) - 1) / 11) {} + return 1 / Math.pow(4, 3 - b) - 7.5625 * Math.pow(( pow2 * 3 - 2 ) / 22 - t, 2) + }; }, + Elastic: function (amplitude, period) { + if ( amplitude === void 0 ) amplitude = 1; + if ( period === void 0 ) period = .5; + + var a = minMax(amplitude, 1, 10); + var p = minMax(period, .1, 2); + return function (t) { + return (t === 0 || t === 1) ? t : + -a * Math.pow(2, 10 * (t - 1)) * Math.sin((((t - 1) - (p / (Math.PI * 2) * Math.asin(1 / a))) * (Math.PI * 2)) / p); + } + } + }; + + var baseEasings = ['Quad', 'Cubic', 'Quart', 'Quint']; + + baseEasings.forEach(function (name, i) { + functionEasings[name] = function () { return function (t) { return Math.pow(t, i + 2); }; }; + }); + + Object.keys(functionEasings).forEach(function (name) { + var easeIn = functionEasings[name]; + eases['easeIn' + name] = easeIn; + eases['easeOut' + name] = function (a, b) { return function (t) { return 1 - easeIn(a, b)(1 - t); }; }; + eases['easeInOut' + name] = function (a, b) { return function (t) { return t < 0.5 ? easeIn(a, b)(t * 2) / 2 : + 1 - easeIn(a, b)(t * -2 + 2) / 2; }; }; + eases['easeOutIn' + name] = function (a, b) { return function (t) { return t < 0.5 ? (1 - easeIn(a, b)(1 - t * 2)) / 2 : + (easeIn(a, b)(t * 2 - 1) + 1) / 2; }; }; + }); + + return eases; + +})(); + +function parseEasings(easing, duration) { + if (is.fnc(easing)) { return easing; } + var name = easing.split('(')[0]; + var ease = penner[name]; + var args = parseEasingParameters(easing); + switch (name) { + case 'spring' : return spring(easing, duration); + case 'cubicBezier' : return applyArguments(bezier, args); + case 'steps' : return applyArguments(steps, args); + default : return applyArguments(ease, args); + } +} + +// Strings + +function selectString(str) { + try { + var nodes = document.querySelectorAll(str); + return nodes; + } catch(e) { + return; + } +} + +// Arrays + +function filterArray(arr, callback) { + var len = arr.length; + var thisArg = arguments.length >= 2 ? arguments[1] : void 0; + var result = []; + for (var i = 0; i < len; i++) { + if (i in arr) { + var val = arr[i]; + if (callback.call(thisArg, val, i, arr)) { + result.push(val); + } + } + } + return result; +} + +function flattenArray(arr) { + return arr.reduce(function (a, b) { return a.concat(is.arr(b) ? flattenArray(b) : b); }, []); +} + +function toArray(o) { + if (is.arr(o)) { return o; } + if (is.str(o)) { o = selectString(o) || o; } + if (o instanceof NodeList || o instanceof HTMLCollection) { return [].slice.call(o); } + return [o]; +} + +function arrayContains(arr, val) { + return arr.some(function (a) { return a === val; }); +} + +// Objects + +function cloneObject(o) { + var clone = {}; + for (var p in o) { clone[p] = o[p]; } + return clone; +} + +function replaceObjectProps(o1, o2) { + var o = cloneObject(o1); + for (var p in o1) { o[p] = o2.hasOwnProperty(p) ? o2[p] : o1[p]; } + return o; +} + +function mergeObjects(o1, o2) { + var o = cloneObject(o1); + for (var p in o2) { o[p] = is.und(o1[p]) ? o2[p] : o1[p]; } + return o; +} + +// Colors + +function rgbToRgba(rgbValue) { + var rgb = /rgb\((\d+,\s*[\d]+,\s*[\d]+)\)/g.exec(rgbValue); + return rgb ? ("rgba(" + (rgb[1]) + ",1)") : rgbValue; +} + +function hexToRgba(hexValue) { + var rgx = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; + var hex = hexValue.replace(rgx, function (m, r, g, b) { return r + r + g + g + b + b; } ); + var rgb = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + var r = parseInt(rgb[1], 16); + var g = parseInt(rgb[2], 16); + var b = parseInt(rgb[3], 16); + return ("rgba(" + r + "," + g + "," + b + ",1)"); +} + +function hslToRgba(hslValue) { + var hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue) || /hsla\((\d+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)/g.exec(hslValue); + var h = parseInt(hsl[1], 10) / 360; + var s = parseInt(hsl[2], 10) / 100; + var l = parseInt(hsl[3], 10) / 100; + var a = hsl[4] || 1; + function hue2rgb(p, q, t) { + if (t < 0) { t += 1; } + if (t > 1) { t -= 1; } + if (t < 1/6) { return p + (q - p) * 6 * t; } + if (t < 1/2) { return q; } + if (t < 2/3) { return p + (q - p) * (2/3 - t) * 6; } + return p; + } + var r, g, b; + if (s == 0) { + r = g = b = l; + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + return ("rgba(" + (r * 255) + "," + (g * 255) + "," + (b * 255) + "," + a + ")"); +} + +function colorToRgb(val) { + if (is.rgb(val)) { return rgbToRgba(val); } + if (is.hex(val)) { return hexToRgba(val); } + if (is.hsl(val)) { return hslToRgba(val); } +} + +// Units + +function getUnit(val) { + var split = /[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(val); + if (split) { return split[1]; } +} + +function getTransformUnit(propName) { + if (stringContains(propName, 'translate') || propName === 'perspective') { return 'px'; } + if (stringContains(propName, 'rotate') || stringContains(propName, 'skew')) { return 'deg'; } +} + +// Values + +function getFunctionValue(val, animatable) { + if (!is.fnc(val)) { return val; } + return val(animatable.target, animatable.id, animatable.total); +} + +function getAttribute(el, prop) { + return el.getAttribute(prop); +} + +function convertPxToUnit(el, value, unit) { + var valueUnit = getUnit(value); + if (arrayContains([unit, 'deg', 'rad', 'turn'], valueUnit)) { return value; } + var cached = cache.CSS[value + unit]; + if (!is.und(cached)) { return cached; } + var baseline = 100; + var tempEl = document.createElement(el.tagName); + var parentEl = (el.parentNode && (el.parentNode !== document)) ? el.parentNode : document.body; + parentEl.appendChild(tempEl); + tempEl.style.position = 'absolute'; + tempEl.style.width = baseline + unit; + var factor = baseline / tempEl.offsetWidth; + parentEl.removeChild(tempEl); + var convertedUnit = factor * parseFloat(value); + cache.CSS[value + unit] = convertedUnit; + return convertedUnit; +} + +function getCSSValue(el, prop, unit) { + if (prop in el.style) { + var uppercasePropName = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + var value = el.style[prop] || getComputedStyle(el).getPropertyValue(uppercasePropName) || '0'; + return unit ? convertPxToUnit(el, value, unit) : value; + } +} + +function getAnimationType(el, prop) { + if (is.dom(el) && !is.inp(el) && (!is.nil(getAttribute(el, prop)) || (is.svg(el) && el[prop]))) { return 'attribute'; } + if (is.dom(el) && arrayContains(validTransforms, prop)) { return 'transform'; } + if (is.dom(el) && (prop !== 'transform' && getCSSValue(el, prop))) { return 'css'; } + if (el[prop] != null) { return 'object'; } +} + +function getElementTransforms(el) { + if (!is.dom(el)) { return; } + var str = el.style.transform || ''; + var reg = /(\w+)\(([^)]*)\)/g; + var transforms = new Map(); + var m; while (m = reg.exec(str)) { transforms.set(m[1], m[2]); } + return transforms; +} + +function getTransformValue(el, propName, animatable, unit) { + var defaultVal = stringContains(propName, 'scale') ? 1 : 0 + getTransformUnit(propName); + var value = getElementTransforms(el).get(propName) || defaultVal; + if (animatable) { + animatable.transforms.list.set(propName, value); + animatable.transforms['last'] = propName; + } + return unit ? convertPxToUnit(el, value, unit) : value; +} + +function getOriginalTargetValue(target, propName, unit, animatable) { + switch (getAnimationType(target, propName)) { + case 'transform': return getTransformValue(target, propName, animatable, unit); + case 'css': return getCSSValue(target, propName, unit); + case 'attribute': return getAttribute(target, propName); + default: return target[propName] || 0; + } +} + +function getRelativeValue(to, from) { + var operator = /^(\*=|\+=|-=)/.exec(to); + if (!operator) { return to; } + var u = getUnit(to) || 0; + var x = parseFloat(from); + var y = parseFloat(to.replace(operator[0], '')); + switch (operator[0][0]) { + case '+': return x + y + u; + case '-': return x - y + u; + case '*': return x * y + u; + } +} + +function validateValue(val, unit) { + if (is.col(val)) { return colorToRgb(val); } + if (/\s/g.test(val)) { return val; } + var originalUnit = getUnit(val); + var unitLess = originalUnit ? val.substr(0, val.length - originalUnit.length) : val; + if (unit) { return unitLess + unit; } + return unitLess; +} + +// getTotalLength() equivalent for circle, rect, polyline, polygon and line shapes +// adapted from https://gist.github.com/SebLambla/3e0550c496c236709744 + +function getDistance(p1, p2) { + return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); +} + +function getCircleLength(el) { + return Math.PI * 2 * getAttribute(el, 'r'); +} + +function getRectLength(el) { + return (getAttribute(el, 'width') * 2) + (getAttribute(el, 'height') * 2); +} + +function getLineLength(el) { + return getDistance( + {x: getAttribute(el, 'x1'), y: getAttribute(el, 'y1')}, + {x: getAttribute(el, 'x2'), y: getAttribute(el, 'y2')} + ); +} + +function getPolylineLength(el) { + var points = el.points; + var totalLength = 0; + var previousPos; + for (var i = 0 ; i < points.numberOfItems; i++) { + var currentPos = points.getItem(i); + if (i > 0) { totalLength += getDistance(previousPos, currentPos); } + previousPos = currentPos; + } + return totalLength; +} + +function getPolygonLength(el) { + var points = el.points; + return getPolylineLength(el) + getDistance(points.getItem(points.numberOfItems - 1), points.getItem(0)); +} + +// Path animation + +function getTotalLength(el) { + if (el.getTotalLength) { return el.getTotalLength(); } + switch(el.tagName.toLowerCase()) { + case 'circle': return getCircleLength(el); + case 'rect': return getRectLength(el); + case 'line': return getLineLength(el); + case 'polyline': return getPolylineLength(el); + case 'polygon': return getPolygonLength(el); + } +} + +function setDashoffset(el) { + var pathLength = getTotalLength(el); + el.setAttribute('stroke-dasharray', pathLength); + return pathLength; +} + +// Motion path + +function getParentSvgEl(el) { + var parentEl = el.parentNode; + while (is.svg(parentEl)) { + if (!is.svg(parentEl.parentNode)) { break; } + parentEl = parentEl.parentNode; + } + return parentEl; +} + +function getParentSvg(pathEl, svgData) { + var svg = svgData || {}; + var parentSvgEl = svg.el || getParentSvgEl(pathEl); + var rect = parentSvgEl.getBoundingClientRect(); + var viewBoxAttr = getAttribute(parentSvgEl, 'viewBox'); + var width = rect.width; + var height = rect.height; + var viewBox = svg.viewBox || (viewBoxAttr ? viewBoxAttr.split(' ') : [0, 0, width, height]); + return { + el: parentSvgEl, + viewBox: viewBox, + x: viewBox[0] / 1, + y: viewBox[1] / 1, + w: width, + h: height, + vW: viewBox[2], + vH: viewBox[3] + } +} + +function getPath(path, percent) { + var pathEl = is.str(path) ? selectString(path)[0] : path; + var p = percent || 100; + return function(property) { + return { + property: property, + el: pathEl, + svg: getParentSvg(pathEl), + totalLength: getTotalLength(pathEl) * (p / 100) + } + } +} + +function getPathProgress(path, progress, isPathTargetInsideSVG) { + function point(offset) { + if ( offset === void 0 ) offset = 0; + + var l = progress + offset >= 1 ? progress + offset : 0; + return path.el.getPointAtLength(l); + } + var svg = getParentSvg(path.el, path.svg); + var p = point(); + var p0 = point(-1); + var p1 = point(+1); + var scaleX = isPathTargetInsideSVG ? 1 : svg.w / svg.vW; + var scaleY = isPathTargetInsideSVG ? 1 : svg.h / svg.vH; + switch (path.property) { + case 'x': return (p.x - svg.x) * scaleX; + case 'y': return (p.y - svg.y) * scaleY; + case 'angle': return Math.atan2(p1.y - p0.y, p1.x - p0.x) * 180 / Math.PI; + } +} + +// Decompose value + +function decomposeValue(val, unit) { + // const rgx = /-?\d*\.?\d+/g; // handles basic numbers + // const rgx = /[+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g; // handles exponents notation + var rgx = /[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g; // handles exponents notation + var value = validateValue((is.pth(val) ? val.totalLength : val), unit) + ''; + return { + original: value, + numbers: value.match(rgx) ? value.match(rgx).map(Number) : [0], + strings: (is.str(val) || unit) ? value.split(rgx) : [] + } +} + +// Animatables + +function parseTargets(targets) { + var targetsArray = targets ? (flattenArray(is.arr(targets) ? targets.map(toArray) : toArray(targets))) : []; + return filterArray(targetsArray, function (item, pos, self) { return self.indexOf(item) === pos; }); +} + +function getAnimatables(targets) { + var parsed = parseTargets(targets); + return parsed.map(function (t, i) { + return {target: t, id: i, total: parsed.length, transforms: { list: getElementTransforms(t) } }; + }); +} + +// Properties + +function normalizePropertyTweens(prop, tweenSettings) { + var settings = cloneObject(tweenSettings); + // Override duration if easing is a spring + if (/^spring/.test(settings.easing)) { settings.duration = spring(settings.easing); } + if (is.arr(prop)) { + var l = prop.length; + var isFromTo = (l === 2 && !is.obj(prop[0])); + if (!isFromTo) { + // Duration divided by the number of tweens + if (!is.fnc(tweenSettings.duration)) { settings.duration = tweenSettings.duration / l; } + } else { + // Transform [from, to] values shorthand to a valid tween value + prop = {value: prop}; + } + } + var propArray = is.arr(prop) ? prop : [prop]; + return propArray.map(function (v, i) { + var obj = (is.obj(v) && !is.pth(v)) ? v : {value: v}; + // Default delay value should only be applied to the first tween + if (is.und(obj.delay)) { obj.delay = !i ? tweenSettings.delay : 0; } + // Default endDelay value should only be applied to the last tween + if (is.und(obj.endDelay)) { obj.endDelay = i === propArray.length - 1 ? tweenSettings.endDelay : 0; } + return obj; + }).map(function (k) { return mergeObjects(k, settings); }); +} + + +function flattenKeyframes(keyframes) { + var propertyNames = filterArray(flattenArray(keyframes.map(function (key) { return Object.keys(key); })), function (p) { return is.key(p); }) + .reduce(function (a,b) { if (a.indexOf(b) < 0) { a.push(b); } return a; }, []); + var properties = {}; + var loop = function ( i ) { + var propName = propertyNames[i]; + properties[propName] = keyframes.map(function (key) { + var newKey = {}; + for (var p in key) { + if (is.key(p)) { + if (p == propName) { newKey.value = key[p]; } + } else { + newKey[p] = key[p]; + } + } + return newKey; + }); + }; + + for (var i = 0; i < propertyNames.length; i++) loop( i ); + return properties; +} + +function getProperties(tweenSettings, params) { + var properties = []; + var keyframes = params.keyframes; + if (keyframes) { params = mergeObjects(flattenKeyframes(keyframes), params); } + for (var p in params) { + if (is.key(p)) { + properties.push({ + name: p, + tweens: normalizePropertyTweens(params[p], tweenSettings) + }); + } + } + return properties; +} + +// Tweens + +function normalizeTweenValues(tween, animatable) { + var t = {}; + for (var p in tween) { + var value = getFunctionValue(tween[p], animatable); + if (is.arr(value)) { + value = value.map(function (v) { return getFunctionValue(v, animatable); }); + if (value.length === 1) { value = value[0]; } + } + t[p] = value; + } + t.duration = parseFloat(t.duration); + t.delay = parseFloat(t.delay); + return t; +} + +function normalizeTweens(prop, animatable) { + var previousTween; + return prop.tweens.map(function (t) { + var tween = normalizeTweenValues(t, animatable); + var tweenValue = tween.value; + var to = is.arr(tweenValue) ? tweenValue[1] : tweenValue; + var toUnit = getUnit(to); + var originalValue = getOriginalTargetValue(animatable.target, prop.name, toUnit, animatable); + var previousValue = previousTween ? previousTween.to.original : originalValue; + var from = is.arr(tweenValue) ? tweenValue[0] : previousValue; + var fromUnit = getUnit(from) || getUnit(originalValue); + var unit = toUnit || fromUnit; + if (is.und(to)) { to = previousValue; } + tween.from = decomposeValue(from, unit); + tween.to = decomposeValue(getRelativeValue(to, from), unit); + tween.start = previousTween ? previousTween.end : 0; + tween.end = tween.start + tween.delay + tween.duration + tween.endDelay; + tween.easing = parseEasings(tween.easing, tween.duration); + tween.isPath = is.pth(tweenValue); + tween.isPathTargetInsideSVG = tween.isPath && is.svg(animatable.target); + tween.isColor = is.col(tween.from.original); + if (tween.isColor) { tween.round = 1; } + previousTween = tween; + return tween; + }); +} + +// Tween progress + +var setProgressValue = { + css: function (t, p, v) { return t.style[p] = v; }, + attribute: function (t, p, v) { return t.setAttribute(p, v); }, + object: function (t, p, v) { return t[p] = v; }, + transform: function (t, p, v, transforms, manual) { + transforms.list.set(p, v); + if (p === transforms.last || manual) { + var str = ''; + transforms.list.forEach(function (value, prop) { str += prop + "(" + value + ") "; }); + t.style.transform = str; + } + } +}; + +// Set Value helper + +function setTargetsValue(targets, properties) { + var animatables = getAnimatables(targets); + animatables.forEach(function (animatable) { + for (var property in properties) { + var value = getFunctionValue(properties[property], animatable); + var target = animatable.target; + var valueUnit = getUnit(value); + var originalValue = getOriginalTargetValue(target, property, valueUnit, animatable); + var unit = valueUnit || getUnit(originalValue); + var to = getRelativeValue(validateValue(value, unit), originalValue); + var animType = getAnimationType(target, property); + setProgressValue[animType](target, property, to, animatable.transforms, true); + } + }); +} + +// Animations + +function createAnimation(animatable, prop) { + var animType = getAnimationType(animatable.target, prop.name); + if (animType) { + var tweens = normalizeTweens(prop, animatable); + var lastTween = tweens[tweens.length - 1]; + return { + type: animType, + property: prop.name, + animatable: animatable, + tweens: tweens, + duration: lastTween.end, + delay: tweens[0].delay, + endDelay: lastTween.endDelay + } + } +} + +function getAnimations(animatables, properties) { + return filterArray(flattenArray(animatables.map(function (animatable) { + return properties.map(function (prop) { + return createAnimation(animatable, prop); + }); + })), function (a) { return !is.und(a); }); +} + +// Create Instance + +function getInstanceTimings(animations, tweenSettings) { + var animLength = animations.length; + var getTlOffset = function (anim) { return anim.timelineOffset ? anim.timelineOffset : 0; }; + var timings = {}; + timings.duration = animLength ? Math.max.apply(Math, animations.map(function (anim) { return getTlOffset(anim) + anim.duration; })) : tweenSettings.duration; + timings.delay = animLength ? Math.min.apply(Math, animations.map(function (anim) { return getTlOffset(anim) + anim.delay; })) : tweenSettings.delay; + timings.endDelay = animLength ? timings.duration - Math.max.apply(Math, animations.map(function (anim) { return getTlOffset(anim) + anim.duration - anim.endDelay; })) : tweenSettings.endDelay; + return timings; +} + +var instanceID = 0; + +function createNewInstance(params) { + var instanceSettings = replaceObjectProps(defaultInstanceSettings, params); + var tweenSettings = replaceObjectProps(defaultTweenSettings, params); + var properties = getProperties(tweenSettings, params); + var animatables = getAnimatables(params.targets); + var animations = getAnimations(animatables, properties); + var timings = getInstanceTimings(animations, tweenSettings); + var id = instanceID; + instanceID++; + return mergeObjects(instanceSettings, { + id: id, + children: [], + animatables: animatables, + animations: animations, + duration: timings.duration, + delay: timings.delay, + endDelay: timings.endDelay + }); +} + +// Core + +var activeInstances = []; + +var engine = (function () { + var raf; + + function play() { + if (!raf && (!isDocumentHidden() || !anime.suspendWhenDocumentHidden) && activeInstances.length > 0) { + raf = requestAnimationFrame(step); + } + } + function step(t) { + // memo on algorithm issue: + // dangerous iteration over mutable `activeInstances` + // (that collection may be updated from within callbacks of `tick`-ed animation instances) + var activeInstancesLength = activeInstances.length; + var i = 0; + while (i < activeInstancesLength) { + var activeInstance = activeInstances[i]; + if (!activeInstance.paused) { + activeInstance.tick(t); + i++; + } else { + activeInstances.splice(i, 1); + activeInstancesLength--; + } + } + raf = i > 0 ? requestAnimationFrame(step) : undefined; + } + + function handleVisibilityChange() { + if (!anime.suspendWhenDocumentHidden) { return; } + + if (isDocumentHidden()) { + // suspend ticks + raf = cancelAnimationFrame(raf); + } else { // is back to active tab + // first adjust animations to consider the time that ticks were suspended + activeInstances.forEach( + function (instance) { return instance ._onDocumentVisibility(); } + ); + engine(); + } + } + if (typeof document !== 'undefined') { + document.addEventListener('visibilitychange', handleVisibilityChange); + } + + return play; +})(); + +function isDocumentHidden() { + return !!document && document.hidden; +} + +// Public Instance + +function anime(params) { + if ( params === void 0 ) params = {}; + + + var startTime = 0, lastTime = 0, now = 0; + var children, childrenLength = 0; + var resolve = null; + + function makePromise(instance) { + var promise = window.Promise && new Promise(function (_resolve) { return resolve = _resolve; }); + instance.finished = promise; + return promise; + } + + var instance = createNewInstance(params); + var promise = makePromise(instance); + + function toggleInstanceDirection() { + var direction = instance.direction; + if (direction !== 'alternate') { + instance.direction = direction !== 'normal' ? 'normal' : 'reverse'; + } + instance.reversed = !instance.reversed; + children.forEach(function (child) { return child.reversed = instance.reversed; }); + } + + function adjustTime(time) { + return instance.reversed ? instance.duration - time : time; + } + + function resetTime() { + startTime = 0; + lastTime = adjustTime(instance.currentTime) * (1 / anime.speed); + } + + function seekChild(time, child) { + if (child) { child.seek(time - child.timelineOffset); } + } + + function syncInstanceChildren(time) { + if (!instance.reversePlayback) { + for (var i = 0; i < childrenLength; i++) { seekChild(time, children[i]); } + } else { + for (var i$1 = childrenLength; i$1--;) { seekChild(time, children[i$1]); } + } + } + + function setAnimationsProgress(insTime) { + var i = 0; + var animations = instance.animations; + var animationsLength = animations.length; + while (i < animationsLength) { + var anim = animations[i]; + var animatable = anim.animatable; + var tweens = anim.tweens; + var tweenLength = tweens.length - 1; + var tween = tweens[tweenLength]; + // Only check for keyframes if there is more than one tween + if (tweenLength) { tween = filterArray(tweens, function (t) { return (insTime < t.end); })[0] || tween; } + var elapsed = minMax(insTime - tween.start - tween.delay, 0, tween.duration) / tween.duration; + var eased = isNaN(elapsed) ? 1 : tween.easing(elapsed); + var strings = tween.to.strings; + var round = tween.round; + var numbers = []; + var toNumbersLength = tween.to.numbers.length; + var progress = (void 0); + for (var n = 0; n < toNumbersLength; n++) { + var value = (void 0); + var toNumber = tween.to.numbers[n]; + var fromNumber = tween.from.numbers[n] || 0; + if (!tween.isPath) { + value = fromNumber + (eased * (toNumber - fromNumber)); + } else { + value = getPathProgress(tween.value, eased * toNumber, tween.isPathTargetInsideSVG); + } + if (round) { + if (!(tween.isColor && n > 2)) { + value = Math.round(value * round) / round; + } + } + numbers.push(value); + } + // Manual Array.reduce for better performances + var stringsLength = strings.length; + if (!stringsLength) { + progress = numbers[0]; + } else { + progress = strings[0]; + for (var s = 0; s < stringsLength; s++) { + var a = strings[s]; + var b = strings[s + 1]; + var n$1 = numbers[s]; + if (!isNaN(n$1)) { + if (!b) { + progress += n$1 + ' '; + } else { + progress += n$1 + b; + } + } + } + } + setProgressValue[anim.type](animatable.target, anim.property, progress, animatable.transforms); + anim.currentValue = progress; + i++; + } + } + + function setCallback(cb) { + if (instance[cb] && !instance.passThrough) { instance[cb](instance); } + } + + function countIteration() { + if (instance.remaining && instance.remaining !== true) { + instance.remaining--; + } + } + + function setInstanceProgress(engineTime) { + var insDuration = instance.duration; + var insDelay = instance.delay; + var insEndDelay = insDuration - instance.endDelay; + var insTime = adjustTime(engineTime); + instance.progress = minMax((insTime / insDuration) * 100, 0, 100); + instance.reversePlayback = insTime < instance.currentTime; + if (children) { syncInstanceChildren(insTime); } + if (!instance.began && instance.currentTime > 0) { + instance.began = true; + setCallback('begin'); + } + if (!instance.loopBegan && instance.currentTime > 0) { + instance.loopBegan = true; + setCallback('loopBegin'); + } + if (insTime <= insDelay && instance.currentTime !== 0) { + setAnimationsProgress(0); + } + if ((insTime >= insEndDelay && instance.currentTime !== insDuration) || !insDuration) { + setAnimationsProgress(insDuration); + } + if (insTime > insDelay && insTime < insEndDelay) { + if (!instance.changeBegan) { + instance.changeBegan = true; + instance.changeCompleted = false; + setCallback('changeBegin'); + } + setCallback('change'); + setAnimationsProgress(insTime); + } else { + if (instance.changeBegan) { + instance.changeCompleted = true; + instance.changeBegan = false; + setCallback('changeComplete'); + } + } + instance.currentTime = minMax(insTime, 0, insDuration); + if (instance.began) { setCallback('update'); } + if (engineTime >= insDuration) { + lastTime = 0; + countIteration(); + if (!instance.remaining) { + instance.paused = true; + if (!instance.completed) { + instance.completed = true; + setCallback('loopComplete'); + setCallback('complete'); + if (!instance.passThrough && 'Promise' in window) { + resolve(); + promise = makePromise(instance); + } + } + } else { + startTime = now; + setCallback('loopComplete'); + instance.loopBegan = false; + if (instance.direction === 'alternate') { + toggleInstanceDirection(); + } + } + } + } + + instance.reset = function() { + var direction = instance.direction; + instance.passThrough = false; + instance.currentTime = 0; + instance.progress = 0; + instance.paused = true; + instance.began = false; + instance.loopBegan = false; + instance.changeBegan = false; + instance.completed = false; + instance.changeCompleted = false; + instance.reversePlayback = false; + instance.reversed = direction === 'reverse'; + instance.remaining = instance.loop; + children = instance.children; + childrenLength = children.length; + for (var i = childrenLength; i--;) { instance.children[i].reset(); } + if (instance.reversed && instance.loop !== true || (direction === 'alternate' && instance.loop === 1)) { instance.remaining++; } + setAnimationsProgress(instance.reversed ? instance.duration : 0); + }; + + // internal method (for engine) to adjust animation timings before restoring engine ticks (rAF) + instance._onDocumentVisibility = resetTime; + + // Set Value helper + + instance.set = function(targets, properties) { + setTargetsValue(targets, properties); + return instance; + }; + + instance.tick = function(t) { + now = t; + if (!startTime) { startTime = now; } + setInstanceProgress((now + (lastTime - startTime)) * anime.speed); + }; + + instance.seek = function(time) { + setInstanceProgress(adjustTime(time)); + }; + + instance.pause = function() { + instance.paused = true; + resetTime(); + }; + + instance.play = function() { + if (!instance.paused) { return; } + if (instance.completed) { instance.reset(); } + instance.paused = false; + activeInstances.push(instance); + resetTime(); + engine(); + }; + + instance.reverse = function() { + toggleInstanceDirection(); + instance.completed = instance.reversed ? false : true; + resetTime(); + }; + + instance.restart = function() { + instance.reset(); + instance.play(); + }; + + instance.remove = function(targets) { + var targetsArray = parseTargets(targets); + removeTargetsFromInstance(targetsArray, instance); + }; + + instance.reset(); + + if (instance.autoplay) { instance.play(); } + + return instance; + +} + +// Remove targets from animation + +function removeTargetsFromAnimations(targetsArray, animations) { + for (var a = animations.length; a--;) { + if (arrayContains(targetsArray, animations[a].animatable.target)) { + animations.splice(a, 1); + } + } +} + +function removeTargetsFromInstance(targetsArray, instance) { + var animations = instance.animations; + var children = instance.children; + removeTargetsFromAnimations(targetsArray, animations); + for (var c = children.length; c--;) { + var child = children[c]; + var childAnimations = child.animations; + removeTargetsFromAnimations(targetsArray, childAnimations); + if (!childAnimations.length && !child.children.length) { children.splice(c, 1); } + } + if (!animations.length && !children.length) { instance.pause(); } +} + +function removeTargetsFromActiveInstances(targets) { + var targetsArray = parseTargets(targets); + for (var i = activeInstances.length; i--;) { + var instance = activeInstances[i]; + removeTargetsFromInstance(targetsArray, instance); + } +} + +// Stagger helpers + +function stagger(val, params) { + if ( params === void 0 ) params = {}; + + var direction = params.direction || 'normal'; + var easing = params.easing ? parseEasings(params.easing) : null; + var grid = params.grid; + var axis = params.axis; + var fromIndex = params.from || 0; + var fromFirst = fromIndex === 'first'; + var fromCenter = fromIndex === 'center'; + var fromLast = fromIndex === 'last'; + var isRange = is.arr(val); + var val1 = isRange ? parseFloat(val[0]) : parseFloat(val); + var val2 = isRange ? parseFloat(val[1]) : 0; + var unit = getUnit(isRange ? val[1] : val) || 0; + var start = params.start || 0 + (isRange ? val1 : 0); + var values = []; + var maxValue = 0; + return function (el, i, t) { + if (fromFirst) { fromIndex = 0; } + if (fromCenter) { fromIndex = (t - 1) / 2; } + if (fromLast) { fromIndex = t - 1; } + if (!values.length) { + for (var index = 0; index < t; index++) { + if (!grid) { + values.push(Math.abs(fromIndex - index)); + } else { + var fromX = !fromCenter ? fromIndex%grid[0] : (grid[0]-1)/2; + var fromY = !fromCenter ? Math.floor(fromIndex/grid[0]) : (grid[1]-1)/2; + var toX = index%grid[0]; + var toY = Math.floor(index/grid[0]); + var distanceX = fromX - toX; + var distanceY = fromY - toY; + var value = Math.sqrt(distanceX * distanceX + distanceY * distanceY); + if (axis === 'x') { value = -distanceX; } + if (axis === 'y') { value = -distanceY; } + values.push(value); + } + maxValue = Math.max.apply(Math, values); + } + if (easing) { values = values.map(function (val) { return easing(val / maxValue) * maxValue; }); } + if (direction === 'reverse') { values = values.map(function (val) { return axis ? (val < 0) ? val * -1 : -val : Math.abs(maxValue - val); }); } + } + var spacing = isRange ? (val2 - val1) / maxValue : val1; + return start + (spacing * (Math.round(values[i] * 100) / 100)) + unit; + } +} + +// Timeline + +function timeline(params) { + if ( params === void 0 ) params = {}; + + var tl = anime(params); + tl.duration = 0; + tl.add = function(instanceParams, timelineOffset) { + var tlIndex = activeInstances.indexOf(tl); + var children = tl.children; + if (tlIndex > -1) { activeInstances.splice(tlIndex, 1); } + function passThrough(ins) { ins.passThrough = true; } + for (var i = 0; i < children.length; i++) { passThrough(children[i]); } + var insParams = mergeObjects(instanceParams, replaceObjectProps(defaultTweenSettings, params)); + insParams.targets = insParams.targets || params.targets; + var tlDuration = tl.duration; + insParams.autoplay = false; + insParams.direction = tl.direction; + insParams.timelineOffset = is.und(timelineOffset) ? tlDuration : getRelativeValue(timelineOffset, tlDuration); + passThrough(tl); + tl.seek(insParams.timelineOffset); + var ins = anime(insParams); + passThrough(ins); + children.push(ins); + var timings = getInstanceTimings(children, params); + tl.delay = timings.delay; + tl.endDelay = timings.endDelay; + tl.duration = timings.duration; + tl.seek(0); + tl.reset(); + if (tl.autoplay) { tl.play(); } + return tl; + }; + return tl; +} + +anime.version = '3.2.2'; +anime.speed = 1; +// TODO:#review: naming, documentation +anime.suspendWhenDocumentHidden = true; +anime.running = activeInstances; +anime.remove = removeTargetsFromActiveInstances; +anime.get = getOriginalTargetValue; +anime.set = setTargetsValue; +anime.convertPx = convertPxToUnit; +anime.path = getPath; +anime.setDashoffset = setDashoffset; +anime.stagger = stagger; +anime.timeline = timeline; +anime.easing = parseEasings; +anime.penner = penner; +anime.random = function (min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }; + +export default anime; diff --git a/brain/Loader.php b/brain/Loader.php new file mode 100644 index 0000000..25b7080 --- /dev/null +++ b/brain/Loader.php @@ -0,0 +1,10 @@ +tapes = new TapesAPI(); + } + + public function handleRoute($params) + { + if (isset($params[1])) { + //grap appropriate api class based on request params + switch ($params[1]) { + case "tapes": + //run the request + return $this->tapes->handleRequest($params); + break; + + default: + break; + } + } else { + return json_encode([ + "message" => "NO API Identifier provided", + "type" => "ERROR", + ]); + } + } +} diff --git a/brain/controllers/IndexController.php b/brain/controllers/IndexController.php new file mode 100644 index 0000000..330d0b6 --- /dev/null +++ b/brain/controllers/IndexController.php @@ -0,0 +1,15 @@ +api = new APIController(); + } + + public function start($request) + { + $request = ltrim($request, " / "); + $params = explode("/", $request); + if ($params[0] == "") { + require "brain/views/front.php"; + } else { + switch ($params[0]) { + case "api": + //get api controller to handle route + $result = $this->api->handleRoute($params); + header("Content-Type: application/json; charset=utf-8"); + echo $result; + break; + default: + http_response_code(404); + require "brain/views/404.php"; + break; + } + } + } +} diff --git a/brain/controllers/TapesAPI.php b/brain/controllers/TapesAPI.php new file mode 100644 index 0000000..fc270f5 --- /dev/null +++ b/brain/controllers/TapesAPI.php @@ -0,0 +1,96 @@ +getList(); + break; + case "tracklist": + return $this->getTrackList($params[3]); + break; + default: + return json_encode([ + "message" => "This method does not exist", + "type" => "ERROR", + ]); + break; + } + } else { + return json_encode([ + "message" => "This method does not exist", + "type" => "ERROR", + ]); + } + } + + public function getTrackList($id) + { + $folder = $this->tapesDir . "/" . urldecode($id); + $contents = scandir($folder); + $tracklist = []; + foreach ($contents as $key => $value) { + $path = realpath($folder . DIRECTORY_SEPARATOR . $value); + if (!is_dir($path) && $value != ".DS_Store") { + $item = explode("/", $path); + if ($item[9] != "cover.jpg") { + $tags = Audio::read($path); + //reading cover data is lil funky, so off for now + //$cover = $tags->getCover(); + //$image = $cover->getContents(); + $album = ""; + if (empty($tags->getAlbum())) { + $album = "None"; + } else { + $album = $tags->getAlbum(); + } + array_push($tracklist, [ + "title" => $tags->getTitle(), + "album" => $album, + "artist" => $tags->getArtist(), + "time" => $tags->getDuration(), + "file" => $item[9], + "tape" => $item[8], + ]); + } + } + } + usort($tracklist, function ($a, $b) { + return strnatcasecmp($a["file"], $b["file"]); + }); + return json_encode($tracklist); + } + + public function getList($obj = false) + { + $current = scandir($this->tapesDir); + $tapeList = []; + foreach ($current as $key => $value) { + $path = realpath($this->tapesDir . DIRECTORY_SEPARATOR . $value); + if (!is_dir($path) && $value != ".DS_Store") { + $results[] = $path; + } elseif ($value != "." && $value != ".." && $value != ".DS_Store") { + $titles = explode("/", $path); + array_push($tapeList, ["path" => $path, "title" => $titles[8]]); + } + } + if (!$obj) { + return json_encode($tapeList); + } else { + return $tapeList; + } + } +} diff --git a/brain/views/404.php b/brain/views/404.php new file mode 100644 index 0000000..6fb21cb --- /dev/null +++ b/brain/views/404.php @@ -0,0 +1,18 @@ + + +
++ This ain't it. Go back! +
+