Compare commits

...

7 Commits

  1. 2
      project.json
  2. 1
      public/data/images/add.svg
  3. 1
      public/data/images/compress.svg
  4. 1
      public/data/images/delete.svg
  5. BIN
      public/data/images/favicon.ico
  6. 1
      public/data/images/pause.svg
  7. 1
      public/data/images/play.svg
  8. 1
      public/data/images/refresh.svg
  9. 34
      public/data/scripts/ts/init.ts
  10. 158
      public/data/scripts/ts/manager.ts
  11. 143
      public/data/scripts/ts/pendulum.ts
  12. 38
      public/data/scripts/ts/vector.ts
  13. 7
      public/data/settings/libraries.json
  14. 23
      public/data/settings/settings.json
  15. 88
      public/data/styles/color_picker.css
  16. 9
      public/data/styles/range_input.css
  17. 108
      public/index.html
  18. 375
      public/package-lock.json
  19. 3
      public/package.json
  20. 122
      public/styles.css
  21. BIN
      public/thumbnail.png

@ -1,6 +1,6 @@
{
"display_name": "Pendulum",
"info_text": "Watch 500 Double-Pendulums diverge into chaos",
"info_text": "The laboratory for n-Pendula simulations, using 'Position Based Dynamics'.",
"visible": true,
"tags": ["Simulation"]
}

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M450.001-450.001h-230v-59.998h230v-230h59.998v230h230v59.998h-230v230h-59.998v-230Z"/></svg>

After

Width:  |  Height:  |  Size: 189 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M180.001-424.617v-59.999h599.998v59.999H180.001Zm0-115.384V-600h599.998v59.999H180.001Zm270 440v-146.463l-74.001 74-42.153-42.152L480-360.769l146.153 146.153L584-172.464l-74.001-72.77v145.233h-59.998ZM480-663.848 333.847-810.001 376-852.154l74.001 74.001v-146.463h59.998v146.463L584-852.154l42.153 42.153L480-663.848Z"/></svg>

After

Width:  |  Height:  |  Size: 423 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m376-327.692 104-104 104 104L612.308-356l-104-104 104-104L584-592.308l-104 104-104-104L347.692-564l104 104-104 104L376-327.692ZM304.615-160Q277-160 258.5-178.5 240-197 240-224.615V-720h-40v-40h160v-30.77h240V-760h160v40h-40v495.385Q720-197 701.5-178.5 683-160 655.385-160h-350.77ZM680-720H280v495.385q0 9.23 7.692 16.923Q295.385-200 304.615-200h350.77q9.23 0 16.923-7.692Q680-215.385 680-224.615V-720Zm-400 0v520-520Z"/></svg>

After

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M531.616-226.463v-507.074h201.921v507.074H531.616Zm-305.153 0v-507.074h201.921v507.074H226.463Zm361.113-55.96h90.001v-395.154h-90.001v395.154Zm-305.153 0h90.001v-395.154h-90.001v395.154Zm0-395.154v395.154-395.154Zm305.153 0v395.154-395.154Z"/></svg>

After

Width:  |  Height:  |  Size: 346 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M345.655-245.041v-469.918L714.19-480 345.655-245.041ZM401.615-480Zm0 132.385L609.962-480 401.615-612.385v264.77Z"/></svg>

After

Width:  |  Height:  |  Size: 218 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M482.013-187.271q-123.018 0-208.265-85.065-85.247-85.066-85.247-207.634 0-122.568 85.247-207.664 85.247-85.095 208.291-85.095 69.654 0 131.461 31.038 61.808 31.039 102.231 88.193v-119.231h55.96v235.728H535.963v-55.96h153.96q-31.34-57.649-86.798-90.729-55.459-33.079-121.047-33.079-98.924 0-168.27 69.156t-69.346 167.731q0 98.574 69.221 167.613 69.221 69.038 168.29 69.038 75.906 0 137.024-43.192 61.119-43.192 85.883-114.385h59.119q-26.423 95.307-104.688 154.422-78.266 59.115-177.298 59.115Z"/></svg>

