diff --git a/zodiac/index.html b/zodiac/index.html
new file mode 100644
index 0000000..c119d90
--- /dev/null
+++ b/zodiac/index.html
@@ -0,0 +1,48 @@
+
+
+
+
+ Zodiac
+
+
+
+
+
+
+
+
diff --git a/zodiac/script/zodiac.js b/zodiac/script/zodiac.js
new file mode 100644
index 0000000..f7c1c98
--- /dev/null
+++ b/zodiac/script/zodiac.js
@@ -0,0 +1,112 @@
+"use static";
+var Zodiac = (function () {
+ function Zodiac(canvas, options) {
+ if (options === void 0) { options = {}; }
+ this.options = {
+ direction: [0, 0],
+ velocity: [[.1, 0.3], [.1, .3]],
+ bounce: [true, true],
+ parallax: .2,
+ density: 5000,
+ proximity: 50,
+ //dotColor: '#aaa',
+ dotRadius: [1, 5],
+ lineColor: '#aaa',
+ lineWidth: 1,
+ };
+ var canvas = (typeof canvas == 'string' || canvas instanceof String) ? document.getElementById(canvas) : canvas;
+ if (canvas.tagName != 'CANVAS')
+ throw "no canvas";
+ for (var key in options) {
+ this.options[key] = options[key];
+ }
+ options = this.options;
+ var ctx = canvas.getContext('2d', { alpha: !options.backgroundColor }), particles = [], tilt = [0, 0], radius = [].concat(options.dotRadius), parallax = options.parallax / 2 / (radius[1] ? (radius[1] - radius[0]) : radius[0]);
+ var update = function () {
+ var w = canvas.width, h = canvas.height;
+ if (options.backgroundColor) {
+ ctx.fillStyle = options.backgroundColor;
+ ctx.fillRect(0, 0, w, h);
+ ctx.fillStyle = options.dotColor;
+ }
+ else {
+ ctx.clearRect(0, 0, w, h);
+ }
+ ctx.beginPath();
+ for (var i = 0, p, x, y; i < particles.length; i++) {
+ p = particles[i];
+ /* MOVE */
+ p[1] += p[3];
+ p[2] += p[4];
+ /* POSITION */
+ if (options.parallax) {
+ var fac = p[0] * parallax;
+ p[5] += (tilt[0] * fac - p[5]) / 10;
+ p[6] += (tilt[1] * fac - p[6]) / 10;
+ }
+ x = p[1] + p[5];
+ y = p[2] + p[6];
+ if (x < 0 || x > w)
+ (options.direction[0]) ? (p[1] = ((x + w) % w) - p[5]) : (p[3] = -p[3]);
+ if (y < 0 || y > h)
+ (options.direction[1]) ? (p[2] = ((y + h) % h) - p[6]) : (p[4] = -p[4]);
+ var r = radius[1] ? p[0] : radius[0];
+ /* DRAW */
+ ctx.moveTo(x + r, y);
+ ctx.arc(x, y, r, 0, Math.PI * 2);
+ for (var j = i - 1; j >= 0; j--) {
+ var q = particles[j], dx = q[1] - p[1], dy = q[2] - p[2], dist = Math.sqrt((dx * dx) + (dy * dy));
+ if (dist < options.proximity) {
+ var x = p[1] + p[5], y = p[2] + p[6], x2 = q[1] + q[5], y2 = q[2] + q[6], a = Math.atan2(y2 - y, x2 - x), r2 = radius[1] ? q[0] : radius[0], cos = Math.cos(a), sin = Math.sin(a);
+ x += r * cos;
+ y += r * sin;
+ x2 -= r2 * cos;
+ y2 -= r2 * sin;
+ ctx.moveTo(x, y);
+ ctx.lineTo(x2, y2);
+ }
+ }
+ }
+ ;
+ ctx.stroke();
+ options.dotColor && ctx.fill();
+ requestAnimationFrame(update);
+ };
+ var onMousemove = function (ev) {
+ tilt[0] = ev.pageX - window.innerWidth / 2;
+ tilt[1] = ev.pageY - window.innerHeight / 2;
+ };
+ var onOrientation = function (ev) {
+ tilt[0] = Math.min(Math.max(-ev.beta, -30), 30) * (window.innerWidth / 30);
+ tilt[1] = Math.min(Math.max(-ev.gamma, -30), 30) * (window.innerHeight / 30);
+ };
+ var onResize = function () {
+ var w = canvas.width = canvas.offsetWidth, h = canvas.height = canvas.offsetHeight, v = options.velocity, d = options.direction, random = Math.random;
+ var num = Math.ceil((w * h) / options.density);
+ for (var i = particles.length - 1; i >= 0; i--)
+ if (particles[i][1] > w || particles[i][2] > h)
+ particles.splice(i, 1);
+ if (num < particles.length)
+ particles.splice(num);
+ while (num > particles.length)
+ particles.push([
+ Math.ceil(radius[1] ? (random() * (radius[1] - radius[0]) + radius[0]) : random() * radius[0]),
+ Math.ceil(random() * w),
+ Math.ceil(random() * h),
+ (d[0] || ((random() > .5) ? 1 : -1)) * (random() * (v[0][1] - v[0][0]) + v[0][0]),
+ (d[1] || ((random() > .5) ? 1 : -1)) * (random() * (v[1][1] - v[1][0]) + v[1][0]),
+ 0,
+ 0
+ ]);
+ ctx.strokeStyle = options.lineColor;
+ ctx.fillStyle = options.dotColor;
+ ctx.lineWidth = options.lineWidth;
+ };
+ window.addEventListener('resize', onResize, false);
+ document.addEventListener('mousemove', onMousemove, false);
+ window.addEventListener('deviceorientation', onOrientation, false);
+ onResize();
+ update();
+ }
+ return Zodiac;
+})();
diff --git a/zodiac/script/zodiac.min.js b/zodiac/script/zodiac.min.js
new file mode 100644
index 0000000..dc35be7
--- /dev/null
+++ b/zodiac/script/zodiac.min.js
@@ -0,0 +1 @@
+"use static";var Zodiac=function(){function t(t,i){void 0===i&&(i={}),this.options={direction:[0,0],velocity:[[.1,.3],[.1,.3]],bounce:[!0,!0],parallax:.2,density:5e3,proximity:50,dotRadius:[1,5],lineColor:"#aaa",lineWidth:1};var t="string"==typeof t||t instanceof String?document.getElementById(t):t;if("CANVAS"!=t.tagName)throw"no canvas";for(var e in i)this.options[e]=i[e];i=this.options;var o=t.getContext("2d",{alpha:!i.backgroundColor}),n=[],a=[0,0],r=[].concat(i.dotRadius),l=i.parallax/2/(r[1]?r[1]-r[0]:r[0]),d=function(){var e=t.width,h=t.height;i.backgroundColor?(o.fillStyle=i.backgroundColor,o.fillRect(0,0,e,h),o.fillStyle=i.dotColor):o.clearRect(0,0,e,h),o.beginPath();for(var c,s,f,v=0;vs||s>e)&&(i.direction[0]?c[1]=(s+e)%e-c[5]:c[3]=-c[3]),(0>f||f>h)&&(i.direction[1]?c[2]=(f+h)%h-c[6]:c[4]=-c[4]);var m=r[1]?c[0]:r[0];o.moveTo(s+m,f),o.arc(s,f,m,0,2*Math.PI);for(var u=v-1;u>=0;u--){var p=n[u],w=p[1]-c[1],M=p[2]-c[2],y=Math.sqrt(w*w+M*M);if(y=0;s--)(n[s][1]>e||n[s][2]>a)&&n.splice(s,1);for(cn.length;)n.push([Math.ceil(r[1]?h()*(r[1]-r[0])+r[0]:h()*r[0]),Math.ceil(h()*e),Math.ceil(h()*a),(d[0]||(h()>.5?1:-1))*(h()*(l[0][1]-l[0][0])+l[0][0]),(d[1]||(h()>.5?1:-1))*(h()*(l[1][1]-l[1][0])+l[1][0]),0,0]);o.strokeStyle=i.lineColor,o.fillStyle=i.dotColor,o.lineWidth=i.lineWidth};window.addEventListener("resize",s,!1),document.addEventListener("mousemove",h,!1),window.addEventListener("deviceorientation",c,!1),s(),d()}return t}()
diff --git a/zodiac/script/zodiac.ts b/zodiac/script/zodiac.ts
new file mode 100644
index 0000000..b0b7666
--- /dev/null
+++ b/zodiac/script/zodiac.ts
@@ -0,0 +1,161 @@
+"use static"
+
+class Zodiac {
+ options: any = {
+ direction: [0, 0],
+ velocity: [[.1, 0.3], [.1, .3]],
+ bounce: [true, true],
+ parallax: .2,
+ density: 5000,
+ proximity: 50,
+ //dotColor: '#aaa',
+ dotRadius: [1,5],
+ lineColor: '#aaa',
+ lineWidth: 1,
+ //backgroundColor: 'rgba(20,20,20,.2)',
+ };
+
+ constructor(canvas: any, options: any = {}) {
+
+ var canvas = (typeof canvas == 'string' || canvas instanceof String) ?
+ document.getElementById(canvas) : canvas;
+
+ if (canvas.tagName != 'CANVAS') throw "no canvas";
+
+ for (var key in options) { this.options[key] = options[key]; }
+ options = this.options;
+
+ var ctx = canvas.getContext('2d', { alpha: !options.backgroundColor }),
+ particles = [],
+ tilt = [0, 0],
+ radius = [].concat(options.dotRadius),
+ parallax = options.parallax/2 / (radius[1] ? (radius[1] - radius[0]) : radius[0]);
+
+ var update = function () {
+ var w = canvas.width,
+ h = canvas.height;
+
+ if (options.backgroundColor) {
+ ctx.fillStyle = options.backgroundColor;
+ ctx.fillRect(0, 0, w, h);
+ ctx.fillStyle = options.dotColor;
+ } else {
+ ctx.clearRect(0, 0, w, h);
+ }
+
+ ctx.beginPath();
+
+ for (var i = 0, p, x, y; i < particles.length; i++) {
+ p = particles[i];
+
+ /* MOVE */
+ p[1] += p[3];
+ p[2] += p[4];
+
+ /* POSITION */
+ if (options.parallax) {
+ var fac = p[0] * parallax;
+ p[5] += (tilt[0] * fac - p[5]) / 10;
+ p[6] += (tilt[1] * fac - p[6]) / 10;
+ }
+
+ x = p[1] + p[5];
+ y = p[2] + p[6];
+
+ if (x < 0 || x > w)
+ (options.direction[0]) ? (p[1] = ((x + w) % w) - p[5]) : (p[3] = -p[3]);
+
+ if (y < 0 || y > h)
+ (options.direction[1]) ? (p[2] = ((y + h) % h) - p[6]) : (p[4] = -p[4]);
+
+ var r = radius[1] ? p[0] : radius[0];
+ /* DRAW */
+ ctx.moveTo(x + r, y);
+ ctx.arc(x, y, r, 0, Math.PI * 2);
+
+ // loop back no double connections
+ for (var j = i - 1; j >= 0; j--) {
+ var q = particles[j],
+ dx = q[1] - p[1],
+ dy = q[2] - p[2],
+ dist = Math.sqrt((dx * dx) + (dy * dy));
+
+ if (dist < options.proximity) {
+ var x = p[1] + p[5],
+ y = p[2] + p[6],
+ x2 = q[1] + q[5],
+ y2 = q[2] + q[6],
+ a = Math.atan2(y2 - y, x2 - x),
+ r2 = radius[1] ? q[0] : radius[0],
+ cos = Math.cos(a),
+ sin = Math.sin(a);
+
+ x += r * cos;
+ y += r * sin;
+ x2 -= r2 * cos;
+ y2 -= r2 * sin;
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x2, y2);
+ }
+ }
+ };
+ ctx.stroke();
+ options.dotColor && ctx.fill();
+
+ requestAnimationFrame(update);
+ }
+
+
+ var onMousemove = function (ev?: MouseEvent) {
+ tilt[0] = ev.pageX - window.innerWidth / 2;
+ tilt[1] = ev.pageY - window.innerHeight / 2;
+ }
+
+ var onOrientation = function (ev?: any) {
+ tilt[0] = Math.min(Math.max(-ev.beta, -30), 30) * (window.innerWidth / 30);
+ tilt[1] = Math.min(Math.max(-ev.gamma, -30), 30) * (window.innerHeight / 30);
+
+ }
+
+ var onResize = function () {
+
+ var w = canvas.width = canvas.offsetWidth,
+ h = canvas.height = canvas.offsetHeight,
+ v = options.velocity,
+ d = options.direction,
+ random = Math.random;
+
+ var num = Math.ceil((w * h) / options.density);
+
+ for (var i = particles.length - 1; i >= 0; i--)
+ if (particles[i][1] > w || particles[i][2] > h)
+ particles.splice(i, 1);
+
+ if (num < particles.length)
+ particles.splice(num);
+
+ while (num > particles.length)
+ particles.push([
+ Math.ceil(radius[1] ? (random() * (radius[1] - radius[0]) + radius[0]) : random() * radius[0]), //z
+ Math.ceil(random() * w), //x
+ Math.ceil(random() * h), //y
+ // (random)direction * clamped random velocity
+ (d[0] || ((random() > .5) ? 1 : -1)) * (random() * (v[0][1] - v[0][0]) + v[0][0]), //vx
+ (d[1] || ((random() > .5) ? 1 : -1)) * (random() * (v[1][1] - v[1][0]) + v[1][0]), //vy
+ 0, 0 // offset
+ ]);
+
+
+ ctx.strokeStyle = options.lineColor;
+ ctx.fillStyle = options.dotColor;
+ ctx.lineWidth = options.lineWidth;
+ }
+
+ window.addEventListener('resize', onResize, false);
+ document.addEventListener('mousemove', onMousemove, false);
+ window.addEventListener('deviceorientation', onOrientation, false);
+ onResize();
+ update();
+ }
+}