diff --git a/.gitignore b/.gitignore
index d50a09fc9169b4517adb37c586c510db9cd4cf8b..f44dd86dd947e16ac740ded342627096007ed8c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
 .env
 __pycache__/
+server/templates/*
diff --git a/Makefile b/Makefile
index 333c386c6c865863498542d6e391c789417fb353..f1f4b3cb66bc64e93c66637c2507496d3b6aa92b 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ venv:
 install: venv
 	cd server && \
 	${VENV}/bin/pip install -r requirements/base.txt
-	
+
 	cd frontend && \
 	npm install
 
diff --git a/VERSION b/VERSION
index 227cea215648b1af34a87c9acf5b707fe02d2072..7ec1d6db40877765247db18e7f9a4e36a0def4ad 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-2.0.0
+2.1.0
diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json
index 66e23359c3dabfe3929b4e2fa049c41037afb15f..ecdf3e07a7b3261f09531736cffbd41ccbb28795 100644
--- a/frontend/.prettierrc.json
+++ b/frontend/.prettierrc.json
@@ -5,4 +5,4 @@
   "singleQuote": true,
   "printWidth": 100,
   "trailingComma": "none"
-}
\ No newline at end of file
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 7c7e522ee541c30748fba4754181a6a3525946c4..8e9304506eb20849ca54ebcfacba05258540c949 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -29,6 +29,15 @@
         "vite": "^4.3.4"
       }
     },
+    "node_modules/@aashutoshrathi/word-wrap": {
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+      "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/@alloc/quick-lru": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -55,47 +64,47 @@
       }
     },
     "node_modules/@babel/code-frame": {
-      "version": "7.21.4",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz",
-      "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
+      "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
       "dev": true,
       "dependencies": {
-        "@babel/highlight": "^7.18.6"
+        "@babel/highlight": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.21.7",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz",
-      "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
+      "integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.21.8",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz",
-      "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.6.tgz",
+      "integrity": "sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==",
       "dev": true,
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.21.4",
-        "@babel/generator": "^7.21.5",
-        "@babel/helper-compilation-targets": "^7.21.5",
-        "@babel/helper-module-transforms": "^7.21.5",
-        "@babel/helpers": "^7.21.5",
-        "@babel/parser": "^7.21.8",
-        "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.21.5",
-        "@babel/types": "^7.21.5",
+        "@babel/code-frame": "^7.22.5",
+        "@babel/generator": "^7.22.5",
+        "@babel/helper-compilation-targets": "^7.22.6",
+        "@babel/helper-module-transforms": "^7.22.5",
+        "@babel/helpers": "^7.22.6",
+        "@babel/parser": "^7.22.6",
+        "@babel/template": "^7.22.5",
+        "@babel/traverse": "^7.22.6",
+        "@babel/types": "^7.22.5",
+        "@nicolo-ribaudo/semver-v6": "^6.3.3",
         "convert-source-map": "^1.7.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
-        "json5": "^2.2.2",
-        "semver": "^6.3.0"
+        "json5": "^2.2.2"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -106,12 +115,12 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz",
-      "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
+      "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.21.5",
+        "@babel/types": "^7.22.5",
         "@jridgewell/gen-mapping": "^0.3.2",
         "@jridgewell/trace-mapping": "^0.3.17",
         "jsesc": "^2.5.1"
@@ -121,28 +130,28 @@
       }
     },
     "node_modules/@babel/helper-annotate-as-pure": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz",
-      "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz",
+      "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz",
-      "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz",
+      "integrity": "sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==",
       "dev": true,
       "dependencies": {
-        "@babel/compat-data": "^7.21.5",
-        "@babel/helper-validator-option": "^7.21.0",
-        "browserslist": "^4.21.3",
-        "lru-cache": "^5.1.1",
-        "semver": "^6.3.0"
+        "@babel/compat-data": "^7.22.6",
+        "@babel/helper-validator-option": "^7.22.5",
+        "@nicolo-ribaudo/semver-v6": "^6.3.3",
+        "browserslist": "^4.21.9",
+        "lru-cache": "^5.1.1"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -152,20 +161,20 @@
       }
     },
     "node_modules/@babel/helper-create-class-features-plugin": {
-      "version": "7.21.8",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.8.tgz",
-      "integrity": "sha512-+THiN8MqiH2AczyuZrnrKL6cAxFRRQDKW9h1YkBvbgKmAm6mwiacig1qT73DHIWMGo40GRnsEfN3LA+E6NtmSw==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz",
+      "integrity": "sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-annotate-as-pure": "^7.18.6",
-        "@babel/helper-environment-visitor": "^7.21.5",
-        "@babel/helper-function-name": "^7.21.0",
-        "@babel/helper-member-expression-to-functions": "^7.21.5",
-        "@babel/helper-optimise-call-expression": "^7.18.6",
-        "@babel/helper-replace-supers": "^7.21.5",
-        "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "semver": "^6.3.0"
+        "@babel/helper-annotate-as-pure": "^7.22.5",
+        "@babel/helper-environment-visitor": "^7.22.5",
+        "@babel/helper-function-name": "^7.22.5",
+        "@babel/helper-member-expression-to-functions": "^7.22.5",
+        "@babel/helper-optimise-call-expression": "^7.22.5",
+        "@babel/helper-replace-supers": "^7.22.5",
+        "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@nicolo-ribaudo/semver-v6": "^6.3.3"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -175,89 +184,89 @@
       }
     },
     "node_modules/@babel/helper-environment-visitor": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz",
-      "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
+      "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-function-name": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
-      "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
+      "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
       "dev": true,
       "dependencies": {
-        "@babel/template": "^7.20.7",
-        "@babel/types": "^7.21.0"
+        "@babel/template": "^7.22.5",
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-hoist-variables": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
-      "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz",
+      "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-member-expression-to-functions": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.5.tgz",
-      "integrity": "sha512-nIcGfgwpH2u4n9GG1HpStW5Ogx7x7ekiFHbjjFRKXbn5zUvqO9ZgotCO4x1aNbKn/x/xOUaXEhyNHCwtFCpxWg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz",
+      "integrity": "sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.21.5"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-module-imports": {
-      "version": "7.21.4",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz",
-      "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
+      "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.21.4"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-module-transforms": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz",
-      "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz",
+      "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-environment-visitor": "^7.21.5",
-        "@babel/helper-module-imports": "^7.21.4",
-        "@babel/helper-simple-access": "^7.21.5",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/helper-validator-identifier": "^7.19.1",
-        "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.21.5",
-        "@babel/types": "^7.21.5"
+        "@babel/helper-environment-visitor": "^7.22.5",
+        "@babel/helper-module-imports": "^7.22.5",
+        "@babel/helper-simple-access": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.5",
+        "@babel/helper-validator-identifier": "^7.22.5",
+        "@babel/template": "^7.22.5",
+        "@babel/traverse": "^7.22.5",
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-optimise-call-expression": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz",
-      "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz",
+      "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -273,106 +282,106 @@
       }
     },
     "node_modules/@babel/helper-replace-supers": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.21.5.tgz",
-      "integrity": "sha512-/y7vBgsr9Idu4M6MprbOVUfH3vs7tsIfnVWv/Ml2xgwvyH6LTngdfbf5AdsKwkJy4zgy1X/kuNrEKvhhK28Yrg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz",
+      "integrity": "sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-environment-visitor": "^7.21.5",
-        "@babel/helper-member-expression-to-functions": "^7.21.5",
-        "@babel/helper-optimise-call-expression": "^7.18.6",
-        "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.21.5",
-        "@babel/types": "^7.21.5"
+        "@babel/helper-environment-visitor": "^7.22.5",
+        "@babel/helper-member-expression-to-functions": "^7.22.5",
+        "@babel/helper-optimise-call-expression": "^7.22.5",
+        "@babel/template": "^7.22.5",
+        "@babel/traverse": "^7.22.5",
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-simple-access": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz",
-      "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz",
+      "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.21.5"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
-      "version": "7.20.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz",
-      "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz",
+      "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.20.0"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-split-export-declaration": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
-      "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
+      "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
       "dev": true,
       "dependencies": {
-        "@babel/types": "^7.18.6"
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz",
-      "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
+      "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.19.1",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
-      "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
+      "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-option": {
-      "version": "7.21.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz",
-      "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
+      "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
       "dev": true,
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz",
-      "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz",
+      "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==",
       "dev": true,
       "dependencies": {
-        "@babel/template": "^7.20.7",
-        "@babel/traverse": "^7.21.5",
-        "@babel/types": "^7.21.5"
+        "@babel/template": "^7.22.5",
+        "@babel/traverse": "^7.22.6",
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/highlight": {
-      "version": "7.18.6",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
-      "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
+      "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-validator-identifier": "^7.18.6",
+        "@babel/helper-validator-identifier": "^7.22.5",
         "chalk": "^2.0.0",
         "js-tokens": "^4.0.0"
       },
@@ -381,9 +390,9 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.21.8",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz",
-      "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.6.tgz",
+      "integrity": "sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==",
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -440,33 +449,33 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.20.7",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
-      "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
+      "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
       "dev": true,
       "dependencies": {
-        "@babel/code-frame": "^7.18.6",
-        "@babel/parser": "^7.20.7",
-        "@babel/types": "^7.20.7"
+        "@babel/code-frame": "^7.22.5",
+        "@babel/parser": "^7.22.5",
+        "@babel/types": "^7.22.5"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.5.tgz",
-      "integrity": "sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw==",
-      "dev": true,
-      "dependencies": {
-        "@babel/code-frame": "^7.21.4",
-        "@babel/generator": "^7.21.5",
-        "@babel/helper-environment-visitor": "^7.21.5",
-        "@babel/helper-function-name": "^7.21.0",
-        "@babel/helper-hoist-variables": "^7.18.6",
-        "@babel/helper-split-export-declaration": "^7.18.6",
-        "@babel/parser": "^7.21.5",
-        "@babel/types": "^7.21.5",
+      "version": "7.22.6",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.6.tgz",
+      "integrity": "sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==",
+      "dev": true,
+      "dependencies": {
+        "@babel/code-frame": "^7.22.5",
+        "@babel/generator": "^7.22.5",
+        "@babel/helper-environment-visitor": "^7.22.5",
+        "@babel/helper-function-name": "^7.22.5",
+        "@babel/helper-hoist-variables": "^7.22.5",
+        "@babel/helper-split-export-declaration": "^7.22.6",
+        "@babel/parser": "^7.22.6",
+        "@babel/types": "^7.22.5",
         "debug": "^4.1.0",
         "globals": "^11.1.0"
       },
@@ -475,13 +484,13 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.21.5",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz",
-      "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==",
+      "version": "7.22.5",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
+      "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
       "dev": true,
       "dependencies": {
-        "@babel/helper-string-parser": "^7.21.5",
-        "@babel/helper-validator-identifier": "^7.19.1",
+        "@babel/helper-string-parser": "^7.22.5",
+        "@babel/helper-validator-identifier": "^7.22.5",
         "to-fast-properties": "^2.0.0"
       },
       "engines": {
@@ -1031,9 +1040,9 @@
       }
     },
     "node_modules/@mapbox/node-pre-gyp/node_modules/semver": {
-      "version": "7.5.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
-      "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
+      "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"
@@ -1051,6 +1060,15 @@
       "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",
+      "integrity": "sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==",
+      "dev": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
     "node_modules/@nodelib/fs.scandir": {
       "version": "2.1.5",
       "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1533,9 +1551,9 @@
       "optional": true
     },
     "node_modules/browserslist": {
-      "version": "4.21.5",
-      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
-      "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
+      "version": "4.21.9",
+      "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz",
+      "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==",
       "dev": true,
       "funding": [
         {
@@ -1545,13 +1563,17 @@
         {
           "type": "tidelift",
           "url": "https://tidelift.com/funding/github/npm/browserslist"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
         }
       ],
       "dependencies": {
-        "caniuse-lite": "^1.0.30001449",
-        "electron-to-chromium": "^1.4.284",
-        "node-releases": "^2.0.8",
-        "update-browserslist-db": "^1.0.10"
+        "caniuse-lite": "^1.0.30001503",
+        "electron-to-chromium": "^1.4.431",
+        "node-releases": "^2.0.12",
+        "update-browserslist-db": "^1.0.11"
       },
       "bin": {
         "browserslist": "cli.js"
@@ -1591,9 +1613,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001486",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz",
-      "integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==",
+      "version": "1.0.30001512",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz",
+      "integrity": "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==",
       "dev": true,
       "funding": [
         {
@@ -1934,9 +1956,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.388",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.388.tgz",
-      "integrity": "sha512-xZ0y4zjWZgp65okzwwt00f2rYibkFPHUv9qBz+Vzn8cB9UXIo9Zc6Dw81LJYhhNt0G/vR1OJEfStZ49NKl0YxQ==",
+      "version": "1.4.449",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.449.tgz",
+      "integrity": "sha512-TxLRpRUj/107ATefeP8VIUWNOv90xJxZZbCW/eIbSZQiuiFANCx2b7u+GbVc9X4gU+xnbvypNMYVM/WArE1DNQ==",
       "dev": true
     },
     "node_modules/emoji-regex": {
@@ -2197,9 +2219,9 @@
       }
     },
     "node_modules/eslint-plugin-vue/node_modules/semver": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
-      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
+      "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"
@@ -3348,9 +3370,9 @@
       }
     },
     "node_modules/node-releases": {
-      "version": "2.0.10",
-      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
-      "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
+      "version": "2.0.12",
+      "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz",
+      "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==",
       "dev": true
     },
     "node_modules/nopt": {
@@ -3444,17 +3466,17 @@
       }
     },
     "node_modules/optionator": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
-      "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+      "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
       "dev": true,
       "dependencies": {
+        "@aashutoshrathi/word-wrap": "^1.2.3",
         "deep-is": "^0.1.3",
         "fast-levenshtein": "^2.0.6",
         "levn": "^0.4.1",
         "prelude-ls": "^1.2.1",
-        "type-check": "^0.4.0",
-        "word-wrap": "^1.2.3"
+        "type-check": "^0.4.0"
       },
       "engines": {
         "node": ">= 0.8.0"
@@ -3956,7 +3978,7 @@
       "version": "6.3.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
       "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-      "devOptional": true,
+      "optional": true,
       "bin": {
         "semver": "bin/semver.js"
       }
@@ -4499,9 +4521,9 @@
       }
     },
     "node_modules/vue-eslint-parser/node_modules/semver": {
-      "version": "7.5.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
-      "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
+      "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"
@@ -4643,7 +4665,7 @@
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
       "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "devOptional": true,
+      "optional": true,
       "engines": {
         "node": ">=0.10.0"
       }
diff --git a/frontend/src/assets/news_sources/rozhlas.png b/frontend/src/assets/news_sources/rozhlas.png
new file mode 100644
index 0000000000000000000000000000000000000000..7edb2d77f158b1a1f411fe385fb0f6a9b2918c42
Binary files /dev/null and b/frontend/src/assets/news_sources/rozhlas.png differ
diff --git a/frontend/src/components/canvas/Canvas.vue b/frontend/src/components/canvas/Canvas.vue
index 729e5a7e3b0a660d55537227cb4b986b57740462..6b535cdeb6c0fbe85a613e28caae4a272b4d313c 100644
--- a/frontend/src/components/canvas/Canvas.vue
+++ b/frontend/src/components/canvas/Canvas.vue
@@ -44,7 +44,7 @@ export default {
             this.canvas.discardActiveObject().renderAll()
 
             let link = document.createElement('a')
-            
+
             link.download = "Vyhrajem.png"
             link.href = this.$refs.canvas.toDataURL()
 
@@ -78,7 +78,8 @@ export default {
         <div class="object-contain">
             <canvas
                 ref="canvas"
-                class="w-full border border-gray-300 drop-shadow-md"
+                class="w-full border border-gray-300 drop-shadow-md duration-150"
+                :class="{'blur': redrawing}"
                 :width="width"
                 :height="height"
             ></canvas>
diff --git a/frontend/src/components/canvas/textbox.js b/frontend/src/components/canvas/textbox.js
index 0c040b377f353a73b0e9f15cb0f7893cce289ee4..5954a3b33d9e53e27ab2b622410fac8874c7706c 100644
--- a/frontend/src/components/canvas/textbox.js
+++ b/frontend/src/components/canvas/textbox.js
@@ -56,7 +56,7 @@ class PaddedHighlightingTextbox extends fabric.Textbox {
                     if (this.direction === 'rtl') {
                         drawStart = this.width - drawStart - boxWidth;
                     }
- 
+
                     ctx.fillStyle = lastColor;
                     lastColor && ctx.fillRect(
                         drawStart,
diff --git a/frontend/src/components/canvas/utils.js b/frontend/src/components/canvas/utils.js
index 8d42301cc16d519ca49cf97c70a8607da567f719..073b008e5043220fa6c0193436913a8c0131796a 100644
--- a/frontend/src/components/canvas/utils.js
+++ b/frontend/src/components/canvas/utils.js
@@ -16,7 +16,7 @@ const clearObjects = (clearableItems, canvas) => {
             canvas.remove(clearableItem);
         }
     }
-} 
+}
 
 const sortObjects = (canvas) => {
     canvas._objects.sort((a, b) => (a.zIndex > b.zIndex) ? 1 : -1)
diff --git a/frontend/src/components/inputs/text/ShortTextInput.vue b/frontend/src/components/inputs/text/ShortTextInput.vue
index 0d95fb60a8789fad1cf86f1706986410fd4e8349..6a35e7da14ef9e4561a128e1774e62f40f8bf662 100644
--- a/frontend/src/components/inputs/text/ShortTextInput.vue
+++ b/frontend/src/components/inputs/text/ShortTextInput.vue
@@ -8,9 +8,6 @@ export default {
     components: { InputHeading },
     props: ['name', 'important', 'zIndex', 'relatedModel', 'predefinedValues', 'modelValue'],
     emits: ['update:modelValue', 'update:relatedModel'],
-    mounted () {
-        console.log(this.predefinedValues)
-    },
     methods: {
         emitChanges (event) {
             this.$emit('update:modelValue', sanitizeValue(event.currentTarget.value))
diff --git a/frontend/src/components/inputs/text/utils.js b/frontend/src/components/inputs/text/utils.js
index f9869c30420168e8b9f023e6cc809f2c0ab67df7..2fc05e2544dd186e1d96061cdefc977cc1cc1886 100644
--- a/frontend/src/components/inputs/text/utils.js
+++ b/frontend/src/components/inputs/text/utils.js
@@ -4,6 +4,6 @@ const sanitizeValue = (value) => {
     }
 
     return value
-} 
+}
 
 export { sanitizeValue }
diff --git a/frontend/src/components/reload/AutoReloadCheckbox.vue b/frontend/src/components/reload/AutoReloadCheckbox.vue
new file mode 100644
index 0000000000000000000000000000000000000000..679a8082b4e05aa63dba8e92e7bfe263bf736f04
--- /dev/null
+++ b/frontend/src/components/reload/AutoReloadCheckbox.vue
@@ -0,0 +1,20 @@
+<script>
+    export default {
+        props: ['modelValue'],
+        emits: ['update:modelValue']
+    }
+</script>
+
+<template>
+    <div class="flex gap-3 justify-end mb-3">
+        <input
+            id="auto-reload-checkbox"
+            name="auto-reload-checkbox"
+            type="checkbox"
+            @input="$emit('update:modelValue', $event.target.value)"
+        >
+        <label
+            for="auto-reload-checkbox"
+        >Obnovovat automaticky (může zatížit prohlížeč)</label>
+    </div>
+</template>
diff --git a/frontend/src/components/reload/ReloadButton.vue b/frontend/src/components/reload/ReloadButton.vue
new file mode 100644
index 0000000000000000000000000000000000000000..49b7d30d44c78996be998ae6b6b9757772d264fa
--- /dev/null
+++ b/frontend/src/components/reload/ReloadButton.vue
@@ -0,0 +1,19 @@
+<script>
+    export default {
+        props: ['parentRefs']
+    }
+</script>
+
+<template>
+    <button
+        class="btn btn--icon max-w-[unset]"
+        :disabled="$props.parentRefs.canvas ? $props.parentRefs.canvas.redrawing : true"
+    >
+        <div class="btn__body-wrap">
+            <div class="btn__body w-full">Obnovit šablonu</div>
+            <div class="btn__icon">
+                <i class="ico--refresh"></i>
+            </div>
+        </div>
+    </button>
+</template>
diff --git a/frontend/src/utils.js b/frontend/src/utils.js
index 0ef47aac5fe2c88ae7c6bc7af852d59c7fc5c76f..9f00d80b65bd6f0a7676f3afb8f00d3d5547e602 100644
--- a/frontend/src/utils.js
+++ b/frontend/src/utils.js
@@ -1,6 +1,6 @@
-const loadFonts = (fonts) => {
+const loadFonts = async (fonts) => {
     for (const font of fonts) {
-        document.fonts.load(
+        await document.fonts.load(
             `${font}`,
             'Příliš žluťoučký kůň úpěl ďábelské ó.'
         )
diff --git a/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue b/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue
index 148b4e135819fc8c7f884f79dc7db0566b1b871f..6fd923c177830daa79ac02191c787f68409db5fe 100644
--- a/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue
+++ b/frontend/src/views/basic_photo_banner/BasicPhotoBanner.vue
@@ -18,6 +18,8 @@ import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue'
 import RangeInput from '../../components/inputs/RangeInput.vue'
 import InputSeparator from '../../components/inputs/InputSeparator.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>
@@ -69,7 +71,23 @@ export default {
             },
             predefinedColors: predefinedColors,
             colors: predefinedColors.base.colors,
-            predefinedLogoImages: generateDefaultLogos('defaultLight')
+            predefinedLogoImages: generateDefaultLogos('defaultLight'),
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    mainImage: this.mainImage,
+                    mainText: this.mainText,
+                    personName: this.personName,
+                    personPosition: this.personPosition,
+                    logoImage: this.logoImage,
+                    gradientHeightMultiplier: this.gradientHeightMultiplier,
+                    colors: this.colors
+                }
+            )
         }
     },
     mounted () {
@@ -84,17 +102,9 @@ export default {
                 vm.colors
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        mainImage: this.mainImage,
-                        mainText: this.mainText,
-                        personName: this.personName,
-                        personPosition: this.personPosition,
-                        logoImage: this.logoImage,
-                        gradientHeightMultiplier: this.gradientHeightMultiplier,
-                        colors: this.colors
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -123,6 +133,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <ImageInput
                     name="Obrázek"
                     v-model="mainImage"
@@ -144,7 +161,7 @@ export default {
                     :important="true"
                     zIndex="8"
                 />
-                <ShortTextInput
+                <LongTextInput
                     ref="refPersonPosition"
                     name="Pozice osoby"
                     v-model="personPosition"
diff --git a/frontend/src/views/basic_photo_banner/canvas.js b/frontend/src/views/basic_photo_banner/canvas.js
index c02aeeba09460192deed20fdd4797d0bde3a393d..8378144352c178e02e1700af96c399f9d448f94a 100644
--- a/frontend/src/views/basic_photo_banner/canvas.js
+++ b/frontend/src/views/basic_photo_banner/canvas.js
@@ -5,7 +5,9 @@ import { PaddedHighlightingTextbox } from '../../components/canvas/textbox'
 let mainTextBox = null
 let mainTextBoxBackground = null
 
-let personInfoText = null
+let personNameText = null
+let personInfoSeparator = null
+let personPositionText = null
 
 let mainImage = null
 let logoImage = null
@@ -17,7 +19,9 @@ const redraw = async (canvas, options) => {
         [
             mainTextBox,
             mainTextBoxBackground,
-            personInfoText,
+            personNameText,
+            personInfoSeparator,
+            personPositionText,
             arrow
         ],
         canvas
@@ -34,9 +38,12 @@ const redraw = async (canvas, options) => {
     const mainTextHeightLimit = Math.ceil(mainTextSize * 3.3)
     const mainTextLineHeight = 1
 
-    const nameTextSize = Math.ceil(canvas.height * 0.03)
+    const bottomTextSize = Math.ceil(canvas.height * 0.03)
     const nameTextMarginTop = Math.ceil(canvas.height * 0.015)
     const nameTextExtraBottomMargin = Math.ceil(canvas.height * 0.06)
+    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 arrowWidth = Math.ceil(canvas.width * 0.047)
     const arrowHeight = Math.ceil(canvas.width * 0.055)
@@ -57,7 +64,7 @@ const redraw = async (canvas, options) => {
             let styles = {
                 0: {}
             }
-            
+
 
             for (let position = 0; position < options.personName.length; position++) {
                 styles[0][position] = {
@@ -65,18 +72,8 @@ const redraw = async (canvas, options) => {
                 }
             }
 
-            let nameText = options.personName;
-
-            if (options.personPosition) {
-                nameText += `|${options.personPosition}`
-
-                styles[0][options.personName.length] = {
-                    fontWeight: 'bold'
-                }
-            }
-
-            personInfoText = new fabric.Text(
-                nameText,
+            personNameText = new fabric.Text(
+                options.personName,
                 {
                     left: textMarginLeft,
                     top: (
@@ -85,7 +82,7 @@ const redraw = async (canvas, options) => {
                         + nameTextMarginTop
                     ),
                     fontFamily: 'Roboto Condensed',
-                    fontSize: nameTextSize,
+                    fontSize: bottomTextSize,
                     styles: styles,
                     fill: options.colors.baseText.value,
                     selectable: false,
@@ -93,7 +90,47 @@ const redraw = async (canvas, options) => {
                 }
             )
 
-            canvas.add(personInfoText)
+            if (options.personPosition !== null) {
+                personInfoSeparator = new fabric.Rect({
+                    left: personNameText.left + personNameText.width + positionTextSideGap,
+                    width: positionTextSeparatorWidth,
+                    fill: options.colors.baseText.value,
+                    selectable: false,
+                    zIndex: 10
+                })
+
+                personPositionText = new fabric.Textbox(
+                    options.personPosition,
+                    {
+                        left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap,
+                        top: personNameText.top,
+                        width: positionTextMaxWidth,
+                        fontFamily: 'Roboto Condensed',
+                        fontSize: bottomTextSize,
+                        fill: options.colors.baseText.value,
+                        selectable: false,
+                        zIndex: 10
+                    }
+                )
+
+                checkTextBoxHeight(personPositionText, 2)
+
+                if (personPositionText._textLines.length === 2) {
+                    mainTextMarginBottom += nameTextExtraBottomMargin
+                    personNameText.set({top: personNameText.top - nameTextExtraBottomMargin})
+                    personPositionText.set({top: personPositionText.top - nameTextExtraBottomMargin})
+                }
+
+                canvas.add(personPositionText)
+
+                personInfoSeparator.set({top: personPositionText.top})
+                personInfoSeparator.set({height: personPositionText.height})
+                canvas.add(personInfoSeparator)
+
+                canvas.renderAll()
+            }
+
+            canvas.add(personNameText)
         }
 
         /* END Name text render */
