diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ec049d38e724e45fb1805b45d3df9bd9a17ca266..ed7d9fe46703df91432dcef71768fa80d359f32c 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
 default_language_version:
-  python: python3.11
+  python: python3.12
 
 repos:
   - repo: https://github.com/pre-commit/pre-commit-hooks
diff --git a/Makefile b/Makefile
index f1f4b3cb66bc64e93c66637c2507496d3b6aa92b..fa3120297fe7c973dc99e4a04a34c6b27105fd54 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 #!/usr/bin/make -f
 
-PYTHON   = python3.11
+PYTHON   = python3.12
 VENV     = .venv
 PORT     = 8016
 
diff --git a/VERSION b/VERSION
index 971e119ac759750bffcd3dc818710439d65a9375..f48f82fa2c475eb6e6bdc5f93fe968f512763b47 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.21.0
\ No newline at end of file
+2.22.0
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 25683bf359fff763767ac8f70c10819ed033bf0a..aedc3d5143196fc5ac6a524adf9ad0ce716c12e0 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,7 +9,7 @@
       "version": "0.0.0",
       "dependencies": {
         "alertifyjs": "^1.13.1",
-        "fabric": "^5.3.0",
+        "fabric": "^6.7.0",
         "vite-plugin-top-level-await": "^1.4.1",
         "vue": "^3.2.47",
         "vue-meta": "^3.0.0-alpha.2",
@@ -989,9 +989,10 @@
       "dev": true
     },
     "node_modules/@mapbox/node-pre-gyp": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.10.tgz",
-      "integrity": "sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==",
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
+      "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
+      "license": "BSD-3-Clause",
       "optional": true,
       "dependencies": {
         "detect-libc": "^2.0.0",
@@ -1008,39 +1009,6 @@
         "node-pre-gyp": "bin/node-pre-gyp"
       }
     },
-    "node_modules/@mapbox/node-pre-gyp/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "optional": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@mapbox/node-pre-gyp/node_modules/semver": {
-      "version": "7.5.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
-      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
-      "optional": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "optional": true
-    },
     "node_modules/@nicolo-ribaudo/semver-v6": {
       "version": "6.3.3",
       "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz",
@@ -1336,6 +1304,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
       "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">= 10"
@@ -1520,19 +1489,23 @@
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
       "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
+      "deprecated": "Use your platform's native atob() and btoa() methods instead",
+      "license": "BSD-3-Clause",
       "optional": true
     },
     "node_modules/abbrev": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/acorn": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
-      "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+      "version": "8.15.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+      "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "devOptional": true,
+      "license": "MIT",
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -1541,25 +1514,14 @@
       }
     },
     "node_modules/acorn-globals": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
-      "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
+      "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
-        "acorn": "^7.1.1",
-        "acorn-walk": "^7.1.1"
-      }
-    },
-    "node_modules/acorn-globals/node_modules/acorn": {
-      "version": "7.4.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
-      "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
-      "optional": true,
-      "bin": {
-        "acorn": "bin/acorn"
-      },
-      "engines": {
-        "node": ">=0.4.0"
+        "acorn": "^8.1.0",
+        "acorn-walk": "^8.0.2"
       }
     },
     "node_modules/acorn-jsx": {
@@ -1572,10 +1534,14 @@
       }
     },
     "node_modules/acorn-walk": {
-      "version": "7.2.0",
-      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
-      "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
+      "version": "8.3.4",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+      "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+      "license": "MIT",
       "optional": true,
+      "dependencies": {
+        "acorn": "^8.11.0"
+      },
       "engines": {
         "node": ">=0.4.0"
       }
@@ -1584,6 +1550,7 @@
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
       "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "debug": "4"
@@ -1657,12 +1624,15 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
       "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/are-we-there-yet": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
       "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
+      "deprecated": "This package is no longer supported.",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "delegates": "^1.0.0",
@@ -1688,6 +1658,7 @@
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/autoprefixer": {
@@ -1766,12 +1737,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/browser-process-hrtime": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
-      "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
-      "optional": true
-    },
     "node_modules/browserslist": {
       "version": "4.21.9",
       "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
@@ -1804,6 +1769,20 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/callsites": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1860,6 +1839,7 @@
       "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
       "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
       "hasInstallScript": true,
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "@mapbox/node-pre-gyp": "^1.0.0",
@@ -1927,6 +1907,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
       "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+      "license": "ISC",
       "optional": true,
       "engines": {
         "node": ">=10"
@@ -1951,6 +1932,7 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
       "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+      "license": "ISC",
       "optional": true,
       "bin": {
         "color-support": "bin.js"
@@ -1960,6 +1942,7 @@
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "delayed-stream": "~1.0.0"
@@ -1987,6 +1970,7 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
       "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/convert-source-map": {
@@ -2025,12 +2009,14 @@
       "version": "0.5.0",
       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
       "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/cssstyle": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
       "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "cssom": "~0.3.6"
@@ -2043,6 +2029,7 @@
       "version": "0.3.8",
       "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
       "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/csstype": {
@@ -2054,6 +2041,7 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
       "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "abab": "^2.0.6",
@@ -2064,19 +2052,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/data-urls/node_modules/whatwg-url": {
-      "version": "11.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
-      "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
-      "optional": true,
-      "dependencies": {
-        "tr46": "^3.0.0",
-        "webidl-conversions": "^7.0.0"
-      },
-      "engines": {
-        "node": ">=12"
-      }
-    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -2095,15 +2070,17 @@
       }
     },
     "node_modules/decimal.js": {
-      "version": "10.4.3",
-      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
-      "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
+      "version": "10.5.0",
+      "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
+      "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/decompress-response": {
       "version": "4.2.1",
       "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
       "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "mimic-response": "^2.0.0"
@@ -2116,12 +2093,13 @@
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
       "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">=0.4.0"
@@ -2131,12 +2109,14 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
       "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/detect-libc": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
-      "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
+      "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
+      "license": "Apache-2.0",
       "optional": true,
       "engines": {
         "node": ">=8"
@@ -2170,6 +2150,8 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
       "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
+      "deprecated": "Use your platform's native DOMException instead",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "webidl-conversions": "^7.0.0"
@@ -2178,6 +2160,21 @@
         "node": ">=12"
       }
     },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.449",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.449.tgz",
@@ -2188,8 +2185,71 @@
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
       "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT",
       "optional": true
     },
+    "node_modules/entities": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+      "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+      "license": "BSD-2-Clause",
+      "optional": true,
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/esbuild": {
       "version": "0.18.20",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
@@ -2245,15 +2305,15 @@
       }
     },
     "node_modules/escodegen": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz",
-      "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+      "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+      "license": "BSD-2-Clause",
       "optional": true,
       "dependencies": {
         "esprima": "^4.0.1",
         "estraverse": "^5.2.0",
-        "esutils": "^2.0.2",
-        "optionator": "^0.8.1"
+        "esutils": "^2.0.2"
       },
       "bin": {
         "escodegen": "bin/escodegen.js",
@@ -2266,57 +2326,6 @@
         "source-map": "~0.6.1"
       }
     },
-    "node_modules/escodegen/node_modules/levn": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
-      "optional": true,
-      "dependencies": {
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/escodegen/node_modules/optionator": {
-      "version": "0.8.3",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
-      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
-      "optional": true,
-      "dependencies": {
-        "deep-is": "~0.1.3",
-        "fast-levenshtein": "~2.0.6",
-        "levn": "~0.3.0",
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2",
-        "word-wrap": "~1.2.3"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/escodegen/node_modules/prelude-ls": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
-      "optional": true,
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
-    "node_modules/escodegen/node_modules/type-check": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
-      "optional": true,
-      "dependencies": {
-        "prelude-ls": "~1.1.2"
-      },
-      "engines": {
-        "node": ">= 0.8.0"
-      }
-    },
     "node_modules/eslint": {
       "version": "8.40.0",
       "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.40.0.tgz",
@@ -2428,39 +2437,6 @@
         "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
       }
     },
-    "node_modules/eslint-plugin-vue/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/eslint-plugin-vue/node_modules/semver": {
-      "version": "7.5.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
-      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/eslint-plugin-vue/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
     "node_modules/eslint-scope": {
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
@@ -2607,6 +2583,7 @@
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
       "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "license": "BSD-2-Clause",
       "optional": true,
       "bin": {
         "esparse": "bin/esparse.js",
@@ -2664,15 +2641,16 @@
       }
     },
     "node_modules/fabric": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/fabric/-/fabric-5.3.0.tgz",
-      "integrity": "sha512-AVayKuzWoXM5cTn7iD3yNWBlfEa8r1tHaOe2g8NsZrmWKAHjryTxT/j6f9ncRfOWOF0I1Ci1AId3y78cC+GExQ==",
+      "version": "6.7.0",
+      "resolved": "https://registry.npmjs.org/fabric/-/fabric-6.7.0.tgz",
+      "integrity": "sha512-+yKumsh1MvJ44Um2eOhb4Q6CyZ6e2XKBV3IfQvzuGKhl2UkRFQtIKPUi6f06m3gd0r5zspgMUl5iwxtT1dmFAQ==",
+      "license": "MIT",
       "engines": {
-        "node": ">=14.0.0"
+        "node": ">=16.20.0"
       },
       "optionalDependencies": {
-        "canvas": "^2.8.0",
-        "jsdom": "^19.0.0"
+        "canvas": "^2.11.2",
+        "jsdom": "^20.0.1"
       }
     },
     "node_modules/fast-deep-equal": {
@@ -2725,7 +2703,7 @@
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
       "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
-      "devOptional": true
+      "dev": true
     },
     "node_modules/fastq": {
       "version": "1.15.0",
@@ -2796,13 +2774,16 @@
       "dev": true
     },
     "node_modules/form-data": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
-      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
+      "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "asynckit": "^0.4.0",
         "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
         "mime-types": "^2.1.12"
       },
       "engines": {
@@ -2826,6 +2807,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
       "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "minipass": "^3.0.0"
@@ -2838,6 +2820,7 @@
       "version": "3.3.6",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "yallist": "^4.0.0"
@@ -2850,6 +2833,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/fs.realpath": {
@@ -2872,15 +2856,21 @@
       }
     },
     "node_modules/function-bind": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
-      "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
-      "dev": true
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "devOptional": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
     },
     "node_modules/gauge": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
       "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
+      "deprecated": "This package is no longer supported.",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "aproba": "^1.0.3 || ^2.0.0",
@@ -2906,6 +2896,45 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/glob": {
       "version": "7.2.3",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -2947,6 +2976,19 @@
         "node": ">=4"
       }
     },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/grapheme-splitter": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
@@ -2974,16 +3016,60 @@
         "node": ">=4"
       }
     },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/has-unicode": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
       "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+      "license": "ISC",
       "optional": true
     },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/html-encoding-sniffer": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
       "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "whatwg-encoding": "^2.0.0"
@@ -3008,6 +3094,7 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
       "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "@tootallnate/once": "2",
@@ -3022,6 +3109,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
       "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "agent-base": "6",
@@ -3035,6 +3123,7 @@
       "version": "0.6.3",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
       "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "safer-buffer": ">= 2.1.2 < 3.0.0"
@@ -3130,6 +3219,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
       "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">=8"
@@ -3169,6 +3259,7 @@
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
       "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/isexe": {
@@ -3215,41 +3306,41 @@
       }
     },
     "node_modules/jsdom": {
-      "version": "19.0.0",
-      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz",
-      "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==",
+      "version": "20.0.3",
+      "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
+      "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
-        "abab": "^2.0.5",
-        "acorn": "^8.5.0",
-        "acorn-globals": "^6.0.0",
+        "abab": "^2.0.6",
+        "acorn": "^8.8.1",
+        "acorn-globals": "^7.0.0",
         "cssom": "^0.5.0",
         "cssstyle": "^2.3.0",
-        "data-urls": "^3.0.1",
-        "decimal.js": "^10.3.1",
+        "data-urls": "^3.0.2",
+        "decimal.js": "^10.4.2",
         "domexception": "^4.0.0",
         "escodegen": "^2.0.0",
         "form-data": "^4.0.0",
         "html-encoding-sniffer": "^3.0.0",
         "http-proxy-agent": "^5.0.0",
-        "https-proxy-agent": "^5.0.0",
+        "https-proxy-agent": "^5.0.1",
         "is-potential-custom-element-name": "^1.0.1",
-        "nwsapi": "^2.2.0",
-        "parse5": "6.0.1",
-        "saxes": "^5.0.1",
+        "nwsapi": "^2.2.2",
+        "parse5": "^7.1.1",
+        "saxes": "^6.0.0",
         "symbol-tree": "^3.2.4",
-        "tough-cookie": "^4.0.0",
-        "w3c-hr-time": "^1.0.2",
-        "w3c-xmlserializer": "^3.0.0",
+        "tough-cookie": "^4.1.2",
+        "w3c-xmlserializer": "^4.0.0",
         "webidl-conversions": "^7.0.0",
         "whatwg-encoding": "^2.0.0",
         "whatwg-mimetype": "^3.0.0",
-        "whatwg-url": "^10.0.0",
-        "ws": "^8.2.3",
+        "whatwg-url": "^11.0.0",
+        "ws": "^8.11.0",
         "xml-name-validator": "^4.0.0"
       },
       "engines": {
-        "node": ">=12"
+        "node": ">=14"
       },
       "peerDependencies": {
         "canvas": "^2.5.0"
@@ -3384,6 +3475,7 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
       "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "semver": "^6.0.0"
@@ -3395,6 +3487,26 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/make-dir/node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "license": "ISC",
+      "optional": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -3421,6 +3533,7 @@
       "version": "1.52.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">= 0.6"
@@ -3430,6 +3543,7 @@
       "version": "2.1.35",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "mime-db": "1.52.0"
@@ -3442,6 +3556,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
       "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">=8"
@@ -3466,6 +3581,7 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
       "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+      "license": "ISC",
       "optional": true,
       "engines": {
         "node": ">=8"
@@ -3475,6 +3591,7 @@
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
       "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "minipass": "^3.0.0",
@@ -3488,6 +3605,7 @@
       "version": "3.3.6",
       "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
       "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "yallist": "^4.0.0"
@@ -3500,12 +3618,14 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/mkdirp": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
       "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+      "license": "MIT",
       "optional": true,
       "bin": {
         "mkdirp": "bin/cmd.js"
@@ -3532,9 +3652,10 @@
       }
     },
     "node_modules/nan": {
-      "version": "2.17.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz",
-      "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==",
+      "version": "2.22.2",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz",
+      "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/nanoid": {
@@ -3561,9 +3682,10 @@
       "dev": true
     },
     "node_modules/node-fetch": {
-      "version": "2.6.11",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz",
-      "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==",
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "whatwg-url": "^5.0.0"
@@ -3584,18 +3706,21 @@
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
       "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/node-fetch/node_modules/webidl-conversions": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
       "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "license": "BSD-2-Clause",
       "optional": true
     },
     "node_modules/node-fetch/node_modules/whatwg-url": {
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
       "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "tr46": "~0.0.3",
@@ -3612,6 +3737,7 @@
       "version": "5.0.0",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
       "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "abbrev": "1"
@@ -3645,6 +3771,8 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
       "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
+      "deprecated": "This package is no longer supported.",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "are-we-there-yet": "^2.0.0",
@@ -3666,9 +3794,10 @@
       }
     },
     "node_modules/nwsapi": {
-      "version": "2.2.4",
-      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz",
-      "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==",
+      "version": "2.2.20",
+      "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
+      "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/object-assign": {
@@ -3758,10 +3887,17 @@
       }
     },
     "node_modules/parse5": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
-      "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==",
-      "optional": true
+      "version": "7.3.0",
+      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "entities": "^6.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/inikulin/parse5?sponsor=1"
+      }
     },
     "node_modules/path-exists": {
       "version": "4.0.0",
@@ -3998,16 +4134,24 @@
       }
     },
     "node_modules/psl": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
