diff --git a/README.md b/README.md index fe581be..ccc0a8e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# Particle Game +# Particle Sandbox diff --git a/assets/fonts/Rubik.ttf b/assets/fonts/Rubik.ttf new file mode 100644 index 0000000..bbab349 Binary files /dev/null and b/assets/fonts/Rubik.ttf differ diff --git a/assets/fonts/RubikItalic.ttf b/assets/fonts/RubikItalic.ttf new file mode 100644 index 0000000..36dbec5 Binary files /dev/null and b/assets/fonts/RubikItalic.ttf differ diff --git a/assets/images/ico/favicon.ico b/assets/images/ico/favicon.ico new file mode 100644 index 0000000..3be4ea6 Binary files /dev/null and b/assets/images/ico/favicon.ico differ diff --git a/assets/images/svg/arrow_down.svg b/assets/images/svg/arrow_down.svg new file mode 100644 index 0000000..2503675 --- /dev/null +++ b/assets/images/svg/arrow_down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/svg/download.svg b/assets/images/svg/download.svg new file mode 100644 index 0000000..28e8f93 --- /dev/null +++ b/assets/images/svg/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/svg/pause.svg b/assets/images/svg/pause.svg new file mode 100644 index 0000000..ff606ce --- /dev/null +++ b/assets/images/svg/pause.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/svg/play_arrow.svg b/assets/images/svg/play_arrow.svg new file mode 100644 index 0000000..5f53dfa --- /dev/null +++ b/assets/images/svg/play_arrow.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/svg/restart.svg b/assets/images/svg/restart.svg new file mode 100644 index 0000000..d5cb6af --- /dev/null +++ b/assets/images/svg/restart.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/svg/upload.svg b/assets/images/svg/upload.svg new file mode 100644 index 0000000..2a16106 --- /dev/null +++ b/assets/images/svg/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html index 97fbe8c..d1ec0d9 100644 --- a/index.html +++ b/index.html @@ -3,14 +3,83 @@ - Document - + Particle Sandbox + +
-

Particles

+
+
+

Particle Sandbox

+
+ + + +
+
+
+
+ +

Particles

+
+
+
+
+
+ +

Interactions