After

Width:  |  Height:  |  Size: 598 B

@ -1,23 +1,15 @@
'use strict';
let debug = false,
font: any,
settings: any;
let socket: any;
font: any;
let antiCacheQuery = '?_=' + new Date().getTime();
let manager;
let manager: Manager;
const p = new p5((p: p5) => {
p.preload = () => {
settings = p.loadJSON('data/settings/settings.json' + antiCacheQuery, {}, 'json', (json: any) => {
console.log('Local settings loaded: ', json);
}, (error: any) => {
console.log('Local settings failed: ', error);
});
font = p.loadFont('data/styles/fonts/Tajawal/Tajawal-Regular.ttf' + antiCacheQuery, (font: any) => {
console.log('Local font loaded: ', font);
@ -27,13 +19,11 @@ const p = new p5((p: p5) => {
}
p.setup = () => {
interfaceSetup();
canvasSetup();
eventsSetup();
loadDynamicScripts().then(() => {
//Load other stuff
manager = new Manager();
});
manager.init();
interfaceSetup();
}
p.draw = () => {
@ -52,7 +42,6 @@ function debugInformation(){
}
function interfaceSetup(){
}
@ -66,18 +55,3 @@ function canvasSetup(){
canvas.parent('canvas_holder');
p.textFont(font);
}
async function loadDynamicScripts(){
const json = await p.httpGet('data/settings/libraries.json' + antiCacheQuery, 'json') as Object;
let requests = [];
for (let script in json) {
if (json[script]) {
let url = '/lib/benjocraeft/' + script + '.js';
requests.push($.getScript(url, () => {
console.log('Successfully loaded script: ', url);
}));
}
}
await $.when(...requests);
console.log('All dynamic scripts have been loaded!');
}

@ -1,31 +1,163 @@
class Manager {
nPendula: NPendulum[] = []
pendula: Pendulum[] = []
h = 0.07
timescale: number;
gravity: number;
constructor() {
playing = false;
static SubSteps: number;
static Size = 20;
init(){
// @ts-ignore
const {createApp} = Vue;
createApp({
data() {
return {
gravity: 0,
timescale: 0,
subSteps: 0,
playingBtn: "play"
};
},
watch: {
gravity(newGravity: number) {
manager.gravity = newGravity;
},
timescale(newScale: number){
manager.timescale = newScale;
},
subSteps(newSteps: number){
Manager.SubSteps = newSteps;
}
},
methods: {
togglePlay(){
manager.playing = !manager.playing;
this.playingBtn = manager.playing ? "pause" : "play";
},
resetSimulationControls(){
this.gravity = 9.81;
this.timescale = 1;
this.subSteps = 30;
}
},
mounted() {
this.resetSimulationControls();
},
name: "Simulation"
}).mount("#simulation");
let app = createApp({
data(){
return {
segmentCount: 1,
maxSegmentCount: 30,
masses: [],
lengths: [],
startAngle: 90,
color: "#ffffff",
rainbow: false,
multiple: false,
pendulumCount: 10,
changeProperty: "angle",
changeAmount: 0.0005,
changeIndex: 0
}
},
methods: {
add() {
if (this.multiple){
let changeAmount = this.changeAmount;
let changeIndex = this.changeIndex;
let color = p.color(this.color);
p.colorMode(p.HSB, 100);
let count = 500;
for (let i = 0; i < count; i++){
let rad = i / count / 1e3 + p.PI * 1.05;
let hue = i / count * 100;
let color = p.color(hue, 100, 100);
this.nPendula.push(
new NPendulum([200, 200], [2, 2], rad, color)
);
for (let i = 0; i < this.pendulumCount; i++){
let M = this.masses.slice(0, this.segmentCount);
let L = this.lengths.slice(0, this.segmentCount);
let startAngle = this.startAngle;
let progress = i / this.pendulumCount - 0.5;
switch (this.changeProperty){
case "angle":
startAngle += progress * 360 * changeAmount;
break;
case "mass":
M[changeIndex] += progress * M[changeIndex] * changeAmount;
break;
case "length":
L[changeIndex] += progress * L[changeIndex] * changeAmount;
break;
}
if (this.rainbow){
let hue = (progress + 0.5) * 100;
color = p.color(hue, 100, 100);
}
let newPendulum = new Pendulum(M, L, color, startAngle);
manager.pendula.push(newPendulum);
}
p.colorMode(p.RGB);
} else {
let M = this.masses.slice(0, this.segmentCount);
let L = this.lengths.slice(0, this.segmentCount);
let color = p.color(this.color);
let newPendulum = new Pendulum(M, L, color, this.startAngle);
manager.pendula.push(newPendulum);
}
},
deleteAll(){
if (confirm("Delete all pendula?")){
manager.pendula.splice(0);
}
},
resetMasses(){
for (let i = 0; i < this.maxSegmentCount; i++)
this.masses[i] = 1;
},
resetLengths() {
for (let i = 0; i < this.maxSegmentCount; i++)
this.lengths[i] = 1;
},
normalize(){
let L: [number] = this.lengths;
let sum = L.slice(0, this.segmentCount).reduce((p, n) => p + n);
let maxLength = Manager.Size / 2;
let factor = maxLength / sum;
for (let i = 0; i < this.segmentCount; i++)
this.lengths[i] *= factor;
}
},
mounted() {
this.resetMasses();
this.resetLengths();
},
name: "Add Pendula"
});
app.config.globalProperties.$filters = {
round(value: number, n: number){
if (!value)
return "0";
return +value.toFixed(n);
}
}
app.mount("#preparation");
}
update(){
this.nPendula.forEach(p => p.update(this.h));
if (this.playing) {
const h = this.timescale / Math.max(p.frameRate(), 30);
this.pendula.forEach(p => p.update(h));
}
}
draw(){
p.push()
p.translate(p.width / 2, p.height / 2);
this.nPendula.forEach(p => p.draw());
this.pendula.forEach(p => p.draw());
p.pop();
}

@ -1,100 +1,99 @@
const g = 9.81
class NPendulum {
pendula: Pendulum[] = []
constructor(lengths, masses, startRad, color) {
switch (lengths.length) {
case 1:
this.pendula.push(new Pendulum(lengths[0], masses[0], startRad, color));
break;
case 2:
let p1 = new Pendulum(lengths[0], masses[0], startRad, color);
let p2 = new Pendulum(lengths[1], masses[1], startRad, color);
p1.calcAcc = function(pendula){
let p2 = pendula[1];
return -g / this.l * p.sin(this.rad) - p2.l * p2.m / this.l / (this.m + p2.m)
* (p.cos(this.rad - p2.rad) * p2.acc + p.sin(this.rad - p2.rad) * p.pow(p2.vel, 2));
}
p2.calcAcc = function (pendula){
let p1 = pendula[0];
return -g / this.l * p.sin(this.rad) - p1.l / this.l
* (p.cos(p1.rad - this.rad) * p1.acc - p.sin(p1.rad - this.rad) * p.pow(p1.vel, 2));
}
this.pendula.push(p1, p2);
break;
}
this.pendula[0].origin = p.createVector(0, 0);
}
class Pendulum {
updateOrigins(){
this.pendula.forEach((p, i) => {
if (i > 0){
let before = this.pendula[i - 1];
p.origin = p5.Vector.add(before.origin, before.pos);
X: Vector[] = [];
V: Vector[] = [];
size: number;
constructor(readonly M: number[], readonly L: number[], readonly color: p5.Color, startAngle: number) {
console.assert(M.length === L.length, M, L, "Masses and Lengths are not of equal length!");
this.size = M.length;
startAngle *= Math.PI / 180;
let direction = new Vector(Math.sin(startAngle), Math.cos(startAngle));
let currentPosition = new Vector(0, 0);
for (let i = 0; i < this.size; i++){
currentPosition.add(Vector.Mult(direction, L[i]));
this.X.push(currentPosition.copy());
this.V.push(new Vector(0, 0));
}
});
}
update(h){
this.pendula.forEach((p, i) => {
p.update(h, this.pendula);
});
this.updateOrigins();
}
// using position based dynamics
update(h: number) {
h /= Manager.SubSteps;
draw(){
this.pendula.forEach(p => p.draw());
}
for (let k = 0; k < Manager.SubSteps; k++) {
}
// Classic PBD needs multiple loops
// Here, I can put all operations safely into one single loop,
// because the positions and velocities in X, V are sorted
// from the pendulum's origin to it's end which means
// that only direct neighbours affect each other
let previousP = new Vector(0, 0);
for (let i = 0; i < this.size; i++) {
class Pendulum {
// apply external force (gravity)
this.V[i].addC(0, manager.gravity * h);
l: number
m: number
// euler step
let currentP = Vector.Add(this.X[i],Vector.Mult(this.V[i], h));
acc: number = 0
vel: number = 0
rad: number
// solve distance constraint
let w1 = i === 0 ? 0 : 1 / this.M[i - 1];
let w2 = 1 / this.M[i];
origin: p5.Vector
let s = Vector.Sub(previousP, currentP);
let n = s.copy();
n.normalize();
let l = s.mag();
color: p5.Color
let deltaP1 = Vector.Mult(n, -w1 / (w1 + w2) * (l - this.L[i]));
let deltaP2 = Vector.Mult(n, w2 / (w1 + w2) * (l - this.L[i]));
previousP.add(deltaP1);
currentP.add(deltaP2);
constructor(l, m, rad, color) {
this.l = l;
this.m = m;
this.rad = rad;
this.color = color;
// integrate
if (i > 0){
this.V[i - 1] = Vector.Mult(Vector.Sub(previousP, this.X[i - 1]), 1 / h);
this.X[i - 1] = previousP;
}
calcAcc(pendula){
return -g / this.l * p.sin(this.rad);
previousP = currentP;
}
update(h, pendula = []){
this.acc = this.calcAcc(pendula);
this.vel += this.acc * h;
this.rad += this.vel * h;
this.V[this.size - 1] = Vector.Mult(Vector.Sub(previousP, this.X[this.size - 1]), 1 / h);
this.X[this.size - 1] = previousP;
}
}
draw(){
let pos = this.pos;
p.push();
p.translate(this.origin);
p.stroke(this.color);
p.strokeWeight(3);
p.line(0, 0, pos.x, pos.y);
p.ellipse(pos.x, pos.y, this.m * 5, this.m * 5);
p.pop();
p.strokeWeight(1);
p.fill(255);
let scale = p.height * 0.95 / Manager.Size;
let p1 = new Vector(0, 0);
for (let p2 of this.X){
p2 = p2.copy();
p2.mult(scale);
p.line(p1.x, p1.y, p2.x, p2.y);
p1 = p2.copy();
}
get pos(){
return p5.Vector.mult(p.createVector(p.sin(this.rad), p.cos(this.rad)), this.l);
for (let i = 0; i < this.size; i++){
let p2 = this.X[i].copy();
p2.mult(scale);
let r = Math.sqrt(this.M[i] * 10);
p.ellipse(p2.x, p2.y, r * 2, r * 2);
}
p.pop();
}
}

@ -0,0 +1,38 @@
class Vector {
constructor(public x: number, public y: number) {}
copy(){
return new Vector(this.x, this.y);
}
static Add(v1: Vector, v2: Vector){
return new Vector(v1.x + v2.x, v1.y + v2.y);
}
static Sub(v1: Vector, v2: Vector){
return new Vector(v1.x - v2.x, v1.y - v2.y);
}
static Mult(v: Vector, n: number){
return new Vector(v.x * n, v.y * n);
}
add(v: Vector){
this.x += v.x;
this.y += v.y;
}
addC(x: number, y: number){
this.x += x;
this.y += y;
}
mult(n: number){
this.x *= n;
this.y *= n;
}
mag(){
return Math.sqrt(this.x * this.x + this.y * this.y);
}
normalize(){
this.mult(1 / this.mag());
}
}

@ -1,7 +0,0 @@
{
"collision": false,
"colorPicker": false,
"cookie": false,
"prototypes": false,
"technical": false
}

@ -1,23 +0,0 @@
{
"project": {
"name": "pendulum",
"author": "BenjoCraeft",
"version": "0.0.0",
"playerCounts": [],
"online": {
"iceServers": [
{"urls": "stun:stun.l.google.com:19302"},
{
"urls": "turn:numb.viagenie.ca",
"credential": "muazkh",
"username": "webrtc@live.com"
}
]
}
},
"frameWork": {
"frameRate": 60,
"width": null,
"height": null
}
}

@ -1,88 +0,0 @@
#color_picker{
width: 300px;
height: 25%;
margin: 20px;
margin-top: 50px;
border: 5px solid #000;
background-color: #000;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
position: relative;
}
#color_picker_numeric{
width: 80%;
padding: 5%;
margin: 5%;
background-color: #888;
border-radius: 10px;
overflow: hidden;
}
.color_picker_rgb{
float: left;
width: 22%;
height: 35px;
font-size: 25px;
color: #000;
}
.color_picker_rgb:nth-child(1){
margin-right: 10%;
margin-left: 3%;
background-color: #F00;
}
.color_picker_rgb:nth-child(2){
background-color: #0F0;
}
.color_picker_rgb:nth-child(3){
margin-left: 10%;
background-color: #00F;
color: #FFF;
}
#color_picker_hex{
width: 50%;
height: 30px;
font-size: 25px;
margin: 10% 25% 0 25%;
}
#saturation{
position: relative;
width: calc(100% - 33px);
height: 100%;
background: linear-gradient(to right, #FFF 0%, #F00 100%);
float: left;
margin-right: 6px;
}
#value {
width: 100%;
height: 100%;
background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%);
}
#sb_picker{
border: 2px solid;
border-color: #FFF;
position: absolute;
width: 14px;
height: 14px;
border-radius: 10px;
bottom: 50px;
left: 50px;
box-sizing: border-box;
z-index: 10;
}
#hue {
width: 27px;
height: 100%;
position: relative;
float: left;
background: linear-gradient(to bottom, #F00 0%, #F0F 17%, #00F 34%, #0FF 50%, #0F0 67%, #FF0 84%, #F00 100%);
}
#hue_picker {
position: absolute;
background: #000;
border-bottom: 1px solid #000;
top: 0;
width: 27px;
height: 2px;
}

@ -1,7 +1,8 @@
input[type=range] {
-webkit-appearance: none;
margin: 18px 0;
margin: 10px 0;
width: 100%;
height: 20px;
background: none;
}
input[type=range]:focus {
@ -9,7 +10,7 @@ input[type=range]:focus {
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 8.4px;
height: 9px;
cursor: pointer;
animate: 0.2s;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
@ -33,7 +34,7 @@ input[type=range]:focus::-webkit-slider-runnable-track {
}
input[type=range]::-moz-range-track {
width: 100%;
height: 8.4px;
height: 9px;
cursor: pointer;
animate: 0.2s;
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
@ -52,7 +53,7 @@ input[type=range]::-moz-range-thumb {
}
input[type=range]::-ms-track {
width: 100%;
height: 8.4px;
height: 9px;
cursor: pointer;
animate: 0.2s;
background: transparent;

@ -2,58 +2,106 @@
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" type="text/javascript"></script>
<script src="data/lib/p5.min.js" type="text/javascript"></script>
<script src="data/lib/jquery-3.6.4.min.js" type="text/javascript"></script>
<script src="data/lib/vue.global.prod.js"></script>
<script src="data/scripts/js/main.js" type="text/javascript"></script>
<link href="styles.css" rel="stylesheet">
<link href="data/styles/color_picker.css" rel="stylesheet">
<link href="data/styles/range_input.css" rel="stylesheet">
<title>Pendulum</title>
</head>
<body>
<div id="p5_loading"></div>
<div id="content">
<div id="options" style="display: none">
<div id="options">
<fieldset id="preparation">
<legend>Add Pendula</legend>
<label>
<input type="radio" name="type">
Single pendulum
Segments: {{ segmentCount }}
<input type="range" v-model.number="segmentCount" min="1" max="30" step="1">
</label>
<div id="segment_view">
<span id="segment_header_mass">Mass</span><span id="segment_header_length">Length</span>
<template v-for="i in segmentCount">
<span class="segment_label">({{ i - 1 }})</span>
<span class="segment_label">{{ masses[i - 1] }}kg</span>
<input type="range" v-model.number="masses[i - 1]" min="0.1" max="10" step=".1">
<span class="segment_label">{{ $filters.round(lengths[i - 1], 1) }}m</span>
<input type="range" v-model.number="lengths[i - 1]" min="0.1" max="10" step=".1">
</template>
</div>
<div class="horizontal_group">
<button class="reset_btn" @click="resetMasses" title="Reset masses to 1"></button>
<button id="normalize_btn" @click="normalize" title="Normalize lengths to fit screen"></button>
<button class="reset_btn" @click="resetLengths" title="Reset lengths to 1"></button>
</div>
<label>
Starting Angle: {{startAngle}}°
<input type="range" v-model.number="startAngle" min="0" max="360" step="1">
</label>
<label v-show="!multiple || !rainbow">
Color:
<input type="color" v-model="color">
</label>
<label>
<input type="checkbox" v-model="multiple">
Add multiple
</label>
<template v-if="multiple">
<label>
<input type="checkbox" v-model="rainbow">
Use rainbow coloring
</label>
<label>
Count: {{pendulumCount}}
<input type="range" v-model.number="pendulumCount" min="10" max="250" step="10">
</label>
<span>Property to change slightly:</span>
<label>
<input type="radio" value="angle" v-model="changeProperty">
Starting Angle
</label>
<br>
<label>
<input type="radio" name="type" checked>
Double pendulum
<input type="radio" value="mass" v-model="changeProperty">
Specific Mass
</label>
<br>
<label>
Count:
<input type="number" min="1" max="500" value="1">
<input type="radio" value="length" v-model="changeProperty">
Specific Length
</label>
<label v-show="changeProperty === 'mass' || changeProperty === 'length'">
Index: {{ changeIndex }}
<input type="range" v-model.number="changeIndex" min="0" :max="segmentCount - 1" step="1">
</label>
<br>
<label>
L1:
<input type="range" min="50" max="400" value="200">
<span>200</span>
Change Amount: {{ $filters.round(changeAmount * 100, 3) }}%
<input type="range" v-model.number="changeAmount" min="0.00001" max="0.001" step="0.00001">
</label>
<br>
</template>
<div class="horizontal_group">
<button @click="add" id="add_btn" :title="'Add configured ' + (multiple ? 'pendula' : 'pendulum')"></button>
<button @click="deleteAll" id="delete_btn" title="Delete all pendula"></button>
</div>
</fieldset>
<fieldset id="simulation">
<legend>Simulation</legend>
<label>
L2:
<input type="range" min="50" max="400" value="200">
<span>200</span>
Gravity: {{ gravity }} <sup>N</sup>&frasl;<sub>kg</sub>
<input type="range" v-model.number="gravity" min="0" max="30" step=".01">
</label>
<br>
<label>
M1:
<input type="range" min="1" max="10" value="1">
<span>1</span>
Timescale: x{{ timescale }}
<input type="range" v-model.number="timescale" min="0.01" max="3" step=".01">
</label>
<br>
<label>
M2:
<input type="range" min="1" max="10" value="1">
<span>1</span>
PBD Substeps: {{ subSteps }}
<input type="range" v-model.number="subSteps" min="1" max="100" step="1">
</label>
<br>
<input type="button" value="Add">
<div class="horizontal_group">
<button class="reset_btn" @click="resetSimulationControls" title="Reset simulation controls"></button>
<button @click="togglePlay" id="play_btn" :class="playingBtn" title="Pause/Resume simulation"></button>
</div>
</fieldset>
</div>
<div id="canvas_holder"></div>
</div>

@ -7,11 +7,30 @@
"": {
"name": "pendulum",
"version": "1.0.0",
"dependencies": {
"vue": "^3.3.4"
},
"devDependencies": {
"@types/jquery": "^3.5.16",
"typescript": "^5.0.2"
}
},
"node_modules/@babel/parser": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.15.tgz",
"integrity": "sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA==",
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@types/jquery": {
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz",
@ -27,6 +46,186 @@
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
"node_modules/@vue/compiler-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
"integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
"dependencies": {
"@babel/parser": "^7.21.3",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
"integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
"dependencies": {
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
"integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
"dependencies": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-ssr": "3.3.4",
"@vue/reactivity-transform": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0",
"postcss": "^8.1.10",
"source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
"integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
"dependencies": {
"@vue/compiler-dom": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/reactivity": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
"integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
"dependencies": {
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/reactivity-transform": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
"integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
"dependencies": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
"integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
"dependencies": {
"@vue/reactivity": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
"integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
"dependencies": {
"@vue/runtime-core": "3.3.4",
"@vue/shared": "3.3.4",
"csstype": "^3.1.1"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
"integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
"dependencies": {
"@vue/compiler-ssr": "3.3.4",
"@vue/shared": "3.3.4"
},
"peerDependencies": {
"vue": "3.3.4"
}
},
"node_modules/@vue/shared": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
},
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/magic-string": {
"version": "0.30.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz",
"integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
}
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/postcss": {
"version": "8.4.29",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
"integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/typescript": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
@ -39,9 +238,31 @@
"engines": {
"node": ">=12.20"
}
},
"node_modules/vue": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
"integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
"dependencies": {
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-sfc": "3.3.4",
"@vue/runtime-dom": "3.3.4",
"@vue/server-renderer": "3.3.4",
"@vue/shared": "3.3.4"
}
}
},
"dependencies": {
"@babel/parser": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.15.tgz",
"integrity": "sha512-RWmQ/sklUN9BvGGpCDgSubhHWfAx24XDTDObup4ffvxaYsptOg2P3KG0j+1eWKLxpkX0j0uHxmpq2Z1SP/VhxA=="
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"@types/jquery": {
"version": "3.5.16",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.16.tgz",
@ -57,11 +278,165 @@
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==",
"dev": true
},
"@vue/compiler-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz",
"integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==",
"requires": {
"@babel/parser": "^7.21.3",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"source-map-js": "^1.0.2"
}
},
"@vue/compiler-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz",
"integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==",
"requires": {
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"@vue/compiler-sfc": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz",
"integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==",
"requires": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-ssr": "3.3.4",
"@vue/reactivity-transform": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0",
"postcss": "^8.1.10",
"source-map-js": "^1.0.2"
}
},
"@vue/compiler-ssr": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz",
"integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==",
"requires": {
"@vue/compiler-dom": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"@vue/reactivity": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz",
"integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==",
"requires": {
"@vue/shared": "3.3.4"
}
},
"@vue/reactivity-transform": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz",
"integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==",
"requires": {
"@babel/parser": "^7.20.15",
"@vue/compiler-core": "3.3.4",
"@vue/shared": "3.3.4",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.0"
}
},
"@vue/runtime-core": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz",
"integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==",
"requires": {
"@vue/reactivity": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"@vue/runtime-dom": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz",
"integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==",
"requires": {
"@vue/runtime-core": "3.3.4",
"@vue/shared": "3.3.4",
"csstype": "^3.1.1"
}
},
"@vue/server-renderer": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz",
"integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==",
"requires": {
"@vue/compiler-ssr": "3.3.4",
"@vue/shared": "3.3.4"
}
},
"@vue/shared": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
},
"csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"magic-string": {
"version": "0.30.3",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.3.tgz",
"integrity": "sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==",
"requires": {
"@jridgewell/sourcemap-codec": "^1.4.15"
}
},
"nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"postcss": {
"version": "8.4.29",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
"integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
"requires": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
},
"source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
},
"typescript": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.2.tgz",
"integrity": "sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==",
"dev": true
},
"vue": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz",
"integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==",
"requires": {
"@vue/compiler-dom": "3.3.4",
"@vue/compiler-sfc": "3.3.4",
"@vue/runtime-dom": "3.3.4",
"@vue/server-renderer": "3.3.4",
"@vue/shared": "3.3.4"
}
}
}
}

@ -4,5 +4,8 @@
"devDependencies": {
"@types/jquery": "^3.5.16",
"typescript": "^5.0.2"
},
"dependencies": {
"vue": "^3.3.4"
}
}

@ -24,6 +24,10 @@ button:hover{cursor: pointer;}
:root{
--width: 100vw;
--height: 100vh;
--opt-width: 400px;
--opt-padding: 10px;
--opt-border: 5px;
--canvas-width: calc(100vw - (var(--opt-width) + var(--opt-padding) * 2 + var(--opt-border)))
}
body {
@ -36,7 +40,7 @@ body {
#canvas_holder{
position: relative;
width: calc(var(--width)); /* -325px);*/
width: var(--canvas-width);
height: var(--height);
float: left;
}
@ -55,18 +59,124 @@ body {
#options {
float: left;
width: 300px;
height: 100vh;
padding: 10px;
border-right: 5px solid rgb(150, 150, 150);
width: var(--opt-width);
height: calc(100vh - var(--opt-padding) * 2);
padding: var(--opt-padding);
border-right: var(--opt-border) solid rgb(150, 150, 150);
background-color: rgb(40, 40, 40);
display: flex;
flex-direction: column;
}
#options *:not(input[type=number], input[type=button]){
color: white;
}
fieldset {
border-radius: 5px;
margin: 10px;
display: flex;
flex-direction: column;
}
fieldset > * {
margin: 5px 0;
}
fieldset > legend {
font-size: 25px;
}
#preparation {
overflow-y: auto;
}
#segment_view {
display: grid;
grid-template-columns: 10% 15% 30% 15% 30%;
}
#segment_header_mass {
grid-column: 2 / span 2;
}
#segment_header_length {
grid-column: 4 / span 2;
}
.segment_label {
display: inline-flex;
align-items: center;
justify-content: center;
}
button {
width: 100%;
height: 40px;
border: 3px solid black;
border-radius: 5px;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
flex-shrink: 0;
}
button:hover{
filter: brightness(80%);
}
button:active {
filter: brightness(60%);
}
input {
.horizontal_group {
display: flex;
justify-content: space-between;
}
.horizontal_group > * {
width: auto;
flex: 1;
margin: 5px;
}
.horizontal_group > *:last-child {
margin-right: 0;
}
.horizontal_group > *:first-child {
margin-left: 0;
}
#normalize_btn {
background-image: url("data/images/compress.svg");
background-size: 50% 80%;
background-color: #cbcbcb;
border-color: #9a9a9a;
}
.reset_btn {
background-image: url("data/images/refresh.svg");
background-color: #cbcbcb;
border-color: #9a9a9a;
}
#add_btn{
border-color: #007a00;
background-color: #d3ffd3;
background-image: url("data/images/add.svg");
}
#delete_btn {
border-color: #810000;
background-color: #ffa7a7;
background-image: url("data/images/delete.svg");
}
#play_btn {
border-color: #0000c2;
background-color: #b1b1ff;
}
#play_btn.play {
background-image: url("data/images/play.svg");
}
#play_btn.pause {
background-image: url("data/images/pause.svg");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 240 KiB

Loading…
Cancel
Save