diff --git a/README.md b/README.md
index e27c080..fe581be 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-# particle-game
+# Particle Game
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..97fbe8c
--- /dev/null
+++ b/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Document
+
+
+
+
+
+
+
+
diff --git a/script/main.js b/script/main.js
new file mode 100644
index 0000000..3687332
--- /dev/null
+++ b/script/main.js
@@ -0,0 +1,86 @@
+window.onload = function () {
+ var Particle = /** @class */ (function () {
+ function Particle(x, y, color, radius) {
+ this.vx = 0;
+ this.vy = 0;
+ this.x = x;
+ this.y = y;
+ this.color = color;
+ this.radius = radius;
+ }
+ Particle.prototype.draw = function () {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+ };
+ Particle.DEFAULT_RADIUS = 2.5;
+ return Particle;
+ }());
+ var canvas = document.querySelector("canvas");
+ var ctx = canvas.getContext("2d");
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ window.addEventListener("resize", function () {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ });
+ var particles = [];
+ function randomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1) + min);
+ }
+ function makeGroup(number, color, options) {
+ var _a;
+ var group = [];
+ for (var i = 0; i < number; i++) {
+ var particle = new Particle(randomInt(0, canvas.width), randomInt(0, canvas.height), color, (_a = options.radius) !== null && _a !== void 0 ? _a : Particle.DEFAULT_RADIUS);
+ group.push(particle);
+ particles.push(particle);
+ }
+ return group;
+ }
+ function interaction(group1, group2, options) {
+ var _a, _b, _c;
+ if (options === void 0) { options = {}; }
+ options.g = (_a = options.g) !== null && _a !== void 0 ? _a : 0.1;
+ options.maxDistance = (_b = options.maxDistance) !== null && _b !== void 0 ? _b : 100;
+ options.minDistance = (_c = options.minDistance) !== null && _c !== void 0 ? _c : 0;
+ for (var i = 0; i < group1.length; i++) {
+ var fx = 0;
+ var fy = 0;
+ for (var j = 0; j < group2.length; j++) {
+ var a = group1[i];
+ var b = group2[j];
+ var dx = a.x - b.x;
+ var dy = a.y - b.y;
+ var d = Math.sqrt(dx * dx + dy * dy);
+ if (d > 0 && d < options.maxDistance && d > options.minDistance) {
+ var F = options.g / d;
+ fx += F * dx;
+ fy += F * dy;
+ }
+ a.vx = (a.vx + fx) * 0.5;
+ a.vy = (a.vy + fy) * 0.5;
+ a.x += a.vx * 0.005;
+ a.y += a.vy * 0.005;
+ }
+ }
+ }
+ var blue = makeGroup(5000, "#0000ff", { radius: 1 });
+ var red = makeGroup(1000, "#ff0000", { radius: 2.5 });
+ var green = makeGroup(50, "#00ff00", { radius: 4 });
+ function animate() {
+ interaction(red, red, { g: -0.05, maxDistance: 250 });
+ interaction(green, red, { g: -0.1, maxDistance: 500 });
+ interaction(red, green, { g: -0.05, maxDistance: 250 });
+ interaction(blue, red, { g: -0.4, maxDistance: 250 });
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.fillStyle = "#000000";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ particles.forEach(function (particle) {
+ particle.draw();
+ });
+ requestAnimationFrame(animate);
+ }
+ animate();
+};
diff --git a/script/main.ts b/script/main.ts
new file mode 100644
index 0000000..1c7756a
--- /dev/null
+++ b/script/main.ts
@@ -0,0 +1,130 @@
+window.onload = function () {
+ class Particle {
+ public x: number;
+ public y: number;
+ public radius: number;
+ public color: string;
+ public vx: number = 0;
+ public vy: number = 0;
+
+ public static DEFAULT_RADIUS = 2.5;
+
+ public constructor(x: number, y: number, color: string, radius: number) {
+ this.x = x;
+ this.y = y;
+ this.color = color;
+ this.radius = radius;
+ }
+
+ public draw() {
+ ctx.beginPath();
+ ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
+ ctx.fillStyle = this.color;
+ ctx.fill();
+ }
+ }
+
+ const canvas = document.querySelector("canvas") as HTMLCanvasElement;
+ const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ window.addEventListener("resize", () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ });
+
+ const particles: Particle[] = [];
+
+ function randomInt(min: number, max: number) {
+ return Math.floor(Math.random() * (max - min + 1) + min);
+ }
+
+ function makeGroup(
+ number: number,
+ color: string,
+ options: { radius?: number }
+ ) {
+ const group: Particle[] = [];
+
+ for (let i = 0; i < number; i++) {
+ const particle = new Particle(
+ randomInt(0, canvas.width),
+ randomInt(0, canvas.height),
+ color,
+ options.radius ?? Particle.DEFAULT_RADIUS
+ );
+
+ group.push(particle);
+ particles.push(particle);
+ }
+
+ return group;
+ }
+
+ type Options = {
+ minDistance?: number;
+ maxDistance?: number;
+ g?: number;
+ };
+
+ function interaction(
+ group1: Particle[],
+ group2: Particle[],
+ options: Options = {}
+ ) {
+ options.g = options.g ?? 0.1;
+ options.maxDistance = options.maxDistance ?? 100;
+ options.minDistance = options.minDistance ?? 0;
+
+ for (let i = 0; i < group1.length; i++) {
+ let fx = 0;
+ let fy = 0;
+
+ for (let j = 0; j < group2.length; j++) {
+ let a = group1[i];
+ let b = group2[j];
+
+ const dx = a.x - b.x;
+ const dy = a.y - b.y;
+ const d = Math.sqrt(dx * dx + dy * dy);
+
+ if (d > 0 && d < options.maxDistance && d > options.minDistance) {
+ const F = options.g / d;
+ fx += F * dx;
+ fy += F * dy;
+ }
+
+ a.vx = (a.vx + fx) * 0.5;
+ a.vy = (a.vy + fy) * 0.5;
+ a.x += a.vx * 0.005;
+ a.y += a.vy * 0.005;
+ }
+ }
+ }
+
+ const blue = makeGroup(5000, "#0000ff", { radius: 1 });
+ const red = makeGroup(1000, "#ff0000", { radius: 2.5 });
+ const green = makeGroup(50, "#00ff00", { radius: 4 });
+
+ function animate() {
+ interaction(red, red, { g: -0.05, maxDistance: 250 });
+ interaction(green, red, { g: -0.1, maxDistance: 500 });
+ interaction(red, green, { g: -0.05, maxDistance: 250 });
+ interaction(blue, red, { g: -0.4, maxDistance: 250 });
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ ctx.fillStyle = "#000000";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ particles.forEach((particle) => {
+ particle.draw();
+ });
+
+ requestAnimationFrame(animate);
+ }
+
+ animate();
+};
diff --git a/style/main.css b/style/main.css
new file mode 100644
index 0000000..2798f58
--- /dev/null
+++ b/style/main.css
@@ -0,0 +1,29 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "Roboto", sans-serif;
+ overflow: hidden;
+}
+
+.gui {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 100;
+ background: rgba(255, 255, 255, 0.0666666667);
+ color: #ffffff;
+ padding: 15px;
+ border-radius: 5px;
+}
+.gui .label {
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 700;
+ color: rgba(255, 255, 255, 0.5333333333);
+}
+
+/*# sourceMappingURL=main.css.map */
diff --git a/style/main.css.map b/style/main.css.map
new file mode 100644
index 0000000..ed8bc90
--- /dev/null
+++ b/style/main.css.map
@@ -0,0 +1 @@
+{"version":3,"sourceRoot":"","sources":["main.scss"],"names":[],"mappings":"AAAA;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA","file":"main.css"}
\ No newline at end of file
diff --git a/style/main.scss b/style/main.scss
new file mode 100644
index 0000000..8e16800
--- /dev/null
+++ b/style/main.scss
@@ -0,0 +1,28 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: "Roboto", sans-serif;
+ overflow: hidden;
+}
+
+.gui {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ z-index: 100;
+ background: #ffffff11;
+ color: #ffffff;
+ padding: 15px;
+ border-radius: 5px;
+
+ .label {
+ text-transform: uppercase;
+ font-size: 12px;
+ font-weight: 700;
+ color: #ffffff88;
+ }
+}