summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--README.md4
-rwxr-xr-xdocs/fonts/NHaasGroteskTXPro-55Rg.ttfbin0 -> 275008 bytes
-rwxr-xr-xdocs/fonts/NHaasGroteskTXPro-56It.ttfbin0 -> 306228 bytes
-rwxr-xr-xdocs/fonts/NHaasGroteskTXPro-75Bd.ttfbin0 -> 300820 bytes
-rwxr-xr-xdocs/fonts/NHaasGroteskTXPro-76BdIt.ttfbin0 -> 297312 bytes
-rw-r--r--docs/img/dreaming-city.jpgbin0 -> 125438 bytes
-rw-r--r--docs/img/escape-from-neukoelln.jpgbin0 -> 167756 bytes
-rw-r--r--docs/img/pause-button.pngbin0 -> 1174 bytes
-rw-r--r--docs/img/pause-inv.pngbin0 -> 1337 bytes
-rw-r--r--docs/img/pause.pngbin0 -> 1336 bytes
-rw-r--r--docs/img/play-button.pngbin0 -> 1336 bytes
-rw-r--r--docs/img/play-fill.pngbin0 -> 1801 bytes
-rw-r--r--docs/img/play-inv.pngbin0 -> 1993 bytes
-rw-r--r--docs/img/play.pngbin0 -> 1978 bytes
-rw-r--r--docs/img/playbutton.psdbin0 -> 50227 bytes
-rw-r--r--docs/img/xena-vectra-logo-sm.pngbin0 -> 9139 bytes
-rw-r--r--docs/img/xena-vectra-logo.pngbin0 -> 30091 bytes
-rw-r--r--docs/img/xenavectra.gifbin0 -> 41515 bytes
-rw-r--r--docs/index.html410
-rw-r--r--docs/js/cielab.js70
-rw-r--r--docs/js/shards.js98
-rw-r--r--docs/js/sounds.js17
-rw-r--r--docs/js/stars.js63
-rw-r--r--docs/js/util.js132
-rw-r--r--docs/sounds/click.mp3bin0 -> 2337 bytes
-rw-r--r--docs/sounds/click.wavbin0 -> 142582 bytes
27 files changed, 801 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c1d93bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+mp3
+wav
+aif
+aiff
+mp4
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..01741e2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+xenavectra
+==========
+
+static site 2018
diff --git a/docs/fonts/NHaasGroteskTXPro-55Rg.ttf b/docs/fonts/NHaasGroteskTXPro-55Rg.ttf
new file mode 100755
index 0000000..e5ad72b
--- /dev/null
+++ b/docs/fonts/NHaasGroteskTXPro-55Rg.ttf
Binary files differ
diff --git a/docs/fonts/NHaasGroteskTXPro-56It.ttf b/docs/fonts/NHaasGroteskTXPro-56It.ttf
new file mode 100755
index 0000000..8aa3443
--- /dev/null
+++ b/docs/fonts/NHaasGroteskTXPro-56It.ttf
Binary files differ
diff --git a/docs/fonts/NHaasGroteskTXPro-75Bd.ttf b/docs/fonts/NHaasGroteskTXPro-75Bd.ttf
new file mode 100755
index 0000000..d80c0ee
--- /dev/null
+++ b/docs/fonts/NHaasGroteskTXPro-75Bd.ttf
Binary files differ
diff --git a/docs/fonts/NHaasGroteskTXPro-76BdIt.ttf b/docs/fonts/NHaasGroteskTXPro-76BdIt.ttf
new file mode 100755
index 0000000..81dfad5
--- /dev/null
+++ b/docs/fonts/NHaasGroteskTXPro-76BdIt.ttf
Binary files differ
diff --git a/docs/img/dreaming-city.jpg b/docs/img/dreaming-city.jpg
new file mode 100644
index 0000000..211a0a9
--- /dev/null
+++ b/docs/img/dreaming-city.jpg
Binary files differ
diff --git a/docs/img/escape-from-neukoelln.jpg b/docs/img/escape-from-neukoelln.jpg
new file mode 100644
index 0000000..dcd1180
--- /dev/null
+++ b/docs/img/escape-from-neukoelln.jpg
Binary files differ
diff --git a/docs/img/pause-button.png b/docs/img/pause-button.png
new file mode 100644
index 0000000..6ab7818
--- /dev/null
+++ b/docs/img/pause-button.png
Binary files differ
diff --git a/docs/img/pause-inv.png b/docs/img/pause-inv.png
new file mode 100644
index 0000000..9bec646
--- /dev/null
+++ b/docs/img/pause-inv.png
Binary files differ
diff --git a/docs/img/pause.png b/docs/img/pause.png
new file mode 100644
index 0000000..c2d30ae
--- /dev/null
+++ b/docs/img/pause.png
Binary files differ
diff --git a/docs/img/play-button.png b/docs/img/play-button.png
new file mode 100644
index 0000000..c0fd3fc
--- /dev/null
+++ b/docs/img/play-button.png
Binary files differ
diff --git a/docs/img/play-fill.png b/docs/img/play-fill.png
new file mode 100644
index 0000000..2205696
--- /dev/null
+++ b/docs/img/play-fill.png
Binary files differ
diff --git a/docs/img/play-inv.png b/docs/img/play-inv.png
new file mode 100644
index 0000000..4baaa5f
--- /dev/null
+++ b/docs/img/play-inv.png
Binary files differ
diff --git a/docs/img/play.png b/docs/img/play.png
new file mode 100644
index 0000000..b5b4a08
--- /dev/null
+++ b/docs/img/play.png
Binary files differ
diff --git a/docs/img/playbutton.psd b/docs/img/playbutton.psd
new file mode 100644
index 0000000..e2dbed4
--- /dev/null
+++ b/docs/img/playbutton.psd
Binary files differ
diff --git a/docs/img/xena-vectra-logo-sm.png b/docs/img/xena-vectra-logo-sm.png
new file mode 100644
index 0000000..b42111b
--- /dev/null
+++ b/docs/img/xena-vectra-logo-sm.png
Binary files differ
diff --git a/docs/img/xena-vectra-logo.png b/docs/img/xena-vectra-logo.png
new file mode 100644
index 0000000..4b3f810
--- /dev/null
+++ b/docs/img/xena-vectra-logo.png
Binary files differ
diff --git a/docs/img/xenavectra.gif b/docs/img/xenavectra.gif
new file mode 100644
index 0000000..fb81d57
--- /dev/null
+++ b/docs/img/xenavectra.gif
Binary files differ
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..0c5217f
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,410 @@
+<!doctype html>
+<html>
+<head>
+<title>Xena Vectra</title>
+<meta charset="utf-8">
+<style>
+@font-face {
+ font-family: nhg;
+ src: url(./fonts/NHaasGroteskTXPro-55Rg.ttf);
+ font-weight: normal;
+ font-style: normal;
+}
+@font-face {
+ font-family: nhg;
+ src: url(./fonts/NHaasGroteskTXPro-56It.ttf);
+ font-weight: normal;
+ font-style: italic;
+}
+@font-face {
+ font-family: nhg;
+ src: url(./fonts/NHaasGroteskTXPro-75Bd.ttf);
+ font-weight: bold;
+ font-style: normal;
+}
+@font-face {
+ font-family: nhg;
+ src: url(./fonts/NHaasGroteskTXPro-76BdIt.ttf);
+ font-weight: bold;
+ font-style: italic;
+}
+html, body {
+ margin: 0; padding: 0; width: 100%; height: 100%;
+ font-family: nhg, sans-serif;
+ color: rgba(255,255,255,0.9);
+}
+html {
+ background-color: rgb(0,0,0);
+ background-image: linear-gradient(rgba(20, 10, 0, 0.0), rgba(20, 10, 0, 1.0));
+ background-attachment: fixed;
+ transition: background-color 1s;
+}
+html.day {
+ background-color: rgb(80,10,60);
+}
+html.night {
+ background-color: rgb(8,10,30);
+}
+body {
+ background-color: rgba(0,0,0,0);
+ transition: background-color 1000ms, opacity 500ms;
+ opacity: 1;
+}
+body.loading {
+ background-color: rgba(255,128,192,1);
+ background-color: black;
+ opacity: 0;
+}
+body.fade {
+ opacity: 1;
+ background-color: black;
+ background-color: rgba(255,128,192,1);
+}
+.shards {
+ position: absolute;
+ top: 0; left: 0;
+ width: 100%; height: 100%;
+ overflow: hidden;
+}
+.bgs {
+ position: absolute;
+ top: 50%; left: 50%;
+ opacity: 0.7;
+ transition: opacity 500ms;
+}
+.stars {
+ transition: all 500ms cubic-bezier(0,0,0,1);
+ transform: translateZ(0) scaleX(1) scaleY(1);
+}
+.stars.fade {
+ transform: translateZ(0) scaleX(1.03) scaleY(1.03);
+}
+.fade {
+ opacity: 0;
+}
+.bg {
+ position: absolute;
+ top: 0; left: 0;
+ transition: all 120s;
+}
+header {
+ background-color: rgba(40,20,30,0.9);
+ position: absolute;
+ width: 100%;
+ bottom: 100px;
+ left: 0;
+}
+p {
+ margin: 0; padding: 0;
+ line-height: 1.4;
+}
+p + p {
+ margin-top: 14px;ß
+}
+a {
+ text-decoration: none;
+ color: rgba(255,255,255,0.8);
+ transition: color 200ms;
+ border-bottom: 1px dotted;
+}
+a:hover {
+ color: rgba(255,255,255,1.0);
+}
+img {
+ max-width: 100%;
+}
+img.cover {
+ max-width: 300px;
+}
+.menu a {
+ margin-right: 10px;
+ border-bottom: 0;
+}
+.menu a.active {
+ border-bottom: 1px dotted;
+ color: rgba(255,255,255,1.0);
+}
+.logo {
+ width: 400px;
+ opacity: 0.8;
+ animation: fade 10s infinite;
+}
+.row {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+}
+h1 {
+ font-size: 32px;
+ margin: 0;
+ padding: 10px 20px;
+ letter-spacing: -2px;
+ text-transform: uppercase;
+ cursor: pointer;
+ opacity: 0.9;
+ transition: opacity 500ms;
+ color: white;
+}
+h1:hover {
+ opacity: 1;
+}
+h2 {
+ margin: 0;
+ padding: 0 0 15px 0;
+ letter-spacing: -1px;
+}
+h3 {
+ margin: 0;
+ padding: 0 0 6px 0;
+ letter-spacing: 1px;
+ font-weight: normal;
+ font-style: italic;
+ font-size: 14px;
+ text-transform: uppercase;
+ opacity: 0.7;
+}
+ul {
+ margin: 0 0 20px 0;
+ padding: 0;
+ list-style-type: none;
+}
+ul li {
+ list-style-type: none;
+ line-height: 1.4;
+}
+ol {
+ margin: 0 0 20px 0;
+ padding: 0;
+}
+ol li {
+ line-height: 1.4;
+}
+.player {
+ padding: 10px 20px;
+ width: 300px;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: center;
+ cursor: pointer;
+ border-left: 1px solid #000;
+}
+.player .icon {
+ width: 1.2em;
+ height: 1.2em;
+ background-image: url(img/play.png);
+ background-size: 100%;
+ background-position: center;
+ border: 2px solid rgba(255,255,255,0.3);
+ border-radius: 50%;
+ margin: 0 10px 0 10px;
+ transition: all 100ms;
+}
+.player:hover .icon {
+ background-image: url(img/play-inv.png);
+}
+.player.paused .icon {
+ background-image: url(img/pause.png);
+}
+.player.paused:hover .icon {
+ background-image: url(img/pause-inv.png);
+}
+.player .track {
+ transition: all 100ms;
+}
+.player:hover .track {
+ color: rgba(255,255,255,1.0);
+}
+.playlist {
+ -webkit-overflow-scrolling: touch;
+ overflow-y: auto;
+ max-height: calc(100vh - 140px);
+ width: 310px;
+ max-width: calc(100vw - 30px);
+ background: black;
+ position: absolute;
+ bottom: 160px; right: 0;
+ margin: 10px 10px;
+ padding: 10px;
+ background: rgba(0,0,0,0.5);
+}
+section {
+ -webkit-overflow-scrolling: touch;
+ overflow-y: auto;
+ max-height: calc(100vh - 140px);
+ max-width: 500px;
+ background: black;
+ position: absolute;
+ bottom: 160px; left: 0;
+ margin: 10px 10px;
+ padding: 10px;
+ background: rgba(0,0,0,0.5);
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 200ms;
+}
+.bio #bio,
+.discography #discography,
+.hardware #hardware,
+.contact #contact {
+ opacity: 1;
+}
+@keyframes fade {
+ 0% { opacity: 0.6; }
+ 50% { opacity: 1.0; }
+ 90% { opacity: 0.7; }
+100% { opacity: 0.6; }
+}
+</style>
+</head>
+<body class='loading'>
+<div class="shards">
+ <div class="bgs fade"></div>
+</div>
+<header>
+ <div class="row">
+ <div class="row menu">
+ <h1>Xena Vectra</h1>
+ <a href="#bio">Biography</a>
+ <a href="#discography">Discography</a>
+ <a href="#hardware">Equipment</a>
+ <a href="#contact">Contact</a>
+ </div>
+ <div class="player">
+ <div class="icon"></div>
+ <div class="track">Dreaming City</div>
+ </div>
+ </div>
+</header>
+<section id="bio">
+ <h2>Biography</h2>
+ <p>
+ Xena Vectra is the alter ego of synth goddess Bethany Barrett. Born in the mountains of the Black Forest and raised in sunny California, Beth has a long history in Berlin's underground music scene. Over the years she has produced everything from shoegaze to noise, avant garde disco and synthwave alike. Her unique style as Xena Vectra is inspired by vintage horror soundtracks, driven by her love of analog electronics.
+ </p>
+ <p>
+ Beth performs live using an all hardware setup without a laptop in sight. She is available for bookings throughout Europe. Her soundtrack work includes scoring indie film <i>Figaros Wölfe</i> (2017) and she is currently working on several upcoming projects with the German artist Hito Steyerl.
+ </p>
+</section>
+<section id="discography">
+ <h2>Discography</h2>
+ <p>
+ <i>As Xena Vectra</i>
+ </p>
+ <p>
+ <img src="img/escape-from-neukoelln.jpg" class="cover"><br/>
+ <b>Escape from Neukölln</b> EP (2018)<br>
+ 12" / digital on <a href="https://xenavectra.bandcamp.com/albums/escape-from-neukolln/">Bandcamp</a>
+ <br>
+ <br>
+ </p>
+ <p>
+ <img src="img/dreaming-city.jpg" class="cover"><br/>
+ <b>Dreaming City</b> EP (2018)<br>
+ Cassette / digital on <a href="https://xenavectra.bandcamp.com/albums/escape-from-neukolln/">Bandcamp</a>
+ </p>
+</section>
+<section id="hardware">
+ <h2>Equipment</h2>
+ <h3>Monosynths</h3>
+ <ul>
+ <li>Moog Subphatty</li>
+ <li>Korg Monologue</li>
+ <li>Arturia Microbrute</li>
+ <li>Pittsburgh Modular Lifeforms SV-1</li>
+ </ul>
+ <h3>Polysynths</h3>
+ <ul>
+ <li>Behringer DeepMind 12</li>
+ <li>Roland JX-8P</li>
+ </ul>
+ <h3>Drum machines</h3>
+ <ul>
+ <li>Roland TR-8</li>
+ <li>Elektron Digitakt</li>
+ </ul>
+ <h3>Pedals</h3>
+ <ul>
+ <li>Strymon Timeline</li>
+ <li>Strymon Big Sky</li>
+ </ul>
+ <i>"So, are softsynths as nasty as they say?" &mdash;Cayde-6</i>
+</section>
+<section id="contact">
+ <h2>Contact</h2>
+ <p>
+ Booking:
+ <a id='email_addr'></a><br>
+ Twitter:
+ @<a id='twitter_addr'></a><br>
+ Soundcloud:
+ <a href="https://soundcloud.com/xenavectra/">xenavectra</a>,
+ <a href="https://soundcloud.com/magisphere/">magisphere</a>
+ <br/>
+ Bandcamp: <a href="https://xenavectra.bandcamp.com/">xenavectra</a><br/>
+ </p>
+</section>
+<div class="playlist">
+ <h2>Playlist</h2>
+</div>
+<script src="./js/util.js"></script>
+<script src="./js/cielab.js"></script>
+<script src="./js/sounds.js"></script>
+<script src="./js/stars.js"></script>
+<script src="./js/shards.js"></script>
+<script type="text/javascript">
+const site = (function(){
+ let section
+ let links = toArray(document.querySelectorAll('.menu a'))
+ let time = new Date()
+ let hour = time.getHours()
+ if (hour < 8 || hour > 16) {
+ document.body.parentNode.classList.add('night')
+ } else {
+ document.body.parentNode.classList.add('day')
+ }
+ setTimeout(() => {
+ document.body.classList.remove('loading')
+ navigateHash(window.location.hash)
+ const email = atob("eGVuYXZlY3RyYTkwOUBnbWFpbC5jb20=")
+ const twitter = atob("dmVjdHJheGVuYQ==")
+ document.querySelector("#email_addr").href = 'mailto:' + email
+ document.querySelector("#email_addr").innerHTML = email
+ document.querySelector("#twitter_addr").innerHTML = twitter
+ document.querySelector("#twitter_addr").href = 'https://twitter.com/' + email
+ }, 0)
+ function navigateHash(url){
+ let new_section = url.split('#')[1]
+ if (section) {
+ document.body.classList.remove(section)
+ links.forEach(link => link.classList.remove('active'))
+ }
+ if (new_section && new_section !== section) {
+ document.body.classList.add(new_section)
+ links.forEach(link => link.getAttribute('href').match(new_section) && link.classList.add('active'))
+ section = new_section
+ } else {
+ section = null
+ }
+ window.location.hash = section || ""
+ }
+ toArray(document.querySelectorAll('.menu a')).forEach(a => {
+ a.addEventListener("click", e => {
+ e.preventDefault()
+ sounds.play('click')
+ navigateHash(e.target.href)
+ })
+ })
+})()
+const player = (function(){
+ let player = {}
+ let tracks = [
+ { file: "sounds/dreaming_city_2.mp3", title: "Dreaming City 2" },
+ { file: "sounds/dreaming_city_2.mp3", title: "Dreaming City 2" },
+ { file: "sounds/dreaming_city_2.mp3", title: "Dreaming City 2" },
+ { file: "sounds/dreaming_city_2.mp3", title: "Dreaming City 2" },
+ ]
+})
+</script>
+</body>
+</html>
diff --git a/docs/js/cielab.js b/docs/js/cielab.js
new file mode 100644
index 0000000..2293f0d
--- /dev/null
+++ b/docs/js/cielab.js
@@ -0,0 +1,70 @@
+var cielab = (function(){
+ var cielab = {}
+
+ var xyz = [0,0,0]
+ var rgb = [0,0,0]
+
+ L_range = [0, 100]
+ a_range = [-86.185, 98.254]
+ b_range = [-107.863, 94.482]
+
+ cielab.gradient = function (n) {
+ n = n || 100
+ var k = 0
+
+ var L, a, b
+
+ var L0 = randrange(L_range[0] + 50, L_range[1])
+ var L1 = randrange(L_range[0]+ 50, L_range[1])
+ var a0 = randrange(a_range[0], a_range[1])
+ var a1 = randrange(a_range[0], a_range[1])
+ var b0 = randrange(b_range[0], b_range[1])
+ var b1 = randrange(b_range[0], b_range[1])
+ return function next (aa){
+ L = mix(k/n, L0, L1)
+ a = mix(k/n, a0, a1)
+ b = mix(k/n, b0, b1)
+ rgb = xyz2rgb(hunterlab2xyz(L, a, b))
+ k += 1
+ return rgba_string(rgb, aa)
+ }
+ }
+ function rgba_string (rgb, a) { return "rgb(" + rgb.map(Math.round).join(",") + "," + a + ")" }
+ function hex_string (rgb) { return "#" + rgb.map(Math.round).map(function(n){ var s = n.toString(16); return s.length == 1 ? "0"+s : s }).join("") }
+
+ function mix(n,a,b){ return n*a + (1-n)*b }
+ function clamp(n,a,b){ return n<a?a:n<b?n:b }
+ function hunterlab2xyz (L,a,b) {
+ var_Y = L / 10
+ var_X = a / 17.5 * L / 10
+ var_Z = b / 7 * L / 10
+
+ Y = Math.pow(var_Y, 2)
+ X = ( var_X + Y ) / 1.02
+ Z = -( var_Z - Y ) / 0.847
+ xyz = [X,Y,Z]
+ }
+ function xyz2rgb(){
+ var var_X = xyz[0] / 100 //X from 0 to 95.047 (Observer = 2°, Illuminant = D65)
+ var var_Y = xyz[1] / 100 //Y from 0 to 100.000
+ var var_Z = xyz[2] / 100 //Z from 0 to 108.883
+
+ var_R = var_X * 3.2406 + var_Y * -1.5372 + var_Z * -0.4986
+ var_G = var_X * -0.9689 + var_Y * 1.8758 + var_Z * 0.0415
+ var_B = var_X * 0.0557 + var_Y * -0.2040 + var_Z * 1.0570
+
+ if ( var_R > 0.0031308 ) var_R = 1.055 * Math.pow( var_R, 1 / 2.4 ) - 0.055
+ else var_R = 12.92 * var_R
+ if ( var_G > 0.0031308 ) var_G = 1.055 * Math.pow( var_G, 1 / 2.4 ) - 0.055
+ else var_G = 12.92 * var_G
+ if ( var_B > 0.0031308 ) var_B = 1.055 * Math.pow( var_B, 1 / 2.4 ) - 0.055
+ else var_B = 12.92 * var_B
+
+ rgb[0] = clamp(var_R * 255, 0, 255)
+ rgb[1] = clamp(var_G * 255, 0, 255)
+ rgb[2] = clamp(var_B * 255, 0, 255)
+ return rgb
+ }
+
+ return cielab
+})() \ No newline at end of file
diff --git a/docs/js/shards.js b/docs/js/shards.js
new file mode 100644
index 0000000..44d2ecd
--- /dev/null
+++ b/docs/js/shards.js
@@ -0,0 +1,98 @@
+const shards = (function(){
+ let count
+ let delay = 120 * 1000
+ let els = []
+ let t = 0
+ let rebuilding = false
+ let nextTimeout
+ let dark, light
+ const bg_el = document.querySelector('.bgs')
+
+ function init(){
+ bind()
+ build()
+ step()
+ setTimeout(next, 20)
+ bg_el.classList.remove('fade')
+ }
+ function bind(){
+ document.querySelector('h1').addEventListener('click', rebuild)
+ }
+ function build(){
+ count = choice([5,7,7,11,11,13,13])
+ light = cielab.gradient(count)
+ dark = cielab.gradient(count)
+ let el
+ for (var i = 0; i < count; i++) {
+ el = append(i)
+ }
+ }
+ function destroy(){
+ for (var i = 0; i < count; i++) {
+ els[i] && bg_el.removeChild(els[i])
+ }
+ els.length = 0
+ }
+ function rebuild(){
+ if (rebuilding) return
+ rebuilding = true
+ sounds.play('click')
+ stars.rebuild()
+ next()
+ t = 0
+ bg_el.classList.add('fade')
+ setTimeout(() => {
+ destroy()
+ build()
+ step()
+ setTimeout(next, 20)
+ bg_el.classList.remove('fade')
+ rebuilding = false
+ sounds.play('click')
+ }, 500)
+ }
+ function append(i){
+ const el = document.createElement('div')
+ el.classList.add('bg')
+ els.push(el)
+ bg_el.appendChild(el)
+ return el
+ }
+ function next(){
+ clearTimeout(nextTimeout)
+ nextTimeout = setTimeout(next, delay)
+ step()
+ }
+ function step() {
+ t += 1
+ light = cielab.gradient(count)
+ let w = { min: randrange(20, 40), max: randrange(10, 90) }
+ if (w.min > w.max) {
+ w.min += 10
+ }
+ let rot = { min: randint(360), max: randrange(720, 1080) }
+ for (var i = 0; i < count; i++) {
+ update(i, t, w, rot, dark, light)
+ }
+ document.body.style.backgroundColor = cielab.gradient(2)(0.05)
+ }
+ function update(i, t, w, rot){
+ const el = els[i]
+ const n = i / count
+ const side = lerp(n, w.min, w.max)
+ const spin = lerp(i % 2 ? (1-n) : (n), rot.min, rot.max)
+ const rotation = "rotate3d(" + [randrange(-1,1), randrange(-1,1), randrange(-1,1)].join(',') + ',' + randint(360) + "deg)"
+ el.style.width = side + 'vmin'
+ el.style.height = side + 'vmin'
+ el.style.transform = "translate3d(-50%, -50%, 0) rotate(" + spin + "deg) translate3d(50%, 50%, 0) " + rotation
+ // el.style.transform = "translate3d(-50%, -50%, 0)"
+ if (t === 1) {
+ light = cielab.gradient(count)
+ el.style.backgroundImage = 'linear-gradient(' + dark(1) + ', ' + light(0) + ')'
+ }
+ el.style.backgroundColor = light(1)
+ el.style.opacity = lerp(n, randrange(0.1, 0.4), randrange(0.2, 0.5))
+ }
+ init()
+ return { rebuild: rebuild, step: step, }
+})() \ No newline at end of file
diff --git a/docs/js/sounds.js b/docs/js/sounds.js
new file mode 100644
index 0000000..39f725d
--- /dev/null
+++ b/docs/js/sounds.js
@@ -0,0 +1,17 @@
+const sounds = (function(){
+ let sounds = {}
+ let els = {}
+ sounds.add = (fn) => {
+ const el = document.createElement('audio')
+ el.src = 'sounds/' + fn + '.mp3'
+ els[fn] = el
+ }
+ sounds.play = (fn) => {
+ const el = els[fn]
+ el.currentTime = 0
+ el.volume = 0.8
+ el.play()
+ }
+ sounds.add('click')
+ return sounds
+})()
diff --git a/docs/js/stars.js b/docs/js/stars.js
new file mode 100644
index 0000000..f1569eb
--- /dev/null
+++ b/docs/js/stars.js
@@ -0,0 +1,63 @@
+const stars = (function(){
+ var canvas = document.createElement("canvas"), ctx = canvas.getContext('2d')
+ var s = Math.sin, c = Math.cos
+ document.addEventListener("DOMContentLoaded", function(){
+ document.body.appendChild(canvas)
+ canvas.classList.add('stars')
+ canvas.style.width="100%"
+ canvas.style.height="100%"
+ canvas.style.position="fixed"
+ canvas.style.top="0px"
+ canvas.style.left="0px"
+ canvas.style.zIndex=-1
+ document.body.addEventListener("resize", build)
+ // document.body.parentNode.style.backgroundColor="black"
+ ctx.strokeStyle="white"
+ build()
+ })
+ function ri(n){ return Math.random() * n }
+ function rr(a,b){ return (b-a) * Math.random() + a }
+ function build(){
+ var w = canvas.width = window.innerWidth
+ var h = canvas.height = window.innerHeight
+ ctx.clearRect(0,0,w,h)
+ var n = Math.sqrt(w*h)|0
+ while (n--) {
+ var x = ri(w)
+ var y = ri(h)
+ var r0 = rr(0, 1)
+ var r1 = rr(0, 1)
+ var r2 = rr(0, 1)
+ var t0 = ri(2*Math.PI)
+ var t1 = ri(2*Math.PI)
+ var t2 = ri(2*Math.PI)
+ var x0 = x+c(t0)*r0
+ var y0 = y+s(t0)*r0
+ var x1 = x+c(t1)*r1
+ var y1 = y+s(t1)*r1
+ var x2 = x+c(t2)*r2
+ var y2 = y+s(t2)*r2
+ ctx.beginPath()
+ ctx.moveTo(x,y)
+ ctx.bezierCurveTo(x0,y0,x1,y1,x2,y2)
+ var color = rr(0, 255)|0
+ ctx.strokeStyle="rgb("+color+","+color+","+color+")"
+ ctx.stroke()
+ }
+ }
+ let rebuilding = false
+ function rebuild(){
+ if (rebuilding) return
+ rebuilding = true
+ canvas.classList.add('fade')
+ document.body.classList.add('fade')
+ setTimeout(() => {
+ // destroy()
+ build()
+ canvas.classList.remove('fade')
+ document.body.classList.remove('fade')
+ rebuilding = false
+ }, 500)
+ }
+ return { rebuild: rebuild }
+})() \ No newline at end of file
diff --git a/docs/js/util.js b/docs/js/util.js
new file mode 100644
index 0000000..9455825
--- /dev/null
+++ b/docs/js/util.js
@@ -0,0 +1,132 @@
+function avg(a,b,n){ return (a*(n-1) + b)/n }
+function rand(n){ return Math.random()*n }
+function randint(n) { return ~~(Math.random()*n) }
+function randrange(a,b){ return a+rand(b-a) }
+function choice(a) { return a[randint(a.length)] }
+function clamp(n,a,b) { return a > n ? a : n > b ? b : n }
+function sign(n){ return n < 0 ? -1 : 1 }
+function mod(n,m){ return n-(m * Math.floor(n/m)) }
+function lerp(n,a,b){ return (b-a)*n+a }
+function quantize(m,n){ return n * ((m/n)|0) }
+
+function randgauss (obj, jog) {
+ var radius = Math.random() * jog
+ var angle = Math.random() * Math.PI * 2
+ obj.left += Math.sin(angle) * radius
+ obj.top += Math.cos(angle) * radius
+}
+
+function shuffle(a){
+ for (var i = a.length; i > 0; i--){
+ var r = randint(i)
+ var swap = a[i-1]
+ a[i-1] = a[r]
+ a[r] = swap
+ }
+ return a
+}
+
+function toArray (els){
+ return Array.prototype.slice.apply(els)
+}
+
+// easing functions
+function circular (t) { return Math.sqrt( 1 - ( --t * t ) ) }
+function quadratic (t) { return t * ( 2 - t ) }
+function back (t) {
+ var b = 4;
+ return ( t = t - 1 ) * t * ( ( b + 1 ) * t + b ) + 1;
+}
+function bounce (t) {
+ if (t >= 1) return 1;
+ if ( ( t /= 1 ) < ( 1 / 2.75 ) ) {
+ return 7.5625 * t * t;
+ } else if ( t < ( 2 / 2.75 ) ) {
+ return 7.5625 * ( t -= ( 1.5 / 2.75 ) ) * t + 0.75;
+ } else if ( t < ( 2.5 / 2.75 ) ) {
+ return 7.5625 * ( t -= ( 2.25 / 2.75 ) ) * t + 0.9375;
+ } else {
+ return 7.5625 * ( t -= ( 2.625 / 2.75 ) ) * t + 0.984375;
+ }
+}
+function elastic (t) {
+ var f = 0.22,
+ e = 0.4;
+
+ if ( t === 0 ) { return 0; }
+ if ( t == 1 ) { return 1; }
+
+ return ( e * Math.pow( 2, - 10 * t ) * Math.sin( ( t - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
+}
+
+function preload (url) {
+ var img = new Image ()
+ var loaded = false, _cb
+ img.onload = function(){
+ if (loaded) return
+ loaded = true
+ if (_cb) { _cb(img) }
+ }
+ img.src = url
+ if (img.complete) { img.onload() }
+ return {
+ then: function(cb){
+ if (loaded) { cb(img) }
+ else { _cb = cb }
+ }
+ }
+}
+
+function preloadImages () {
+ var images = toArray(document.getElementById("images").children)
+ var count = 0, ready = false, _cb
+ images.forEach(function(img){
+ var loaded = false
+ img.onload = function(){
+ if (loaded) return
+ loaded = true
+ done(img)
+ }
+ if (img.complete) { img.onload() }
+ })
+ function done () {
+ if (++count == images.length && ! ready) {
+ ready = true
+ _cb && _cb()
+ }
+ }
+ return {
+ then: function(cb){
+ if (ready) { cb() }
+ else { _cb = cb }
+ }
+ }
+}
+
+function smoothstep(min,max,n){
+ var t = clamp((n - min) / (max - min), 0.0, 1.0);
+ return t * t * (3.0 - 2.0 * t)
+}
+
+function detrand (x,y) {
+ return (Math.sin( x * 12.9898 + y * 78.233 ) * 43758.5453) % 1
+}
+
+function gray_to_transparency(img) {
+ var canvas = document.createElement("canvas")
+ var ctx = canvas.getContext('2d')
+ var nw = img.naturalWidth, nh = img.naturalHeight
+ canvas.width = nw
+ canvas.height = nh
+ ctx.drawImage(img,0,0)
+ var pixels = ctx.getImageData(0,0,nw,nh)
+ var data = pixels.data
+ for (var i = 0, len = nw*nh*4; i < len; i += 4) {
+ data[i+3] = data[i]
+ data[i] = data[i+1] = data[i+2] = 255
+ }
+ ctx.putImageData(pixels,0,0)
+ return canvas
+}
+
+var TWO_PI = 2*Math.PI \ No newline at end of file
diff --git a/docs/sounds/click.mp3 b/docs/sounds/click.mp3
new file mode 100644
index 0000000..37e7df0
--- /dev/null
+++ b/docs/sounds/click.mp3
Binary files differ
diff --git a/docs/sounds/click.wav b/docs/sounds/click.wav
new file mode 100644
index 0000000..7c45aed
--- /dev/null
+++ b/docs/sounds/click.wav
Binary files differ