@@ -204,9 +241,9 @@ const redraw = async (canvas, options) => {
 
         mainTextBoxBackground = new fabric.Rect(
             {
-                width: canvas.width,
+                width: canvas.width + 30,  // FIXME: Whhhhyyyyyy????
                 height: backgroundHeight * options.gradientHeightMultiplier,
-                left: 0,
+                left: -20,
                 top: (
                     mainTextBoxTop
                     - mainTextBackgroundMarginTop
@@ -302,6 +339,11 @@ const redraw = async (canvas, options) => {
             }
         )
 
+        mainImage.controls = {
+            ...fabric.Image.prototype.controls,
+            mtr: new fabric.Control({ visible: false })
+        }
+
         if (mainImage.width >= mainImage.height) {
             mainImage.scaleToHeight(canvas.height)
         } else {
diff --git a/frontend/src/views/facebook_survey/FacebookSurvey.vue b/frontend/src/views/facebook_survey/FacebookSurvey.vue
index 9d8a9c76d903bd9ac066c836489c9569752089a6..4bcab6024be31c161f7cfec39e198e249a407b1c 100644
--- a/frontend/src/views/facebook_survey/FacebookSurvey.vue
+++ b/frontend/src/views/facebook_survey/FacebookSurvey.vue
@@ -18,6 +18,8 @@ import EmojiInput from '../../components/inputs/EmojiInput.vue'
 import RangeInput from '../../components/inputs/RangeInput.vue'
 import InputSeparator from '../../components/inputs/InputSeparator.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>
@@ -69,7 +71,25 @@ export default {
             gradientHeightMultiplier: 1,
             predefinedColors: predefinedColors,
             colors: predefinedColors.base.colors,
-            predefinedLogoImages: generateDefaultLogos('defaultDark')
+            predefinedLogoImages: generateDefaultLogos('defaultDark'),
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    mainImage: this.mainImage,
+                    mainText: this.mainText,
+                    logoImage: this.logoImage,
+                    colors: this.colors,
+                    firstEmojiImage: this.firstEmojiImage,
+                    secondEmojiImage: this.secondEmojiImage,
+                    firstEmojiText: this.firstEmojiText,
+                    secondEmojiText: this.secondEmojiText,
+                    gradientHeightMultiplier: this.gradientHeightMultiplier
+                }
+            )
         }
     },
     mounted () {
@@ -80,25 +100,15 @@ export default {
                 vm.logoImage,
                 vm.colors,
                 vm.firstEmojiImage,
-                vm.firstEmojiText,
                 vm.secondEmojiImage,
+                vm.firstEmojiText,
                 vm.secondEmojiText,
-                vm.gradientHeightMultiplier,
+                vm.gradientHeightMultiplier
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        mainImage: this.mainImage,
-                        mainText: this.mainText,
-                        logoImage: this.logoImage,
-                        colors: this.colors,
-                        firstEmojiImage: this.firstEmojiImage,
-                        secondEmojiImage: this.secondEmojiImage,
-                        firstEmojiText: this.firstEmojiText,
-                        secondEmojiText: this.secondEmojiText,
-                        gradientHeightMultiplier: this.gradientHeightMultiplier
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -112,7 +122,7 @@ export default {
 <template>
     <header>
         <Navbar
-            :defaultTemplate="TEMPLATES.basic_photo_banner"
+            :defaultTemplate="TEMPLATES.facebook_survey"
         ></Navbar>
     </header>
     <main>
@@ -127,6 +137,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <ImageInput
                     name="Obrázek"
                     v-model="mainImage"
diff --git a/frontend/src/views/facebook_survey/canvas.js b/frontend/src/views/facebook_survey/canvas.js
index 8ce15a81e107b79030fe79cfd70608bbd605ddfb..73f7d136610c4f7569c53ac6069fec6f8a06bfc1 100644
--- a/frontend/src/views/facebook_survey/canvas.js
+++ b/frontend/src/views/facebook_survey/canvas.js
@@ -1,3 +1,6 @@
+import alertifyjs from "alertifyjs"
+import "alertifyjs/build/css/alertify.css"
+
 import { fabric } from 'fabric'
 import { clearObjects, sortObjects, transformHighlightedText, checkTextBoxHeight } from '../../components/canvas/utils'
 import { PaddedHighlightingTextbox } from '../../components/canvas/textbox'
@@ -29,8 +32,7 @@ const redraw = async (canvas, options) => {
 
     canvas.preserveObjectStacking = true
 
-    const textMarginLeft = Math.ceil(canvas.width * 0.14)
-    const textMarginRight = Math.ceil(canvas.width * 0.14)
+    const textMarginSides = Math.ceil(canvas.width * 0.14)
 
     let mainTextMarginBottom = Math.ceil(canvas.height * 0.06)
     const mainTextBackgroundMarginTop = Math.ceil(canvas.height * 0.14)
@@ -50,8 +52,8 @@ const redraw = async (canvas, options) => {
     if (options.mainText !== null) {
         /* BEGIN Main text render */
 
-        const mainTextWidth = (canvas.width - textMarginLeft - textMarginRight)
-        
+        const mainTextWidth = (canvas.width - textMarginSides * 2)
+
         const highlightedData = transformHighlightedText(
             options.mainText,
             mainTextSize,
@@ -65,7 +67,7 @@ const redraw = async (canvas, options) => {
             highlightedData.text,
             {
                 width: canvas.width,
-                left: textMarginLeft,
+                left: 0,
                 textAlign: 'center',
                 fontFamily: 'Bebas Neue',
                 fontSize: mainTextSize,
@@ -114,10 +116,11 @@ const redraw = async (canvas, options) => {
         }
 
         if (options.firstEmojiText !== null && options.firstEmojiImage !== null) {
-            firstEmojiText = new fabric.Text(
+            firstEmojiText = new fabric.Textbox(
                 options.firstEmojiText,
                 {
                     left: firstEmojiImage.left + 250,
+                    width: 500,
                     top: (
                         mainTextBox.top
                         + mainTextBox.height
@@ -133,6 +136,37 @@ const redraw = async (canvas, options) => {
             )
 
             canvas.add(firstEmojiText)
+
+            if (firstEmojiText._textLines.length > 1) {
+                firstEmojiText.set({
+                    fontSize: firstEmojiText.fontSize - (mainTextSize / 2),
+                    lineHeight: 0.9
+                })
+                canvas.renderAll()
+            } else if (firstEmojiText.width > 500) {
+                firstEmojiText.set({
+                    fontSize: firstEmojiText.fontSize - (mainTextSize / 2),
+                    lineHeight: 0.9
+                })
+                firstEmojiText.top += firstEmojiText.fontSize / 2.5
+                canvas.renderAll()
+            }
+
+            if (firstEmojiText._textLines.length > 2) {
+                canvas.remove(firstEmojiText)
+
+                if (!window.showingMaxLinesWarning) {
+                    window.showingMaxLinesWarning = true
+
+                    const errorMessage = alertifyjs.error(
+                        "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho."
+                    )
+
+                    errorMessage.callback = () => {
+                        window.showingMaxLinesWarning = false
+                    }
+                }
+            }
         }
 
         /* END First emoji render */
@@ -158,10 +192,11 @@ const redraw = async (canvas, options) => {
         }
 
         if (options.secondEmojiText !== null && options.secondEmojiImage !== null) {
-            secondEmojiText = new fabric.Text(
+            secondEmojiText = new fabric.Textbox(
                 options.secondEmojiText,
                 {
                     left: secondEmojiImage.left + 250,
+                    width: 300,
                     top: (
                         mainTextBox.top
                         + mainTextBox.height
@@ -177,6 +212,37 @@ const redraw = async (canvas, options) => {
             )
 
             canvas.add(secondEmojiText)
+
+            if (secondEmojiText._textLines.length > 1) {
+                secondEmojiText.set({
+                    fontSize: secondEmojiText.fontSize - (mainTextSize / 2),
+                    lineHeight: 0.9
+                })
+                canvas.renderAll()
+            } else if (secondEmojiText.width > 500) {
+                secondEmojiText.set({
+                    fontSize: secondEmojiText.fontSize - (mainTextSize / 2),
+                    lineHeight: 0.9
+                })
+                secondEmojiText.top += secondEmojiText.fontSize / 2.5
+                canvas.renderAll()
+            }
+
+            if (secondEmojiText._textLines.length > 2) {
+                canvas.remove(secondEmojiText)
+
+                if (!window.showingMaxLinesWarning) {
+                    window.showingMaxLinesWarning = true
+
+                    const errorMessage = alertifyjs.error(
+                        "Text je moc dlouhý a nevejde se do 2 řádků. Prosím, zkrať ho."
+                    )
+
+                    errorMessage.callback = () => {
+                        window.showingMaxLinesWarning = false
+                    }
+                }
+            }
         }
 
         /* END Second Emoji render */
@@ -192,9 +258,9 @@ const redraw = async (canvas, options) => {
 
         mainTextBoxBackground = new fabric.Rect(
             {
-                width: canvas.width,
+                width: canvas.width + 40,  // FIXME: My god!
                 height: backgroundHeight * options.gradientHeightMultiplier,
-                left: 0,
+                left: -20,
                 top: (
                     mainTextBox.top
                     - mainTextBackgroundMarginTop
@@ -290,6 +356,11 @@ const redraw = async (canvas, options) => {
             }
         )
 
+        mainImage.controls = {
+            ...fabric.Image.prototype.controls,
+            mtr: new fabric.Control({ visible: false })
+        }
+
         if (mainImage.width >= mainImage.height) {
             mainImage.scaleToHeight(canvas.height)
         } else {
diff --git a/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue b/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue
index aa2ba812d440d9018f4a1cf9c0139335f1678498..3fe3be7a22d0db062448e322980e4107086a6d2e 100644
--- a/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue
+++ b/frontend/src/views/newspaper_quote_bottom/NewspaperQuoteBottom.vue
@@ -18,6 +18,8 @@ import LongTextInput from '../../components/inputs/text/LongTextInput.vue'
 import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue';
 import InputSeparator from '../../components/inputs/InputSeparator.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>
@@ -79,7 +81,23 @@ export default {
             predefinedColors: predefinedColors,
             colors: predefinedColors.base.colors,
             predefinedLogoImages: generateDefaultLogos('defaultLight'),
-            predefinedSourceImages: SOURCE_IMAGES
+            predefinedSourceImages: SOURCE_IMAGES,
+            autoRedraw: false
+        }
+    },
+    methods: {
+        async reloadCanvasProperties () {
+            await this.$refs.canvas.redraw(
+                {
+                    mainImage: this.mainImage,
+                    sourceImage: this.sourceImage,
+                    mainText: this.mainText,
+                    personName: this.personName,
+                    personPosition: this.personPosition,
+                    logoImage: this.logoImage,
+                    colors: this.colors
+                }
+            )
         }
     },
     mounted () {
@@ -94,17 +112,9 @@ export default {
                 vm.colors
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        mainImage: this.mainImage,
-                        sourceImage: this.sourceImage,
-                        mainText: this.mainText,
-                        personName: this.personName,
-                        personPosition: this.personPosition,
-                        logoImage: this.logoImage,
-                        colors: this.colors
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -133,6 +143,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <ImageInput
                     name="Obrázek"
                     v-model="mainImage"
diff --git a/frontend/src/views/newspaper_quote_bottom/canvas.js b/frontend/src/views/newspaper_quote_bottom/canvas.js
index ca8492f2d93c71e795867cfefbb74ee6d41b7c5a..66cda5c510cd9e796b6ffb8b6982f4a1a7ea0713 100644
--- a/frontend/src/views/newspaper_quote_bottom/canvas.js
+++ b/frontend/src/views/newspaper_quote_bottom/canvas.js
@@ -51,7 +51,7 @@ const redraw = async (canvas, options) => {
     const nameTextMarginTop = Math.ceil(canvas.height * 0.025)
     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 personInfoTextMaxWidth = Math.ceil(canvas.width * 0.5)
 
     const bottomTextSize = Math.ceil(canvas.height * 0.03)
     const additionalContentExtraBottomMargin = Math.ceil(canvas.height * 0.1)
@@ -60,7 +60,7 @@ const redraw = async (canvas, options) => {
     const logoSideMargin = Math.ceil(canvas.width * 0.07)
 
     const sourceImageHeight = Math.ceil(canvas.height * 0.07)
-    const sourceImageMarginBottom = Math.ceil(canvas.width * 0.07)
+    const sourceImageMarginBottom = Math.ceil(canvas.width * 0.075)
     const sourceImageMarginSide = Math.ceil(canvas.width * 0.07)
 
     const sourceTextMarginSide = Math.ceil(canvas.width * 0.005)
@@ -112,7 +112,7 @@ const redraw = async (canvas, options) => {
                     selectable: false,
                     zIndex: 10
                 })
- 
+
                 canvas.add(personInfoSeparator)
 
                 personPositionText = new fabric.Textbox(
@@ -120,7 +120,7 @@ const redraw = async (canvas, options) => {
                     {
                         left: personInfoSeparator.left + personInfoSeparator.width + positionTextSideGap,
                         top: personNameText.top,
-                        width: positionTextMaxWidth,
+                        width: personInfoTextMaxWidth - personNameText.width,
                         fontFamily: 'Roboto Condensed',
                         fontSize: bottomTextSize,
                         fill: options.colors.baseText.value,
@@ -174,6 +174,7 @@ const redraw = async (canvas, options) => {
                     - sourceImageHeight
                     - sourceImageMarginBottom
                 ),
+                selectable: false,
                 zIndex: 10,
             })
 
@@ -186,6 +187,7 @@ const redraw = async (canvas, options) => {
                 fontFamily: 'Roboto Condensed',
                 left: sourceImage.left - sourceTextMarginSide,
                 top: sourceImage.top,
+                selectable: false,
                 zIndex: 10
             })
 
@@ -444,6 +446,11 @@ const redraw = async (canvas, options) => {
             }
         )
 
+        mainImage.controls = {
+            ...fabric.Image.prototype.controls,
+            mtr: new fabric.Control({ visible: false })
+        }
+
         if (mainImage.width >= mainImage.height) {
             mainImage.scaleToHeight(canvas.height)
         } else {
diff --git a/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue b/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue
index 356dc98c127b0a67d36eddccd025d6daa328de3c..7f000233ce43e0b22ac2e1ace8f05ccd1e3364bf 100644
--- a/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue
+++ b/frontend/src/views/newspaper_quote_middle/NewspaperQuoteMiddle.vue
@@ -18,6 +18,8 @@ import LongTextInput from '../../components/inputs/text/LongTextInput.vue'
 import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue'
 import InputSeparator from '../../components/inputs/InputSeparator.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>
@@ -84,7 +86,22 @@ export default {
             predefinedColors: predefinedColors,
             colors: predefinedColors.base.colors,
             predefinedLogoImages: generateDefaultLogos('defaultDark'),
-            predefinedSourceImages: SOURCE_IMAGES
+            predefinedSourceImages: SOURCE_IMAGES,
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    sourceImage: this.sourceImage,
+                    mainText: this.mainText,
+                    personName: this.personName,
+                    personPosition: this.personPosition,
+                    logoImage: this.logoImage,
+                    colors: this.colors
+                }
+            )
         }
     },
     mounted () {
@@ -98,16 +115,9 @@ export default {
                 vm.colors
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        sourceImage: this.sourceImage,
-                        mainText: this.mainText,
-                        personName: this.personName,
-                        personPosition: this.personPosition,
-                        logoImage: this.logoImage,
-                        colors: this.colors
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -136,6 +146,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <LongTextInput
                     name="Hlavní text"
                     v-model="mainText"
diff --git a/frontend/src/views/newspaper_quote_middle/canvas.js b/frontend/src/views/newspaper_quote_middle/canvas.js
index d8e0be2f26989952518cae72bedd10b0d2579fca..63cdaacfcd50b8f690bc89f5efc8861af9ad0ab4 100644
--- a/frontend/src/views/newspaper_quote_middle/canvas.js
+++ b/frontend/src/views/newspaper_quote_middle/canvas.js
@@ -90,10 +90,10 @@ const redraw = async (canvas, options) => {
     /* BEGIN Background render */
 
     background = new fabric.Rect({
-        left: 0,
-        top: 0,
-        width: canvas.width,
-        height: canvas.height,
+        width: canvas.width * 1.1,
+        height: canvas.height * 1.1,
+        top: -20,  // FIXME: Why???? Fabric.js, what are you trying to tell me?!
+        left: -20,
         fill: options.colors.background.value,
         selectable: false,
         zIndex: 0
@@ -151,7 +151,7 @@ const redraw = async (canvas, options) => {
                     selectable: false,
                     zIndex: 10
                 })
- 
+
                 canvas.add(personInfoSeparator)
 
                 personPositionText = new fabric.Text(
@@ -454,7 +454,7 @@ const redraw = async (canvas, options) => {
                 // Hacky seam fix
                 {x: canvas.width, y: mainTextBoxForegroundBottomY},
                 {x: canvas.width, y: bottomTearBottom - (canvas.height * 0.063)},
-                {x: canvas.width * 0.95, y: bottomTearBottom - (canvas.height * 0.055)},
+                {x: canvas.width * 0.95, y: bottomTearBottom - (canvas.height * 0.06)},
                 {x: canvas.width * 0.92, y: bottomTearBottom - (canvas.height * 0.045)},
                 {x: canvas.width * 0.85, y: bottomTearBottom - (canvas.height * 0.07)},
                 {x: canvas.width * 0.77, y: bottomTearBottom - (canvas.height * 0.06)},
@@ -543,6 +543,7 @@ const redraw = async (canvas, options) => {
         sourceImage.set({
             left: sourceImageMarginSide,
             top: sourceImageMarginTop,
+            selectable: false,
             zIndex: 10,
         })
 
@@ -554,7 +555,8 @@ const redraw = async (canvas, options) => {
             fontFamily: 'Roboto Condensed',
             left: sourceImage.left - sourceTextMarginSide,
             top: sourceImage.top,
-            zIndex: 10
+            selectable: false,
+            zIndex: 10,
         })
 
         sourceText.set({
diff --git a/frontend/src/views/text_banner/TextBanner.vue b/frontend/src/views/text_banner/TextBanner.vue
index 7f3e1aae8427e8bd63c03bbd4c1900f58513bb5d..8db304a740495e2054e30a14f8e3cdd04d864c5e 100644
--- a/frontend/src/views/text_banner/TextBanner.vue
+++ b/frontend/src/views/text_banner/TextBanner.vue
@@ -14,6 +14,8 @@ import MainContainer from '../../components/MainContainer.vue';
 import ImageInput from '../../components/inputs/ImageInput.vue';
 import LongTextInput from '../../components/inputs/text/LongTextInput.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>
@@ -54,7 +56,19 @@ export default {
             },
             predefinedColors: predefinedColors,
             colors: predefinedColors.base.colors,
-            predefinedLogoImages: generateDefaultLogos('defaultLight')
+            predefinedLogoImages: generateDefaultLogos('defaultLight'),
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    text: this.text,
+                    logoImage: this.logoImage,
+                    colors: this.colors
+                }
+            )
         }
     },
     mounted () {
@@ -65,13 +79,9 @@ export default {
                 vm.colors
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        text: this.text,
-                        logoImage: this.logoImage,
-                        colors: this.colors
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -100,6 +110,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <LongTextInput
                     name="Text"
                     v-model="text"
diff --git a/frontend/src/views/text_banner/canvas.js b/frontend/src/views/text_banner/canvas.js
index 8b7da06ba598f3a588067c1dfcfae0a39bed1150..3c614fd3ad2af5b5650e3456037be18af1f7b512 100644
--- a/frontend/src/views/text_banner/canvas.js
+++ b/frontend/src/views/text_banner/canvas.js
@@ -42,10 +42,10 @@ const redraw = async (canvas, options) => {
     /* BEGIN Background render */
 
     backgroundRect = new fabric.Rect({
-        width: canvas.width,
-        height: canvas.height,
-        top: 0,
-        left: 0,
+        width: canvas.width * 1.1,
+        height: canvas.height * 1.1,
+        top: -20,  // FIXME: Why???? Fabric.js, what are you trying to tell me?!
+        left: -20,
         fill: options.colors.background.value,
         selectable: false,
         zIndex: 0
diff --git a/frontend/src/views/twitter_banner/PersonInput.vue b/frontend/src/views/twitter_banner/PersonInput.vue
index 99d784867f4640c059385e4cd63377624b00b31e..4255c9e8f492c664947df8e3049dc8520333fe2b 100644
--- a/frontend/src/views/twitter_banner/PersonInput.vue
+++ b/frontend/src/views/twitter_banner/PersonInput.vue
@@ -25,7 +25,6 @@ export default {
     watch: {
         selectedOption: {
             async handler (value) {
-                console.log(value)
                 const mainImage = new Image()
 
                 await new Promise(
diff --git a/frontend/src/views/twitter_banner/TwitterBanner.vue b/frontend/src/views/twitter_banner/TwitterBanner.vue
index cf6ff49d166a747f5bbc379f1ebbafdd9cf7e025..c9a4f82dd030ccc6af2389c8c7ee51fec843a882 100644
--- a/frontend/src/views/twitter_banner/TwitterBanner.vue
+++ b/frontend/src/views/twitter_banner/TwitterBanner.vue
@@ -15,6 +15,8 @@ import Navbar from '../../components/Navbar.vue'
 import MainContainer from '../../components/MainContainer.vue'
 import LongTextInput from '../../components/inputs/text/LongTextInput.vue'
 import PersonInput from './PersonInput.vue'
+import ReloadButton from '../../components/reload/ReloadButton.vue'
+import AutoReloadCheckbox from '../../components/reload/AutoReloadCheckbox.vue'
 
 import klaraImage from '../../assets/template/twitter_banner/klara.png'
 import ivanImage from '../../assets/template/twitter_banner/ivan.png'
@@ -83,33 +85,43 @@ export default {
                 text: COLORS.black,
                 highlight: COLORS.yellow1,
                 highlightedText: COLORS.black
-            }
+            },
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    colors: this.colors,
+                    logoImageSource: this.logoImageSource,
+                    twitterLogoImageSource: this.twitterLogoImageSource,
+                    mainImage: this.mainImage,
+                    mainText: this.mainText,
+                    personName: this.personName,
+                    personTwitter: this.personTwitter
+                }
+            )
         }
     },
     mounted () {
         this.$watch(
             vm => [
+                vm.colors,
+                vm.logoImageSource,
+                vm.twitterLogoImageSource,
                 vm.mainImage,
                 vm.mainText,
                 vm.personName,
                 vm.personTwitter
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        colors: this.colors,
-                        logoImageSource: this.logoImageSource,
-                        twitterLogoImageSource: this.twitterLogoImageSource,
-                        mainImage: this.mainImage,
-                        mainText: this.mainText,
-                        personName: this.personName,
-                        personTwitter: this.personTwitter
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
-                // Let the person chooser element run the first redraw
-                // immediate: true,
+                immediate: true,
                 deep: true
             }
         )
@@ -135,6 +147,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <PersonInput
                     name="Člověk"
                     v-model:mainImage="mainImage"
diff --git a/frontend/src/views/twitter_banner/canvas.js b/frontend/src/views/twitter_banner/canvas.js
index cdc7b320f0c523ad120e2c400d889af15e380e6d..2156f877b38f1878abfd01ad2365058b482dfd5b 100644
--- a/frontend/src/views/twitter_banner/canvas.js
+++ b/frontend/src/views/twitter_banner/canvas.js
@@ -53,11 +53,11 @@ const redraw = async (canvas, options) => {
 
     if (background === null) {
         background = new fabric.Rect({
-            left: 0,
-            top: 0,
-            fill: options.colors.background.value,
-            width: canvas.width,
-            height: canvas.height
+            width: canvas.width * 1.1,
+            height: canvas.height * 1.1,
+            top: -20,  // FIXME: Why???? Fabric.js, what are you trying to tell me?!
+            left: -20,
+            fill: options.colors.background.value
         })
 
         canvas.add(background)
diff --git a/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue b/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue
index 847af4f8e3d92d11234328eb8d715c407af52c5f..b33702e87a9733039a6dcb98149a5ed6b0bf1db0 100644
--- a/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue
+++ b/frontend/src/views/urgent_basic_photo_banner/UrgentBasicPhotoBanner.vue
@@ -18,6 +18,8 @@ import ShortTextInput from '../../components/inputs/text/ShortTextInput.vue'
 import RangeInput from '../../components/inputs/RangeInput.vue'
 import InputSeparator from '../../components/inputs/InputSeparator.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>
@@ -69,7 +71,23 @@ export default {
             },
             predefinedColors: predefinedColors,
             colors: predefinedColors.base.colors,
-            predefinedLogoImages: generateDefaultLogos('defaultLight')
+            predefinedLogoImages: generateDefaultLogos('defaultLight'),
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    mainImage: this.mainImage,
+                    mainText: this.mainText,
+                    personName: this.personName,
+                    personPosition: this.personPosition,
+                    logoImage: this.logoImage,
+                    gradientHeightMultiplier: this.gradientHeightMultiplier,
+                    colors: this.colors
+                }
+            )
         }
     },
     mounted () {
@@ -84,17 +102,9 @@ export default {
                 vm.colors
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        mainImage: this.mainImage,
-                        mainText: this.mainText,
-                        personName: this.personName,
-                        personPosition: this.personPosition,
-                        logoImage: this.logoImage,
-                        gradientHeightMultiplier: this.gradientHeightMultiplier,
-                        colors: this.colors
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -123,6 +133,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <ImageInput
                     name="Obrázek"
                     v-model="mainImage"
diff --git a/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue b/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue
index 5eb79475c964c7e0ff6a141fca65cf575b3fa6cb..0d9f15e45471738e11267495cdbe5068618d9312 100644
--- a/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue
+++ b/frontend/src/views/urgent_text_banner/UrgentTextBanner.vue
@@ -14,6 +14,8 @@ import MainContainer from '../../components/MainContainer.vue';
 import ImageInput from '../../components/inputs/ImageInput.vue';
 import LongTextInput from '../../components/inputs/text/LongTextInput.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>
@@ -60,7 +62,19 @@ export default {
                     src: LOGOS.defaultDark.src,
                     defaultSelected: true,
                 },
-            ]
+            ],
+            autoRedraw: false
+        }
+    },
+    methods: {
+        reloadCanvasProperties () {
+            this.$refs.canvas.redraw(
+                {
+                    text: this.text,
+                    logoImage: this.logoImage,
+                    colors: this.colors
+                }
+            )
         }
     },
     mounted () {
@@ -71,13 +85,9 @@ export default {
                 vm.colors
             ],
             async (value) => {
-                await this.$refs.canvas.redraw(
-                    {
-                        text: this.text,
-                        logoImage: this.logoImage,
-                        colors: this.colors
-                    }
-                );
+                if (this.autoRedraw) {
+                    await this.reloadCanvasProperties()
+                }
             },
             {
                 immediate: true,
@@ -106,6 +116,13 @@ export default {
             </template>
 
             <template v-slot:right>
+                <ReloadButton
+                    :parentRefs="$refs"
+                    @click="reloadCanvasProperties"
+                />
+                <AutoReloadCheckbox
+                    v-model="autoRedraw"
+                />
                 <LongTextInput
                     name="Text"
                     v-model="text"
diff --git a/frontend/src/views/urgent_text_banner/canvas.js b/frontend/src/views/urgent_text_banner/canvas.js
index 80687235a18bebe6a062272b17ead657e307227e..9e7a9adc880ac3cad8731c8e55553caa644ddccc 100644
--- a/frontend/src/views/urgent_text_banner/canvas.js
+++ b/frontend/src/views/urgent_text_banner/canvas.js
@@ -39,10 +39,10 @@ const redraw = async (canvas, options) => {
         backgroundImage = new fabric.Image(
             backgroundImage,
             {
-                left: 0,
-                top: 0,
-                width: canvas.width,
-                height: canvas.height,
+                width: canvas.width * 1.1,
+                height: canvas.height * 1.1,
+                top: -20,  // FIXME: Why???? Fabric.js, what are you trying to tell me?!
+                left: -20,
                 zIndex: 0,
                 selectable: false
             }
diff --git a/frontend/src/views/utils/newspaper_quotes.js b/frontend/src/views/utils/newspaper_quotes.js
index ac04b8f6450efffd6620e0aa2b5de0672ed84473..04ea6f810b96ca2554d4e2b63a4fa41f0a6802f7 100644
--- a/frontend/src/views/utils/newspaper_quotes.js
+++ b/frontend/src/views/utils/newspaper_quotes.js
@@ -2,6 +2,7 @@ import sourceImageCT from '../../assets/news_sources/ct.png'
 import sourceImageDenikN from '../../assets/news_sources/denik_n.png'
 import sourceImageSeznam from '../../assets/news_sources/seznam_zpravy.png'
 import sourceImageCNN from '../../assets/news_sources/cnn.png'
+import sourceImageCzechRadio from '../../assets/news_sources/rozhlas.png'
 
 const SOURCE_IMAGES = [
     {
@@ -23,7 +24,12 @@ const SOURCE_IMAGES = [
         name: 'CNN',
         src: sourceImageCNN,
         defaultSelected: false
+    },
+    {
+        name: 'Český Rozhlas',
+        src: sourceImageCzechRadio,
+        defaultSelected: false
     }
-] 
+]
 
 export { SOURCE_IMAGES }
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 4f73cadebd562a73a6c4d2761543e93bf493777b..76edd3b45c9f66fef1df1ef708786cf0f761507b 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -9,4 +9,3 @@ module.exports = {
   },
   plugins: [],
 }
-
diff --git a/server/server/__init__.py b/server/server/__init__.py
index d07d396624e60960031ee6e0cfe533197fac7106..1eca9d8e276b677738d490e46b1373150cc144c4 100644
--- a/server/server/__init__.py
+++ b/server/server/__init__.py
@@ -21,9 +21,7 @@ def create_app() -> flask.Flask:
             "OIDC_RP_REALM_URL",
             "OIDC_RP_CLIENT_ID",
             "OIDC_RP_CLIENT_SECRET",
-
             "SITE_URL",
-
             "SECRET_KEY",
             "JWT_SECRET_KEY",
         ):
@@ -32,14 +30,8 @@ def create_app() -> flask.Flask:
         app.config["JWT_EXPIRES_AFTER"] = int(os.getenv("JWT_EXPIRES_AFTER"))
         app.config["ALLOWED_OIDC_GROUPS"] = os.getenv("ALLOWED_OIDC_GROUPS").split(",")
 
-        for blueprint in (
-            views.frontend_blueprint,
-            views.oidc_blueprint
-        ):
-            app.logger.debug(
-                "Registering blueprint: %s",
-                blueprint
-            )
+        for blueprint in (views.frontend_blueprint, views.oidc_blueprint):
+            app.logger.debug("Registering blueprint: %s", blueprint)
 
             app.register_blueprint(blueprint)
 
diff --git a/server/server/authentication/__init__.py b/server/server/authentication/__init__.py
index 443bf8566bcc932ef680c3dfb59acaecaa60285f..80804f8de7e6295f791f92b681544e1a55aea28e 100644
--- a/server/server/authentication/__init__.py
+++ b/server/server/authentication/__init__.py
@@ -6,20 +6,16 @@ import flask
 import werkzeug
 
 
-def authentication_required(
-    func: typing.Callable
-) -> typing.Callable:
+def authentication_required(func: typing.Callable) -> typing.Callable:
     @functools.wraps(func)
     def decorator(*args, **kwargs) -> typing.Any:
         if "token" not in flask.request.cookies:
-            return flask.redirect(
-                flask.url_for("oidc.login")
-            )
+            return flask.redirect(flask.url_for("oidc.login"))
 
         try:
             claims = authlib.jose.jwt.decode(
                 flask.request.cookies["token"],
-                flask.current_app.config["JWT_SECRET_KEY"]
+                flask.current_app.config["JWT_SECRET_KEY"],
             )
         except authlib.jose.JoseError as exception:
             raise werkzeug.exceptions.BadRequest from exception
@@ -28,14 +24,11 @@ def authentication_required(
             claims.validate()
         except authlib.jose.JoseError as exception:
             flask.current_app.logger.info(
-                "Claim validation failed for user %s: %s",
-                claims["sub"],
-                exception
+                "Claim validation failed for user %s: %s", claims["sub"], exception
             )
 
-            return flask.redirect(
-                flask.url_for("oidc.login")
-            )
+            return flask.redirect(flask.url_for("oidc.login"))
 
         return func(*args, **kwargs)
+
     return decorator
diff --git a/server/server/templates/index.html b/server/server/templates/index.html
deleted file mode 100644
index 3d4b487726882c45175985519984ce3b65b965e6..0000000000000000000000000000000000000000
--- a/server/server/templates/index.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8">
-    <link rel="icon" href="/static/favicon.ico">
-    <link rel="stylesheet" href="https://styleguide.pirati.cz/2.12.x/css/styles.css">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>Generátor grafiky</title>
-    <script type="module" crossorigin src="/static/index-a12fbb13.js"></script>
-    <link rel="stylesheet" href="/static/index-ea030523.css">
-  </head>
-  <body>
-    <div id="app"></div>
-    
-  </body>
-</html>
diff --git a/server/server/views/frontend.py b/server/server/views/frontend.py
index a30ce77b57293f54bbdfa31f0e291ed5e1673763..adbef73d70cc5c1a6a78da6d4659ad20f747ed9d 100644
--- a/server/server/views/frontend.py
+++ b/server/server/views/frontend.py
@@ -2,11 +2,7 @@ import flask
 
 from server.authentication import authentication_required
 
-
-frontend_blueprint = flask.Blueprint(
-    "frontend",
-    __name__
-)
+frontend_blueprint = flask.Blueprint("frontend", __name__)
 
 
 @frontend_blueprint.route("/", defaults={"path": ""})
diff --git a/server/server/views/oidc.py b/server/server/views/oidc.py
index cc03d6bd8059563cdd21145c76f03a95683cb7a4..bfae17d86092dec85370456a11ab729b218f313f 100644
--- a/server/server/views/oidc.py
+++ b/server/server/views/oidc.py
@@ -8,12 +8,7 @@ import flask
 import requests
 import werkzeug.exceptions
 
-
-oidc_blueprint = flask.Blueprint(
-    "oidc",
-    __name__,
-    url_prefix="/oidc"
-)
+oidc_blueprint = flask.Blueprint("oidc", __name__, url_prefix="/oidc")
 
 
 def generate_session() -> authlib.integrations.requests_client.OAuth2Session:
@@ -26,7 +21,7 @@ def generate_session() -> authlib.integrations.requests_client.OAuth2Session:
         token_endpoint=f"{realm_url}protocol/openid-connect/token",
         userinfo_endpoint=f"{realm_url}protocol/openid-connect/userinfo",
         jwks_uri=f"{realm_url}protocol/openid-connect/certs",
-        scope="openid profile groups"
+        scope="openid profile groups",
     )
 
 
@@ -37,9 +32,9 @@ def generate_redirect_uri() -> str:
         _scheme=(
             flask.request.headers.get(
                 "X-Forwarded-Proto",
-                "http" if flask.request.url.startswith("http://") else "https"
+                "http" if flask.request.url.startswith("http://") else "https",
             )
-        )
+        ),
     )
 
 
@@ -48,7 +43,7 @@ def login() -> flask.Response:
     with generate_session() as session:
         url, state = session.create_authorization_url(
             session.metadata["authorization_endpoint"],
-            redirect_uri=generate_redirect_uri()
+            redirect_uri=generate_redirect_uri(),
         )
 
         return flask.redirect(url)
@@ -68,15 +63,14 @@ def authenticate() -> flask.Response:
                 session.metadata["token_endpoint"],
                 code=code,
                 state=state,
-                redirect_uri=generate_redirect_uri()
+                redirect_uri=generate_redirect_uri(),
             )
         except (
             authlib.integrations.requests_client.OAuthError,
-            requests.HTTPError
+            requests.HTTPError,
         ) as exception:
             flask.current_app.logger.debug(
-                "Error during OIDC authentication: %s",
-                exception
+                "Error during OIDC authentication: %s", exception
             )
 
             raise werkzeug.exceptions.Unauthorized from exception
@@ -84,9 +78,7 @@ def authenticate() -> flask.Response:
         jwks = session.get(session.metadata["jwks_uri"]).json()
 
         claims = authlib.jose.jwt.decode(
-            token["id_token"],
-            jwks,
-            claims_cls=authlib.oidc.core.CodeIDToken
+            token["id_token"], jwks, claims_cls=authlib.oidc.core.CodeIDToken
         )
         claims.validate()
 
@@ -94,46 +86,31 @@ def authenticate() -> flask.Response:
 
     current_timestamp = round(datetime.datetime.now().timestamp())
 
-    #found_allowed_group = False
-
-    #for group in claims["groups"]:
-        #if group in flask.current_app.config["ALLOWED_OIDC_GROUPS"]:
-            #found_allowed_group = True
-            #break
-
-    #if not found_allowed_group:
-        #raise werkzeug.exceptions.Unauthorized(
-            #"Nemáš dostatečné skupiny k přístupu do Generátoru."
-        #)
-
-    jwt = (
-        authlib.jose.jwt.encode(
-            {
-                "alg": "HS256"
-            },
-            {
-                "iss": "graphics-generator",
-                "iat": current_timestamp,
-                "exp": (
-                    current_timestamp
-                    + flask.current_app.config["JWT_EXPIRES_AFTER"]
-                ),
-                "sub": userinfo["sub"]  # Currently unused, maybe of use later
-            },
-            flask.current_app.config["JWT_SECRET_KEY"]
-        ).
-        decode("utf-8")
-    )
+    # found_allowed_group = False
 
-    response = flask.make_response(
-        flask.redirect(
-            flask.url_for("frontend.index")
-        )
-    )
+    # for group in claims["groups"]:
+    # if group in flask.current_app.config["ALLOWED_OIDC_GROUPS"]:
+    # found_allowed_group = True
+    # break
 
-    response.set_cookie(
-        "token",
-        jwt
-    )
+    # if not found_allowed_group:
+    # raise werkzeug.exceptions.Unauthorized(
+    # "Nemáš dostatečné skupiny k přístupu do Generátoru."
+    # )
+
+    jwt = authlib.jose.jwt.encode(
+        {"alg": "HS256"},
+        {
+            "iss": "graphics-generator",
+            "iat": current_timestamp,
+            "exp": (current_timestamp + flask.current_app.config["JWT_EXPIRES_AFTER"]),
+            "sub": userinfo["sub"],  # Currently unused, maybe of use later
+        },
+        flask.current_app.config["JWT_SECRET_KEY"],
+    ).decode("utf-8")
+
+    response = flask.make_response(flask.redirect(flask.url_for("frontend.index")))
+
+    response.set_cookie("token", jwt)
 
-    return response 
+    return response