diff --git a/index.html b/index.html
index 74a47cb..ca24a58 100644
--- a/index.html
+++ b/index.html
@@ -4,9 +4,25 @@
-
-
+
+
+ Total number of neurons:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
diff --git a/js/Layer.js b/js/Layer.js
new file mode 100644
index 0000000..7c7098a
--- /dev/null
+++ b/js/Layer.js
@@ -0,0 +1,200 @@
+class Layer extends Draggable {
+ constructor(neuronNum, x, y, label, cnv, act_func = ActivationFunction.TANH) {
+ super("layer");
+ this.canvas = cnv;
+ this.act_func = act_func;
+ this.nextLayer = null;
+ this.prevLayer = null;
+ this.label = label;
+ this.x = x;
+ this.w = 50;
+ this.yGap = 40;
+ this.h = this.yGap * (neuronNum - 1) + 50;
+ this.neurons = Array.from(
+ { length: neuronNum },
+ () => new Neuron(x, y, false, cnv),
+ );
+ this.shrinked = false;
+ this.shownNeuronNum = this.getNeuronNum();
+
+ let middleYPoint = 350;
+ this.y = y + middleYPoint - this.h / 2;
+
+ this.updateNeuronsCoordinates();
+ // this.neurons.length > 4 && this.shrink();
+ }
+
+ addNeuron(neuron) {
+ this.neurons.push(neuron);
+ }
+
+ removeNeuron() {
+ this.neurons.pop();
+ }
+
+ getNeuronNum() {
+ return this.neurons.length;
+ }
+
+ getShownNeuronNum() {
+ return this.shownNeuronNum;
+ }
+
+ setShownNeuronNum(shownNeuronNum) {
+ this.shownNeuronNum = shownNeuronNum;
+ }
+
+ shrink() {
+ const { neurons, yGap } = this;
+
+ if (
+ this.getShownNeuronNum() === this.getNeuronNum() ||
+ this.getNeuronNum() < 4
+ ) {
+ this.expand();
+ return;
+ }
+
+ const mid = this.getShownNeuronNum() / 2;
+ for (let i = 0; i < this.getNeuronNum(); i++) {
+ if (!(i < mid || i >= this.getNeuronNum() - mid)) {
+ neurons[i].hide();
+ }
+ }
+
+ this.h = yGap * (this.getShownNeuronNum() - 1) + 50;
+ this.shrinked = true;
+ this.updateNeuronsCoordinates();
+ }
+
+ expand() {
+ this.neurons.forEach((neuron) => neuron.visible());
+ this.shrinked = false;
+
+ this.updateNeuronsCoordinates();
+ }
+
+ isShrinked() {
+ return this.shrinked;
+ }
+
+ resetP5Settings() {
+ const commands = [
+ { func: "textSize", args: [12] },
+ { func: "textAlign", args: [LEFT, BASELINE] },
+ { func: "textLeading", args: [15] },
+ { func: "fill", args: [255] },
+ ];
+
+ executeDrawingCommands(this.canvas, commands);
+ }
+
+ showInfoBox() {
+ const commands = [
+ { func: "fill", args: [0] },
+ { func: "textSize", args: [18] },
+ { func: "textAlign", args: [CENTER, TOP] },
+ { func: "textLeading", args: [7] },
+ { func: "text", args: [`.\n.\n.`, this.x, this.infoBoxY + 10, 50, 100] },
+ { func: "textAlign", args: [CENTER, CENTER] },
+ {
+ func: "text",
+ args: [
+ this.getNeuronNum() - this.getShownNeuronNum(),
+ this.x,
+ this.infoBoxY + 15,
+ 50,
+ 100,
+ ],
+ },
+ { func: "textAlign", args: [CENTER, BOTTOM] },
+ { func: "textLeading", args: [7] },
+ { func: "text", args: [`.\n.\n.`, this.x, this.infoBoxY, 50, 110] },
+ ];
+
+ executeDrawingCommands(this.canvas, commands);
+ this.resetP5Settings();
+ }
+
+ updateNeuronsCoordinates() {
+ const isShrinked = this.isShrinked();
+ const neuronNum = isShrinked
+ ? this.getShownNeuronNum()
+ : this.getNeuronNum();
+ const infoBoxH = 90;
+
+ this.h = this.yGap * (neuronNum - 1) + 50;
+
+ let index = 0;
+ this.neurons.forEach((neuron) => {
+ if (!neuron.isHidden()) {
+ const externalHeight =
+ index > neuronNum / 2 && isShrinked ? infoBoxH : 0;
+ const x = this.x + this.w / 2;
+ const y =
+ this.y +
+ this.h / 2 +
+ externalHeight +
+ this.yGap * (index - (neuronNum - 1) / 2);
+
+ if (index == Math.floor(neuronNum / 2)) {
+ this.infoBoxY = y;
+ }
+ neuron.updateCoordinates(x, y);
+ index++;
+ }
+ });
+
+ this.h += isShrinked ? infoBoxH : 0;
+ }
+
+ call(x) {
+ let outs = this.neurons.map((neuron) => neuron.call(x));
+ return outs.length === 1 ? outs[0] : outs;
+ }
+
+ parameters() {
+ return this.neurons.flatMap((neuron) => neuron.parameters());
+ }
+
+ change_act_func(act_func) {
+ this.act_func = act_func;
+ this.neurons.forEach((neuron) => neuron.change_act_func(this.act_func));
+ }
+
+ setNextLayer(layer) {
+ this.nextLayer = layer;
+
+ this.neurons.forEach((neuron) => {
+ let lines = [];
+ this.nextLayer.neurons.forEach((toNeuron) => {
+ lines.push(new Line(neuron, toNeuron));
+ });
+ neuron.setLines(lines);
+ });
+ }
+
+ setPrevLayer(layer) {
+ this.prevLayer = layer;
+ }
+
+ show() {
+ let commands = [
+ { func: "noFill", args: [] },
+ { func: "rect", args: [this.x, this.y, 50, this.h] },
+ { func: "fill", args: [0] },
+ { func: "text", args: [this.label, this.x, this.y - 10] },
+ { func: "fill", args: [255] },
+ ];
+ executeDrawingCommands(this.canvas, commands);
+ this.isShrinked() && this.showInfoBox();
+ }
+
+ draw() {
+ this.show();
+ this.neurons.forEach((neuron) => neuron.draw());
+
+ !organizer.getDragActive() && this.over();
+ (organizer.getDragActive() || this.dragging) && this.updateCoordinates();
+ }
+}
diff --git a/js/Line.js b/js/Line.js
new file mode 100644
index 0000000..813ab48
--- /dev/null
+++ b/js/Line.js
@@ -0,0 +1,12 @@
+class Line {
+ constructor(from, to) {
+ this.from = from;
+ this.to = to;
+ this.w = new Value(Math.random() * 2 - 1);
+ }
+
+ draw() {
+ !(this.from.isHidden() || this.to.isHidden()) &&
+ line(this.from.x, this.from.y, this.to.x, this.to.y);
+ }
+}
diff --git a/js/MLP.js b/js/MLP.js
index 0ed809f..5bec895 100644
--- a/js/MLP.js
+++ b/js/MLP.js
@@ -1,257 +1,3 @@
-class Neuron {
- constructor(x, y, hidden, cnv) {
- this.w = [];
- // for (let i = 0; i < nin; i++) {
- // this.w.push(new Value(Math.random() * 2 - 1));
- // }
- this.canvas = cnv;
- this.b = new Value(Math.random() * 2 - 1);
- this.act_func = ActivationFunction.TANH;
- this.output = null;
- this.lines = [];
- this.x = x;
- this.y = y;
- this.hidden = hidden;
- }
-
- hide() {
- this.hidden = true;
- }
-
- visible() {
- this.hidden = false;
- }
-
- call(x) {
- let act = this.b;
- for (let i = 0; i < this.w.length; i++) {
- act = act.add(this.w[i].mul(x[i]));
- }
-
- this.output = activation_functions[this.act_func](act);
- return this.output;
- }
-
- parameters() {
- return [...this.w, this.b];
- }
-
- change_act_func(act_func) {
- this.act_func = act_func;
- }
-
- setLines(lines) {
- this.lines = lines;
- }
-
- updateCoordinates(x, y) {
- this.x = x;
- this.y = y;
- }
-
- show() {
- const commands = [
- { func: "circle", args: [this.x, this.y, 25, 25] },
- { func: "fill", args: [0] },
- {
- func: "text",
- args: [this.output?.data.toFixed(2), this.x + 30, this.y],
- },
- {
- func: "text",
- args: [this.output?.grad.toFixed(2), this.x + 30, this.y + 25],
- },
- { func: "fill", args: [255] },
- ];
- executeDrawingCommands(this.canvas, commands);
- }
-
- draw() {
- if (this.hidden) return;
- this.lines.forEach((line) => line.draw());
- this.show();
- }
-}
-
-class Line {
- constructor(from, to) {
- this.from = from;
- this.to = to;
- this.w = new Value(Math.random() * 2 - 1);
- }
-
- draw() {
- !(this.from.hidden || this.to.hidden) &&
- line(this.from.x, this.from.y, this.to.x, this.to.y);
- }
-}
-
-class Layer extends Draggable {
- constructor(neuronNum, x, y, label, cnv, act_func = ActivationFunction.TANH) {
- super("layer");
- this.canvas = cnv;
- this.act_func = act_func;
- this.nextLayer = null;
- this.prevLayer = null;
- this.label = label;
- this.x = x;
- this.w = 50;
- this.yGap = 40;
- this.h = this.yGap * (neuronNum - 1) + 50;
- this.neurons = Array.from(
- { length: neuronNum },
- () => new Neuron(x, y, false, cnv),
- );
- this.shrinked = false;
- this.shownNeuronsNum = 3;
-
- let middleYPoint = 350;
- this.y = y + middleYPoint - this.h / 2;
-
- this.updateNeuronsCoordinates();
- this.neurons.length > 4 && this.shrink();
- }
-
- shrink() {
- let mid = this.shownNeuronsNum / 2;
- for (let i = 0; i < this.neurons.length; i++) {
- if (!(i < mid || i >= this.neurons.length - mid)) {
- this.neurons[i].hide();
- }
- }
-
- this.h = this.yGap * (this.shownNeuronsNum - 1) + 50;
-
- this.shrinked = true;
- this.updateNeuronsCoordinates();
- }
-
- expand() {
- this.neurons.forEach((neuron) => neuron.visible());
- this.shrinked = false;
-
- this.updateNeuronsCoordinates();
- }
-
- resetP5Settings() {
- const commands = [
- { func: "textSize", args: [12] },
- { func: "textAlign", args: [LEFT, BASELINE] },
- { func: "textLeading", args: [15] },
- { func: "fill", args: [255] },
- ];
-
- executeDrawingCommands(this.canvas, commands);
- }
-
- showInfoBox() {
- const commands = [
- { func: "fill", args: [0] },
- { func: "textSize", args: [18] },
- { func: "textAlign", args: [CENTER, TOP] },
- { func: "textLeading", args: [7] },
- { func: "text", args: [`.\n.\n.`, this.x, this.infoBoxY + 10, 50, 100] },
- { func: "textAlign", args: [CENTER, CENTER] },
- {
- func: "text",
- args: [
- this.neurons.length - this.shownNeuronsNum,
- this.x,
- this.infoBoxY + 15,
- 50,
- 100,
- ],
- },
- { func: "textAlign", args: [CENTER, BOTTOM] },
- { func: "textLeading", args: [7] },
- { func: "text", args: [`.\n.\n.`, this.x, this.infoBoxY, 50, 110] },
- ];
-
- executeDrawingCommands(this.canvas, commands);
- this.resetP5Settings();
- }
-
- updateNeuronsCoordinates() {
- const neuronNum = this.shrinked
- ? this.shownNeuronsNum
- : this.neurons.length;
- const infoBoxH = 90;
-
- this.h = this.yGap * (neuronNum - 1) + 50;
-
- let index = 0;
- this.neurons.forEach((neuron) => {
- if (!neuron.hidden) {
- const externalHeight =
- index >= neuronNum / 2 && this.shrinked ? infoBoxH : 0;
- const x = this.x + this.w / 2;
- const y =
- this.y +
- this.h / 2 +
- externalHeight +
- this.yGap * (index - (neuronNum - 1) / 2);
-
- if (index == Math.floor(neuronNum / 2)) {
- this.infoBoxY = y;
- }
- neuron.updateCoordinates(x, y);
- index++;
- }
- });
-
- this.h += this.shrinked ? infoBoxH : 0;
- }
-
- call(x) {
- let outs = this.neurons.map((neuron) => neuron.call(x));
- return outs.length === 1 ? outs[0] : outs;
- }
-
- parameters() {
- return this.neurons.flatMap((neuron) => neuron.parameters());
- }
-
- change_act_func(act_func) {
- this.act_func = act_func;
- this.neurons.forEach((neuron) => neuron.change_act_func(this.act_func));
- }
-
- setNextLayer(layer) {
- this.nextLayer = layer;
-
- this.neurons.forEach((neuron) => {
- let lines = [];
- this.nextLayer.neurons.forEach((toNeuron) => {
- lines.push(new Line(neuron, toNeuron));
- });
- neuron.setLines(lines);
- });
- }
-
- setPrevLayer(layer) {
- this.prevLayer = layer;
- }
-
- show() {
- let commands = [
- { func: "rect", args: [this.x, this.y, 50, this.h] },
- { func: "fill", args: [0] },
- { func: "text", args: [this.label, this.x, this.y - 10] },
- { func: "fill", args: [255] },
- ];
- executeDrawingCommands(this.canvas, commands);
- this.shrinked && this.showInfoBox();
- }
-
- draw() {
- this.show();
- this.neurons.forEach((neuron) => neuron.draw());
-
- !organizer.getDragActive() && this.over();
- (organizer.getDragActive() || this.dragging) && this.updateCoordinates();
- }
-}
-
class MLP extends Draggable {
constructor(nin, nouts, x, y, cnv) {
super("mlp");
diff --git a/js/Neuron.js b/js/Neuron.js
new file mode 100644
index 0000000..96c9aee
--- /dev/null
+++ b/js/Neuron.js
@@ -0,0 +1,78 @@
+class Neuron {
+ constructor(x, y, hidden, cnv) {
+ this.w = [];
+ // for (let i = 0; i < nin; i++) {
+ // this.w.push(new Value(Math.random() * 2 - 1));
+ // }
+ this.canvas = cnv;
+ this.b = new Value(Math.random() * 2 - 1);
+ this.act_func = ActivationFunction.TANH;
+ this.output = null;
+ this.lines = [];
+ this.x = x;
+ this.y = y;
+ this.hidden = hidden;
+ }
+
+ hide() {
+ this.hidden = true;
+ }
+
+ visible() {
+ this.hidden = false;
+ }
+
+ isHidden() {
+ return this.hidden;
+ }
+
+ call(x) {
+ let act = this.b;
+ for (let i = 0; i < this.w.length; i++) {
+ act = act.add(this.w[i].mul(x[i]));
+ }
+
+ this.output = activation_functions[this.act_func](act);
+ return this.output;
+ }
+
+ parameters() {
+ return [...this.w, this.b];
+ }
+
+ change_act_func(act_func) {
+ this.act_func = act_func;
+ }
+
+ setLines(lines) {
+ this.lines = lines;
+ }
+
+ updateCoordinates(x, y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ show() {
+ const commands = [
+ { func: "circle", args: [this.x, this.y, 25, 25] },
+ { func: "fill", args: [0] },
+ {
+ func: "text",
+ args: [this.output?.data.toFixed(2), this.x + 30, this.y],
+ },
+ {
+ func: "text",
+ args: [this.output?.grad.toFixed(2), this.x + 30, this.y + 25],
+ },
+ { func: "fill", args: [255] },
+ ];
+ executeDrawingCommands(this.canvas, commands);
+ }
+
+ draw() {
+ if (this.isHidden()) return;
+ this.lines.forEach((line) => line.draw());
+ this.show();
+ }
+}
diff --git a/js/canvas.js b/js/canvas.js
index f53cb3f..06152da 100644
--- a/js/canvas.js
+++ b/js/canvas.js
@@ -3,18 +3,14 @@ var organizer;
var editOrganizer;
function setup() {
- const mainCanvas = createCanvas(1920, 1080);
- const editCanvas = createGraphics(1920, 1080);
+ const mainCanvas = createCanvas(windowWidth, windowHeight);
+ const editCanvas = createGraphics(windowWidth, windowHeight);
organizer = new Organizer();
editOrganizer = new EditOrganizer(editCanvas);
+ mlps.push(new MLP(4, [3, 15, 1], 600, 100, mainCanvas));
mlps.push(new MLP(4, [3, 5, 1], 600, 100, mainCanvas));
- mlps.push(new MLP(4, [3, 5, 1], 600, 100, mainCanvas));
-}
-
-function setupPopup() {
- organizer.disable();
}
function draw() {
@@ -38,6 +34,11 @@ function doubleClicked() {
mlps.forEach((mlp) => mlp.handleDoubleClicked());
}
+function windowResized() {
+ resizeCanvas(windowWidth, windowHeight);
+ editOrganizer.resize();
+}
+
// mlps.push(new MLP(3, [3, 2, 1], 500, 300));
//
// xs = [
@@ -65,15 +66,3 @@ function doubleClicked() {
// clearInterval(intervalId);
// }
// }, 500);
-function executeDrawingCommands(cnv, arr) {
- const parent = cnv instanceof p5.Graphics ? cnv : window;
-
- for (let i = 0; i < arr.length; i++) {
- let { func, args } = arr[i];
- if (typeof parent[func] === "function") {
- parent[func](...args);
- } else {
- console.error(`Function '${func}' does not exist on canvas`);
- }
- }
-}
diff --git a/js/draggable.js b/js/draggable.js
index 498fe7c..4e6d9ea 100644
--- a/js/draggable.js
+++ b/js/draggable.js
@@ -31,8 +31,9 @@ class Draggable {
doubleClicked() {
if (this.rollover && !editOrganizer.getSelected()) {
- editOrganizer.enable();
editOrganizer.setSelected(this);
+
+ editOrganizer.enable();
}
}
diff --git a/js/drawable.js b/js/drawable.js
deleted file mode 100644
index 6072806..0000000
--- a/js/drawable.js
+++ /dev/null
@@ -1,5 +0,0 @@
-class Showable {
- constructor() {
- this.canvas;
- }
-}
diff --git a/js/editOrganizer.js b/js/editOrganizer.js
index 863ada2..3f05012 100644
--- a/js/editOrganizer.js
+++ b/js/editOrganizer.js
@@ -4,11 +4,30 @@ class EditOrganizer {
this.enabled = false;
this.selected = null;
this.selectedCopy = null;
- this.originX = (width - 500) / 2;
- this.originY = 150;
+ this.originX;
+ this.originY;
this.w = 500;
this.h = 500;
- this.setup();
+ this.editPanel = getElementById("edit-panel");
+ this.resize();
+ this.setShownNeuronContainer = getElementById("shown-neuron-container");
+ this.buttonsContainer = getElementById("buttons-container");
+ this.neuronNumContainer = getElementById("neuron-container");
+ }
+
+ getCanvas() {
+ return this.canvas;
+ }
+
+ setLayout() {
+ this.setShownNeuronContainer.style.left = this.originX + "px";
+ this.setShownNeuronContainer.style.top = this.originY + "px";
+
+ this.buttonsContainer.style.left = this.originX + "px";
+ this.buttonsContainer.style.top = this.originY + 100 + "px";
+
+ this.neuronNumContainer.style.left = this.originX + "px";
+ this.neuronNumContainer.style.top = this.originY + this.h - 50 + "px";
}
setup() {
@@ -17,25 +36,66 @@ class EditOrganizer {
button.mousePressed(() => {
this.disable();
});
- let button1 = createButton("Add neuron");
- button1.position(100, 100);
- button1.mousePressed(() => {
- this.selectedCopy.neurons.push(
- new Neuron(5, 0, this.selectedCopy.shrinked, this.canvas),
- );
- this.selectedCopy.updateNeuronsCoordinates();
- });
+ const layer = this.selectedCopy;
+ this.setLayout();
+
+ const properties = {
+ value: layer.getShownNeuronNum(),
+ max: layer.getNeuronNum().toString(),
+ min: "3",
+ };
- let button2 = createButton("shrink");
- button2.position(200, 200);
- button2.mousePressed(() => {
- this.selectedCopy.shrink();
+ setElementProperties("set-shown-neuron", properties);
+
+ addEventToElement("set-shown-neuron", "input", (e) => {
+ const val = e.target.value;
+
+ layer.setShownNeuronNum(val);
+ setElementProperties("shown-neuron-label", { innerText: val });
+
+ layer.expand();
+ layer.shrink();
});
- let button3 = createButton("expand");
- button3.position(200, 300);
- button3.mousePressed(() => {
- this.selectedCopy.expand();
+
+ addEventToElement("shrink-btn", "click", () => layer.shrink());
+ addEventToElement("expand-btn", "click", () => layer.expand());
+
+ setElementProperties("set-neuron-num", { value: layer.getNeuronNum() });
+
+ addEventToElement("set-neuron-num", "change", (e) => {
+ const diff = e.target.value - layer.getNeuronNum();
+
+ if (diff > 0) {
+ for (let i = 0; i < diff; i++) {
+ layer.addNeuron(
+ new Neuron(5, 0, this.selectedCopy.isShrinked(), this.canvas),
+ );
+ }
+ } else {
+ for (let i = 0; i < -diff; i++) {
+ layer.removeNeuron();
+ }
+ }
+
+ layer.setShownNeuronNum(
+ Math.min(layer.getNeuronNum(), layer.getShownNeuronNum()),
+ );
+
+ layer.updateNeuronsCoordinates();
+ const newProperties = {
+ max: layer.getNeuronNum().toString(),
+ min: "3",
+ value: layer.getShownNeuronNum(),
+ };
+
+ setElementProperties("set-shown-neuron", newProperties);
+ setElementProperties("shown-neuron-label", {
+ innerText: newProperties.value,
+ });
+
+ layer.expand();
+ layer.shrink();
});
}
@@ -63,7 +123,7 @@ class EditOrganizer {
this.selected = layer;
this.selectedCopy = new Layer(
- layer.neurons.length,
+ layer.getNeuronNum(),
x,
y - 100,
layer.label,
@@ -76,13 +136,24 @@ class EditOrganizer {
this.enabled = false;
this.selected = null;
this.selectedCopy = null;
+ this.editPanel.style.display = "none";
}
enable() {
this.enabled = true;
+ this.editPanel.style.display = "block";
+ this.setup();
}
isEnabled() {
return this.enabled;
}
+
+ resize() {
+ this.getCanvas().resizeCanvas(windowWidth, windowHeight);
+
+ this.originX = (width - 500) / 2;
+
+ this.originY = 150;
+ }
}
diff --git a/js/script.js b/js/script.js
new file mode 100644
index 0000000..dc1b8cd
--- /dev/null
+++ b/js/script.js
@@ -0,0 +1,27 @@
+function executeDrawingCommands(cnv, arr) {
+ const parent = cnv instanceof p5.Graphics ? cnv : window;
+
+ for (let i = 0; i < arr.length; i++) {
+ let { func, args } = arr[i];
+ if (typeof parent[func] === "function") {
+ parent[func](...args);
+ } else {
+ console.error(`Function '${func}' does not exist on canvas`);
+ }
+ }
+}
+
+function getElementById(el) {
+ return document.getElementById(el);
+}
+
+function setElementProperties(elId, properties) {
+ const el = getElementById(elId);
+ for (let prop in properties) {
+ el[prop] = properties[prop];
+ }
+}
+
+function addEventToElement(elId, eventName, func) {
+ getElementById(elId).addEventListener(eventName, func);
+}
diff --git a/js/showable.js b/js/showable.js
deleted file mode 100644
index d325f0a..0000000
--- a/js/showable.js
+++ /dev/null
@@ -1,10 +0,0 @@
-let Showable = (Base) =>
- class extends Base {
- constructor(label, canvas) {
- super(label);
- this.canvas = canvas;
- }
- show() {
- throw new Error("Method 'show' must be implemented.");
- }
- };
diff --git a/style.css b/style.css
index 6dd492f..51d1f6a 100644
--- a/style.css
+++ b/style.css
@@ -3,13 +3,29 @@ body {
padding: 0;
}
-#disabled-background {
- position: fixed;
- width: 100%;
- height: 100%;
- z-index: 1;
- background: rgba(0, 0, 0, 0.2);
+#edit-panel {
display: none;
+}
+
+.edit-container {
+ position: absolute;
+ display: flex;
justify-content: center;
align-items: center;
+
+}
+
+.inp {
+ background: white;
+ width: 50px;
+ height: 30px;
+ border: none;
+ border-bottom: 1px lightgray solid;
+ border-radius: 5px;
+ text-align: center;
+ font-size: 18px;
+ transition: background-color 0.3s;
+ cursor: pointer;
+ padding: 0;
+ margin: 5px
}