diff --git a/index.html b/index.html
new file mode 100644
index 0000000..2fb994d
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..839fb13
--- /dev/null
+++ b/script.js
@@ -0,0 +1,233 @@
+var DELTAT = .01;
+var SEGLEN = 10;
+var SPRINGK = 10;
+var MASS = 1;
+var GRAVITY = 50;
+var RESISTANCE = 10;
+var STOPVEL = 0.1;
+var STOPACC = 0.1;
+var DOTSIZE = 32;
+var BOUNCE = 0.75;
+
+class Vector2 {
+ constructor(vec, y) {
+ if(typeof vec === "undefined" &&
+ typeof y === "undefined") {
+ this.x = 0;
+ this.y = 0;
+ } else if(typeof vec === "number" &&
+ typeof y === "undefined") {
+ this.x = vec;
+ this.y = vec;
+ } else if(typeof vec === "number" &&
+ typeof y === "number") {
+ this.x = vec;
+ this.y = y;
+ } else if(typeof vec === "object" && vec.constructor == Vector2) {
+ this.x = vec.getX()
+ this.y = vec.getY();
+ } else
+ throw 'Vector2::constructor(): Invalid types ' + (typeof vec) + ' ' + (typeof y);
+ }
+
+ getX() {
+ return this.x;
+ }
+
+ getY() {
+ return this.y;
+ }
+
+ setX(x) {
+ if(typeof x !== "number")
+ throw 'Vector2::setX(): Invalid type ' + (typeof x);
+
+ this.x = x;
+ }
+
+ setY(y) {
+ if(typeof y !== "number")
+ throw 'Vector2::setY(): Invalid type ' + (typeof y);
+
+ this.y = y;
+ }
+
+ add(vec, y) {
+ if(typeof vec === "number" &&
+ typeof y === "undefined") {
+ return new Vector2(
+ this.x + vec,
+ this.y + vec
+ );
+ } else if(typeof vec === "number" &&
+ typeof y === "number") {
+ return new Vector2(
+ this.x + vec,
+ this.y + y
+ );
+ } else if(typeof vec === "object" && vec.constructor == Vector2) {
+ return new Vector2(
+ this.x + vec.getX(),
+ this.y + vec.getY()
+ );
+ } else
+ throw 'Vector2::add(): Invalid types ' + (typeof vec) + ' ' + (typeof y);
+ }
+
+ sub(vec, y) {
+ if(typeof vec === "number" &&
+ typeof y === "undefined") {
+ return new Vector2(
+ this.x - vec,
+ this.y - vec
+ );
+ } else if(typeof vec === "number" &&
+ typeof y === "number") {
+ return new Vector2(
+ this.x - vec,
+ this.y - y
+ );
+ } else if(typeof vec === "object" && vec.constructor == Vector2) {
+ return new Vector2(
+ this.x - vec.getX(),
+ this.y - vec.getY()
+ );
+ } else
+ throw 'Vector2::sub(): Invalid types ' + (typeof vec) + ' ' + (typeof y);
+ }
+
+ mul(vec, y) {
+ if(typeof vec === "number" &&
+ typeof y === "undefined") {
+ return new Vector2(
+ this.x * vec,
+ this.y * vec
+ );
+ } else if(typeof vec === "number" &&
+ typeof y === "number") {
+ return new Vector2(
+ this.x * vec,
+ this.y * y
+ );
+ } else if(typeof vec === "object" && vec.constructor == Vector2) {
+ return new Vector2(
+ this.x * vec.getX(),
+ this.y * vec.getY()
+ );
+ } else
+ throw 'Vector2::mul(): Invalid types ' + (typeof vec) + ' ' + (typeof y);
+ }
+
+ div(vec, y) {
+ if(typeof vec === "number" &&
+ typeof y === "undefined") {
+ return new Vector2(
+ this.x / vec,
+ this.y / vec
+ );
+ } else if(typeof vec === "number" &&
+ typeof y === "number") {
+ return new Vector2(
+ this.x / vec,
+ this.y / y
+ );
+ } else if(typeof vec === "object" && vec.constructor == Vector2) {
+ return new Vector2(
+ this.x / vec.getX(),
+ this.y / vec.getY()
+ );
+ } else
+ throw 'Vector2::div(): Invalid types ' + (typeof vec) + ' ' + (typeof y);
+ }
+
+ length() {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+};
+
+$(document).ready(function($) {
+ var entities = [];
+
+ class Entity {
+ constructor() {
+ this.position = new Vector2();
+ this.speed = new Vector2();
+ this.elem = $('').addClass('point').appendTo('body')
+ }
+ };
+
+ for (var i = 0; i < 8; i++) {
+ entities.push(new Entity());
+ }
+
+ $(document).mousemove(function(event) {
+ entities[0].position = new Vector2(
+ event.pageX,
+ event.pageY
+ );
+ });
+
+ setInterval(function() {
+ var windowSize = new Vector2($(window).width(), $(window).height());
+
+ $.each(entities, function(i, entity){
+ if(i > 0) {
+ var spring = new Vector2();
+
+ var springForce = function(i, j) {
+ var d = i.sub(j);
+ var len = d.length();
+ return len > SEGLEN ? d.div(len).mul(SPRINGK).mul(len - SEGLEN) : new Vector2();
+ }
+
+ if (i > 0) {
+ spring = spring.add(springForce(entities[i - 1].position, entity.position));
+ }
+
+ if (i < (entities.length - 1)) {
+ spring = spring.add(springForce(entities[i + 1].position, entity.position));
+ }
+
+ var resist = entity.speed.mul(-RESISTANCE);
+ var accel = spring.add(resist).div(MASS).add(0, GRAVITY);
+
+ entity.speed = entity.speed.add(accel.mul(DELTAT));
+
+ if (Math.abs(entity.speed.getX()) < STOPVEL &&
+ Math.abs(entity.speed.getY()) < STOPVEL &&
+ Math.abs(accel.getX()) < STOPACC &&
+ Math.abs(accel.getY()) < STOPACC) {
+ entity.speed = new Vector2();
+ }
+
+ entity.position = entity.position.add(entity.speed);
+ }
+
+ if (entity.position.getX() < 0) {
+ if (entity.speed.getX() < 0) {
+ entity.speed.setX(BOUNCE * -entity.speed.getX());
+ }
+ entity.position.setX(0);
+ }
+
+ if (entity.position.getX() >= windowSize.getX() - entity.elem.width()) {
+ if (entity.speed.getX() > 0) {
+ entity.speed.setX(BOUNCE * -entity.speed.getX());
+ }
+ entity.position.setX(windowSize.getX() - entity.elem.width());
+ }
+
+ if (entity.position.getY() >= windowSize.getY() - entity.elem.height()) {
+ if (entity.speed.getY() > 0) {
+ entity.speed.setY(BOUNCE * -entity.speed.getY());
+ }
+ entity.position.setY(windowSize.getY() - entity.elem.height());
+ }
+
+ entity.elem.css({
+ 'left': entity.position.getX(),
+ 'top': entity.position.getY()
+ });
+ });
+ }, 20);
+});
\ No newline at end of file