-      "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
-      "optional": true
+      "version": "1.15.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+      "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "punycode": "^2.3.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/lupomontero"
+      }
     },
     "node_modules/punycode": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
-      "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
       "devOptional": true,
+      "license": "MIT",
       "engines": {
         "node": ">=6"
       }
@@ -4016,6 +4160,7 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
       "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/queue-microtask": {
@@ -4051,6 +4196,7 @@
       "version": "3.6.2",
       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
       "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "inherits": "^2.0.3",
@@ -4077,6 +4223,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/resolve": {
@@ -4186,39 +4333,47 @@
           "url": "https://feross.org/support"
         }
       ],
+      "license": "MIT",
       "optional": true
     },
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/saxes": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
-      "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+      "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "xmlchars": "^2.2.0"
       },
       "engines": {
-        "node": ">=10"
+        "node": ">=v12.22.7"
       }
     },
     "node_modules/semver": {
-      "version": "6.3.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
-      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
-      "optional": true,
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
+      "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+      "devOptional": true,
+      "license": "ISC",
       "bin": {
         "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
       }
     },
     "node_modules/set-blocking": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
       "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/shebang-command": {
@@ -4246,6 +4401,7 @@
       "version": "3.0.7",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/simple-concat": {
@@ -4266,12 +4422,14 @@
           "url": "https://feross.org/support"
         }
       ],
+      "license": "MIT",
       "optional": true
     },
     "node_modules/simple-get": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
       "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "decompress-response": "^4.2.0",
@@ -4305,6 +4463,7 @@
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
       "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "safe-buffer": "~5.2.0"
@@ -4314,6 +4473,7 @@
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
       "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "emoji-regex": "^8.0.0",
@@ -4424,6 +4584,7 @@
       "version": "3.2.4",
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
       "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/tailwindcss": {
@@ -4465,9 +4626,10 @@
       }
     },
     "node_modules/tar": {
-      "version": "6.1.14",
-      "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.14.tgz",
-      "integrity": "sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw==",
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+      "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "chownr": "^2.0.0",
@@ -4485,6 +4647,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "license": "ISC",
       "optional": true
     },
     "node_modules/text-table": {
@@ -4536,9 +4699,10 @@
       }
     },
     "node_modules/tough-cookie": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
-      "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
+      "version": "4.1.4",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+      "license": "BSD-3-Clause",
       "optional": true,
       "dependencies": {
         "psl": "^1.1.33",
@@ -4554,6 +4718,7 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
       "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "punycode": "^2.1.1"
@@ -4596,6 +4761,7 @@
       "version": "0.2.0",
       "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
       "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">= 4.0.0"
@@ -4644,6 +4810,7 @@
       "version": "1.5.10",
       "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
       "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "querystringify": "^2.1.1",
@@ -4771,39 +4938,6 @@
         "eslint": ">=6.0.0"
       }
     },
-    "node_modules/vue-eslint-parser/node_modules/lru-cache": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
-      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
-      "dev": true,
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/vue-eslint-parser/node_modules/semver": {
-      "version": "7.5.3",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
-      "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
-      "dev": true,
-      "dependencies": {
-        "lru-cache": "^6.0.0"
-      },
-      "bin": {
-        "semver": "bin/semver.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/vue-eslint-parser/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
-      "dev": true
-    },
     "node_modules/vue-meta": {
       "version": "3.0.0-alpha.2",
       "resolved": "https://registry.npmjs.org/vue-meta/-/vue-meta-3.0.0-alpha.2.tgz",
@@ -4835,32 +4969,24 @@
         "vue": "3.x"
       }
     },
-    "node_modules/w3c-hr-time": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
-      "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
-      "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.",
-      "optional": true,
-      "dependencies": {
-        "browser-process-hrtime": "^1.0.0"
-      }
-    },
     "node_modules/w3c-xmlserializer": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