+
+
+
+
+ + +
+
diff --git a/script/main.js b/script/main.js index 3687332..706e9ed 100644 --- a/script/main.js +++ b/script/main.js @@ -1,86 +1,465 @@ -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 _a, _b, _c, _d, _e, _f, _g; +var SLIDER_MIN = 50; +var SLIDER_MAX = 1000; +var SLIDER_STEP = 50; +var SLIDER_DEFAULT = 350; +var MAX_INTERACTION_NUMBER = 20; +var MIN_INTENSITY = 1; +var MAX_INTENSITY = 1000; +var DEFAULT_INTENSITY = 100; +var INTENSITY_STEP = 1; +var INTENSITY_DIVIDER = 10000; +var DISTANCE_MIN = 10; +var DISTANCE_MAX = 500; +var DISTANCE_STEP = 10; +var DISTANCE_DEFAULT = 200; +var Group1 = 0; +var Group2 = 1; +var canvas = document.querySelector("canvas"); +var ctx = canvas.getContext("2d"); +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; +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) { + ctx.fillStyle = ColorsHex[this.color]; + ctx.fillRect(this.x, this.y, this.radius, this.radius); + }; + Particle.DEFAULT_RADIUS = 5; + return Particle; +}()); +var paused = true; +var particles = []; +var interactions = []; +var ColorsHex = Object.freeze({ + red: "#f91d4d", + green: "#0db342", + blue: "#4a4ad7", + yellow: "#f0e246", + magenta: "#d742d7", + cyan: "#42cedb", +}); +var colors = { + red: null, + green: null, + blue: null, + yellow: null, + magenta: null, + cyan: null, +}; +function reset() { + particles.forEach(function (particle) { + particle.x = randomInt(0, canvas.width); + particle.y = randomInt(0, canvas.height); }); - 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); + paused = true; + var img = document.querySelector(".play-pause img"); + img.src = "/assets/images/svg/play_arrow.svg"; +} +function addColor(color, autoGroup) { + if (autoGroup === void 0) { autoGroup = true; } + if (autoGroup) + colors[color] = makeGroup(350, color, { radius: Particle.DEFAULT_RADIUS }); + var container = document.querySelector(".particle-containers"); + var particle = document.createElement("div"); + particle.classList.add("particle-container"); + particle.classList.add(color); + var head = document.createElement("div"); + head.classList.add("container-head"); + head.innerHTML = "

".concat(color, "

"); + var close = head.querySelector(".close"); + close.addEventListener("click", function () { + particles = particles.filter(function (p) { return p.color !== color; }); + colors[color] = null; + container.removeChild(particle); + interactions = interactions.filter(function (itn) { + var _a, _b, _c, _d; + if (itn.group1[0].color === color || itn.group2[0].color === color) { + (_d = (_c = (_b = (_a = document + .querySelector(".interaction-container .colors .color.".concat(color))) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.parentElement) === null || _c === void 0 ? void 0 : _c.parentElement) === null || _d === void 0 ? void 0 : _d.remove(); + return false; + } + return true; + }); + }); + var particleSetting = document.createElement("h4"); + particleSetting.classList.add("setting"); + particleSetting.innerHTML = "Particle number : ".concat(SLIDER_DEFAULT.toLocaleString("en-US"), ""); + var range = document.createElement("div"); + range.classList.add("range"); + range.innerHTML = "

".concat(SLIDER_MIN.toLocaleString("en-US"), "

").concat(SLIDER_MAX.toLocaleString("en-US"), "

"); + var input = range.querySelector("input"); + input.addEventListener("input", function () { + var value = parseInt(input.value); + particleSetting.innerHTML = "Particle number : ".concat(value.toLocaleString("en-US"), ""); + particles = particles.filter(function (p) { return p.color !== color; }); + colors[color] = makeGroup(value, color, { + radius: Particle.DEFAULT_RADIUS, + }); + interactions = interactions.map(function (itn) { + if (itn.group1[0].color === color) { + itn.group1 = colors[color]; + } + else if (itn.group2[0].color === color) { + itn.group2 = colors[color]; + } + return itn; + }); + }); + particle.appendChild(head); + particle.appendChild(particleSetting); + particle.appendChild(range); + container.appendChild(particle); +} +function colorSelector(selected, group, interaction) { + var selector = document.createElement("div"); + selector.classList.add("color-selector"); + var selectedColor = document.createElement("button"); + selectedColor.classList.add("color"); + selectedColor.classList.add(selected); + var colorList = document.createElement("div"); + colorList.classList.add("color-list"); + selectedColor.addEventListener("click", function (e) { + e.stopPropagation(); + if (colorList.classList.contains("show")) { + colorList.classList.remove("show"); } - 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; + else { + var colorButtons = document.querySelectorAll(".color-list"); + colorButtons.forEach(function (colorButton) { + if (colorButton.classList.contains("show")) { + colorButton.classList.remove("show"); } - 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; + }); + colorList.classList.add("show"); + colorList.innerHTML = ""; + var _loop_1 = function (color) { + if (!colors[color]) + return "continue"; + var colorButton = document.createElement("button"); + colorButton.classList.add("color"); + colorButton.classList.add(color); + colorButton.addEventListener("click", function (e) { + e.stopPropagation(); + selectedColor.classList.remove(selected); + selected = color; + selectedColor.classList.add(selected); + colorList.classList.remove("show"); + switch (group) { + case Group1: + interaction.group1 = colors[color]; + break; + case Group2: + interaction.group2 = colors[color]; + break; + } + }); + colorList.appendChild(colorButton); + }; + for (var color in colors) { + _loop_1(color); } } + }); + selector.appendChild(selectedColor); + selector.appendChild(colorList); + return selector; +} +function addInteraction(interaction) { + interactions.push(interaction); + var isAttractionForce = true; + var container = document.querySelector(".interaction-containers"); + var interactionContainer = document.createElement("div"); + interactionContainer.classList.add("interaction-container"); + var colors = document.createElement("div"); + colors.classList.add("colors"); + var color1 = colorSelector(interaction.group1[0].color, 0, interaction); + var color2 = colorSelector(interaction.group2[0].color, 1, interaction); + var liaison = document.createElement("h3"); + liaison.classList.add("setting"); + if (isAttractionForce) { + liaison.textContent = "is attracted to"; } - 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(); + else { + liaison.textContent = "is repulsed by"; + } + var close = document.createElement("button"); + close.classList.add("close"); + close.classList.add("interaction"); + close.textContent = "⨯"; + colors.appendChild(color1); + colors.appendChild(liaison); + colors.appendChild(color2); + colors.appendChild(close); + var forceContainer = document.createElement("div"); + forceContainer.classList.add("force-container"); + forceContainer.innerHTML = "

Force Type

"; + var forceTypes = forceContainer.querySelectorAll(".force-type"); + forceTypes.forEach(function (forceType) { + forceType.addEventListener("click", function () { + forceTypes.forEach(function (ft) { return ft.classList.remove("selected"); }); + forceType.classList.add("selected"); + isAttractionForce = forceType.textContent === "Attraction"; + if (isAttractionForce) { + liaison.textContent = "is attracted to"; + interaction.options.g = -Math.abs(interaction.options.g); + } + else { + liaison.textContent = "is repulsed by"; + interaction.options.g = Math.abs(interaction.options.g); + } }); - requestAnimationFrame(animate); + }); + var intensityContainer = document.createElement("h4"); + intensityContainer.classList.add("setting"); + intensityContainer.innerHTML = "Intensity : ".concat(DEFAULT_INTENSITY.toLocaleString("en-US"), ""); + var intensityRange = document.createElement("div"); + intensityRange.classList.add("range"); + intensityRange.innerHTML = "

".concat(MIN_INTENSITY.toLocaleString("en-US"), "

").concat(MAX_INTENSITY.toLocaleString("en-US"), "

"); + var intensityInput = intensityRange.querySelector("input"); + intensityInput.addEventListener("input", function () { + var value = parseInt(intensityInput.value); + intensityContainer.innerHTML = "Intensity : ".concat(value.toLocaleString("en-US"), ""); + if (isAttractionForce) { + interaction.options.g = -value / INTENSITY_DIVIDER; + } + else { + interaction.options.g = value / INTENSITY_DIVIDER; + } + }); + var distanceContainer = document.createElement("h4"); + distanceContainer.classList.add("setting"); + distanceContainer.innerHTML = "Distance : ".concat(DISTANCE_DEFAULT.toLocaleString("en-US"), ""); + var distanceRange = document.createElement("div"); + distanceRange.classList.add("range"); + distanceRange.innerHTML = "

".concat(DISTANCE_MIN.toLocaleString("en-US"), "

").concat(DISTANCE_MAX.toLocaleString("en-US"), "

"); + var distanceInput = distanceRange.querySelector("input"); + distanceInput.addEventListener("input", function () { + var value = parseInt(distanceInput.value); + distanceContainer.innerHTML = "Distance : ".concat(value.toLocaleString("en-US"), ""); + interaction.options.distance = value; + }); + close.addEventListener("click", function () { + interactions = interactions.filter(function (itn) { return itn !== interaction; }); + container.removeChild(interactionContainer); + }); + interactionContainer.appendChild(colors); + interactionContainer.appendChild(forceContainer); + interactionContainer.appendChild(intensityContainer); + interactionContainer.appendChild(intensityRange); + interactionContainer.appendChild(distanceContainer); + interactionContainer.appendChild(distanceRange); + container.appendChild(interactionContainer); +} +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); } - animate(); -}; + return group; +} +function interaction(group1, group2, options) { + var _a, _b; + if (options === void 0) { options = {}; } + var g = (_a = options.g) !== null && _a !== void 0 ? _a : 0.1; + var distance = (_b = options.distance) !== null && _b !== void 0 ? _b : 100; + 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 < distance) { + var F = 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.01; + a.y += a.vy * 0.01; + } + } +} +(_a = document.querySelector(".add.particle")) === null || _a === void 0 ? void 0 : _a.addEventListener("click", function () { + var color = null; + for (var c in colors) { + if (!colors[c]) { + color = c; + break; + } + } + if (color) + addColor(color); +}); +(_b = document.querySelector(".add.interaction")) === null || _b === void 0 ? void 0 : _b.addEventListener("click", function () { + if (interactions.length >= MAX_INTERACTION_NUMBER) + return; + var color1 = null; + var color2 = null; + for (var c in colors) { + if (colors[c]) { + color1 = c; + break; + } + } + if (!color1) + return; + for (var c in colors) { + if (colors[c] && c !== color1) { + color2 = c; + break; + } + } + if (!color2) + color2 = color1; + var group1 = colors[color1]; + var group2 = colors[color2]; + if (!group1 || !group2) + return; + var intensity = DEFAULT_INTENSITY; + var distance = 100; + var options = { + distance: distance, + g: intensity / INTENSITY_DIVIDER, + }; + var interaction = { + group1: group1, + group2: group2, + options: options, + }; + addInteraction(interaction); +}); +(_c = document.querySelector(".play-pause")) === null || _c === void 0 ? void 0 : _c.addEventListener("click", function () { + paused = !paused; + var img = document.querySelector(".play-pause img"); + img.src = paused + ? "/assets/images/svg/play_arrow.svg" + : "/assets/images/svg/pause.svg"; +}); +(_d = document.querySelector(".reset")) === null || _d === void 0 ? void 0 : _d.addEventListener("click", function () { + reset(); +}); +(_e = document.querySelector(".save")) === null || _e === void 0 ? void 0 : _e.addEventListener("click", function () { + var element = document.createElement("a"); + element.style.display = "none"; + element.download = "ps".concat(Date.now(), ".json"); + element.setAttribute("href", "data:text/plain;charset=utf-8," + + encodeURIComponent(JSON.stringify([ + interactions.map(function (itn) { return ({ + group1: itn.group1[0].color, + group2: itn.group2[0].color, + options: itn.options, + }); }), + Object.entries(colors) + .filter(function (_a) { + var _ = _a[0], v = _a[1]; + return v; + }) + .map(function (_a) { + var k = _a[0], _ = _a[1]; + return ({ + color: k, + number: colors[k].length, + }); + }), + ]))); + element.click(); + element.remove(); +}); +(_f = document.querySelector(".load")) === null || _f === void 0 ? void 0 : _f.addEventListener("click", function () { + var element = document.createElement("input"); + element.style.display = "none"; + element.type = "file"; + element.accept = ".json"; + element.addEventListener("change", function (e) { + var file = e.target.files[0]; + var reader = new FileReader(); + reader.addEventListener("load", function (e) { + var _a = JSON.parse(e.target.result), itn = _a[0], groups = _a[1]; + interactions = []; + particles = []; + var particleContainer = document.querySelector(".particle-containers"); + var interactionContainer = document.querySelector(".interaction-containers"); + particleContainer.innerHTML = ""; + interactionContainer.innerHTML = ""; + for (var _i = 0, groups_1 = groups; _i < groups_1.length; _i++) { + var group = groups_1[_i]; + addColor(group.color, false); + colors[group.color] = makeGroup(group.number, group.color, { + radius: Particle.DEFAULT_RADIUS, + }); + } + for (var _b = 0, itn_1 = itn; _b < itn_1.length; _b++) { + var it = itn_1[_b]; + var group1 = colors[it.group1]; + var group2 = colors[it.group2]; + if (!group1 || !group2) + continue; + addInteraction({ + group1: group1, + group2: group2, + options: it.options, + }); + } + }); + reader.readAsText(file); + }); + element.click(); + element.remove(); +}); +(_g = document.querySelector(".minimize")) === null || _g === void 0 ? void 0 : _g.addEventListener("click", function () { + var gui = document.querySelector(".gui .content"); + gui.classList.toggle("minimized"); +}); +window.addEventListener("resize", function () { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + reset(); +}); +window.addEventListener("click", function () { + document.querySelectorAll(".color-list").forEach(function (colorList) { + if (colorList.classList.contains("show")) { + colorList.classList.remove("show"); + } + }); +}); +addColor("red"); +addColor("green"); +addInteraction({ + group1: colors.red, + group2: colors.green, + options: { + distance: DISTANCE_DEFAULT, + g: -DEFAULT_INTENSITY / INTENSITY_DIVIDER, + }, +}); +function animate() { + if (!paused) { + interactions.forEach(function (itn) { + return interaction(itn.group1, itn.group2, itn.options); + }); + } + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#000000"; + ctx.fillRect(0, 0, canvas.width, canvas.height); + particles.forEach(function (particle) { return particle.draw(ctx); }); + return requestAnimationFrame(animate); +} +animate(); diff --git a/script/main.ts b/script/main.ts index 1c7756a..f2fc51b 100644 --- a/script/main.ts +++ b/script/main.ts @@ -1,130 +1,636 @@ -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; +const SLIDER_MIN = 50; +const SLIDER_MAX = 1000; +const SLIDER_STEP = 50; +const SLIDER_DEFAULT = 350; - public static DEFAULT_RADIUS = 2.5; +const MAX_INTERACTION_NUMBER = 20; - public constructor(x: number, y: number, color: string, radius: number) { - this.x = x; - this.y = y; - this.color = color; - this.radius = radius; +const MIN_INTENSITY = 1; +const MAX_INTENSITY = 1000; +const DEFAULT_INTENSITY = 100; +const INTENSITY_STEP = 1; +const INTENSITY_DIVIDER = 10000; + +const DISTANCE_MIN = 10; +const DISTANCE_MAX = 500; +const DISTANCE_STEP = 10; +const DISTANCE_DEFAULT = 200; + +const Group1 = 0; +const Group2 = 1; + +const canvas = document.querySelector("canvas") as HTMLCanvasElement; +const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; + +canvas.width = window.innerWidth; +canvas.height = window.innerHeight; + +type Options = { + distance?: number; + g?: number; +}; + +type Interaction = { + group1: Particle[]; + group2: Particle[]; + options: Options; +}; + +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 = 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: CanvasRenderingContext2D) { + ctx.fillStyle = ColorsHex[this.color]; + ctx.fillRect(this.x, this.y, this.radius, this.radius); + } +} + +let paused = true; +let particles: Particle[] = []; +let interactions: Interaction[] = []; + +const ColorsHex = Object.freeze({ + red: "#f91d4d", + green: "#0db342", + blue: "#4a4ad7", + yellow: "#f0e246", + magenta: "#d742d7", + cyan: "#42cedb", +}); + +const colors = { + red: null, + green: null, + blue: null, + yellow: null, + magenta: null, + cyan: null, +}; + +function reset() { + particles.forEach((particle) => { + particle.x = randomInt(0, canvas.width); + particle.y = randomInt(0, canvas.height); + }); + + paused = true; + const img = document.querySelector(".play-pause img") as HTMLImageElement; + img.src = "/assets/images/svg/play_arrow.svg"; +} + +function addColor(color: string, autoGroup: boolean = true) { + if (autoGroup) + colors[color] = makeGroup(350, color, { radius: Particle.DEFAULT_RADIUS }); + + const container = document.querySelector(".particle-containers")!; + + const particle = document.createElement("div"); + particle.classList.add("particle-container"); + particle.classList.add(color); + + const head = document.createElement("div"); + head.classList.add("container-head"); + head.innerHTML = `

${color}

`; + + const close = head.querySelector(".close") as HTMLButtonElement; + + close.addEventListener("click", () => { + particles = particles.filter((p) => p.color !== color); + colors[color] = null; + container.removeChild(particle); + + interactions = interactions.filter((itn) => { + if (itn.group1[0].color === color || itn.group2[0].color === color) { + document + .querySelector(`.interaction-container .colors .color.${color}`) + ?.parentElement?.parentElement?.parentElement?.remove(); + + return false; + } + + return true; + }); + }); + + const particleSetting = document.createElement("h4"); + particleSetting.classList.add("setting"); + particleSetting.innerHTML = `Particle number : ${SLIDER_DEFAULT.toLocaleString( + "en-US" + )}`; + + const range = document.createElement("div"); + range.classList.add("range"); + + range.innerHTML = `

${SLIDER_MIN.toLocaleString( + "en-US" + )}

${SLIDER_MAX.toLocaleString( + "en-US" + )}

`; + + const input = range.querySelector("input") as HTMLInputElement; + + input.addEventListener("input", () => { + const value = parseInt(input.value); + particleSetting.innerHTML = `Particle number : ${value.toLocaleString( + "en-US" + )}`; + + particles = particles.filter((p) => p.color !== color); + + colors[color] = makeGroup(value, color, { + radius: Particle.DEFAULT_RADIUS, + }); + + interactions = interactions.map((itn) => { + if (itn.group1[0].color === color) { + itn.group1 = colors[color]!; + } else if (itn.group2[0].color === color) { + itn.group2 = colors[color]!; + } + + return itn; + }); + }); + + particle.appendChild(head); + particle.appendChild(particleSetting); + particle.appendChild(range); + + container.appendChild(particle); +} + +function colorSelector( + selected: string, + group: number, + interaction: Interaction +) { + const selector = document.createElement("div"); + selector.classList.add("color-selector"); + + const selectedColor = document.createElement("button"); + selectedColor.classList.add("color"); + selectedColor.classList.add(selected); + + const colorList = document.createElement("div"); + colorList.classList.add("color-list"); + + selectedColor.addEventListener("click", (e) => { + e.stopPropagation(); + + if (colorList.classList.contains("show")) { + colorList.classList.remove("show"); + } else { + const colorButtons = document.querySelectorAll(".color-list"); + + colorButtons.forEach((colorButton) => { + if (colorButton.classList.contains("show")) { + colorButton.classList.remove("show"); + } + }); + + colorList.classList.add("show"); + colorList.innerHTML = ""; + + for (const color in colors) { + if (!colors[color]) continue; + + const colorButton = document.createElement("button"); + colorButton.classList.add("color"); + colorButton.classList.add(color); + + colorButton.addEventListener("click", (e) => { + e.stopPropagation(); + + selectedColor.classList.remove(selected); + selected = color; + selectedColor.classList.add(selected); + colorList.classList.remove("show"); + + switch (group) { + case Group1: + interaction.group1 = colors[color]; + break; + + case Group2: + interaction.group2 = colors[color]; + break; + } + }); + + colorList.appendChild(colorButton); + } } + }); - public draw() { - ctx.beginPath(); - ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); - ctx.fillStyle = this.color; - ctx.fill(); + selector.appendChild(selectedColor); + selector.appendChild(colorList); + + return selector; +} + +function addInteraction(interaction: Interaction) { + interactions.push(interaction); + let isAttractionForce = true; + const container = document.querySelector(".interaction-containers")!; + + const interactionContainer = document.createElement("div"); + interactionContainer.classList.add("interaction-container"); + + const colors = document.createElement("div"); + colors.classList.add("colors"); + + const color1 = colorSelector(interaction.group1[0].color, 0, interaction); + const color2 = colorSelector(interaction.group2[0].color, 1, interaction); + + const liaison = document.createElement("h3"); + liaison.classList.add("setting"); + + if (isAttractionForce) { + liaison.textContent = "is attracted to"; + } else { + liaison.textContent = "is repulsed by"; + } + + const close = document.createElement("button"); + close.classList.add("close"); + close.classList.add("interaction"); + close.textContent = "⨯"; + + colors.appendChild(color1); + colors.appendChild(liaison); + colors.appendChild(color2); + colors.appendChild(close); + + const forceContainer = document.createElement("div"); + forceContainer.classList.add("force-container"); + forceContainer.innerHTML = `

Force Type

`; + + const forceTypes = forceContainer.querySelectorAll(".force-type"); + + forceTypes.forEach((forceType) => { + forceType.addEventListener("click", () => { + forceTypes.forEach((ft) => ft.classList.remove("selected")); + forceType.classList.add("selected"); + + isAttractionForce = forceType.textContent === "Attraction"; + + if (isAttractionForce) { + liaison.textContent = "is attracted to"; + interaction.options.g = -Math.abs(interaction.options.g!); + } else { + liaison.textContent = "is repulsed by"; + interaction.options.g = Math.abs(interaction.options.g!); + } + }); + }); + + const intensityContainer = document.createElement("h4"); + intensityContainer.classList.add("setting"); + intensityContainer.innerHTML = `Intensity : ${DEFAULT_INTENSITY.toLocaleString( + "en-US" + )}`; + + const intensityRange = document.createElement("div"); + intensityRange.classList.add("range"); + intensityRange.innerHTML = `

${MIN_INTENSITY.toLocaleString( + "en-US" + )}

${MAX_INTENSITY.toLocaleString( + "en-US" + )}

`; + + const intensityInput = intensityRange.querySelector( + "input" + ) as HTMLInputElement; + + intensityInput.addEventListener("input", () => { + const value = parseInt(intensityInput.value); + intensityContainer.innerHTML = `Intensity : ${value.toLocaleString( + "en-US" + )}`; + + if (isAttractionForce) { + interaction.options.g = -value / INTENSITY_DIVIDER; + } else { + interaction.options.g = value / INTENSITY_DIVIDER; + } + }); + + const distanceContainer = document.createElement("h4"); + distanceContainer.classList.add("setting"); + distanceContainer.innerHTML = `Distance : ${DISTANCE_DEFAULT.toLocaleString( + "en-US" + )}`; + + const distanceRange = document.createElement("div"); + distanceRange.classList.add("range"); + distanceRange.innerHTML = `

${DISTANCE_MIN.toLocaleString( + "en-US" + )}

${DISTANCE_MAX.toLocaleString( + "en-US" + )}

`; + + const distanceInput = distanceRange.querySelector( + "input" + ) as HTMLInputElement; + + distanceInput.addEventListener("input", () => { + const value = parseInt(distanceInput.value); + distanceContainer.innerHTML = `Distance : ${value.toLocaleString( + "en-US" + )}`; + + interaction.options.distance = value; + }); + + close.addEventListener("click", () => { + interactions = interactions.filter((itn) => itn !== interaction); + container.removeChild(interactionContainer); + }); + + interactionContainer.appendChild(colors); + interactionContainer.appendChild(forceContainer); + interactionContainer.appendChild(intensityContainer); + interactionContainer.appendChild(intensityRange); + interactionContainer.appendChild(distanceContainer); + interactionContainer.appendChild(distanceRange); + + container.appendChild(interactionContainer); +} + +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; +} + +function interaction( + group1: Particle[], + group2: Particle[], + options: Options = {} +) { + const g = options.g ?? 0.1; + const distance = options.distance ?? 100; + + 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 < distance) { + const F = 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.01; + a.y += a.vy * 0.01; + } + } +} + +document.querySelector(".add.particle")?.addEventListener("click", () => { + let color: string | null = null; + + for (const c in colors) { + if (!colors[c]) { + color = c; + break; } } - const canvas = document.querySelector("canvas") as HTMLCanvasElement; - const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; + if (color) addColor(color); +}); +document.querySelector(".add.interaction")?.addEventListener("click", () => { + if (interactions.length >= MAX_INTERACTION_NUMBER) return; + + let color1: string | null = null; + let color2: string | null = null; + + for (const c in colors) { + if (colors[c]) { + color1 = c; + break; + } + } + + if (!color1) return; + + for (const c in colors) { + if (colors[c] && c !== color1) { + color2 = c; + break; + } + } + + if (!color2) color2 = color1; + + const group1 = colors[color1]; + const group2 = colors[color2]; + if (!group1 || !group2) return; + + const intensity = DEFAULT_INTENSITY; + const distance = 100; + + const options: Options = { + distance, + g: intensity / INTENSITY_DIVIDER, + }; + + const interaction: Interaction = { + group1, + group2, + options, + }; + + addInteraction(interaction); +}); + +document.querySelector(".play-pause")?.addEventListener("click", () => { + paused = !paused; + + const img = document.querySelector(".play-pause img") as HTMLImageElement; + + img.src = paused + ? "/assets/images/svg/play_arrow.svg" + : "/assets/images/svg/pause.svg"; +}); + +document.querySelector(".reset")?.addEventListener("click", () => { + reset(); +}); + +document.querySelector(".save")?.addEventListener("click", () => { + const element = document.createElement("a"); + element.style.display = "none"; + element.download = `ps${Date.now()}.json`; + + element.setAttribute( + "href", + "data:text/plain;charset=utf-8," + + encodeURIComponent( + JSON.stringify([ + interactions.map((itn) => ({ + group1: itn.group1[0].color, + group2: itn.group2[0].color, + options: itn.options, + })), + Object.entries(colors) + .filter(([_, v]) => v) + .map(([k, _]) => ({ + color: k, + number: colors[k]!.length, + })), + ]) + ) + ); + + element.click(); + element.remove(); +}); + +document.querySelector(".load")?.addEventListener("click", () => { + const element = document.createElement("input"); + element.style.display = "none"; + element.type = "file"; + element.accept = ".json"; + + element.addEventListener("change", (e) => { + const file = (e.target as HTMLInputElement).files![0]; + const reader = new FileReader(); + + reader.addEventListener("load", (e) => { + const [itn, groups] = JSON.parse(e.target!.result as string); + + interactions = []; + particles = []; + + const particleContainer = document.querySelector(".particle-containers")!; + + const interactionContainer = document.querySelector( + ".interaction-containers" + )!; + + particleContainer.innerHTML = ""; + interactionContainer.innerHTML = ""; + + for (const group of groups) { + addColor(group.color, false); + + colors[group.color] = makeGroup(group.number, group.color, { + radius: Particle.DEFAULT_RADIUS, + }); + } + + for (const it of itn) { + const group1 = colors[it.group1]; + const group2 = colors[it.group2]; + + if (!group1 || !group2) continue; + + addInteraction({ + group1, + group2, + options: it.options, + }); + } + }); + + reader.readAsText(file); + }); + + element.click(); + element.remove(); +}); + +document.querySelector(".minimize")?.addEventListener("click", () => { + const gui = document.querySelector(".gui .content") as HTMLDivElement; + gui.classList.toggle("minimized"); +}); + +window.addEventListener("resize", () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; - window.addEventListener("resize", () => { - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; + reset(); +}); + +window.addEventListener("click", () => { + document.querySelectorAll(".color-list").forEach((colorList) => { + if (colorList.classList.contains("show")) { + colorList.classList.remove("show"); + } }); +}); - const particles: Particle[] = []; +addColor("red"); +addColor("green"); - function randomInt(min: number, max: number) { - return Math.floor(Math.random() * (max - min + 1) + min); +addInteraction({ + group1: colors.red!, + group2: colors.green!, + options: { + distance: DISTANCE_DEFAULT, + g: -DEFAULT_INTENSITY / INTENSITY_DIVIDER, + }, +}); + +function animate() { + if (!paused) { + interactions.forEach((itn) => + interaction(itn.group1, itn.group2, itn.options) + ); } - function makeGroup( - number: number, - color: string, - options: { radius?: number } - ) { - const group: Particle[] = []; + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = "#000000"; + ctx.fillRect(0, 0, canvas.width, canvas.height); - 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 - ); + particles.forEach((particle) => particle.draw(ctx)); + return requestAnimationFrame(animate); +} - 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(); -}; +animate(); diff --git a/style/main.css b/style/main.css index 2798f58..7142952 100644 --- a/style/main.css +++ b/style/main.css @@ -1,11 +1,21 @@ +@font-face { + font-family: "Rubik"; + src: url(../assets/fonts/Rubik.ttf); +} +@font-face { + font-family: "Rubik"; + font-style: italic; + src: url(../assets/fonts/RubikItalic.ttf); +} * { margin: 0; padding: 0; box-sizing: border-box; + font-family: "Rubik", sans-serif; + user-select: none; } body { - font-family: "Roboto", sans-serif; overflow: hidden; } @@ -13,17 +23,650 @@ body { position: absolute; top: 10px; left: 10px; + padding: 5px; z-index: 100; - background: rgba(255, 255, 255, 0.0666666667); + background: #111111; color: #ffffff; - padding: 15px; + border-radius: 10px; + transition: all 0.4s ease-in-out; +} +.gui .content { + padding: 10px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 35px; + min-width: 350px; + max-height: calc(100vh - 30px); + transition: all 0.3s ease-in-out; +} +.gui .content.minimized { + max-height: 55px; + overflow: hidden; +} +.gui .content.minimized .head .head-buttons .minimize img { + transform: rotate(-180deg); +} +.gui .content::-webkit-scrollbar { + width: 5px; +} +.gui .content::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0); +} +.gui .content::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1333333333); border-radius: 5px; } -.gui .label { +.gui .content::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.2666666667); +} +.gui .content .head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; +} +.gui .content .head .title { + text-transform: uppercase; + font-size: 20px; + font-weight: 700; + color: #ffffff; + margin-left: 5px; +} +.gui .content .head .head-buttons { + display: flex; + gap: 10px; +} +.gui .content .head .head-buttons .reset, +.gui .content .head .head-buttons .minimize, +.gui .content .head .head-buttons .play-pause { + background: rgba(255, 255, 255, 0.0666666667); + border: none; + border-radius: 5px; + color: #ffffff; + font-size: 12px; + cursor: pointer; + padding: 0; + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease-in-out; + outline: none; +} +.gui .content .head .head-buttons .reset img, +.gui .content .head .head-buttons .minimize img, +.gui .content .head .head-buttons .play-pause img { + transition: all 0.2s ease-in-out; +} +.gui .content .head .head-buttons .reset:hover, +.gui .content .head .head-buttons .minimize:hover, +.gui .content .head .head-buttons .play-pause:hover { + background: rgba(255, 255, 255, 0.1647058824); +} +.gui .content .category-head { + display: flex; + align-items: center; + gap: 5px; + margin-bottom: 5px; +} +.gui .content .category-head .label { + text-transform: uppercase; + font-size: 16px; + font-weight: 700; + color: rgba(255, 255, 255, 0.8); +} +.gui .content .category-head .add { + height: 15px; + width: 15px; + border-radius: 50%; + background: rgba(255, 255, 255, 0); + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: rgba(255, 255, 255, 0.5333333333); +} +.gui .content .category-head .add:hover { + background: rgba(255, 255, 255, 0.1333333333); + color: rgba(255, 255, 255, 0.6666666667); +} +.gui .content .interaction-container .setting { + margin-top: 5px; +} +.gui .content .particle-containers, +.gui .content .interaction-containers { + display: flex; + flex-direction: column; + gap: 10px; +} +.gui .content .particle-containers .particle-container, +.gui .content .particle-containers .interaction-container, +.gui .content .interaction-containers .particle-container, +.gui .content .interaction-containers .interaction-container { + display: flex; + flex-direction: column; + gap: 5px; + transition: all 0.2s ease-in-out; + background: rgba(255, 255, 255, 0); + border-radius: 5px; + padding: 10px; +} +.gui .content .particle-containers .particle-container .colors, +.gui .content .particle-containers .interaction-container .colors, +.gui .content .interaction-containers .particle-container .colors, +.gui .content .interaction-containers .interaction-container .colors { + display: flex; + gap: 5px; + align-items: center; +} +.gui .content .particle-containers .particle-container .colors .setting, +.gui .content .particle-containers .interaction-container .colors .setting, +.gui .content .interaction-containers .particle-container .colors .setting, +.gui .content .interaction-containers .interaction-container .colors .setting { + text-transform: uppercase; + font-size: 14px; + font-weight: 500; + color: rgba(255, 255, 255, 0.6666666667); + margin: 0; +} +.gui .content .particle-containers .particle-container .colors .color-selector, +.gui .content .particle-containers .interaction-container .colors .color-selector, +.gui .content .interaction-containers .particle-container .colors .color-selector, +.gui .content .interaction-containers .interaction-container .colors .color-selector { + position: relative; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color, +.gui .content .particle-containers .interaction-container .colors .color-selector .color, +.gui .content .interaction-containers .particle-container .colors .color-selector .color, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color { + height: 12px; + width: 12px; + border-radius: 50%; + cursor: pointer; + border: none; + outline: none; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color.red, +.gui .content .particle-containers .interaction-container .colors .color-selector .color.red, +.gui .content .interaction-containers .particle-container .colors .color-selector .color.red, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color.red { + background: #f91d4d; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color.green, +.gui .content .particle-containers .interaction-container .colors .color-selector .color.green, +.gui .content .interaction-containers .particle-container .colors .color-selector .color.green, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color.green { + background: #0db342; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color.blue, +.gui .content .particle-containers .interaction-container .colors .color-selector .color.blue, +.gui .content .interaction-containers .particle-container .colors .color-selector .color.blue, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color.blue { + background: #4a4ad7; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color.yellow, +.gui .content .particle-containers .interaction-container .colors .color-selector .color.yellow, +.gui .content .interaction-containers .particle-container .colors .color-selector .color.yellow, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color.yellow { + background: #f0e246; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color.cyan, +.gui .content .particle-containers .interaction-container .colors .color-selector .color.cyan, +.gui .content .interaction-containers .particle-container .colors .color-selector .color.cyan, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color.cyan { + background: #42cedb; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color.magenta, +.gui .content .particle-containers .interaction-container .colors .color-selector .color.magenta, +.gui .content .interaction-containers .particle-container .colors .color-selector .color.magenta, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color.magenta { + background: #d742d7; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color-list, +.gui .content .particle-containers .interaction-container .colors .color-selector .color-list, +.gui .content .interaction-containers .particle-container .colors .color-selector .color-list, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color-list { + position: absolute; + top: -5px; + left: 50%; + transform: translate(-25%, -100%) scale(0.5); + opacity: 0; + display: flex; + align-items: center; + gap: 5px; + padding-inline: 5px; + height: 20px; + border-radius: 10px; + background: #262626; + transition: all 0.1s ease-in-out; +} +.gui .content .particle-containers .particle-container .colors .color-selector .color-list.show, +.gui .content .particle-containers .interaction-container .colors .color-selector .color-list.show, +.gui .content .interaction-containers .particle-container .colors .color-selector .color-list.show, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color-list.show { + opacity: 1; + transform: translate(-15px, -100%) scale(1); +} +.gui .content .particle-containers .particle-container .colors .color-selector .color-list::after, +.gui .content .particle-containers .interaction-container .colors .color-selector .color-list::after, +.gui .content .interaction-containers .particle-container .colors .color-selector .color-list::after, +.gui .content .interaction-containers .interaction-container .colors .color-selector .color-list::after { + content: ""; + position: absolute; + top: 100%; + left: 12px; + transform: translate(-25%, 0); + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #262626; +} +.gui .content .particle-containers .particle-container .colors .close, +.gui .content .particle-containers .interaction-container .colors .close, +.gui .content .interaction-containers .particle-container .colors .close, +.gui .content .interaction-containers .interaction-container .colors .close { + margin-left: auto; + margin-bottom: auto; + height: 15px; + width: 15px; + border-radius: 50%; + background: rgba(255, 255, 255, 0); + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: rgba(255, 255, 255, 0.5333333333); + opacity: 0; + transition: all 0.2s ease-in-out; +} +.gui .content .particle-containers .particle-container .colors .close:hover, +.gui .content .particle-containers .interaction-container .colors .close:hover, +.gui .content .interaction-containers .particle-container .colors .close:hover, +.gui .content .interaction-containers .interaction-container .colors .close:hover { + background: rgba(255, 255, 255, 0.1333333333); + color: rgba(255, 255, 255, 0.6666666667); +} +.gui .content .particle-containers .particle-container .force-container, +.gui .content .particle-containers .interaction-container .force-container, +.gui .content .interaction-containers .particle-container .force-container, +.gui .content .interaction-containers .interaction-container .force-container { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 10px; +} +.gui .content .particle-containers .particle-container .force-container .force-types, +.gui .content .particle-containers .interaction-container .force-container .force-types, +.gui .content .interaction-containers .particle-container .force-container .force-types, +.gui .content .interaction-containers .interaction-container .force-container .force-types { + display: flex; + gap: 10px; +} +.gui .content .particle-containers .particle-container .force-container .force-types .force-type, +.gui .content .particle-containers .interaction-container .force-container .force-types .force-type, +.gui .content .interaction-containers .particle-container .force-container .force-types .force-type, +.gui .content .interaction-containers .interaction-container .force-container .force-types .force-type { + width: 100%; + height: 30px; + background: rgba(255, 255, 255, 0.1333333333); + border-radius: 5px; + border: none; + outline: none; + cursor: pointer; + color: #ffffff; + font-size: 12px; + transition: all 0.1s ease-in-out; +} +.gui .content .particle-containers .particle-container .force-container .force-types .force-type:hover, +.gui .content .particle-containers .interaction-container .force-container .force-types .force-type:hover, +.gui .content .interaction-containers .particle-container .force-container .force-types .force-type:hover, +.gui .content .interaction-containers .interaction-container .force-container .force-types .force-type:hover { + background: rgba(255, 255, 255, 0.2); +} +.gui .content .particle-containers .particle-container .force-container .force-types .force-type.selected, +.gui .content .particle-containers .interaction-container .force-container .force-types .force-type.selected, +.gui .content .interaction-containers .particle-container .force-container .force-types .force-type.selected, +.gui .content .interaction-containers .interaction-container .force-container .force-types .force-type.selected { + background: rgba(255, 255, 255, 0.2666666667); +} +.gui .content .particle-containers .particle-container .force-container .force-types .force-type.selected:hover, +.gui .content .particle-containers .interaction-container .force-container .force-types .force-type.selected:hover, +.gui .content .interaction-containers .particle-container .force-container .force-types .force-type.selected:hover, +.gui .content .interaction-containers .interaction-container .force-container .force-types .force-type.selected:hover { + background: rgba(255, 255, 255, 0.3333333333); +} +.gui .content .particle-containers .particle-container .container-head, +.gui .content .particle-containers .interaction-container .container-head, +.gui .content .interaction-containers .particle-container .container-head, +.gui .content .interaction-containers .interaction-container .container-head { + display: flex; + gap: 5px; + align-items: center; +} +.gui .content .particle-containers .particle-container .container-head .color, +.gui .content .particle-containers .interaction-container .container-head .color, +.gui .content .interaction-containers .particle-container .container-head .color, +.gui .content .interaction-containers .interaction-container .container-head .color { + height: 12px; + width: 12px; + border-radius: 50%; +} +.gui .content .particle-containers .particle-container .container-head .sublabel, +.gui .content .particle-containers .interaction-container .container-head .sublabel, +.gui .content .interaction-containers .particle-container .container-head .sublabel, +.gui .content .interaction-containers .interaction-container .container-head .sublabel { + text-transform: uppercase; + font-size: 14px; + font-weight: 500; + color: rgba(255, 255, 255, 0.6666666667); +} +.gui .content .particle-containers .particle-container .container-head .close, +.gui .content .particle-containers .interaction-container .container-head .close, +.gui .content .interaction-containers .particle-container .container-head .close, +.gui .content .interaction-containers .interaction-container .container-head .close { + margin-left: auto; + margin-bottom: auto; + height: 15px; + width: 15px; + border-radius: 50%; + background: rgba(255, 255, 255, 0); + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: rgba(255, 255, 255, 0.5333333333); + opacity: 0; + transition: all 0.2s ease-in-out; +} +.gui .content .particle-containers .particle-container .container-head .close:hover, +.gui .content .particle-containers .interaction-container .container-head .close:hover, +.gui .content .interaction-containers .particle-container .container-head .close:hover, +.gui .content .interaction-containers .interaction-container .container-head .close:hover { + background: rgba(255, 255, 255, 0.1333333333); + color: rgba(255, 255, 255, 0.6666666667); +} +.gui .content .particle-containers .particle-container:hover, +.gui .content .particle-containers .interaction-container:hover, +.gui .content .interaction-containers .particle-container:hover, +.gui .content .interaction-containers .interaction-container:hover { + background: rgba(255, 255, 255, 0.0196078431); +} +.gui .content .particle-containers .particle-container:hover .container-head .close, +.gui .content .particle-containers .interaction-container:hover .container-head .close, +.gui .content .interaction-containers .particle-container:hover .container-head .close, +.gui .content .interaction-containers .interaction-container:hover .container-head .close { + opacity: 1; +} +.gui .content .particle-containers .particle-container:hover .colors .close, +.gui .content .particle-containers .interaction-container:hover .colors .close, +.gui .content .interaction-containers .particle-container:hover .colors .close, +.gui .content .interaction-containers .interaction-container:hover .colors .close { + opacity: 1; +} +.gui .content .particle-containers .particle-container .setting, +.gui .content .particle-containers .interaction-container .setting, +.gui .content .interaction-containers .particle-container .setting, +.gui .content .interaction-containers .interaction-container .setting { text-transform: uppercase; font-size: 12px; - font-weight: 700; + font-weight: 500; color: rgba(255, 255, 255, 0.5333333333); } +.gui .content .particle-containers .particle-container .setting .setting-value, +.gui .content .particle-containers .interaction-container .setting .setting-value, +.gui .content .interaction-containers .particle-container .setting .setting-value, +.gui .content .interaction-containers .interaction-container .setting .setting-value { + background: rgba(255, 255, 255, 0.1333333333); + color: #ffffff; + padding: 2px 6px; + font-weight: 400; + border-radius: 5px; + margin-left: 2px; + border: none; + outline: none; + appearance: textfield; + -moz-appearance: textfield; +} +.gui .content .particle-containers .particle-container .setting .setting-value::-webkit-outer-spin-button, .gui .content .particle-containers .particle-container .setting .setting-value::-webkit-inner-spin-button, +.gui .content .particle-containers .interaction-container .setting .setting-value::-webkit-outer-spin-button, +.gui .content .particle-containers .interaction-container .setting .setting-value::-webkit-inner-spin-button, +.gui .content .interaction-containers .particle-container .setting .setting-value::-webkit-outer-spin-button, +.gui .content .interaction-containers .particle-container .setting .setting-value::-webkit-inner-spin-button, +.gui .content .interaction-containers .interaction-container .setting .setting-value::-webkit-outer-spin-button, +.gui .content .interaction-containers .interaction-container .setting .setting-value::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + padding: 0; +} +.gui .content .particle-containers .particle-container .range, +.gui .content .particle-containers .interaction-container .range, +.gui .content .interaction-containers .particle-container .range, +.gui .content .interaction-containers .interaction-container .range { + display: flex; + align-items: center; + gap: 10px; +} +.gui .content .particle-containers .particle-container .range .value, +.gui .content .particle-containers .interaction-container .range .value, +.gui .content .interaction-containers .particle-container .range .value, +.gui .content .interaction-containers .interaction-container .range .value { + text-align: center; + color: rgba(255, 255, 255, 0.5333333333); + font-size: 12px; +} +.gui .content .particle-containers .particle-container .range .range-content, +.gui .content .particle-containers .interaction-container .range .range-content, +.gui .content .interaction-containers .particle-container .range .range-content, +.gui .content .interaction-containers .interaction-container .range .range-content { + width: 100%; + height: 5px; + background: rgba(255, 255, 255, 0.1333333333); + border-radius: 5px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + outline: none; + cursor: pointer; + border: none; + transition: all 0.2s ease-in-out; +} +.gui .content .particle-containers .particle-container .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container .range .range-content::-webkit-slider-thumb { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 15px; + height: 15px; + border-radius: 50%; + cursor: pointer; + transition: all 0.1s ease-in-out; + background: #6536fe; +} +.gui .content .particle-containers .particle-container .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container .range .range-content::-moz-range-thumb { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 15px; + height: 15px; + border-radius: 50%; + cursor: pointer; + transition: all 0.1s ease-in-out; + background: #6536fe; +} +.gui .content .particle-containers .particle-container .range .range-content::-webkit-slider-thumb:hover, +.gui .content .particle-containers .interaction-container .range .range-content::-webkit-slider-thumb:hover, +.gui .content .interaction-containers .particle-container .range .range-content::-webkit-slider-thumb:hover, +.gui .content .interaction-containers .interaction-container .range .range-content::-webkit-slider-thumb:hover { + transform: scale(1.2); +} +.gui .content .particle-containers .particle-container .range .range-content::-moz-range-thumb:hover, +.gui .content .particle-containers .interaction-container .range .range-content::-moz-range-thumb:hover, +.gui .content .interaction-containers .particle-container .range .range-content::-moz-range-thumb:hover, +.gui .content .interaction-containers .interaction-container .range .range-content::-moz-range-thumb:hover { + transform: scale(1.2); +} +.gui .content .particle-containers .particle-container .range .range-content::-webkit-slider-thumb:active, +.gui .content .particle-containers .interaction-container .range .range-content::-webkit-slider-thumb:active, +.gui .content .interaction-containers .particle-container .range .range-content::-webkit-slider-thumb:active, +.gui .content .interaction-containers .interaction-container .range .range-content::-webkit-slider-thumb:active { + transform: scale(1.2); +} +.gui .content .particle-containers .particle-container .range .range-content::-moz-range-thumb:active, +.gui .content .particle-containers .interaction-container .range .range-content::-moz-range-thumb:active, +.gui .content .interaction-containers .particle-container .range .range-content::-moz-range-thumb:active, +.gui .content .interaction-containers .interaction-container .range .range-content::-moz-range-thumb:active { + transform: scale(1.2); +} +.gui .content .particle-containers .particle-container.red .container-head .color, +.gui .content .particle-containers .interaction-container.red .container-head .color, +.gui .content .interaction-containers .particle-container.red .container-head .color, +.gui .content .interaction-containers .interaction-container.red .container-head .color { + background: #f91d4d; +} +.gui .content .particle-containers .particle-container.red .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container.red .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container.red .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container.red .range .range-content::-webkit-slider-thumb { + background: #f91d4d; +} +.gui .content .particle-containers .particle-container.red .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container.red .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container.red .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container.red .range .range-content::-moz-range-thumb { + background: #f91d4d; +} +.gui .content .particle-containers .particle-container.green .container-head .color, +.gui .content .particle-containers .interaction-container.green .container-head .color, +.gui .content .interaction-containers .particle-container.green .container-head .color, +.gui .content .interaction-containers .interaction-container.green .container-head .color { + background: #0db342; +} +.gui .content .particle-containers .particle-container.green .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container.green .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container.green .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container.green .range .range-content::-webkit-slider-thumb { + background: #0db342; +} +.gui .content .particle-containers .particle-container.green .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container.green .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container.green .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container.green .range .range-content::-moz-range-thumb { + background: #0db342; +} +.gui .content .particle-containers .particle-container.blue .container-head .color, +.gui .content .particle-containers .interaction-container.blue .container-head .color, +.gui .content .interaction-containers .particle-container.blue .container-head .color, +.gui .content .interaction-containers .interaction-container.blue .container-head .color { + background: #4a4ad7; +} +.gui .content .particle-containers .particle-container.blue .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container.blue .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container.blue .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container.blue .range .range-content::-webkit-slider-thumb { + background: #4a4ad7; +} +.gui .content .particle-containers .particle-container.blue .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container.blue .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container.blue .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container.blue .range .range-content::-moz-range-thumb { + background: #4a4ad7; +} +.gui .content .particle-containers .particle-container.yellow .container-head .color, +.gui .content .particle-containers .interaction-container.yellow .container-head .color, +.gui .content .interaction-containers .particle-container.yellow .container-head .color, +.gui .content .interaction-containers .interaction-container.yellow .container-head .color { + background: #f0e246; +} +.gui .content .particle-containers .particle-container.yellow .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container.yellow .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container.yellow .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container.yellow .range .range-content::-webkit-slider-thumb { + background: #f0e246; +} +.gui .content .particle-containers .particle-container.yellow .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container.yellow .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container.yellow .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container.yellow .range .range-content::-moz-range-thumb { + background: #f0e246; +} +.gui .content .particle-containers .particle-container.cyan .container-head .color, +.gui .content .particle-containers .interaction-container.cyan .container-head .color, +.gui .content .interaction-containers .particle-container.cyan .container-head .color, +.gui .content .interaction-containers .interaction-container.cyan .container-head .color { + background: #42cedb; +} +.gui .content .particle-containers .particle-container.cyan .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container.cyan .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container.cyan .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container.cyan .range .range-content::-webkit-slider-thumb { + background: #42cedb; +} +.gui .content .particle-containers .particle-container.cyan .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container.cyan .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container.cyan .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container.cyan .range .range-content::-moz-range-thumb { + background: #42cedb; +} +.gui .content .particle-containers .particle-container.magenta .container-head .color, +.gui .content .particle-containers .interaction-container.magenta .container-head .color, +.gui .content .interaction-containers .particle-container.magenta .container-head .color, +.gui .content .interaction-containers .interaction-container.magenta .container-head .color { + background: #d742d7; +} +.gui .content .particle-containers .particle-container.magenta .range .range-content::-webkit-slider-thumb, +.gui .content .particle-containers .interaction-container.magenta .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .particle-container.magenta .range .range-content::-webkit-slider-thumb, +.gui .content .interaction-containers .interaction-container.magenta .range .range-content::-webkit-slider-thumb { + background: #d742d7; +} +.gui .content .particle-containers .particle-container.magenta .range .range-content::-moz-range-thumb, +.gui .content .particle-containers .interaction-container.magenta .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .particle-container.magenta .range .range-content::-moz-range-thumb, +.gui .content .interaction-containers .interaction-container.magenta .range .range-content::-moz-range-thumb { + background: #d742d7; +} +.gui .content .down-buttons { + display: flex; + gap: 10px; +} +.gui .content .down-buttons .save, +.gui .content .down-buttons .load { + width: 100%; + padding-block: 10px; + background: rgba(255, 255, 255, 0.1333333333); + border-radius: 5px; + border: none; + outline: none; + color: #ffffff; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + transition: all 0.1s ease-in-out; +} +.gui .content .down-buttons .save:hover, +.gui .content .down-buttons .load:hover { + background: rgba(255, 255, 255, 0.2); +} +.gui .content .down-buttons .save { + background: #6536fe; +} +.gui .content .down-buttons .save:hover { + background: #784ffe; +} +.gui:has(.content.minimized) { + opacity: 0.5; +} /*# sourceMappingURL=main.css.map */ diff --git a/style/main.css.map b/style/main.css.map index ed8bc90..f391c75 100644 --- a/style/main.css.map +++ b/style/main.css.map @@ -1 +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 +{"version":3,"sourceRoot":"","sources":["main.scss"],"names":[],"mappings":"AAAA;EACC;EACA;;AAGD;EACC;EACA;EACA;;AAKD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAEA;EACC;;AAIF;EACC;;AAGD;EACC;;AAGD;EACC;EACA;;AAGD;EACC;;AA4BD;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;;AAGD;EACC;EACA;;AAEA;AAAA;AAAA;EA1CD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;EACC;;AA8BF;EACC;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAKH;EACC;;AAGD;AAAA;EAEC;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;;AAwBA;AAAA;AAAA;AAAA;EACC;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;;AAGD;AAAA;AAAA;AAAA;EACC;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAIF;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;;AAGD;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKH;AAAA;AAAA;AAAA;EAvGA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;;AA2FF;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAEA;AAAA;AAAA;AAAA;EACC;;AAOL;AAAA;AAAA;AAAA;EACC;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;EACA;;AAGD;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;;AAGD;AAAA;AAAA;AAAA;EAnKA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;;AAuJF;AAAA;AAAA;AAAA;EACC;;AAEA;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAIF;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAEC;EACA;EACA;;AAKH;AAAA;AAAA;AAAA;EACC;EACA;EACA;;AAEA;AAAA;AAAA;AAAA;EACC;EACA;EACA;;AAGD;AAAA;AAAA;AAAA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAcA;AAAA;AAAA;AAAA;EAXC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YArZQ;;AA4ZT;AAAA;AAAA;AAAA;EAfC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YArZQ;;AAgaT;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAGD;AAAA;AAAA;AAAA;EACC;;AAMF;AAAA;AAAA;AAAA;EACC,YAec;;AAXd;AAAA;AAAA;AAAA;EACC,YAUa;;AAPd;AAAA;AAAA;AAAA;EACC,YAMa;;AAhBf;AAAA;AAAA;AAAA;EACC,YAmBc;;AAfd;AAAA;AAAA;AAAA;EACC,YAca;;AAXd;AAAA;AAAA;AAAA;EACC,YAUa;;AApBf;AAAA;AAAA;AAAA;EACC,YAuBc;;AAnBd;AAAA;AAAA;AAAA;EACC,YAkBa;;AAfd;AAAA;AAAA;AAAA;EACC,YAca;;AAxBf;AAAA;AAAA;AAAA;EACC,YA2Bc;;AAvBd;AAAA;AAAA;AAAA;EACC,YAsBa;;AAnBd;AAAA;AAAA;AAAA;EACC,YAkBa;;AA5Bf;AAAA;AAAA;AAAA;EACC,YA+Bc;;AA3Bd;AAAA;AAAA;AAAA;EACC,YA0Ba;;AAvBd;AAAA;AAAA;AAAA;EACC,YAsBa;;AAhCf;AAAA;AAAA;AAAA;EACC,YAmCc;;AA/Bd;AAAA;AAAA;AAAA;EACC,YA8Ba;;AA3Bd;AAAA;AAAA;AAAA;EACC,YA0Ba;;AAKlB;EACC;EACA;;AAEA;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;AAAA;EACC;;AAIF;EACC,YAvfW;;AAyfX;EACC;;AAMJ;EACC","file":"main.css"} \ No newline at end of file diff --git a/style/main.scss b/style/main.scss index 8e16800..595de8a 100644 --- a/style/main.scss +++ b/style/main.scss @@ -1,11 +1,25 @@ +@font-face { + font-family: "Rubik"; + src: url(../assets/fonts/Rubik.ttf); +} + +@font-face { + font-family: "Rubik"; + font-style: italic; + src: url(../assets/fonts/RubikItalic.ttf); +} + +$accent-color: #6536fe; + * { margin: 0; padding: 0; box-sizing: border-box; + font-family: "Rubik", sans-serif; + user-select: none; } body { - font-family: "Roboto", sans-serif; overflow: hidden; } @@ -13,16 +27,501 @@ body { position: absolute; top: 10px; left: 10px; + padding: 5px; z-index: 100; - background: #ffffff11; + background: #111111; color: #ffffff; - padding: 15px; - border-radius: 5px; + border-radius: 10px; + transition: all 0.4s ease-in-out; - .label { - text-transform: uppercase; - font-size: 12px; - font-weight: 700; - color: #ffffff88; + .content { + padding: 10px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 35px; + min-width: 350px; + max-height: calc(100vh - 30px); + transition: all 0.3s ease-in-out; + + &.minimized { + max-height: 55px; + overflow: hidden; + + .head .head-buttons .minimize img { + transform: rotate(-180deg); + } + } + + &::-webkit-scrollbar { + width: 5px; + } + + &::-webkit-scrollbar-track { + background: #ffffff00; + } + + &::-webkit-scrollbar-thumb { + background: #ffffff22; + border-radius: 5px; + } + + &::-webkit-scrollbar-thumb:hover { + background: #ffffff44; + } + + @mixin button { + background: #ffffff11; + border: none; + border-radius: 5px; + color: #ffffff; + font-size: 12px; + cursor: pointer; + padding: 0; + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease-in-out; + outline: none; + + img { + transition: all 0.2s ease-in-out; + } + + &:hover { + background: #ffffff2a; + } + } + + .head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + + .title { + text-transform: uppercase; + font-size: 20px; + font-weight: 700; + color: #ffffff; + margin-left: 5px; + } + + .head-buttons { + display: flex; + gap: 10px; + + .reset, + .minimize, + .play-pause { + @include button; + } + } + } + + .category-head { + display: flex; + align-items: center; + gap: 5px; + margin-bottom: 5px; + + .label { + text-transform: uppercase; + font-size: 16px; + font-weight: 700; + color: #ffffffcc; + } + + .add { + height: 15px; + width: 15px; + border-radius: 50%; + background: #ffffff00; + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: #ffffff88; + + &:hover { + background: #ffffff22; + color: #ffffffaa; + } + } + } + + .interaction-container .setting { + margin-top: 5px; + } + + .particle-containers, + .interaction-containers { + display: flex; + flex-direction: column; + gap: 10px; + + .particle-container, + .interaction-container { + display: flex; + flex-direction: column; + gap: 5px; + transition: all 0.2s ease-in-out; + background: #ffffff00; + border-radius: 5px; + padding: 10px; + + @mixin close { + margin-left: auto; + margin-bottom: auto; + height: 15px; + width: 15px; + border-radius: 50%; + background: #ffffff00; + border: none; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + color: #ffffff88; + opacity: 0; + transition: all 0.2s ease-in-out; + + &:hover { + background: #ffffff22; + color: #ffffffaa; + } + } + + .colors { + display: flex; + gap: 5px; + align-items: center; + + .setting { + text-transform: uppercase; + font-size: 14px; + font-weight: 500; + color: #ffffffaa; + margin: 0; + } + + .color-selector { + position: relative; + + .color { + height: 12px; + width: 12px; + border-radius: 50%; + cursor: pointer; + border: none; + outline: none; + + &.red { + background: #f91d4d; + } + + &.green { + background: #0db342; + } + + &.blue { + background: #4a4ad7; + } + + &.yellow { + background: #f0e246; + } + + &.cyan { + background: #42cedb; + } + + &.magenta { + background: #d742d7; + } + } + + .color-list { + position: absolute; + top: -5px; + left: 50%; + transform: translate(-25%, -100%) scale(0.5); + opacity: 0; + display: flex; + align-items: center; + gap: 5px; + padding-inline: 5px; + height: 20px; + border-radius: 10px; + background: #262626; + transition: all 0.1s ease-in-out; + + &.show { + opacity: 1; + transform: translate(-15px, -100%) scale(1); + } + + &::after { + content: ""; + position: absolute; + top: 100%; + left: 12px; + transform: translate(-25%, 0); + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #262626; + } + } + } + + .close { + @include close; + } + } + + .force-container { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 10px; + + .force-types { + display: flex; + gap: 10px; + + .force-type { + width: 100%; + height: 30px; + background: #ffffff22; + border-radius: 5px; + border: none; + outline: none; + cursor: pointer; + color: #ffffff; + font-size: 12px; + transition: all 0.1s ease-in-out; + + &:hover { + background: #ffffff33; + } + + &.selected { + background: #ffffff44; + + &:hover { + background: #ffffff55; + } + } + } + } + } + + .container-head { + display: flex; + gap: 5px; + align-items: center; + + .color { + height: 12px; + width: 12px; + border-radius: 50%; + } + + .sublabel { + text-transform: uppercase; + font-size: 14px; + font-weight: 500; + color: #ffffffaa; + } + + .close { + @include close; + } + } + + &:hover { + background: #ffffff05; + + .container-head .close { + opacity: 1; + } + + .colors .close { + opacity: 1; + } + } + + .setting { + text-transform: uppercase; + font-size: 12px; + font-weight: 500; + color: #ffffff88; + + .setting-value { + background: #ffffff22; + color: #ffffff; + padding: 2px 6px; + font-weight: 400; + border-radius: 5px; + margin-left: 2px; + border: none; + outline: none; + appearance: textfield; + -moz-appearance: textfield; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + padding: 0; + } + } + } + + .range { + display: flex; + align-items: center; + gap: 10px; + + .value { + text-align: center; + color: #ffffff88; + font-size: 12px; + } + + .range-content { + width: 100%; + height: 5px; + background: #ffffff22; + border-radius: 5px; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + outline: none; + cursor: pointer; + border: none; + transition: all 0.2s ease-in-out; + + @mixin thumb { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 15px; + height: 15px; + border-radius: 50%; + cursor: pointer; + transition: all 0.1s ease-in-out; + background: $accent-color; + } + + &::-webkit-slider-thumb { + @include thumb; + } + + &::-moz-range-thumb { + @include thumb; + } + + &::-webkit-slider-thumb:hover { + transform: scale(1.2); + } + + &::-moz-range-thumb:hover { + transform: scale(1.2); + } + + &::-webkit-slider-thumb:active { + transform: scale(1.2); + } + + &::-moz-range-thumb:active { + transform: scale(1.2); + } + } + } + + @mixin color($color) { + .container-head .color { + background: $color; + } + + .range .range-content { + &::-webkit-slider-thumb { + background: $color; + } + + &::-moz-range-thumb { + background: $color; + } + } + } + + &.red { + @include color(#f91d4d); + } + + &.green { + @include color(#0db342); + } + + &.blue { + @include color(#4a4ad7); + } + + &.yellow { + @include color(#f0e246); + } + + &.cyan { + @include color(#42cedb); + } + + &.magenta { + @include color(#d742d7); + } + } + } + + .down-buttons { + display: flex; + gap: 10px; + + .save, + .load { + width: 100%; + padding-block: 10px; + background: #ffffff22; + border-radius: 5px; + border: none; + outline: none; + color: #ffffff; + font-size: 12px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + transition: all 0.1s ease-in-out; + + &:hover { + background: #ffffff33; + } + } + + .save { + background: $accent-color; + + &:hover { + background: lighten($accent-color, 5); + } + } + } + } + + &:has(.content.minimized) { + opacity: 0.5; } }