-      "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
+      "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "xml-name-validator": "^4.0.0"
       },
       "engines": {
-        "node": ">=12"
+        "node": ">=14"
       }
     },
     "node_modules/webidl-conversions": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
       "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+      "license": "BSD-2-Clause",
       "optional": true,
       "engines": {
         "node": ">=12"
@@ -4870,6 +4996,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
       "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "iconv-lite": "0.6.3"
@@ -4882,15 +5009,17 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
       "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">=12"
       }
     },
     "node_modules/whatwg-url": {
-      "version": "10.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz",
-      "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==",
+      "version": "11.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
+      "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
+      "license": "MIT",
       "optional": true,
       "dependencies": {
         "tr46": "^3.0.0",
@@ -4919,20 +5048,12 @@
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
       "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+      "license": "ISC",
       "optional": true,
       "dependencies": {
         "string-width": "^1.0.2 || 2 || 3 || 4"
       }
     },
-    "node_modules/word-wrap": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
-      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
-      "optional": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -4940,9 +5061,10 @@
       "devOptional": true
     },
     "node_modules/ws": {
-      "version": "8.13.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
-      "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
+      "version": "8.18.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
+      "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+      "license": "MIT",
       "optional": true,
       "engines": {
         "node": ">=10.0.0"
@@ -4973,6 +5095,7 @@
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
       "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+      "license": "MIT",
       "optional": true
     },
     "node_modules/yallist": {
diff --git a/frontend/package.json b/frontend/package.json
index 9df00f5bb7e7440528956b51ad35738253cae85e..239db316983dd440ff7774e8bfd666bf400a04fb 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,7 +11,7 @@
   },
   "dependencies": {
     "alertifyjs": "^1.13.1",
-    "fabric": "^5.3.0",
+    "fabric": "^6.7.0",
     "vite-plugin-top-level-await": "^1.4.1",
     "vue": "^3.2.47",
     "vue-meta": "^3.0.0-alpha.2",
diff --git a/frontend/src/assets/badges/default-dark.png b/frontend/src/assets/badges/default-dark.png
new file mode 100644
index 0000000000000000000000000000000000000000..42a16370621580402d567090822a9c667f06ce15
Binary files /dev/null and b/frontend/src/assets/badges/default-dark.png differ
diff --git a/frontend/src/assets/badges/default-light.png b/frontend/src/assets/badges/default-light.png
new file mode 100644
index 0000000000000000000000000000000000000000..bf8a12cb47a921043a482f2a780a8554ea9cdbf2
Binary files /dev/null and b/frontend/src/assets/badges/default-light.png differ
diff --git a/frontend/src/assets/previews/nakopneme_basic_photo_banner.png b/frontend/src/assets/previews/nakopneme_basic_photo_banner.png
new file mode 100644
index 0000000000000000000000000000000000000000..61b8ea1bf62e8c477db02517b7a07df660dbebf1
Binary files /dev/null and b/frontend/src/assets/previews/nakopneme_basic_photo_banner.png differ
diff --git a/frontend/src/assets/template/nakopneme_basic_photo_banner/nakopneme_quote_left.png b/frontend/src/assets/template/nakopneme_basic_photo_banner/nakopneme_quote_left.png
new file mode 100644
index 0000000000000000000000000000000000000000..e157c389667fa7e8aa65378d4bbd540023f38b34
Binary files /dev/null and b/frontend/src/assets/template/nakopneme_basic_photo_banner/nakopneme_quote_left.png differ
diff --git a/frontend/src/assets/template/nakopneme_basic_photo_banner/nakopneme_quote_right.png b/frontend/src/assets/template/nakopneme_basic_photo_banner/nakopneme_quote_right.png
new file mode 100644
index 0000000000000000000000000000000000000000..8d1a7b9599a3bc01a30415b6c192750d91349354
Binary files /dev/null and b/frontend/src/assets/template/nakopneme_basic_photo_banner/nakopneme_quote_right.png differ
diff --git a/frontend/src/components/canvas/Canvas.vue b/frontend/src/components/canvas/Canvas.vue
index de68381cd2bdd8fe1dabc804a5e54d115113cf1d..5abd781606312a5bf20b4238a1ae636d2a50befe 100644
--- a/frontend/src/components/canvas/Canvas.vue
+++ b/frontend/src/components/canvas/Canvas.vue
@@ -1,5 +1,5 @@
 <script>
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 
 export default {
   props: ["width", "height", "redrawFunction"],
@@ -48,12 +48,9 @@ export default {
           window.fileName instanceof String) &&
         window.fileName.length >= 1
       ) {
-        window.fileName = (
-          window.
-          fileName.
-          replace(/\r\n|\n/, ' _ ').
-          replace(/\./, '_')
-        );
+        window.fileName = window.fileName
+          .replace(/\r\n|\n/, " _ ")
+          .replace(/\./, "_");
 
         link.download = window.fileName;
       } else {
diff --git a/frontend/src/components/canvas/textbox.js b/frontend/src/components/canvas/textbox.js
index 6584210b70d4292712772d2de053670bbc3fc0f3..2b98839e23049164824eea053e3eb0d7967626b8 100644
--- a/frontend/src/components/canvas/textbox.js
+++ b/frontend/src/components/canvas/textbox.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 
 class PaddedHighlightingTextbox extends fabric.Textbox {
   _renderTextLinesBackground(ctx) {
diff --git a/frontend/src/components/canvas/utils.js b/frontend/src/components/canvas/utils.js
index c06b758c9983e0ca56817c85ce232ff4ad868a16..e9a067e729b9f70d5b2ee7a5bcf34532091b9bf1 100644
--- a/frontend/src/components/canvas/utils.js
+++ b/frontend/src/components/canvas/utils.js
@@ -1,7 +1,7 @@
 import alertifyjs from "alertifyjs";
 import "alertifyjs/build/css/alertify.css";
 
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 
 const setCharAt = (str, index, chr) => {
   if (index > str.length - 1) return str;
@@ -35,7 +35,7 @@ const getSingleLineTextBoxWidth = (text, fontSize, fontFamily) => {
   // hacky browser font shit anymore.
   const maxWidth = 999999999999999;
   let currentWidth = 0;
-  
+
   for (let wordPosition = 0; wordPosition < splitWords.length; wordPosition++) {
     let currentWord = splitWords[wordPosition];
     let skipNewLineGeneration = false;
@@ -101,7 +101,7 @@ const getSingleLineTextBoxWidth = (text, fontSize, fontFamily) => {
   }
 
   return currentWidth;
-}
+};
 
 const sortObjects = (canvas) => {
   canvas._objects.sort((a, b) => (a.zIndex > b.zIndex ? 1 : -1));
@@ -119,8 +119,10 @@ const transformTextLineBreaks = (
     options = {};
   }
 
-  text = text.replace(/[^\S\r\n]+/g, " ");
-  text = text.replace(/\r\n/g, "\n");
+  if (!options.skipWhitespaceNormalization) {
+    text = text.replace(/[^\S\r\n]+/g, " ");
+    text = text.replace(/\r\n/g, "\n");
+  }
 
   let positionWithinString = -1;
   let currentWidth = 0;
diff --git a/frontend/src/logos.js b/frontend/src/logos.js
index f3fb2da5439c450064fa0d2a96cb98afbc164102..6837a9d9b1de69a1c3787bcc20774fa6ab400b9b 100644
--- a/frontend/src/logos.js
+++ b/frontend/src/logos.js
@@ -1,6 +1,9 @@
 import defaultLogoLight from "./assets/logos/default-light.png";
 import defaultLogoDark from "./assets/logos/default-dark.png";
 
+import defaultBadgeLight from "./assets/badges/default-light.png";
+import defaultBadgeDark from "./assets/badges/default-dark.png";
+
 const LOGOS = {
   defaultLight: {
     name: "Základní - světlé",
@@ -12,6 +15,17 @@ const LOGOS = {
   },
 };
 
+const BADGES = {
+  defaultLight: {
+    name: "Základní - světlé",
+    src: defaultBadgeLight,
+  },
+  defaultDark: {
+    name: "Základní - tmavé",
+    src: defaultBadgeDark,
+  },
+};
+
 const LOGO_POSITIONS = {
   top_left: {
     id: "top-left",
@@ -45,6 +59,16 @@ const generateLogoPositions = (identifiers) => {
   return logoPositionsList;
 };
 
+const generateDefaultBadges = (identifier) => {
+  let badgesCopy = BADGES;
+
+  for (const [badgeIdentifier, badge] of Object.entries(badgesCopy)) {
+    badge.defaultSelected = badgeIdentifier === identifier;
+  }
+
+  return Object.values(badgesCopy);
+};
+
 const generateDefaultLogos = (identifier) => {
   let logosCopy = LOGOS;
 
@@ -55,4 +79,10 @@ const generateDefaultLogos = (identifier) => {
   return Object.values(logosCopy);
 };
 
-export { LOGOS, generateDefaultLogos, LOGO_POSITIONS, generateLogoPositions };
+export {
+  LOGOS,
+  generateDefaultLogos,
+  generateDefaultBadges,
+  LOGO_POSITIONS,
+  generateLogoPositions,
+};
diff --git a/frontend/src/templates.js b/frontend/src/templates.js
index 6fc44dc11ae5874184c377c827efcbb9308daab5..019b318f7ec9874b80b7b0a0bddec0afeb48afca 100644
--- a/frontend/src/templates.js
+++ b/frontend/src/templates.js
@@ -1,9 +1,8 @@
 import basicPhotoBannerImage from "./assets/previews/basic_photo_banner.png";
+import nakopnemeBasicPhotoBannerImage from "./assets/previews/nakopneme_basic_photo_banner.png";
 import urgentBasicPhotoBannerImage from "./assets/previews/urgent_basic_photo_banner.png";
 import makeawishPhotoBannerImage from "./assets/previews/make_a_wish_photo_banner.png";
 import makeawishTourSocialImage from "./assets/previews/make_a_wish_tour_social.png";
-import backInFullForcePhotoBannerImage from "./assets/previews/back_in_full_force_photo_banner.png";
-import backInFullForceTourSocialImage from "./assets/previews/back_in_full_force_tour_social.png"
 import urgentTextBannerImage from "./assets/previews/urgent_text_banner.png";
 import textBannerImage from "./assets/previews/text_banner.png";
 import newspaperQuoteBottomImage from "./assets/previews/newspaper_quote_bottom.png";
@@ -39,6 +38,16 @@ const TEMPLATES = {
       title: "Základní banner s fotkou",
     },
   },
+  basic_photo_banner_ig: {
+    name: "Základní banner s fotkou (IG 5:4)",
+    image: basicPhotoBannerImage,
+    path: "/basic-photo-banner-id",
+    component: () =>
+      import("./views/basic_photo_banner/BasicPhotoBannerIG.vue"),
+    meta: {
+      title: "Základní banner s fotkou (IG 5:4)",
+    },
+  },
   urgent_basic_photo_banner: {
     name: "Urgentní banner s fotkou",
     image: urgentBasicPhotoBannerImage,
@@ -49,6 +58,18 @@ const TEMPLATES = {
       title: "Urgentní banner s fotkou",
     },
   },
+  nakopneme_basic_photo_banner: {
+    name: "Nakopneme to! - Základní banner s fotkou",
+    image: nakopnemeBasicPhotoBannerImage,
+    path: "/nakopneme-basic-photo-banner",
+    component: () =>
+      import(
+        "./views/nakopneme_basic_photo_banner/NakopnemeBasicPhotoBanner.vue"
+      ),
+    meta: {
+      title: "Nakopneme to! Základní banner s fotkou",
+    },
+  },
   make_a_wish_banner: {
     name: "Máte přání banner",
     image: makeawishPhotoBannerImage,
@@ -62,7 +83,8 @@ const TEMPLATES = {
     name: "Máte přání tour banner na soc. sítě",
     image: makeawishTourSocialImage,
     path: "/make-a-wish-tour-social",
-    component: () => import("./views/make_a_wish_tour_social/MakeAWishTourSocial.vue"),
+    component: () =>
+      import("./views/make_a_wish_tour_social/MakeAWishTourSocial.vue"),
     meta: {
       title: "Máte přání tour banner na soc. sítě",
     },
@@ -71,7 +93,8 @@ const TEMPLATES = {
     name: "A2 - Máte přání tour plakát",
     image: makeawishTourSocialImage,
     path: "/make-a-wish-tour-social-a2",
-    component: () => import("./views/make_a_wish_tour_social_A2/MakeAWishTourSocialA2.vue"),
+    component: () =>
+      import("./views/make_a_wish_tour_social_A2/MakeAWishTourSocialA2.vue"),
     meta: {
       title: "A2 - Máte přání tour plakát",
     },
@@ -80,12 +103,15 @@ const TEMPLATES = {
     name: "Široký banner - Máte přání tour",
     image: makeawishTourSocialImage,
     path: "/make-a-wish-tour-social-wide",
-    component: () => import("./views/make_a_wish_tour_social_wide/MakeAWishTourSocialWide.vue"),
+    component: () =>
+      import(
+        "./views/make_a_wish_tour_social_wide/MakeAWishTourSocialWide.vue"
+      ),
     meta: {
       title: "Široký banner - Máte přání tour",
     },
   },
-  
+
   /*
   back_in_full_force_banner: {
     name: "Zpátky v plné síle banner",
diff --git a/frontend/src/views/angle_event_left/canvas.js b/frontend/src/views/angle_event_left/canvas.js
index 3e3268917e6a8bccd4d6992de6cdba4cff5401b7..dab73a0c0708ab442731e231d0e8bf5b24861834 100644
--- a/frontend/src/views/angle_event_left/canvas.js
+++ b/frontend/src/views/angle_event_left/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/angle_event_right/canvas.js b/frontend/src/views/angle_event_right/canvas.js
index c4eba342c747d75223cefa65ee2599532359aa48..054352c85b4caa816a2307550411f148096df7b6 100644
--- a/frontend/src/views/angle_event_right/canvas.js
+++ b/frontend/src/views/angle_event_right/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/angle_person_event_left/canvas.js b/frontend/src/views/angle_person_event_left/canvas.js
index c2d01936a6fc9ef4cdc4a8ae054650cd6a051432..c11b991e11882a3aa959b039e5aa794379f1111e 100644
--- a/frontend/src/views/angle_person_event_left/canvas.js
+++ b/frontend/src/views/angle_person_event_left/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/angle_person_event_right/canvas.js b/frontend/src/views/angle_person_event_right/canvas.js
index df263990f3b7939a17bdf102233b9352e65ebbe5..e932835662faeb3c3fcb235159f803d25cc4e50a 100644
--- a/frontend/src/views/angle_person_event_right/canvas.js
+++ b/frontend/src/views/angle_person_event_right/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/avatar/canvas.js b/frontend/src/views/avatar/canvas.js
index c9f9d183d774e7d2d20d12d6d09298804fef5380..b9e5a14ac32c528da5607eef3fe083ed562cef2f 100644
--- a/frontend/src/views/avatar/canvas.js
+++ b/frontend/src/views/avatar/canvas.js
@@ -120,14 +120,14 @@ const redraw = async (canvas, options) => {
   }
 
   const clipCircle = new fabric.Circle({
-    left: canvas.width/2,
-    top: canvas.height/2,
-    originX: 'center',
-    originY: 'center',
+    left: canvas.width / 2,
+    top: canvas.height / 2,
+    originX: "center",
+    originY: "center",
     radius: canvas.width / 2,
-    absolutePositioned: true   // clip relative to canvas
+    absolutePositioned: true, // clip relative to canvas
   });
-  
+
   // assign it to the canvas
   canvas.clipPath = clipCircle;
   canvas.renderAll();
diff --git a/frontend/src/views/back_in_full_force_banner/canvas.js b/frontend/src/views/back_in_full_force_banner/canvas.js
index c13cbccc5cecf255a4ffad268904fd90fe74eb8d..25c7bff398a62ebcbcb87fd7d32e29b41a8319d7 100644
--- a/frontend/src/views/back_in_full_force_banner/canvas.js
+++ b/frontend/src/views/back_in_full_force_banner/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -25,12 +25,7 @@ let mainImageSource = null;
 
 const redraw = async (canvas, options) => {
   clearObjects(
-    [
-      mainTextBox,
-      personNameText,
-      personPositionText,
-      contractedByTextbox,
-    ],
+    [mainTextBox, personNameText, personPositionText, contractedByTextbox],
     canvas,
   );
 
@@ -63,7 +58,10 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -93,7 +91,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Name text render */
 
     if (options.personName !== null) {
diff --git a/frontend/src/views/back_in_full_force_tour_social/canvas.js b/frontend/src/views/back_in_full_force_tour_social/canvas.js
index 50862e73c0f93a600eefc2dc77c27a123d0dc204..1389ea9b8c93c6960b0a9f9308b9b9bb02023330 100644
--- a/frontend/src/views/back_in_full_force_tour_social/canvas.js
+++ b/frontend/src/views/back_in_full_force_tour_social/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -43,7 +43,7 @@ const redraw = async (canvas, options) => {
       attendeesTextBox,
       dateTextBox,
       timeTextBox,
-      locationTextBox
+      locationTextBox,
     ],
     canvas,
   );
@@ -63,11 +63,16 @@ const redraw = async (canvas, options) => {
   const textMarginLeft = Math.ceil(canvas.width * 0.1);
   const textMarginRight = Math.ceil(canvas.width * 0.078);
 
-  let mainTextMarginBottom = Math.ceil(canvas.height * 0.35) + Math.ceil(canvas.height * 0.15);
-  const dateTextMarginBottom = Math.ceil(canvas.height * 0.63) + Math.ceil(canvas.height * 0.25);
-  const timeTextBoxMarginBottom = Math.ceil(canvas.height * 0.565) + Math.ceil(canvas.height * 0.235);
-  const locationTextBoxMarginBottom = Math.ceil(canvas.height * 0.32) + Math.ceil(canvas.height * 0.14);
-  const attendeesTextBoxMarginBottom = Math.ceil(canvas.height * 0.1) + Math.ceil(canvas.height * 0.11);
+  let mainTextMarginBottom =
+    Math.ceil(canvas.height * 0.35) + Math.ceil(canvas.height * 0.15);
+  const dateTextMarginBottom =
+    Math.ceil(canvas.height * 0.63) + Math.ceil(canvas.height * 0.25);
+  const timeTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.565) + Math.ceil(canvas.height * 0.235);
+  const locationTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.32) + Math.ceil(canvas.height * 0.14);
+  const attendeesTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.1) + Math.ceil(canvas.height * 0.11);
 
   const mainTextSize = Math.ceil(canvas.height * 0.185);
   const mainTextLineHeight = 0.8;
@@ -121,12 +126,11 @@ const redraw = async (canvas, options) => {
     const dateTextBoxTop =
       canvas.height - dateTextBox.height - dateTextMarginBottom;
 
-      dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
+    dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
 
-    const dateTextBoxLeft =
-      canvas.width - dateTextBox.width - textMarginRight;
+    const dateTextBoxLeft = canvas.width - dateTextBox.width - textMarginRight;
 
-      dateTextBox.left = dateTextBoxLeft;
+    dateTextBox.left = dateTextBoxLeft;
 
     canvas.renderAll();
 
@@ -170,8 +174,7 @@ const redraw = async (canvas, options) => {
 
     timeTextBox.top = timeTextBoxTop - highlightedData.paddingBottom;
 
-    const timeTextBoxLeft =
-      canvas.width - timeTextBox.width - textMarginRight;
+    const timeTextBoxLeft = canvas.width - timeTextBox.width - textMarginRight;
 
     timeTextBox.left = timeTextBoxLeft;
 
@@ -213,14 +216,22 @@ const redraw = async (canvas, options) => {
     canvas.add(attendeesTextBox);
 
     const attendeesTextBoxTop =
-      canvas.height - attendeesTextBox.height - attendeesTextBoxMarginBottom + (attendeesTextSize * attendeesTextLineHeight * (attendeesTextBox.textLines.length - 1)) + ((3 - attendeesTextBox.textLines.length - 1) * attendeesTextSize * attendeesTextLineHeight);
+      canvas.height -
+      attendeesTextBox.height -
+      attendeesTextBoxMarginBottom +
+      attendeesTextSize *
+        attendeesTextLineHeight *
+        (attendeesTextBox.textLines.length - 1) +
+      (3 - attendeesTextBox.textLines.length - 1) *
+        attendeesTextSize *
+        attendeesTextLineHeight;
 
     attendeesTextBox.top = attendeesTextBoxTop - highlightedData.paddingBottom;
 
     const attendeesTextBoxLeft =
       canvas.width - attendeesTextBox.width - textMarginRight;
 
-      attendeesTextBox.left = attendeesTextBoxLeft;
+    attendeesTextBox.left = attendeesTextBoxLeft;
 
     canvas.renderAll();
 
@@ -230,7 +241,11 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value || !canvas.getObjects().includes(backgroundImage)) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value ||
+      !canvas.getObjects().includes(backgroundImage)
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -260,7 +275,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Main text render */
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
@@ -294,12 +308,17 @@ const redraw = async (canvas, options) => {
     canvas.add(mainTextBox);
 
     const mainTextBoxTop =
-      canvas.height - mainTextBox.height - mainTextMarginBottom + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
+      canvas.height -
+      mainTextBox.height -
+      mainTextMarginBottom +
+      mainTextSize *
+        (mainTextBox._textLines.length - 1) *
+        0.9 *
+        mainTextLineHeight;
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
-    const mainTextBoxLeft =
-      canvas.width - mainTextBox.width - textMarginRight;
+    const mainTextBoxLeft = canvas.width - mainTextBox.width - textMarginRight;
 
     mainTextBox.left = mainTextBoxLeft;
 
@@ -309,9 +328,9 @@ const redraw = async (canvas, options) => {
 
     if (options.locationText !== null) {
       /* BEGIN Location text render */
-  
+
       const locationTextWidth = canvas.width - textMarginLeft - textMarginRight;
-  
+
       const highlightedData = transformHighlightedText(
         options.locationText,
         locationTextSize,
@@ -321,7 +340,7 @@ const redraw = async (canvas, options) => {
         options.colors.highlight.value,
         { padWhenDiacritics: false, invertHighlight: true },
       );
-  
+
       locationTextBox = new PaddedHighlightingTextbox(highlightedData.text, {
         width: canvas.width,
         left: 0,
@@ -335,26 +354,34 @@ const redraw = async (canvas, options) => {
         highlightPadding: canvas.height * 0.003,
         zIndex: 20,
       });
-  
+
       checkTextBoxHeight(locationTextBox, 2);
-  
+
       canvas.add(locationTextBox);
-  
+
       canvas.renderAll();
-  
+
       const locationTextBoxTop =
-        canvas.height - locationTextBox.height - locationTextBoxMarginBottom + (locationTextSize * locationTextLineHeight * (locationTextBox.textLines.length - 1))
-        + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
-  
+        canvas.height -
+        locationTextBox.height -
+        locationTextBoxMarginBottom +
+        locationTextSize *
+          locationTextLineHeight *
+          (locationTextBox.textLines.length - 1) +
+        mainTextSize *
+          (mainTextBox._textLines.length - 1) *
+          0.9 *
+          mainTextLineHeight;
+
       locationTextBox.top = locationTextBoxTop - highlightedData.paddingBottom;
-  
+
       const locationTextBoxLeft =
         canvas.width - locationTextBox.width - textMarginRight;
-  
+
       locationTextBox.left = locationTextBoxLeft;
-  
+
       canvas.renderAll();
-  
+
       /* END Location text render */
     }
   }
@@ -444,10 +471,10 @@ const redraw = async (canvas, options) => {
     pointerDownEventAssigned = true;
   }
 
-  const colors = {...options.colors};
+  const colors = { ...options.colors };
 
   upEventFunction = (event) => {
-    redraw(canvas, {...options, colors: colors});
+    redraw(canvas, { ...options, colors: colors });
   };
 
   document
diff --git a/frontend/src/views/back_in_full_force_tour_social_A2/canvas.js b/frontend/src/views/back_in_full_force_tour_social_A2/canvas.js
index cc5fe2db6e32572f5c6f28b706acdde8fc1645bf..be375fc7a726ee4a53842cb59aaf0ca08d63e2d1 100644
--- a/frontend/src/views/back_in_full_force_tour_social_A2/canvas.js
+++ b/frontend/src/views/back_in_full_force_tour_social_A2/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -43,7 +43,7 @@ const redraw = async (canvas, options) => {
       attendeesTextBox,
       dateTextBox,
       timeTextBox,
-      locationTextBox
+      locationTextBox,
     ],
     canvas,
   );
@@ -63,11 +63,16 @@ const redraw = async (canvas, options) => {
   const textMarginLeft = Math.ceil(canvas.width * 0.1);
   const textMarginRight = Math.ceil(canvas.width * 0.078);
 
-  let mainTextMarginBottom = Math.ceil(canvas.height * 0.3) + Math.ceil(canvas.height * 0.13);
-  const dateTextMarginBottom = Math.ceil(canvas.height * 0.605) + Math.ceil(canvas.height * 0.305);
-  const timeTextBoxMarginBottom = Math.ceil(canvas.height * 0.535) + Math.ceil(canvas.height * 0.312);
-  const locationTextBoxMarginBottom = Math.ceil(canvas.height * 0.27) + Math.ceil(canvas.height * 0.12);
-  const attendeesTextBoxMarginBottom = Math.ceil(canvas.height * 0.09) + Math.ceil(canvas.height * 0.075);
+  let mainTextMarginBottom =
+    Math.ceil(canvas.height * 0.3) + Math.ceil(canvas.height * 0.13);
+  const dateTextMarginBottom =
+    Math.ceil(canvas.height * 0.605) + Math.ceil(canvas.height * 0.305);
+  const timeTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.535) + Math.ceil(canvas.height * 0.312);
+  const locationTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.27) + Math.ceil(canvas.height * 0.12);
+  const attendeesTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.09) + Math.ceil(canvas.height * 0.075);
 
   const mainTextSize = Math.ceil(canvas.height * 0.175);
   const mainTextLineHeight = 0.8;
@@ -121,12 +126,11 @@ const redraw = async (canvas, options) => {
     const dateTextBoxTop =
       canvas.height - dateTextBox.height - dateTextMarginBottom;
 
-      dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
+    dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
 
-    const dateTextBoxLeft =
-      canvas.width - dateTextBox.width - textMarginRight;
+    const dateTextBoxLeft = canvas.width - dateTextBox.width - textMarginRight;
 
-      dateTextBox.left = dateTextBoxLeft;
+    dateTextBox.left = dateTextBoxLeft;
 
     canvas.renderAll();
 
@@ -170,8 +174,7 @@ const redraw = async (canvas, options) => {
 
     timeTextBox.top = timeTextBoxTop - highlightedData.paddingBottom;
 
-    const timeTextBoxLeft =
-      canvas.width - timeTextBox.width - textMarginRight;
+    const timeTextBoxLeft = canvas.width - timeTextBox.width - textMarginRight;
 
     timeTextBox.left = timeTextBoxLeft;
 
@@ -213,14 +216,22 @@ const redraw = async (canvas, options) => {
     canvas.add(attendeesTextBox);
 
     const attendeesTextBoxTop =
-      canvas.height - attendeesTextBox.height - attendeesTextBoxMarginBottom + (attendeesTextSize * attendeesTextLineHeight * (attendeesTextBox.textLines.length - 1)) + ((3 - attendeesTextBox.textLines.length - 1) * attendeesTextSize * attendeesTextLineHeight);
+      canvas.height -
+      attendeesTextBox.height -
+      attendeesTextBoxMarginBottom +
+      attendeesTextSize *
+        attendeesTextLineHeight *
+        (attendeesTextBox.textLines.length - 1) +
+      (3 - attendeesTextBox.textLines.length - 1) *
+        attendeesTextSize *
+        attendeesTextLineHeight;
 
     attendeesTextBox.top = attendeesTextBoxTop - highlightedData.paddingBottom;
 
     const attendeesTextBoxLeft =
       canvas.width - attendeesTextBox.width - textMarginRight;
 
-      attendeesTextBox.left = attendeesTextBoxLeft;
+    attendeesTextBox.left = attendeesTextBoxLeft;
 
     canvas.renderAll();
 
@@ -230,7 +241,11 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value || !canvas.getObjects().includes(backgroundImage)) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value ||
+      !canvas.getObjects().includes(backgroundImage)
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -260,7 +275,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Main text render */
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
@@ -294,12 +308,17 @@ const redraw = async (canvas, options) => {
     canvas.add(mainTextBox);
 
     const mainTextBoxTop =
-      canvas.height - mainTextBox.height - mainTextMarginBottom + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
+      canvas.height -
+      mainTextBox.height -
+      mainTextMarginBottom +
+      mainTextSize *
+        (mainTextBox._textLines.length - 1) *
+        0.9 *
+        mainTextLineHeight;
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
-    const mainTextBoxLeft =
-      canvas.width - mainTextBox.width - textMarginRight;
+    const mainTextBoxLeft = canvas.width - mainTextBox.width - textMarginRight;
 
     mainTextBox.left = mainTextBoxLeft;
 
@@ -309,9 +328,9 @@ const redraw = async (canvas, options) => {
 
     if (options.locationText !== null) {
       /* BEGIN Location text render */
-  
+
       const locationTextWidth = canvas.width - textMarginLeft - textMarginRight;
-  
+
       const highlightedData = transformHighlightedText(
         options.locationText,
         locationTextSize,
@@ -321,7 +340,7 @@ const redraw = async (canvas, options) => {
         options.colors.highlight.value,
         { padWhenDiacritics: false, invertHighlight: true },
       );
-  
+
       locationTextBox = new PaddedHighlightingTextbox(highlightedData.text, {
         width: canvas.width,
         left: 0,
@@ -335,26 +354,34 @@ const redraw = async (canvas, options) => {
         highlightPadding: canvas.height * 0.003,
         zIndex: 20,
       });
-  
+
       checkTextBoxHeight(locationTextBox, 2);
-  
+
       canvas.add(locationTextBox);
-  
+
       canvas.renderAll();
-  
+
       const locationTextBoxTop =
-        canvas.height - locationTextBox.height - locationTextBoxMarginBottom + (locationTextSize * locationTextLineHeight * (locationTextBox.textLines.length - 1))
-        + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
-  
+        canvas.height -
+        locationTextBox.height -
+        locationTextBoxMarginBottom +
+        locationTextSize *
+          locationTextLineHeight *
+          (locationTextBox.textLines.length - 1) +
+        mainTextSize *
+          (mainTextBox._textLines.length - 1) *
+          0.9 *
+          mainTextLineHeight;
+
       locationTextBox.top = locationTextBoxTop - highlightedData.paddingBottom;
-  
+
       const locationTextBoxLeft =
         canvas.width - locationTextBox.width - textMarginRight;
-  
+
       locationTextBox.left = locationTextBoxLeft;
-  
+
       canvas.renderAll();
-  
+
       /* END Location text render */
     }
   }
@@ -444,10 +471,10 @@ const redraw = async (canvas, options) => {
     pointerDownEventAssigned = true;
   }
 
-  const colors = {...options.colors};
+  const colors = { ...options.colors };
 
   upEventFunction = (event) => {
-    redraw(canvas, {...options, colors: colors});
+    redraw(canvas, { ...options, colors: colors });
   };
 
   document
diff --git a/frontend/src/views/back_in_full_force_tour_social_wide/canvas.js b/frontend/src/views/back_in_full_force_tour_social_wide/canvas.js
index f9aa91b6079e3b66a4dbc7ed0fb6727a3cea85b7..f28b57c1be0ed9f916f472b0727e3bf2aec7a964 100644
--- a/frontend/src/views/back_in_full_force_tour_social_wide/canvas.js
+++ b/frontend/src/views/back_in_full_force_tour_social_wide/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -43,7 +43,7 @@ const redraw = async (canvas, options) => {
       attendeesTextBox,
       dateTextBox,
       timeTextBox,
-      locationTextBox
+      locationTextBox,
     ],
     canvas,
   );
@@ -63,13 +63,18 @@ const redraw = async (canvas, options) => {
   const textMarginLeft = Math.ceil(canvas.width * 0.1);
   const textMarginRight = Math.ceil(canvas.width * 0.04);
 
-  let mainTextMarginBottom = Math.ceil(canvas.height * 0.47) + Math.ceil(canvas.height * 0.02);
-  const dateTextMarginBottom = Math.ceil(canvas.height * 0.8) + Math.ceil(canvas.height * 0.069);
-  const timeTextBoxMarginBottom = Math.ceil(canvas.height * 0.715) + Math.ceil(canvas.height * 0.075);
-  const locationTextBoxMarginBottom = Math.ceil(canvas.height * 0.43) + Math.ceil(canvas.height * 0.02);
-  const attendeesTextBoxMarginBottom = Math.ceil(canvas.height * 0.11) + Math.ceil(canvas.height * 0.12);
-
-  const mainTextSize = Math.ceil(canvas.height * 0.210);
+  let mainTextMarginBottom =
+    Math.ceil(canvas.height * 0.47) + Math.ceil(canvas.height * 0.02);
+  const dateTextMarginBottom =
+    Math.ceil(canvas.height * 0.8) + Math.ceil(canvas.height * 0.069);
+  const timeTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.715) + Math.ceil(canvas.height * 0.075);
+  const locationTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.43) + Math.ceil(canvas.height * 0.02);
+  const attendeesTextBoxMarginBottom =
+    Math.ceil(canvas.height * 0.11) + Math.ceil(canvas.height * 0.12);
+
+  const mainTextSize = Math.ceil(canvas.height * 0.21);
   const mainTextLineHeight = 1;
 
   const dateTextSize = Math.ceil(canvas.height * 0.062);
@@ -121,12 +126,11 @@ const redraw = async (canvas, options) => {
     const dateTextBoxTop =
       canvas.height - dateTextBox.height - dateTextMarginBottom;
 
-      dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
+    dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
 
-    const dateTextBoxLeft =
-      canvas.width - dateTextBox.width - textMarginRight;
+    const dateTextBoxLeft = canvas.width - dateTextBox.width - textMarginRight;
 
-      dateTextBox.left = dateTextBoxLeft;
+    dateTextBox.left = dateTextBoxLeft;
 
     canvas.renderAll();
 
@@ -170,8 +174,7 @@ const redraw = async (canvas, options) => {
 
     timeTextBox.top = timeTextBoxTop - highlightedData.paddingBottom;
 
-    const timeTextBoxLeft =
-      canvas.width - timeTextBox.width - textMarginRight;
+    const timeTextBoxLeft = canvas.width - timeTextBox.width - textMarginRight;
 
     timeTextBox.left = timeTextBoxLeft;
 
@@ -216,7 +219,12 @@ const redraw = async (canvas, options) => {
     canvas.renderAll();
 
     const locationTextBoxTop =
-      canvas.height - locationTextBox.height - locationTextBoxMarginBottom + (locationTextSize * locationTextLineHeight * (locationTextBox.textLines.length - 1));
+      canvas.height -
+      locationTextBox.height -
+      locationTextBoxMarginBottom +
+      locationTextSize *
+        locationTextLineHeight *
+        (locationTextBox.textLines.length - 1);
 
     locationTextBox.top = locationTextBoxTop - highlightedData.paddingBottom;
 
@@ -263,14 +271,22 @@ const redraw = async (canvas, options) => {
     canvas.add(attendeesTextBox);
 
     const attendeesTextBoxTop =
-      canvas.height - attendeesTextBox.height - attendeesTextBoxMarginBottom + (attendeesTextSize * attendeesTextLineHeight * (attendeesTextBox.textLines.length - 1)) + ((3 - attendeesTextBox.textLines.length - 1) * attendeesTextSize * attendeesTextLineHeight);
+      canvas.height -
+      attendeesTextBox.height -
+      attendeesTextBoxMarginBottom +
+      attendeesTextSize *
+        attendeesTextLineHeight *
+        (attendeesTextBox.textLines.length - 1) +
+      (3 - attendeesTextBox.textLines.length - 1) *
+        attendeesTextSize *
+        attendeesTextLineHeight;
 
     attendeesTextBox.top = attendeesTextBoxTop - highlightedData.paddingBottom;
 
     const attendeesTextBoxLeft =
       canvas.width - attendeesTextBox.width - textMarginRight;
 
-      attendeesTextBox.left = attendeesTextBoxLeft;
+    attendeesTextBox.left = attendeesTextBoxLeft;
 
     canvas.renderAll();
 
@@ -280,7 +296,11 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value || !canvas.getObjects().includes(backgroundImage)) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value ||
+      !canvas.getObjects().includes(backgroundImage)
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -310,7 +330,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Main text render */
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
@@ -348,8 +367,7 @@ const redraw = async (canvas, options) => {
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
-    const mainTextBoxLeft =
-      canvas.width - mainTextBox.width - textMarginRight;
+    const mainTextBoxLeft = canvas.width - mainTextBox.width - textMarginRight;
 
     mainTextBox.left = mainTextBoxLeft;
 
@@ -443,10 +461,10 @@ const redraw = async (canvas, options) => {
     pointerDownEventAssigned = true;
   }
 
-  const colors = {...options.colors};
+  const colors = { ...options.colors };
 
   upEventFunction = (event) => {
-    redraw(canvas, {...options, colors: colors});
+    redraw(canvas, { ...options, colors: colors });
   };
 
   document
diff --git a/frontend/src/views/base_event/canvas.js b/frontend/src/views/base_event/canvas.js
index b6adcaa6bbd9addddfd2c1738ab84d3502c96e99..d10487b9feacd51446effaa2dcd51715c93bb03a 100644
--- a/frontend/src/views/base_event/canvas.js
+++ b/frontend/src/views/base_event/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/base_person_event/canvas.js b/frontend/src/views/base_person_event/canvas.js
index 72f3aba7b8d9d8e74b14d3fe90d1811509f70e32..679f5fb28ce1a76789e79d54286103dcc9c98837 100644
--- a/frontend/src/views/base_person_event/canvas.js
+++ b/frontend/src/views/base_person_event/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/basic_photo_banner/BasicPhotoBannerIG.vue b/frontend/src/views/basic_photo_banner/BasicPhotoBannerIG.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9fc9bc15cd6d3bab99ab93561a4f6180999df819
--- /dev/null
+++ b/frontend/src/views/basic_photo_banner/BasicPhotoBannerIG.vue
@@ -0,0 +1,256 @@
+<script setup>
+import { watch, ref } from "vue";
+
+import COLORS from "../../colors";
+import PEOPLE from "../../people";
+import TEMPLATES from "../../templates";
+import DEFAULT_CONTRACTOR from "../../contractors";
+import {
+  generateDefaultLogos,
+  LOGO_POSITIONS,
+  generateLogoPositions,
+} from "../../logos";
+import {
+  loadFonts,
+  loadCanvasStorage,
+  setCanvasStorage,
+  updateAutoRedrawStorage,
+} from "../../utils";
+
+import Canvas from "../../components/canvas/Canvas.vue";
+import redraw from "./canvas";
+
+import Navbar from "../../components/Navbar.vue";
+import MainContainer from "../../components/MainContainer.vue";
+import ImageInput from "../../components/inputs/ImageInput.vue";
+import LongTextInput from "../../components/inputs/text/LongTextInput.vue";
+import ShortTextInput from "../../components/inputs/text/ShortTextInput.vue";
+import RangeInput from "../../components/inputs/RangeInput.vue";
+import InputSeparator from "../../components/inputs/InputSeparator.vue";
+import SelectInput from "../../components/inputs/SelectInput.vue";
+import MultipleColorPicker from "../../components/inputs/colors/MultipleColorPicker.vue";
+import ReloadButton from "../../components/reload/ReloadButton.vue";
+import AutoReloadCheckbox from "../../components/reload/AutoReloadCheckbox.vue";
+</script>
+
+<script>
+await loadFonts([
+  "12px Bebas Neue",
+  "12px Roboto Condensed",
+  "bold 12px Roboto Condensed",
+]);
+
+export default {
+  components: {
+    Canvas,
+    Navbar,
+    MainContainer,
+    ImageInput,
+    LongTextInput,
+    ShortTextInput,
+    RangeInput,
+    SelectInput,
+    InputSeparator,
+    MultipleColorPicker,
+  },
+  data() {
+    const predefinedColors = {
+      base: {
+        name: "Základní barvy",
+        colors: {
+          background: COLORS.black,
+          highlight: COLORS.yellow1,
+          arrow: COLORS.yellow1,
+          baseText: COLORS.white,
+          highlightedText: COLORS.black,
+          contractedByText: COLORS.gray1,
+        },
+      },
+    };
+
+    return {
+      mainImage: null,
+      mainText: null,
+      personName: null,
+      personPosition: null,
+      contractedBy: DEFAULT_CONTRACTOR,
+      logoImage: null,
+      logoPosition: LOGO_POSITIONS.top_right,
+      logoOptions: generateLogoPositions(["top_right", "top_left"]),
+      gradientHeightMultiplier: 1,
+      colorLabels: {
+        background: "Pozadí",
+        highlight: "Zvýraznění",
+        arrow: "Šipka",
+        baseText: "Text",
+        highlightedText: "Zvýrazněný text",
+      },
+      predefinedColors: predefinedColors,
+      colors: predefinedColors.base.colors,
+      predefinedLogoImages: generateDefaultLogos("defaultLight"),
+      autoRedraw: false,
+    };
+  },
+  async created() {
+    await loadCanvasStorage(this);
+  },
+  methods: {
+    async reloadCanvasProperties() {
+      const canvasProperties = {
+        mainImage: this.mainImage,
+        mainText: this.mainText,
+        logoPosition: this.logoPosition,
+        personName: this.personName,
+        personPosition: this.personPosition,
+        contractedBy: this.contractedBy,
+        logoImage: this.logoImage,
+        gradientHeightMultiplier: this.gradientHeightMultiplier,
+        colors: this.colors,
+      };
+
+      if (canvasProperties.mainText) {
+        window.fileName = canvasProperties.mainText;
+      }
+
+      await this.$refs.canvas.redraw(canvasProperties);
+
+      delete canvasProperties.colors;
+      setCanvasStorage(canvasProperties);
+    },
+  },
+  mounted() {
+    this.$watch(
+      (vm) => [
+        vm.mainImage,
+        vm.mainText,
+        vm.logoPosition,
+        vm.personName,
+        vm.personPosition,
+        vm.contractedBy,
+        vm.logoImage,
+        vm.gradientHeightMultiplier,
+        vm.colors,
+      ],
+      async (value) => {
+        if (this.autoRedraw) {
+          await this.reloadCanvasProperties();
+        }
+      },
+      {
+        immediate: true,
+        deep: true,
+      },
+    );
+
+    this.$watch(
+      (vm) => [vm.autoRedraw],
+      async (value) => {
+        updateAutoRedrawStorage(this.autoRedraw);
+
+        if (this.autoRedraw) {
+          await this.reloadCanvasProperties();
+        }
+      },
+    );
+  },
+};
+</script>
+
+<template>
+  <header>
+    <Navbar :defaultTemplate="TEMPLATES.basic_photo_banner"></Navbar>
+  </header>
+  <main>
+    <MainContainer>
+      <template v-slot:left>
+        <Canvas
+          ref="canvas"
+          :redrawFunction="redraw"
+          width="1080"
+          height="1350"
+        />
+      </template>
+
+      <template v-slot:right>
+        <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" />
+        <AutoReloadCheckbox v-model="autoRedraw" />
+        <ImageInput
+          name="Obrázek"
+          v-model="mainImage"
+          :important="true"
+          zIndex="10"
+        />
+        <LongTextInput
+          name="Hlavní text"
+          v-model="mainText"
+          :important="true"
+          :highlightable="true"
+          zIndex="9"
+        />
+        <ShortTextInput
+          name="Jméno osoby"
+          v-model="personName"
+          v-model:relatedModel="personPosition"
+          :predefinedValues="PEOPLE"
+          :important="true"
+          zIndex="8"
+        />
+        <LongTextInput
+          ref="refPersonPosition"
+          name="Pozice osoby"
+          v-model="personPosition"
+          :important="false"
+          zIndex="7"
+        />
+
+        <InputSeparator />
+
+        <ImageInput
+          name="Obrázek loga"
+          v-model="logoImage"
+          :important="false"
+          :predefinedImages="predefinedLogoImages"
+          :mustSelectPredefinedImage="true"
+          :disableImageInput="true"
+          zIndex="7"
+        />
+
+        <SelectInput
+          name="Pozice loga"
+          :options="logoOptions"
+          v-model="logoPosition"
+          zIndex="6"
+        />
+
+        <RangeInput
+          name="Výška gradientu"
+          v-model="gradientHeightMultiplier"
+          min="0"
+          max="3"
+        />
+
+        <MultipleColorPicker
+          name="Barvy"
+          v-model="colors"
+          :important="false"
+          :colorLabels="colorLabels"
+          :predefinedColors="predefinedColors"
+          :defaultPredefinedColors="predefinedColors.base"
+          zIndex="5"
+        ></MultipleColorPicker>
+
+        <ShortTextInput
+          name="Zadavatel a zpracovatel"
+          v-model="contractedBy"
+          :defaultValue="DEFAULT_CONTRACTOR"
+          :important="false"
+          zIndex="4"
+        />
+      </template>
+    </MainContainer>
+  </main>
+</template>
+
+<style>
+@import "vue-select/dist/vue-select.css";
+</style>
diff --git a/frontend/src/views/basic_photo_banner/canvas.js b/frontend/src/views/basic_photo_banner/canvas.js
index 305a61e670ab2ed10589d161641ea76b35a55593..80788a896dafd06eea6b820c3bde159fc7d19d97 100644
--- a/frontend/src/views/basic_photo_banner/canvas.js
+++ b/frontend/src/views/basic_photo_banner/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/event_poster/canvas.js b/frontend/src/views/event_poster/canvas.js
index 96259ebecb6e358291660e7fd5824254148bbaa4..e3113dc983979a1f4bd420f0fb5e65472adcf052 100644
--- a/frontend/src/views/event_poster/canvas.js
+++ b/frontend/src/views/event_poster/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/facebook_survey/canvas.js b/frontend/src/views/facebook_survey/canvas.js
index 652c0026dd56801785c44968df240025db691e08..12dbb51f7ca5f054752086d7d3520b8753922a1a 100644
--- a/frontend/src/views/facebook_survey/canvas.js
+++ b/frontend/src/views/facebook_survey/canvas.js
@@ -1,7 +1,7 @@
 import alertifyjs from "alertifyjs";
 import "alertifyjs/build/css/alertify.css";
 
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/make_a_wish_banner/canvas.js b/frontend/src/views/make_a_wish_banner/canvas.js
index 883243c9b90d7af4edb14ee609c2f6677ee93104..39230366ba10a824c002c14b7cfbc0fb843f2416 100644
--- a/frontend/src/views/make_a_wish_banner/canvas.js
+++ b/frontend/src/views/make_a_wish_banner/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -71,7 +71,10 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -101,7 +104,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Name text render */
 
     if (options.personName !== null) {
@@ -169,11 +171,17 @@ const redraw = async (canvas, options) => {
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
     let mainTextLeft = textMarginLeft;
-    
+
     if (personNameText !== null) {
       mainTextLeft = Math.max(
-        personNameText.left + getSingleLineTextBoxWidth(options.personName, bottomTextSize, "Roboto Condensed") + 150,
-        mainTextLeft
+        personNameText.left +
+          getSingleLineTextBoxWidth(
+            options.personName,
+            bottomTextSize,
+            "Roboto Condensed",
+          ) +
+          150,
+        mainTextLeft,
       );
     }
 
@@ -206,7 +214,13 @@ const redraw = async (canvas, options) => {
     canvas.add(mainTextBox);
 
     const mainTextBoxTop =
-      canvas.height - mainTextBox.height - mainTextMarginBottom + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.5 * mainTextLineHeight);
+      canvas.height -
+      mainTextBox.height -
+      mainTextMarginBottom +
+      mainTextSize *
+        (mainTextBox._textLines.length - 1) *
+        0.5 *
+        mainTextLineHeight;
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
@@ -216,7 +230,10 @@ const redraw = async (canvas, options) => {
 
     /* BEGIN Line render */
 
-    if (lineImage === null || options.colors.background.value != previousBackgroundImageColor.value) {
+    if (
+      lineImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value
+    ) {
       lineImage = new Image();
 
       await new Promise((resolve) => {
diff --git a/frontend/src/views/make_a_wish_tour_social/canvas.js b/frontend/src/views/make_a_wish_tour_social/canvas.js
index 32a5bb4c11233ba2367c3d258cf65aec08b9efb1..9299de08e30b9e4ddb8db66ddd7ae98bc5f0485c 100644
--- a/frontend/src/views/make_a_wish_tour_social/canvas.js
+++ b/frontend/src/views/make_a_wish_tour_social/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -43,7 +43,7 @@ const redraw = async (canvas, options) => {
       attendeesTextBox,
       dateTextBox,
       timeTextBox,
-      locationTextBox
+      locationTextBox,
     ],
     canvas,
   );
@@ -121,12 +121,11 @@ const redraw = async (canvas, options) => {
     const dateTextBoxTop =
       canvas.height - dateTextBox.height - dateTextMarginBottom;
 
-      dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
+    dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
 
-    const dateTextBoxLeft =
-      canvas.width - dateTextBox.width - textMarginRight;
+    const dateTextBoxLeft = canvas.width - dateTextBox.width - textMarginRight;
 
-      dateTextBox.left = dateTextBoxLeft;
+    dateTextBox.left = dateTextBoxLeft;
 
     canvas.renderAll();
 
@@ -170,8 +169,7 @@ const redraw = async (canvas, options) => {
 
     timeTextBox.top = timeTextBoxTop - highlightedData.paddingBottom;
 
-    const timeTextBoxLeft =
-      canvas.width - timeTextBox.width - textMarginRight;
+    const timeTextBoxLeft = canvas.width - timeTextBox.width - textMarginRight;
 
     timeTextBox.left = timeTextBoxLeft;
 
@@ -213,14 +211,19 @@ const redraw = async (canvas, options) => {
     canvas.add(attendeesTextBox);
 
     const attendeesTextBoxTop =
-      canvas.height - attendeesTextBox.height - attendeesTextBoxMarginBottom + (attendeesTextSize * attendeesTextLineHeight * (attendeesTextBox.textLines.length - 1));
+      canvas.height -
+      attendeesTextBox.height -
+      attendeesTextBoxMarginBottom +
+      attendeesTextSize *
+        attendeesTextLineHeight *
+        (attendeesTextBox.textLines.length - 1);
 
     attendeesTextBox.top = attendeesTextBoxTop - highlightedData.paddingBottom;
 
     const attendeesTextBoxLeft =
       canvas.width - attendeesTextBox.width - textMarginRight;
 
-      attendeesTextBox.left = attendeesTextBoxLeft;
+    attendeesTextBox.left = attendeesTextBoxLeft;
 
     canvas.renderAll();
 
@@ -230,7 +233,11 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value || !canvas.getObjects().includes(backgroundImage)) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value ||
+      !canvas.getObjects().includes(backgroundImage)
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -260,7 +267,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Main text render */
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
@@ -294,12 +300,17 @@ const redraw = async (canvas, options) => {
     canvas.add(mainTextBox);
 
     const mainTextBoxTop =
-      canvas.height - mainTextBox.height - mainTextMarginBottom + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
+      canvas.height -
+      mainTextBox.height -
+      mainTextMarginBottom +
+      mainTextSize *
+        (mainTextBox._textLines.length - 1) *
+        0.9 *
+        mainTextLineHeight;
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
-    const mainTextBoxLeft =
-      canvas.width - mainTextBox.width - textMarginRight;
+    const mainTextBoxLeft = canvas.width - mainTextBox.width - textMarginRight;
 
     mainTextBox.left = mainTextBoxLeft;
 
@@ -309,9 +320,9 @@ const redraw = async (canvas, options) => {
 
     if (options.locationText !== null) {
       /* BEGIN Location text render */
-  
+
       const locationTextWidth = canvas.width - textMarginLeft - textMarginRight;
-  
+
       const highlightedData = transformHighlightedText(
         options.locationText,
         locationTextSize,
@@ -321,7 +332,7 @@ const redraw = async (canvas, options) => {
         options.colors.highlight.value,
         { padWhenDiacritics: false, invertHighlight: true },
       );
-  
+
       locationTextBox = new PaddedHighlightingTextbox(highlightedData.text, {
         width: canvas.width,
         left: 0,
@@ -335,26 +346,34 @@ const redraw = async (canvas, options) => {
         highlightPadding: canvas.height * 0.003,
         zIndex: 20,
       });
-  
+
       checkTextBoxHeight(locationTextBox, 2);
-  
+
       canvas.add(locationTextBox);
-  
+
       canvas.renderAll();
-  
+
       const locationTextBoxTop =
-        canvas.height - locationTextBox.height - locationTextBoxMarginBottom + (locationTextSize * locationTextLineHeight * (locationTextBox.textLines.length - 1))
-        + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
-  
+        canvas.height -
+        locationTextBox.height -
+        locationTextBoxMarginBottom +
+        locationTextSize *
+          locationTextLineHeight *
+          (locationTextBox.textLines.length - 1) +
+        mainTextSize *
+          (mainTextBox._textLines.length - 1) *
+          0.9 *
+          mainTextLineHeight;
+
       locationTextBox.top = locationTextBoxTop - highlightedData.paddingBottom;
-  
+
       const locationTextBoxLeft =
         canvas.width - locationTextBox.width - textMarginRight;
-  
+
       locationTextBox.left = locationTextBoxLeft;
-  
+
       canvas.renderAll();
-  
+
       /* END Location text render */
     }
   }
@@ -444,10 +463,10 @@ const redraw = async (canvas, options) => {
     pointerDownEventAssigned = true;
   }
 
-  const colors = {...options.colors};
+  const colors = { ...options.colors };
 
   upEventFunction = (event) => {
-    redraw(canvas, {...options, colors: colors});
+    redraw(canvas, { ...options, colors: colors });
   };
 
   document
diff --git a/frontend/src/views/make_a_wish_tour_social_A2/canvas.js b/frontend/src/views/make_a_wish_tour_social_A2/canvas.js
index 968d4909f2863fbeb079f5b03cb5bae5dbeb414f..41983aca11434f319394e72735a8e851a3ac6613 100644
--- a/frontend/src/views/make_a_wish_tour_social_A2/canvas.js
+++ b/frontend/src/views/make_a_wish_tour_social_A2/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -43,7 +43,7 @@ const redraw = async (canvas, options) => {
       attendeesTextBox,
       dateTextBox,
       timeTextBox,
-      locationTextBox
+      locationTextBox,
     ],
     canvas,
   );
@@ -87,7 +87,7 @@ const redraw = async (canvas, options) => {
   if (options.dateText !== null) {
     /* BEGIN Date text render */
 
-    const dateTextWidth = canvas.width * 5;  // IDFK
+    const dateTextWidth = canvas.width * 5; // IDFK
 
     console.log(options);
 
@@ -121,12 +121,11 @@ const redraw = async (canvas, options) => {
     const dateTextBoxTop =
       canvas.height - dateTextBox.height - dateTextMarginBottom;
 
-      dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
+    dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
 
-    const dateTextBoxLeft =
-      canvas.width - dateTextBox.width - textMarginRight;
+    const dateTextBoxLeft = canvas.width - dateTextBox.width - textMarginRight;
 
-      dateTextBox.left = dateTextBoxLeft;
+    dateTextBox.left = dateTextBoxLeft;
 
     canvas.renderAll();
 
@@ -170,8 +169,7 @@ const redraw = async (canvas, options) => {
 
     timeTextBox.top = timeTextBoxTop - highlightedData.paddingBottom;
 
-    const timeTextBoxLeft =
-      canvas.width - timeTextBox.width - textMarginRight;
+    const timeTextBoxLeft = canvas.width - timeTextBox.width - textMarginRight;
 
     timeTextBox.left = timeTextBoxLeft;
 
@@ -213,14 +211,19 @@ const redraw = async (canvas, options) => {
     canvas.add(attendeesTextBox);
 
     const attendeesTextBoxTop =
-      canvas.height - attendeesTextBox.height - attendeesTextBoxMarginBottom + (attendeesTextSize * attendeesTextLineHeight * (attendeesTextBox.textLines.length - 1));
+      canvas.height -
+      attendeesTextBox.height -
+      attendeesTextBoxMarginBottom +
+      attendeesTextSize *
+        attendeesTextLineHeight *
+        (attendeesTextBox.textLines.length - 1);
 
     attendeesTextBox.top = attendeesTextBoxTop - highlightedData.paddingBottom;
 
     const attendeesTextBoxLeft =
       canvas.width - attendeesTextBox.width - textMarginRight;
 
-      attendeesTextBox.left = attendeesTextBoxLeft;
+    attendeesTextBox.left = attendeesTextBoxLeft;
 
     canvas.renderAll();
 
@@ -230,7 +233,11 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value || !canvas.getObjects().includes(backgroundImage)) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value ||
+      !canvas.getObjects().includes(backgroundImage)
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -260,7 +267,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Main text render */
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
@@ -316,14 +322,19 @@ const redraw = async (canvas, options) => {
 
       canvas.add(mainTextBox);
     }
-    
+
     const mainTextBoxTop =
-      canvas.height - mainTextBox.height - mainTextMarginBottom + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
+      canvas.height -
+      mainTextBox.height -
+      mainTextMarginBottom +
+      mainTextSize *
+        (mainTextBox._textLines.length - 1) *
+        0.9 *
+        mainTextLineHeight;
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
-    const mainTextBoxLeft =
-      canvas.width - mainTextBox.width - textMarginRight;
+    const mainTextBoxLeft = canvas.width - mainTextBox.width - textMarginRight;
 
     mainTextBox.left = mainTextBoxLeft;
 
@@ -333,9 +344,9 @@ const redraw = async (canvas, options) => {
 
     if (options.locationText !== null) {
       /* BEGIN Location text render */
-  
+
       const locationTextWidth = canvas.width - textMarginLeft - textMarginRight;
-  
+
       const highlightedData = transformHighlightedText(
         options.locationText,
         locationTextSize,
@@ -345,7 +356,7 @@ const redraw = async (canvas, options) => {
         options.colors.highlight.value,
         { padWhenDiacritics: false, invertHighlight: true },
       );
-  
+
       locationTextBox = new PaddedHighlightingTextbox(highlightedData.text, {
         width: canvas.width,
         left: 0,
@@ -359,26 +370,34 @@ const redraw = async (canvas, options) => {
         highlightPadding: canvas.height * 0.003,
         zIndex: 20,
       });
-  
+
       checkTextBoxHeight(locationTextBox, 2);
-  
+
       canvas.add(locationTextBox);
-  
+
       canvas.renderAll();
-  
+
       const locationTextBoxTop =
-        canvas.height - locationTextBox.height - locationTextBoxMarginBottom + (locationTextSize * locationTextLineHeight * (locationTextBox.textLines.length - 1))
-        + (mainTextSize * (mainTextBox._textLines.length - 1) * 0.9 * mainTextLineHeight);
+        canvas.height -
+        locationTextBox.height -
+        locationTextBoxMarginBottom +
+        locationTextSize *
+          locationTextLineHeight *
+          (locationTextBox.textLines.length - 1) +
+        mainTextSize *
+          (mainTextBox._textLines.length - 1) *
+          0.9 *
+          mainTextLineHeight;
 
       locationTextBox.top = locationTextBoxTop - highlightedData.paddingBottom;
-  
+
       const locationTextBoxLeft =
         canvas.width - locationTextBox.width - textMarginRight;
-  
+
       locationTextBox.left = locationTextBoxLeft;
-  
+
       canvas.renderAll();
-  
+
       /* END Location text render */
     }
   }
@@ -468,10 +487,10 @@ const redraw = async (canvas, options) => {
     pointerDownEventAssigned = true;
   }
 
-  const colors = {...options.colors};
+  const colors = { ...options.colors };
 
   upEventFunction = (event) => {
-    redraw(canvas, {...options, colors: colors});
+    redraw(canvas, { ...options, colors: colors });
   };
 
   document
diff --git a/frontend/src/views/make_a_wish_tour_social_wide/canvas.js b/frontend/src/views/make_a_wish_tour_social_wide/canvas.js
index 98fe9b0f7927f17edb58899fd42dcddd97635981..2548ba9d327d6c5a79c34fa593da0ea5112c5a4a 100644
--- a/frontend/src/views/make_a_wish_tour_social_wide/canvas.js
+++ b/frontend/src/views/make_a_wish_tour_social_wide/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
@@ -43,7 +43,7 @@ const redraw = async (canvas, options) => {
       attendeesTextBox,
       dateTextBox,
       timeTextBox,
-      locationTextBox
+      locationTextBox,
     ],
     canvas,
   );
@@ -71,7 +71,7 @@ const redraw = async (canvas, options) => {
   const locationTextBoxMarginBottom = Math.ceil(canvas.height * 0.43);
   const attendeesTextBoxMarginBottom = Math.ceil(canvas.height * 0.11);
 
-  const mainTextSize = Math.ceil(canvas.height * 0.210);
+  const mainTextSize = Math.ceil(canvas.height * 0.21);
   const mainTextLineHeight = 1;
 
   const dateTextSize = Math.ceil(canvas.height * 0.062);
@@ -89,7 +89,7 @@ const redraw = async (canvas, options) => {
   if (options.dateText !== null) {
     /* BEGIN Date text render */
 
-    const dateTextWidth = canvas.width * 5;  // IDFK
+    const dateTextWidth = canvas.width * 5; // IDFK
 
     console.log(options);
 
@@ -123,12 +123,12 @@ const redraw = async (canvas, options) => {
     const dateTextBoxTop =
       canvas.height - dateTextBox.height - dateTextMarginBottom;
 
-      dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
+    dateTextBox.top = dateTextBoxTop - highlightedData.paddingBottom;
 
     const dateTextBoxLeft =
       canvas.width - dateTextBox.width - topTextMarginRight;
 
-      dateTextBox.left = dateTextBoxLeft;
+    dateTextBox.left = dateTextBoxLeft;
 
     canvas.renderAll();
 
@@ -218,7 +218,12 @@ const redraw = async (canvas, options) => {
     canvas.renderAll();
 
     const locationTextBoxTop =
-      canvas.height - locationTextBox.height - locationTextBoxMarginBottom + (locationTextSize * locationTextLineHeight * (locationTextBox.textLines.length - 1));
+      canvas.height -
+      locationTextBox.height -
+      locationTextBoxMarginBottom +
+      locationTextSize *
+        locationTextLineHeight *
+        (locationTextBox.textLines.length - 1);
 
     locationTextBox.top = locationTextBoxTop - highlightedData.paddingBottom;
 
@@ -265,14 +270,19 @@ const redraw = async (canvas, options) => {
     canvas.add(attendeesTextBox);
 
     const attendeesTextBoxTop =
-      canvas.height - attendeesTextBox.height - attendeesTextBoxMarginBottom + (attendeesTextSize * attendeesTextLineHeight * (attendeesTextBox.textLines.length - 1));
+      canvas.height -
+      attendeesTextBox.height -
+      attendeesTextBoxMarginBottom +
+      attendeesTextSize *
+        attendeesTextLineHeight *
+        (attendeesTextBox.textLines.length - 1);
 
     attendeesTextBox.top = attendeesTextBoxTop - highlightedData.paddingBottom;
 
     const attendeesTextBoxLeft =
       canvas.width - attendeesTextBox.width - textMarginRight;
 
-      attendeesTextBox.left = attendeesTextBoxLeft;
+    attendeesTextBox.left = attendeesTextBoxLeft;
 
     canvas.renderAll();
 
@@ -282,7 +292,11 @@ const redraw = async (canvas, options) => {
   if (options.mainText !== null) {
     /* BEGIN Background render */
 
-    if (backgroundImage === null || options.colors.background.value != previousBackgroundImageColor.value || !canvas.getObjects().includes(backgroundImage)) {
+    if (
+      backgroundImage === null ||
+      options.colors.background.value != previousBackgroundImageColor.value ||
+      !canvas.getObjects().includes(backgroundImage)
+    ) {
       backgroundImage = new Image();
 
       await new Promise((resolve) => {
@@ -312,7 +326,6 @@ const redraw = async (canvas, options) => {
 
     /* END Background render */
 
-
     /* BEGIN Main text render */
 
     const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
@@ -350,8 +363,7 @@ const redraw = async (canvas, options) => {
 
     mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
 
-    const mainTextBoxLeft =
-      canvas.width - mainTextBox.width - textMarginRight;
+    const mainTextBoxLeft = canvas.width - mainTextBox.width - textMarginRight;
 
     mainTextBox.left = mainTextBoxLeft;
 
@@ -445,10 +457,10 @@ const redraw = async (canvas, options) => {
     pointerDownEventAssigned = true;
   }
 
-  const colors = {...options.colors};
+  const colors = { ...options.colors };
 
   upEventFunction = (event) => {
-    redraw(canvas, {...options, colors: colors});
+    redraw(canvas, { ...options, colors: colors });
   };
 
   document
diff --git a/frontend/src/views/nakopneme_basic_photo_banner/NakopnemeBasicPhotoBanner.vue b/frontend/src/views/nakopneme_basic_photo_banner/NakopnemeBasicPhotoBanner.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c6a9a68491434251f90a7ca3c2c0cc91a6f2651b
--- /dev/null
+++ b/frontend/src/views/nakopneme_basic_photo_banner/NakopnemeBasicPhotoBanner.vue
@@ -0,0 +1,254 @@
+<script setup>
+import { watch, ref } from "vue";
+
+import COLORS from "../../colors";
+import PEOPLE from "../../people";
+import TEMPLATES from "../../templates";
+import DEFAULT_CONTRACTOR from "../../contractors";
+import {
+  generateDefaultBadges,
+  LOGO_POSITIONS,
+  generateLogoPositions,
+} from "../../logos";
+import {
+  loadFonts,
+  loadCanvasStorage,
+  setCanvasStorage,
+  updateAutoRedrawStorage,
+} from "../../utils";
+
+import Canvas from "../../components/canvas/Canvas.vue";
+import redraw from "./canvas";
+
+import Navbar from "../../components/Navbar.vue";
+import MainContainer from "../../components/MainContainer.vue";
+import ImageInput from "../../components/inputs/ImageInput.vue";
+import LongTextInput from "../../components/inputs/text/LongTextInput.vue";
+import ShortTextInput from "../../components/inputs/text/ShortTextInput.vue";
+import RangeInput from "../../components/inputs/RangeInput.vue";
+import InputSeparator from "../../components/inputs/InputSeparator.vue";
+import SelectInput from "../../components/inputs/SelectInput.vue";
+import MultipleColorPicker from "../../components/inputs/colors/MultipleColorPicker.vue";
+import ReloadButton from "../../components/reload/ReloadButton.vue";
+import AutoReloadCheckbox from "../../components/reload/AutoReloadCheckbox.vue";
+</script>
+
+<script>
+await loadFonts([
+  "12px Bebas Neue",
+  "12px Roboto Condensed",
+  "bold 12px Roboto Condensed",
+]);
+
+export default {
+  components: {
+    Canvas,
+    Navbar,
+    MainContainer,
+    ImageInput,
+    LongTextInput,
+    ShortTextInput,
+    RangeInput,
+    SelectInput,
+    InputSeparator,
+    MultipleColorPicker,
+  },
+  data() {
+    const predefinedColors = {
+      base: {
+        name: "Základní barvy",
+        colors: {
+          background: COLORS.black,
+          highlight: COLORS.yellow1,
+          baseText: COLORS.white,
+          highlightedText: COLORS.black,
+          contractedByText: COLORS.gray1,
+        },
+      },
+    };
+
+    return {
+      mainImage: null,
+      mainText: null,
+      personName: null,
+      personPosition: null,
+      contractedBy: DEFAULT_CONTRACTOR,
+      logoImage: null,
+      logoPosition: LOGO_POSITIONS.top_right,
+      logoOptions: generateLogoPositions(["top_right", "top_left"]),
+      gradientHeightMultiplier: 1,
+      colorLabels: {
+        background: "Pozadí",
+        highlight: "Zvýraznění",
+        baseText: "Text",
+        highlightedText: "Zvýrazněný text",
+      },
+      predefinedColors: predefinedColors,
+      colors: predefinedColors.base.colors,
+      predefinedBadgeImages: generateDefaultBadges("defaultDark"),
+      autoRedraw: false,
+    };
+  },
+  async created() {
+    await loadCanvasStorage(this);
+  },
+  methods: {
+    async reloadCanvasProperties() {
+      const canvasProperties = {
+        mainImage: this.mainImage,
+        mainText: this.mainText,
+        logoPosition: this.logoPosition,
+        personName: this.personName,
+        personPosition: this.personPosition,
+        contractedBy: this.contractedBy,
+        logoImage: this.logoImage,
+        gradientHeightMultiplier: this.gradientHeightMultiplier,
+        colors: this.colors,
+      };
+
+      if (canvasProperties.mainText) {
+        window.fileName = canvasProperties.mainText;
+      }
+
+      await this.$refs.canvas.redraw(canvasProperties);
+
+      delete canvasProperties.colors;
+      setCanvasStorage(canvasProperties);
+    },
+  },
+  mounted() {
+    this.$watch(
+      (vm) => [
+        vm.mainImage,
+        vm.mainText,
+        vm.logoPosition,
+        vm.personName,
+        vm.personPosition,
+        vm.contractedBy,
+        vm.logoImage,
+        vm.gradientHeightMultiplier,
+        vm.colors,
+      ],
+      async (value) => {
+        if (this.autoRedraw) {
+          await this.reloadCanvasProperties();
+        }
+      },
+      {
+        immediate: true,
+        deep: true,
+      },
+    );
+
+    this.$watch(
+      (vm) => [vm.autoRedraw],
+      async (value) => {
+        updateAutoRedrawStorage(this.autoRedraw);
+
+        if (this.autoRedraw) {
+          await this.reloadCanvasProperties();
+        }
+      },
+    );
+  },
+};
+</script>
+
+<template>
+  <header>
+    <Navbar :defaultTemplate="TEMPLATES.basic_photo_banner"></Navbar>
+  </header>
+  <main>
+    <MainContainer>
+      <template v-slot:left>
+        <Canvas
+          ref="canvas"
+          :redrawFunction="redraw"
+          width="1080"
+          height="1350"
+        />
+      </template>
+
+      <template v-slot:right>
+        <ReloadButton :parentRefs="$refs" @click="reloadCanvasProperties" />
+        <AutoReloadCheckbox v-model="autoRedraw" />
+        <ImageInput
+          name="Obrázek"
+          v-model="mainImage"
+          :important="true"
+          zIndex="10"
+        />
+        <LongTextInput
+          name="Hlavní text"
+          v-model="mainText"
+          :important="true"
+          :highlightable="true"
+          zIndex="9"
+        />
+        <ShortTextInput
+          name="Jméno osoby"
+          v-model="personName"
+          v-model:relatedModel="personPosition"
+          :predefinedValues="PEOPLE"
+          :important="true"
+          zIndex="8"
+        />
+        <LongTextInput
+          ref="refPersonPosition"
+          name="Pozice osoby"
+          v-model="personPosition"
+          :important="false"
+          zIndex="7"
+        />
+
+        <InputSeparator />
+
+        <ImageInput
+          name="Obrázek loga"
+          v-model="logoImage"
+          :important="false"
+          :predefinedImages="predefinedBadgeImages"
+          :mustSelectPredefinedImage="true"
+          :disableImageInput="true"
+          zIndex="7"
+        />
+
+        <SelectInput
+          name="Pozice loga"
+          :options="logoOptions"
+          v-model="logoPosition"
+          zIndex="6"
+        />
+
+        <RangeInput
+          name="Výška gradientu"
+          v-model="gradientHeightMultiplier"
+          min="0"
+          max="3"
+        />
+
+        <MultipleColorPicker
+          name="Barvy"
+          v-model="colors"
+          :important="false"
+          :colorLabels="colorLabels"
+          :predefinedColors="predefinedColors"
+          :defaultPredefinedColors="predefinedColors.base"
+          zIndex="5"
+        ></MultipleColorPicker>
+
+        <ShortTextInput
+          name="Zadavatel a zpracovatel"
+          v-model="contractedBy"
+          :defaultValue="DEFAULT_CONTRACTOR"
+          :important="false"
+          zIndex="4"
+        />
+      </template>
+    </MainContainer>
+  </main>
+</template>
+
+<style>
+@import "vue-select/dist/vue-select.css";
+</style>
diff --git a/frontend/src/views/nakopneme_basic_photo_banner/canvas.js b/frontend/src/views/nakopneme_basic_photo_banner/canvas.js
new file mode 100644
index 0000000000000000000000000000000000000000..c0703dcc5d51ac8c3f0fd7fc92fbc34c22d8e0ef
--- /dev/null
+++ b/frontend/src/views/nakopneme_basic_photo_banner/canvas.js
@@ -0,0 +1,359 @@
+import * as fabric from "fabric";
+import {
+  clearObjects,
+  sortObjects,
+  transformHighlightedText,
+  checkTextBoxHeight,
+  getSingleLineTextBoxWidth,
+} from "../../components/canvas/utils";
+import { PaddedHighlightingTextbox } from "../../components/canvas/textbox";
+import leftQuoteImage from '../../assets/template/nakopneme_basic_photo_banner/nakopneme_quote_left.png';
+import rightQuoteImage from '../../assets/template/nakopneme_basic_photo_banner/nakopneme_quote_right.png';
+
+let mainTextBox = null;
+let mainTextBoxBackground = null;
+
+let personNameText = null;
+let personInfoSeparator = null;
+let personPositionText = null;
+
+let mainImage = null;
+let logoImage = null;
+
+let contractedByTextbox = null;
+
+let mainImageSource = null;
+let previousLogoPosition = null;
+
+let leftQuote = null;
+let rightQuote = null;
+
+const redraw = async (canvas, options) => {
+  canvas.controlsAboveOverlay = true;
+
+  clearObjects(
+    [
+      mainTextBox,
+      mainTextBoxBackground,
+      personNameText,
+      personInfoSeparator,
+      personPositionText,
+      contractedByTextbox,
+      leftQuote,
+      rightQuote,
+    ],
+    canvas,
+  );
+
+  canvas.preserveObjectStacking = true;
+
+  const textMarginLeft = Math.ceil(canvas.width * 0.14);
+  const textMarginRight = Math.ceil(canvas.width * 0.075);
+
+  let mainTextMarginBottom = Math.ceil(canvas.height * 0.06);
+  const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.1);
+  const mainTextSize = Math.ceil(canvas.height * 0.0725);
+  const mainTextHeightLimit = Math.ceil(mainTextSize * 3.3);
+  const mainTextLineHeight = 0.9;
+
+  const bottomTextSize = Math.ceil(canvas.height * 0.055);
+  const nameTextMarginBottom = Math.ceil(canvas.height * 0.065);
+  const positionTextSideGap = Math.ceil(canvas.width * 0.01);
+  const positionTextSeparatorWidth = Math.ceil(canvas.width * 0.0035);
+  const positionTextMaxWidth = Math.ceil(canvas.width * 0.4);
+
+  const contractedByTextSize = Math.ceil(canvas.height * 0.02);
+  const contractedByTextMaxWidth = Math.ceil(canvas.width * 0.9);
+  const contractedByTextSidesMargin = Math.ceil(canvas.width * 0.03);
+
+  const logoWidth = Math.ceil(canvas.width * 0.13);
+  const logoSideMargin = Math.ceil(canvas.width * 0.07);
+
+  if (options.mainText !== null) {
+    /* BEGIN Main text render */
+
+    const mainText = `      ${options.mainText}`;
+    const mainTextWidth = canvas.width - textMarginLeft - textMarginRight;
+
+    const highlightedData = transformHighlightedText(
+      mainText,
+      mainTextSize,
+      mainTextWidth,
+      "Bebas Neue",
+      options.colors.highlight.value,
+      options.colors.highlightedText.value,
+      { padWhenDiacritics: true, skipWhitespaceNormalization: true },
+    );
+
+    mainTextBox = new PaddedHighlightingTextbox(highlightedData.text, {
+      width: canvas.width,
+      left: textMarginLeft,
+      textAlign: "left",
+      fontFamily: "Bebas Neue",
+      fontSize: mainTextSize,
+      lineHeight: mainTextLineHeight,
+      fill: options.colors.baseText.value,
+      styles: highlightedData.styles,
+      selectable: false,
+      highlightPadding: canvas.height * 0.003,
+      zIndex: 10,
+    });
+
+    checkTextBoxHeight(mainTextBox, 4);
+
+    canvas.add(mainTextBox);
+
+    const mainTextBoxTop =
+      canvas.height - mainTextBox.height - mainTextMarginBottom;
+
+    mainTextBox.top = mainTextBoxTop - highlightedData.paddingBottom;
+
+    canvas.renderAll();
+
+    /* END Main text render */
+
+    /* BEGIN Quotes render */
+
+    // Left quote
+    leftQuote = new Image();
+
+    const imageLoadPromiseL = new Promise((resolve) => {
+      leftQuote.onload = () => {
+        resolve();
+      };
+
+      leftQuote.src = leftQuoteImage;
+    });
+    await imageLoadPromiseL;
+
+    leftQuote = new fabric.Image(leftQuote, {
+      left: 157,
+      top: mainTextBox.top + 17,
+      zIndex: 10,
+      selectable: false,
+    });
+
+    canvas.add(leftQuote);
+
+    // Right quote
+
+    rightQuote = new Image();
+
+    const imageLoadPromiseR = new Promise((resolve) => {
+      rightQuote.onload = () => {
+        resolve();
+      };
+
+      rightQuote.src = rightQuoteImage;
+    });
+    await imageLoadPromiseR;
+
+    rightQuote = new fabric.Image(rightQuote, {
+      left: getSingleLineTextBoxWidth(
+        mainTextBox.textLines[mainTextBox.textLines.length - 1],
+        mainTextSize,
+        "Bebas Neue"
+      ) + (
+        (mainTextBox.textLines.length == 1) ?
+        260 : 170
+      ),
+      top: mainTextBox.top + mainTextBox.height - mainTextSize + 6,
+      zIndex: 20,
+      selectable: false,
+    });
+
+    canvas.add(rightQuote);
+
+    /* END Quotes render */
+
+    /* BEGIN Name text render */
+
+    if (options.personName !== null) {
+      let styles = {
+        0: {},
+      };
+
+      for (let position = 0; position < options.personName.length; position++) {
+        styles[0][position] = {
+          fontStyle: "italic bold",
+        };
+      }
+
+      personNameText = new fabric.Text(options.personName, {
+        left: textMarginLeft,
+        top: mainTextBox.top - nameTextMarginBottom,
+        fontFamily: "Roboto Condensed",
+        fontSize: bottomTextSize,
+        styles: styles,
+        selectable: false,
+        zIndex: 10,
+      });
+
+      personNameText.set('fill', new fabric.Gradient({
+        type: 'linear',
+        coords: {
+          x1: 0,
+          y1: 0,
+          x2: canvas.width * 0.3,
+          y2: 0
+        },
+        colorStops: [
+          { offset: 0, color: '#CF7BCC' },
+          { offset: 1, color: '#FDC801' }
+        ]
+      }));
+
+      canvas.add(personNameText);
+    }
+
+    /* END Name text render */
+
+    /* BEGIN Main text background render */
+
+    const backgroundHeight =
+      canvas.height - mainTextBoxTop + mainTextBackgroundMarginTop;
+
+    mainTextBoxBackground = new fabric.Rect({
+      width: canvas.width + 30, // FIXME: Whhhhyyyyyy????
+      height: backgroundHeight * options.gradientHeightMultiplier,
+      left: -20,
+      top:
+        mainTextBoxTop -
+        mainTextBackgroundMarginTop -
+        backgroundHeight * (options.gradientHeightMultiplier - 1),
+      fill: new fabric.Gradient({
+        type: "linear",
+        gradientUnits: "pixels",
+        coords: {
+          x1: 0,
+          y1: 0,
+          x2: 0,
+          y2: backgroundHeight * options.gradientHeightMultiplier,
+        },
+        colorStops: [
+          {
+            offset: 1,
+            color: options.colors.background.value,
+          },
+          {
+            offset: 0.2,
+            color: `${options.colors.background.value}00`,
+          },
+        ],
+      }),
+      selectable: false,
+      zIndex: 9,
+    });
+
+    canvas.add(mainTextBoxBackground);
+
+    /* END Main text background render */
+  }
+
+  /* BEGIN Logo render */
+
+  // A logo is provided, and it either hasn't been rendered yet or is a new one.
+  const createNewLogo =
+    (options.logoImage !== null &&
+      (logoImage === null ||
+        (options.logoImage !== null &&
+          options.logoImage !== logoImage._element))) ||
+    previousLogoPosition != options.logoPosition.id;
+
+  previousLogoPosition = options.logoPosition.id;
+
+  if (createNewLogo) {
+    canvas.remove(logoImage);
+
+    logoImage = new fabric.Image(options.logoImage, { selectable: false });
+    logoImage.scaleToWidth(logoWidth);
+
+    if (options.logoPosition.id == "top-right") {
+      logoImage.set({
+        left: canvas.width - logoWidth - logoSideMargin,
+        top: logoSideMargin,
+        zIndex: 11,
+      });
+    } else {
+      logoImage.set({
+        left: logoSideMargin,
+        top: logoSideMargin,
+        zIndex: 11,
+      });
+    }
+
+    canvas.add(logoImage);
+  }
+
+  /* END Logo render */
+
+  /* BEGIN Contracted by render */
+
+  if (options.contractedBy !== null) {
+    contractedByTextbox = new fabric.Textbox(options.contractedBy, {
+      left:
+        canvas.width - contractedByTextMaxWidth - contractedByTextSidesMargin,
+      top: canvas.height - contractedByTextSidesMargin - contractedByTextSize,
+      width: contractedByTextMaxWidth,
+      fontFamily: "Roboto Condensed",
+      fontSize: contractedByTextSize,
+      textAlign: "right",
+      fill: options.colors.contractedByText.value,
+      selectable: false,
+      zIndex: 10,
+    });
+
+    checkTextBoxHeight(contractedByTextbox, 1);
+
+    canvas.add(contractedByTextbox);
+  }
+
+  /* END Contracted by render */
+
+  /* BEGIN Main image render */
+
+  if (
+    options.mainImage !== null &&
+    (!canvas.contains(mainImage) ||
+      mainImage === null ||
+      options.mainImage.src !== mainImageSource)
+  ) {
+    if (mainImage !== null) {
+      canvas.remove(mainImage);
+    }
+
+    mainImage = new fabric.Image(options.mainImage, {
+      left: 0,
+      top: 0,
+      zIndex: 0,
+    });
+
+    mainImage.setControlsVisibility({
+      // corners (uniform scale)
+      tl: true, tr: true, bl: true, br: true,
+      // mids (scale X/Y independently)
+      ml: true, mr: true, mt: true, mb: true,
+      // rotation
+      mtr: false,
+    });
+
+
+    if (mainImage.width >= mainImage.height) {
+      mainImage.scaleToHeight(canvas.height);
+    } else {
+      mainImage.scaleToWidth(canvas.width);
+    }
+
+    canvas.add(mainImage);
+    mainImageSource = options.mainImage.src;
+    // canvas.centerObject(mainImage)
+  } else if (mainImage !== null && options.mainImage === null) {
+    canvas.remove(mainImage);
+  }
+
+  /* END Main image render */
+
+  sortObjects(canvas);
+};
+
+export default redraw;
diff --git a/frontend/src/views/newspaper_quote_bottom/canvas.js b/frontend/src/views/newspaper_quote_bottom/canvas.js
index 7cd01cf73db89a061ca3277a51ad6eafd05f2e9b..b4e55d260386092c1bbe574f87bfe3e880257e32 100644
--- a/frontend/src/views/newspaper_quote_bottom/canvas.js
+++ b/frontend/src/views/newspaper_quote_bottom/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/newspaper_quote_middle/canvas.js b/frontend/src/views/newspaper_quote_middle/canvas.js
index c33bf9d252e7bba11d220016daae08e9b2e160ed..521c015f71117d73b267fa187b75616aac7cb8d6 100644
--- a/frontend/src/views/newspaper_quote_middle/canvas.js
+++ b/frontend/src/views/newspaper_quote_middle/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/people_banner_with_custom_text/canvas.js b/frontend/src/views/people_banner_with_custom_text/canvas.js
index 1b84b7dcd078fa29f84d73646297aea5c7bfdf56..f9b3946ea4e2e998c324195eeb3eaa316961e3ef 100644
--- a/frontend/src/views/people_banner_with_custom_text/canvas.js
+++ b/frontend/src/views/people_banner_with_custom_text/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/people_banner_with_predefined_text/canvas.js b/frontend/src/views/people_banner_with_predefined_text/canvas.js
index 076178e66962a1eedccaff7829be0babffe7d464..dda2677d4d6e44be8b8d359c43b8414b84652da6 100644
--- a/frontend/src/views/people_banner_with_predefined_text/canvas.js
+++ b/frontend/src/views/people_banner_with_predefined_text/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/poster/canvas.js b/frontend/src/views/poster/canvas.js
index e92293e6689fa74eebd662170d2dc19f4a4b789b..3de73499b8a5d32abe173491f846ab54a6fde7a4 100644
--- a/frontend/src/views/poster/canvas.js
+++ b/frontend/src/views/poster/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/reel/canvas.js b/frontend/src/views/reel/canvas.js
index 6aabd092cae81657b64969eb2c4fee4970c333c5..9b5941eb5304fa442d11aa369bd5e0764140391a 100644
--- a/frontend/src/views/reel/canvas.js
+++ b/frontend/src/views/reel/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/regional_success/canvas.js b/frontend/src/views/regional_success/canvas.js
index 6ffb4e91a451b6faf6209e533d6a566c96d3d19e..2c8a61657453bac76eacc802662aaa7d49d8652c 100644
--- a/frontend/src/views/regional_success/canvas.js
+++ b/frontend/src/views/regional_success/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/right_event/canvas.js b/frontend/src/views/right_event/canvas.js
index d6c6998e4a4002d7f58f5d0bd040ed8aef8be425..6a6125fb449db44b5994bc1de36a917387b9825e 100644
--- a/frontend/src/views/right_event/canvas.js
+++ b/frontend/src/views/right_event/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/right_person_event/canvas.js b/frontend/src/views/right_person_event/canvas.js
index 0a6704f33a09ed549dba1e8dd22328c90ea9016a..6b3dcdda2275363bc4e37d5225c55ca4c4db3feb 100644
--- a/frontend/src/views/right_person_event/canvas.js
+++ b/frontend/src/views/right_person_event/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/social_cover_large_text/canvas.js b/frontend/src/views/social_cover_large_text/canvas.js
index cd7812ae72ac972d9ab9adcca308f812d61a7566..eee3fdb7a6aff4bd71dd100e5278d963ba5a2ae9 100644
--- a/frontend/src/views/social_cover_large_text/canvas.js
+++ b/frontend/src/views/social_cover_large_text/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/text_banner/canvas.js b/frontend/src/views/text_banner/canvas.js
index 442785f6f48dc225fa21d3af68b6af2c628be2b5..bbfa16687a028536e980e9fe6e45f54068766b78 100644
--- a/frontend/src/views/text_banner/canvas.js
+++ b/frontend/src/views/text_banner/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/twitter_banner/canvas.js b/frontend/src/views/twitter_banner/canvas.js
index 56ce082d4d2bc05d0d7b3da09e61137e4bf59105..7eb999a3e544f3fcb6114972dd026c7367151a19 100644
--- a/frontend/src/views/twitter_banner/canvas.js
+++ b/frontend/src/views/twitter_banner/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,
diff --git a/frontend/src/views/urgent_text_banner/canvas.js b/frontend/src/views/urgent_text_banner/canvas.js
index 6fa9976366e706a848ab5bdf362b8aba187f4c79..1f0d19d61d26984381a6f0ab4ac183bbe47405bf 100644
--- a/frontend/src/views/urgent_text_banner/canvas.js
+++ b/frontend/src/views/urgent_text_banner/canvas.js
@@ -1,4 +1,4 @@
-import { fabric } from "fabric";
+import * as fabric from "fabric";
 import {
   clearObjects,
   sortObjects,