diff --git a/package-lock.json b/package-lock.json
index 9e613422d0..05d414c9ae 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
         "@claviska/jquery-minicolors": "2.3.6",
         "@mcaptcha/vanilla-glue": "0.1.0-alpha-2",
         "@primer/octicons": "17.5.0",
+        "@vue/compiler-sfc": "3.2.37",
         "add-asset-webpack-plugin": "2.0.1",
         "css-loader": "6.7.1",
         "dropzone": "6.0.0-beta.2",
@@ -34,11 +35,10 @@
         "tippy.js": "6.3.7",
         "tributejs": "5.1.3",
         "uint8-to-base64": "0.2.0",
-        "vue": "2.6.14",
-        "vue-bar-graph": "1.3.1",
-        "vue-calendar-heatmap": "0.8.4",
-        "vue-loader": "15.9.8",
-        "vue-template-compiler": "2.6.14",
+        "vue": "3.2.37",
+        "vue-bar-graph": "2.0.0",
+        "vue-loader": "17.0.0",
+        "vue3-calendar-heatmap": "2.0.0",
         "webpack": "5.74.0",
         "webpack-cli": "4.10.0",
         "workbox-routing": "6.5.4",
@@ -431,7 +431,6 @@
       "version": "7.19.0",
       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz",
       "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==",
-      "dev": true,
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -616,17 +615,6 @@
         "@babel/core": "^7.0.0-0"
       }
     },
-    "node_modules/@babel/runtime": {
-      "version": "7.19.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz",
-      "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==",
-      "dependencies": {
-        "regenerator-runtime": "^0.13.4"
-      },
-      "engines": {
-        "node": ">=6.9.0"
-      }
-    },
     "node_modules/@babel/template": {
       "version": "7.18.10",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -2303,58 +2291,107 @@
       "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
       "dev": true
     },
-    "node_modules/@vue/component-compiler-utils": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
-      "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==",
+    "node_modules/@vue/compiler-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
+      "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
       "dependencies": {
-        "consolidate": "^0.15.1",
-        "hash-sum": "^1.0.2",
-        "lru-cache": "^4.1.2",
-        "merge-source-map": "^1.1.0",
-        "postcss": "^7.0.36",
-        "postcss-selector-parser": "^6.0.2",
-        "source-map": "~0.6.1",
-        "vue-template-es2015-compiler": "^1.9.0"
-      },
-      "optionalDependencies": {
-        "prettier": "^1.18.2 || ^2.0.0"
-      }
-    },
-    "node_modules/@vue/component-compiler-utils/node_modules/lru-cache": {
-      "version": "4.1.5",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
-      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
-      "dependencies": {
-        "pseudomap": "^1.0.2",
-        "yallist": "^2.1.2"
-      }
-    },
-    "node_modules/@vue/component-compiler-utils/node_modules/picocolors": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
-      "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
-    },
-    "node_modules/@vue/component-compiler-utils/node_modules/postcss": {
-      "version": "7.0.39",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
-      "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
-      "dependencies": {
-        "picocolors": "^0.2.1",
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
         "source-map": "^0.6.1"
-      },
-      "engines": {
-        "node": ">=6.0.0"
-      },
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/postcss/"
       }
     },
-    "node_modules/@vue/component-compiler-utils/node_modules/yallist": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-      "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
+    "node_modules/@vue/compiler-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
+      "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
+      "dependencies": {
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/compiler-sfc": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
+      "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/reactivity-transform": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      }
+    },
+    "node_modules/@vue/compiler-ssr": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
+      "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
+      "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
+      "dependencies": {
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/reactivity-transform": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
+      "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
+      "dependencies": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
+      "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
+      "dependencies": {
+        "@vue/reactivity": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
+      "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
+      "dependencies": {
+        "@vue/runtime-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "csstype": "^2.6.8"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
+      "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/shared": "3.2.37"
+      },
+      "peerDependencies": {
+        "vue": "3.2.37"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
+      "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
     },
     "node_modules/@webassemblyjs/ast": {
       "version": "1.11.1",
@@ -2978,11 +3015,6 @@
         "node": "*"
       }
     },
-    "node_modules/bluebird": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
-      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
-    },
     "node_modules/blueimp-md5": {
       "version": "2.18.0",
       "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
@@ -3156,7 +3188,6 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
       "dependencies": {
         "ansi-styles": "^4.1.0",
         "supports-color": "^7.1.0"
@@ -3358,17 +3389,6 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
-    "node_modules/consolidate": {
-      "version": "0.15.1",
-      "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz",
-      "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==",
-      "dependencies": {
-        "bluebird": "^3.1.1"
-      },
-      "engines": {
-        "node": ">= 0.10.0"
-      }
-    },
     "node_modules/convert-source-map": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
@@ -3546,6 +3566,11 @@
       "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
       "dev": true
     },
+    "node_modules/csstype": {
+      "version": "2.6.21",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
+      "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+    },
     "node_modules/d3": {
       "version": "7.6.1",
       "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz",
@@ -4258,11 +4283,6 @@
         "node": ">=12"
       }
     },
-    "node_modules/de-indent": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
-      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="
-    },
     "node_modules/debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -5658,8 +5678,7 @@
     "node_modules/estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
-      "dev": true
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "node_modules/esutils": {
       "version": "2.0.3",
@@ -6341,17 +6360,9 @@
       }
     },
     "node_modules/hash-sum": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
-      "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
-    },
-    "node_modules/he": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
-      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
-      "bin": {
-        "he": "bin/he"
-      }
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
+      "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
     },
     "node_modules/hosted-git-info": {
       "version": "2.8.9",
@@ -8068,7 +8079,6 @@
       "version": "0.25.9",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
       "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
-      "dev": true,
       "dependencies": {
         "sourcemap-codec": "^1.4.8"
       }
@@ -8341,14 +8351,6 @@
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
       "dev": true
     },
-    "node_modules/merge-source-map": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
-      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
-      "dependencies": {
-        "source-map": "^0.6.1"
-      }
-    },
     "node_modules/merge-stream": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -8471,7 +8473,8 @@
     "node_modules/minimist": {
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
-      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+      "dev": true
     },
     "node_modules/minimist-options": {
       "version": "4.1.0",
@@ -9144,16 +9147,6 @@
         "node": ">=12.0.0"
       }
     },
-    "node_modules/popper.js": {
-      "version": "1.16.1",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
-      "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
-      "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
-      "funding": {
-        "type": "opencollective",
-        "url": "https://opencollective.com/popperjs"
-      }
-    },
     "node_modules/postcss": {
       "version": "8.4.16",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
@@ -9298,21 +9291,6 @@
         "node": ">= 0.8.0"
       }
     },
-    "node_modules/prettier": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
-      "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
-      "optional": true,
-      "bin": {
-        "prettier": "bin-prettier.js"
-      },
-      "engines": {
-        "node": ">=10.13.0"
-      },
-      "funding": {
-        "url": "https://github.com/prettier/prettier?sponsor=1"
-      }
-    },
     "node_modules/pretty-format": {
       "version": "29.0.3",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.3.tgz",
@@ -9426,11 +9404,6 @@
       "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
       "optional": true
     },
-    "node_modules/pseudomap": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
-    },
     "node_modules/psl": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -9658,11 +9631,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/regenerator-runtime": {
-      "version": "0.13.9",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
-      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
-    },
     "node_modules/regexp-tree": {
       "version": "0.1.24",
       "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz",
@@ -10154,8 +10122,7 @@
     "node_modules/sourcemap-codec": {
       "version": "1.4.8",
       "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
-      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
-      "dev": true
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
     },
     "node_modules/spdx-compare": {
       "version": "1.0.0",
@@ -10497,7 +10464,6 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
       "dependencies": {
         "has-flag": "^4.0.0"
       },
@@ -11060,17 +11026,6 @@
         "node": ">= 4"
       }
     },
-    "node_modules/v-tooltip": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
-      "integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
-      "dependencies": {
-        "@babel/runtime": "^7.13.10",
-        "lodash": "^4.17.21",
-        "popper.js": "^1.16.1",
-        "vue-resize": "^1.0.1"
-      }
-    },
     "node_modules/v8-compile-cache": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -11136,25 +11091,24 @@
       }
     },
     "node_modules/vue": {
-      "version": "2.6.14",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
-      "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
-    },
-    "node_modules/vue-bar-graph": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/vue-bar-graph/-/vue-bar-graph-1.3.1.tgz",
-      "integrity": "sha512-C0U594QoEI91PuXIrygfIRDRPDrpICrsJ0iYxuJJzDUENpWqahZGsqZZj8XRJGXsPUI8Ri1rIo91uaNI/ht79w==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz",
+      "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
       "dependencies": {
-        "gsap": "^3.6.1",
-        "vue": "^2.6.12"
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-sfc": "3.2.37",
+        "@vue/runtime-dom": "3.2.37",
+        "@vue/server-renderer": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
-    "node_modules/vue-calendar-heatmap": {
-      "version": "0.8.4",
-      "resolved": "https://registry.npmjs.org/vue-calendar-heatmap/-/vue-calendar-heatmap-0.8.4.tgz",
-      "integrity": "sha512-Hx7OYBY1ghUIxKmFIIzpLT4XlcrwnI3WpadJEj/sKj5quoxwEuSDKmf94v0zWOHeQ/2CrB1G66geaKR/O56+OQ==",
+    "node_modules/vue-bar-graph": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/vue-bar-graph/-/vue-bar-graph-2.0.0.tgz",
+      "integrity": "sha512-IoYP+r5Ggjys6QdUNYFPh7qD41wi/uDOJj9nMawvDgvV6niOz3Dw8O2/98ZnUgjTpcgcGFDaaAaK6qa9x1jgpw==",
       "dependencies": {
-        "v-tooltip": "^2.0.0-rc.32"
+        "gsap": "^3.10.4",
+        "vue": "^3.2.37"
       }
     },
     "node_modules/vue-eslint-parser": {
@@ -11181,117 +11135,33 @@
         "eslint": ">=6.0.0"
       }
     },
-    "node_modules/vue-hot-reload-api": {
-      "version": "2.3.4",
-      "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
-      "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
-    },
     "node_modules/vue-loader": {
-      "version": "15.9.8",
-      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
-      "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==",
+      "version": "17.0.0",
+      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz",
+      "integrity": "sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg==",
       "dependencies": {
-        "@vue/component-compiler-utils": "^3.1.0",
-        "hash-sum": "^1.0.2",
-        "loader-utils": "^1.1.0",
-        "vue-hot-reload-api": "^2.3.0",
-        "vue-style-loader": "^4.1.0"
+        "chalk": "^4.1.0",
+        "hash-sum": "^2.0.0",
+        "loader-utils": "^2.0.0"
       },
       "peerDependencies": {
-        "css-loader": "*",
-        "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0"
-      },
-      "peerDependenciesMeta": {
-        "cache-loader": {
-          "optional": true
-        },
-        "vue-template-compiler": {
-          "optional": true
-        }
+        "webpack": "^4.1.0 || ^5.0.0-0"
       }
     },
-    "node_modules/vue-loader/node_modules/json5": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+    "node_modules/vue3-calendar-heatmap": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.0.tgz",
+      "integrity": "sha512-BchyC33WiZryYatFINj3LWqgyE6X82Huzf7abA23tsF/IbaRZVwZzie8SmGaYvezEBiPXhJogQ3dtxIuXFjkBw==",
       "dependencies": {
-        "minimist": "^1.2.0"
-      },
-      "bin": {
-        "json5": "lib/cli.js"
-      }
-    },
-    "node_modules/vue-loader/node_modules/loader-utils": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
-      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
-      "dependencies": {
-        "big.js": "^5.2.2",
-        "emojis-list": "^3.0.0",
-        "json5": "^1.0.1"
+        "tippy.js": "^6.3.7"
       },
       "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/vue-resize": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
-      "integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
-      "dependencies": {
-        "@babel/runtime": "^7.13.10"
+        "node": ">=12"
       },
       "peerDependencies": {
-        "vue": "^2.6.0"
+        "vue": "^3.2.24"
       }
     },
-    "node_modules/vue-style-loader": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
-      "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==",
-      "dependencies": {
-        "hash-sum": "^1.0.2",
-        "loader-utils": "^1.0.2"
-      }
-    },
-    "node_modules/vue-style-loader/node_modules/json5": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-      "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
-      "dependencies": {
-        "minimist": "^1.2.0"
-      },
-      "bin": {
-        "json5": "lib/cli.js"
-      }
-    },
-    "node_modules/vue-style-loader/node_modules/loader-utils": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
-      "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
-      "dependencies": {
-        "big.js": "^5.2.2",
-        "emojis-list": "^3.0.0",
-        "json5": "^1.0.1"
-      },
-      "engines": {
-        "node": ">=4.0.0"
-      }
-    },
-    "node_modules/vue-template-compiler": {
-      "version": "2.6.14",
-      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
-      "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==",
-      "dependencies": {
-        "de-indent": "^1.0.2",
-        "he": "^1.1.0"
-      }
-    },
-    "node_modules/vue-template-es2015-compiler": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
-      "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw=="
-    },
     "node_modules/w3c-hr-time": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@@ -12205,8 +12075,7 @@
     "@babel/parser": {
       "version": "7.19.0",
       "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz",
-      "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==",
-      "dev": true
+      "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw=="
     },
     "@babel/plugin-syntax-async-generators": {
       "version": "7.8.4",
@@ -12334,14 +12203,6 @@
         "@babel/helper-plugin-utils": "^7.18.6"
       }
     },
-    "@babel/runtime": {
-      "version": "7.19.0",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz",
-      "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==",
-      "requires": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
     "@babel/template": {
       "version": "7.18.10",
       "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
@@ -13698,52 +13559,105 @@
       "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==",
       "dev": true
     },
-    "@vue/component-compiler-utils": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz",
-      "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==",
+    "@vue/compiler-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.37.tgz",
+      "integrity": "sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==",
       "requires": {
-        "consolidate": "^0.15.1",
-        "hash-sum": "^1.0.2",
-        "lru-cache": "^4.1.2",
-        "merge-source-map": "^1.1.0",
-        "postcss": "^7.0.36",
-        "postcss-selector-parser": "^6.0.2",
-        "prettier": "^1.18.2 || ^2.0.0",
-        "source-map": "~0.6.1",
-        "vue-template-es2015-compiler": "^1.9.0"
-      },
-      "dependencies": {
-        "lru-cache": {
-          "version": "4.1.5",
-          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
-          "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
-          "requires": {
-            "pseudomap": "^1.0.2",
-            "yallist": "^2.1.2"
-          }
-        },
-        "picocolors": {
-          "version": "0.2.1",
-          "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz",
-          "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA=="
-        },
-        "postcss": {
-          "version": "7.0.39",
-          "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz",
-          "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==",
-          "requires": {
-            "picocolors": "^0.2.1",
-            "source-map": "^0.6.1"
-          }
-        },
-        "yallist": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-          "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
-        }
+        "@babel/parser": "^7.16.4",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "source-map": "^0.6.1"
       }
     },
+    "@vue/compiler-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz",
+      "integrity": "sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==",
+      "requires": {
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/compiler-sfc": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz",
+      "integrity": "sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/reactivity-transform": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7",
+        "postcss": "^8.1.10",
+        "source-map": "^0.6.1"
+      }
+    },
+    "@vue/compiler-ssr": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz",
+      "integrity": "sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==",
+      "requires": {
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/reactivity": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.37.tgz",
+      "integrity": "sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==",
+      "requires": {
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/reactivity-transform": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz",
+      "integrity": "sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==",
+      "requires": {
+        "@babel/parser": "^7.16.4",
+        "@vue/compiler-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.25.7"
+      }
+    },
+    "@vue/runtime-core": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.37.tgz",
+      "integrity": "sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==",
+      "requires": {
+        "@vue/reactivity": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/runtime-dom": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz",
+      "integrity": "sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==",
+      "requires": {
+        "@vue/runtime-core": "3.2.37",
+        "@vue/shared": "3.2.37",
+        "csstype": "^2.6.8"
+      }
+    },
+    "@vue/server-renderer": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.37.tgz",
+      "integrity": "sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==",
+      "requires": {
+        "@vue/compiler-ssr": "3.2.37",
+        "@vue/shared": "3.2.37"
+      }
+    },
+    "@vue/shared": {
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.37.tgz",
+      "integrity": "sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw=="
+    },
     "@webassemblyjs/ast": {
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz",
@@ -14222,11 +14136,6 @@
       "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
       "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ=="
     },
-    "bluebird": {
-      "version": "3.7.2",
-      "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
-      "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
-    },
     "blueimp-md5": {
       "version": "2.18.0",
       "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
@@ -14347,7 +14256,6 @@
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
       "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-      "dev": true,
       "requires": {
         "ansi-styles": "^4.1.0",
         "supports-color": "^7.1.0"
@@ -14512,14 +14420,6 @@
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
       "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
     },
-    "consolidate": {
-      "version": "0.15.1",
-      "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz",
-      "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==",
-      "requires": {
-        "bluebird": "^3.1.1"
-      }
-    },
     "convert-source-map": {
       "version": "1.8.0",
       "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz",
@@ -14653,6 +14553,11 @@
         }
       }
     },
+    "csstype": {
+      "version": "2.6.21",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
+      "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+    },
     "d3": {
       "version": "7.6.1",
       "resolved": "https://registry.npmjs.org/d3/-/d3-7.6.1.tgz",
@@ -15239,11 +15144,6 @@
         "whatwg-url": "^11.0.0"
       }
     },
-    "de-indent": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
-      "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg=="
-    },
     "debug": {
       "version": "4.3.4",
       "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -16187,8 +16087,7 @@
     "estree-walker": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
-      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
-      "dev": true
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
     },
     "esutils": {
       "version": "2.0.3",
@@ -16703,14 +16602,9 @@
       }
     },
     "hash-sum": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz",
-      "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
-    },
-    "he": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
-      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz",
+      "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg=="
     },
     "hosted-git-info": {
       "version": "2.8.9",
@@ -17993,7 +17887,6 @@
       "version": "0.25.9",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
       "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
-      "dev": true,
       "requires": {
         "sourcemap-codec": "^1.4.8"
       }
@@ -18202,14 +18095,6 @@
         }
       }
     },
-    "merge-source-map": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
-      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
-      "requires": {
-        "source-map": "^0.6.1"
-      }
-    },
     "merge-stream": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -18295,7 +18180,8 @@
     "minimist": {
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
-      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
+      "dev": true
     },
     "minimist-options": {
       "version": "4.1.0",
@@ -18791,11 +18677,6 @@
       "integrity": "sha512-PxkIc/2ZpLiEzQXu5YRDOUgBlfGYBY8156HY5ZcRAwwonMk5W/MrJP2LLkG/hF7GEQzaHo2aS7ho6ZLCOvf+6g==",
       "dev": true
     },
-    "popper.js": {
-      "version": "1.16.1",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
-      "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
-    },
     "postcss": {
       "version": "8.4.16",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
@@ -18884,12 +18765,6 @@
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
       "dev": true
     },
-    "prettier": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
-      "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
-      "optional": true
-    },
     "pretty-format": {
       "version": "29.0.3",
       "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.3.tgz",
@@ -18980,11 +18855,6 @@
       "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
       "optional": true
     },
-    "pseudomap": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
-    },
     "psl": {
       "version": "1.9.0",
       "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -19156,11 +19026,6 @@
         "strip-indent": "^3.0.0"
       }
     },
-    "regenerator-runtime": {
-      "version": "0.13.9",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
-      "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
-    },
     "regexp-tree": {
       "version": "0.1.24",
       "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.24.tgz",
@@ -19523,8 +19388,7 @@
     "sourcemap-codec": {
       "version": "1.4.8",
       "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
-      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
-      "dev": true
+      "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
     },
     "spdx-compare": {
       "version": "1.0.0",
@@ -19812,7 +19676,6 @@
       "version": "7.2.0",
       "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
       "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-      "dev": true,
       "requires": {
         "has-flag": "^4.0.0"
       }
@@ -20227,17 +20090,6 @@
       "integrity": "sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==",
       "dev": true
     },
-    "v-tooltip": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/v-tooltip/-/v-tooltip-2.1.3.tgz",
-      "integrity": "sha512-xXngyxLQTOx/yUEy50thb8te7Qo4XU6h4LZB6cvEfVd9mnysUxLEoYwGWDdqR+l69liKsy3IPkdYff3J1gAJ5w==",
-      "requires": {
-        "@babel/runtime": "^7.13.10",
-        "lodash": "^4.17.21",
-        "popper.js": "^1.16.1",
-        "vue-resize": "^1.0.1"
-      }
-    },
     "v8-compile-cache": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -20293,25 +20145,24 @@
       }
     },
     "vue": {
-      "version": "2.6.14",
-      "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
-      "integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
-    },
-    "vue-bar-graph": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/vue-bar-graph/-/vue-bar-graph-1.3.1.tgz",
-      "integrity": "sha512-C0U594QoEI91PuXIrygfIRDRPDrpICrsJ0iYxuJJzDUENpWqahZGsqZZj8XRJGXsPUI8Ri1rIo91uaNI/ht79w==",
+      "version": "3.2.37",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.37.tgz",
+      "integrity": "sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==",
       "requires": {
-        "gsap": "^3.6.1",
-        "vue": "^2.6.12"
+        "@vue/compiler-dom": "3.2.37",
+        "@vue/compiler-sfc": "3.2.37",
+        "@vue/runtime-dom": "3.2.37",
+        "@vue/server-renderer": "3.2.37",
+        "@vue/shared": "3.2.37"
       }
     },
-    "vue-calendar-heatmap": {
-      "version": "0.8.4",
-      "resolved": "https://registry.npmjs.org/vue-calendar-heatmap/-/vue-calendar-heatmap-0.8.4.tgz",
-      "integrity": "sha512-Hx7OYBY1ghUIxKmFIIzpLT4XlcrwnI3WpadJEj/sKj5quoxwEuSDKmf94v0zWOHeQ/2CrB1G66geaKR/O56+OQ==",
+    "vue-bar-graph": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/vue-bar-graph/-/vue-bar-graph-2.0.0.tgz",
+      "integrity": "sha512-IoYP+r5Ggjys6QdUNYFPh7qD41wi/uDOJj9nMawvDgvV6niOz3Dw8O2/98ZnUgjTpcgcGFDaaAaK6qa9x1jgpw==",
       "requires": {
-        "v-tooltip": "^2.0.0-rc.32"
+        "gsap": "^3.10.4",
+        "vue": "^3.2.37"
       }
     },
     "vue-eslint-parser": {
@@ -20329,94 +20180,24 @@
         "semver": "^7.3.6"
       }
     },
-    "vue-hot-reload-api": {
-      "version": "2.3.4",
-      "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
-      "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog=="
-    },
     "vue-loader": {
-      "version": "15.9.8",
-      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
-      "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==",
+      "version": "17.0.0",
+      "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz",
+      "integrity": "sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg==",
       "requires": {
-        "@vue/component-compiler-utils": "^3.1.0",
-        "hash-sum": "^1.0.2",
-        "loader-utils": "^1.1.0",
-        "vue-hot-reload-api": "^2.3.0",
-        "vue-style-loader": "^4.1.0"
-      },
-      "dependencies": {
-        "json5": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
-          "requires": {
-            "minimist": "^1.2.0"
-          }
-        },
-        "loader-utils": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
-          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^1.0.1"
-          }
-        }
+        "chalk": "^4.1.0",
+        "hash-sum": "^2.0.0",
+        "loader-utils": "^2.0.0"
       }
     },
-    "vue-resize": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/vue-resize/-/vue-resize-1.0.1.tgz",
-      "integrity": "sha512-z5M7lJs0QluJnaoMFTIeGx6dIkYxOwHThlZDeQnWZBizKblb99GSejPnK37ZbNE/rVwDcYcHY+Io+AxdpY952w==",
+    "vue3-calendar-heatmap": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/vue3-calendar-heatmap/-/vue3-calendar-heatmap-2.0.0.tgz",
+      "integrity": "sha512-BchyC33WiZryYatFINj3LWqgyE6X82Huzf7abA23tsF/IbaRZVwZzie8SmGaYvezEBiPXhJogQ3dtxIuXFjkBw==",
       "requires": {
-        "@babel/runtime": "^7.13.10"
+        "tippy.js": "^6.3.7"
       }
     },
-    "vue-style-loader": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",
-      "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==",
-      "requires": {
-        "hash-sum": "^1.0.2",
-        "loader-utils": "^1.0.2"
-      },
-      "dependencies": {
-        "json5": {
-          "version": "1.0.1",
-          "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
-          "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
-          "requires": {
-            "minimist": "^1.2.0"
-          }
-        },
-        "loader-utils": {
-          "version": "1.4.0",
-          "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
-          "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
-          "requires": {
-            "big.js": "^5.2.2",
-            "emojis-list": "^3.0.0",
-            "json5": "^1.0.1"
-          }
-        }
-      }
-    },
-    "vue-template-compiler": {
-      "version": "2.6.14",
-      "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz",
-      "integrity": "sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g==",
-      "requires": {
-        "de-indent": "^1.0.2",
-        "he": "^1.1.0"
-      }
-    },
-    "vue-template-es2015-compiler": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz",
-      "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw=="
-    },
     "w3c-hr-time": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
diff --git a/package.json b/package.json
index 37571c01c2..c7add76aae 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
     "@claviska/jquery-minicolors": "2.3.6",
     "@mcaptcha/vanilla-glue": "0.1.0-alpha-2",
     "@primer/octicons": "17.5.0",
+    "@vue/compiler-sfc": "3.2.37",
     "add-asset-webpack-plugin": "2.0.1",
     "css-loader": "6.7.1",
     "dropzone": "6.0.0-beta.2",
@@ -34,11 +35,10 @@
     "tippy.js": "6.3.7",
     "tributejs": "5.1.3",
     "uint8-to-base64": "0.2.0",
-    "vue": "2.6.14",
-    "vue-bar-graph": "1.3.1",
-    "vue-calendar-heatmap": "0.8.4",
-    "vue-loader": "15.9.8",
-    "vue-template-compiler": "2.6.14",
+    "vue": "3.2.37",
+    "vue-bar-graph": "2.0.0",
+    "vue-loader": "17.0.0",
+    "vue3-calendar-heatmap": "2.0.0",
     "webpack": "5.74.0",
     "webpack-cli": "4.10.0",
     "workbox-routing": "6.5.4",
diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl
index 4624a53ba3..264a04f6df 100644
--- a/templates/repo/diff/box.tmpl
+++ b/templates/repo/diff/box.tmpl
@@ -63,9 +63,9 @@
 					}
 				})();
 				</script>
-		<div id="diff-file-list-container"></div>
+		<div id="diff-file-list"></div>
 		<div id="diff-container">
-				<div id="diff-file-tree-container"></div>
+				<div id="diff-file-tree"></div>
 				<div id="diff-file-boxes" class="sixteen wide column">
 					{{range $i, $file := .Diff.Files}}
 						{{/*notice: the index of Diff.Files should not be used for element ID, because the index will be restarted from 0 when doing load-more for PRs with a lot of files*/}}
diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl
index 3e9ff99692..620cc322f0 100644
--- a/templates/user/dashboard/repolist.tmpl
+++ b/templates/user/dashboard/repolist.tmpl
@@ -19,7 +19,10 @@
 	{{end}}
 	inline-template
 	v-cloak
-	>
+	></repo-search>
+</div>
+
+<template id="dashboard-repo-list-template">
 	<div>
 		<div v-if="!isOrganization" class="ui two item tabable menu">
 			<a :class="{item: true, active: tab === 'repos'}" @click="changeTab('repos')">{{.locale.Tr "repository"}}</a>
@@ -193,5 +196,4 @@
 			</div>
 		</div>
 	</div>
-</repo-search>
-</div>
+</template>
diff --git a/web_src/js/components/ActivityHeatmap.vue b/web_src/js/components/ActivityHeatmap.vue
index fa2c43b5b4..b5c5c65bbf 100644
--- a/web_src/js/components/ActivityHeatmap.vue
+++ b/web_src/js/components/ActivityHeatmap.vue
@@ -15,7 +15,7 @@
   </div>
 </template>
 <script>
-import {CalendarHeatmap} from 'vue-calendar-heatmap';
+import {CalendarHeatmap} from 'vue3-calendar-heatmap';
 
 export default {
   name: 'ActivityHeatmap',
diff --git a/web_src/js/components/ContextPopup.vue b/web_src/js/components/ContextPopup.vue
index c176a18659..0b086690a9 100644
--- a/web_src/js/components/ContextPopup.vue
+++ b/web_src/js/components/ContextPopup.vue
@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div ref="root">
     <div v-if="loading" class="ui active centered inline loader"/>
     <div v-if="!loading && issue !== null">
       <p><small>{{ issue.repository.full_name }} on {{ createdAt }}</small></p>
@@ -109,15 +109,16 @@ export default {
   },
 
   mounted() {
-    this.$root.$on('load-context-popup', (data, callback) => {
+    this.$refs.root.addEventListener('us-load-context-popup', (e) => {
+      const data = e.detail;
       if (!this.loading && this.issue === null) {
-        this.load(data, callback);
+        this.load(data);
       }
     });
   },
 
   methods: {
-    load(data, callback) {
+    load(data) {
       this.loading = true;
       this.i18nErrorMessage = null;
       $.get(`${appSubUrl}/${data.owner}/${data.repo}/issues/${data.index}/info`).done((issue) => {
@@ -130,9 +131,6 @@ export default {
         }
       }).always(() => {
         this.loading = false;
-        if (callback) {
-          this.$nextTick(callback);
-        }
       });
     }
   }
diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js
index cbbc12c2c4..0a009e78d1 100644
--- a/web_src/js/components/DashboardRepoList.js
+++ b/web_src/js/components/DashboardRepoList.js
@@ -1,12 +1,12 @@
-import Vue from 'vue';
+import {createApp, nextTick} from 'vue';
 import $ from 'jquery';
 import {initVueSvg, vueDelimiters} from './VueComponentLoader.js';
 import {initTooltip} from '../modules/tippy.js';
 
 const {appSubUrl, assetUrlPrefix, pageData} = window.config;
 
-function initVueComponents() {
-  Vue.component('repo-search', {
+function initVueComponents(app) {
+  app.component('repo-search', {
     delimiters: vueDelimiters,
     props: {
       searchLimit: {
@@ -138,13 +138,14 @@ function initVueComponents() {
     },
 
     mounted() {
+      const el = document.getElementById('dashboard-repo-list');
       this.changeReposFilter(this.reposFilter);
-      for (const el of this.$el.querySelectorAll('.tooltip')) {
-        initTooltip(el);
+      for (const elTooltip of el.querySelectorAll('.tooltip')) {
+        initTooltip(elTooltip);
       }
-      $(this.$el).find('.dropdown').dropdown();
+      $(el).find('.dropdown').dropdown();
       this.setCheckboxes();
-      Vue.nextTick(() => {
+      nextTick(() => {
         this.$refs.search.focus();
       });
     },
@@ -192,7 +193,7 @@ function initVueComponents() {
         this.reposFilter = filter;
         this.repos = [];
         this.page = 1;
-        Vue.set(this.counts, `${filter}:${this.archivedFilter}:${this.privateFilter}`, 0);
+        this.counts[`${filter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
         this.searchRepos();
       },
 
@@ -261,7 +262,7 @@ function initVueComponents() {
         this.page = 1;
         this.repos = [];
         this.setCheckboxes();
-        Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
+        this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
         this.searchRepos();
       },
 
@@ -283,7 +284,7 @@ function initVueComponents() {
         this.page = 1;
         this.repos = [];
         this.setCheckboxes();
-        Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
+        this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
         this.searchRepos();
       },
 
@@ -297,7 +298,7 @@ function initVueComponents() {
           this.page = 1;
         }
         this.repos = [];
-        Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, 0);
+        this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0;
         this.searchRepos();
       },
 
@@ -331,7 +332,7 @@ function initVueComponents() {
           if (searchedQuery === '' && searchedMode === '' && this.archivedFilter === 'both') {
             this.reposTotalCount = count;
           }
-          Vue.set(this.counts, `${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`, count);
+          this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = count;
           this.finalPage = Math.ceil(count / this.searchLimit);
           this.updateHistory();
           this.isLoading = false;
@@ -352,22 +353,20 @@ function initVueComponents() {
         }
         return 'octicon-repo';
       }
-    }
+    },
+
+    template: document.getElementById('dashboard-repo-list-template'),
   });
 }
 
-
 export function initDashboardRepoList() {
   const el = document.getElementById('dashboard-repo-list');
   const dashboardRepoListData = pageData.dashboardRepoList || null;
   if (!el || !dashboardRepoListData) return;
 
-  initVueSvg();
-  initVueComponents();
-  new Vue({
-    el,
+  const app = createApp({
     delimiters: vueDelimiters,
-    data: () => {
+    data() {
       return {
         searchLimit: dashboardRepoListData.searchLimit || 0,
         subUrl: appSubUrl,
@@ -375,4 +374,7 @@ export function initDashboardRepoList() {
       };
     },
   });
+  initVueSvg(app);
+  initVueComponents(app);
+  app.mount(el);
 }
diff --git a/web_src/js/components/DiffFileList.vue b/web_src/js/components/DiffFileList.vue
index 2a9aa77377..da53be3b46 100644
--- a/web_src/js/components/DiffFileList.vue
+++ b/web_src/js/components/DiffFileList.vue
@@ -1,5 +1,5 @@
 <template>
-  <ol class="diff-detail-box diff-stats m-0" id="diff-files" v-if="fileListIsVisible">
+  <ol class="diff-detail-box diff-stats m-0" ref="root" v-if="fileListIsVisible">
     <li v-for="file in files" :key="file.NameHash">
       <div class="bold df ac pull-right">
         <span v-if="file.IsBin" class="ml-1 mr-3">{{ binaryFileMessage }}</span>
@@ -37,7 +37,7 @@ export default {
     fileListIsVisible(newValue) {
       if (newValue === true) {
         this.$nextTick(() => {
-          for (const el of this.$el.querySelectorAll('.tooltip')) {
+          for (const el of this.$refs.root.querySelectorAll('.tooltip')) {
             initTooltip(el);
           }
         });
diff --git a/web_src/js/components/DiffFileTree.vue b/web_src/js/components/DiffFileTree.vue
index d0962254cd..717ba70f7f 100644
--- a/web_src/js/components/DiffFileTree.vue
+++ b/web_src/js/components/DiffFileTree.vue
@@ -1,11 +1,10 @@
 <template>
   <div
-    v-show="fileTreeIsVisible"
-    id="diff-file-tree"
+    v-if="fileTreeIsVisible"
     class="mr-3 mt-3 diff-detail-box"
   >
     <!-- only render the tree if we're visible. in many cases this is something that doesn't change very often -->
-    <div class="ui list" v-if="fileTreeIsVisible">
+    <div class="ui list">
       <DiffFileTreeItem v-for="item in fileTree" :key="item.name" :item="item" />
     </div>
     <div v-if="isIncomplete" id="diff-too-many-files-stats" class="pt-2">
@@ -117,6 +116,9 @@ export default {
       const [toShow, toHide] = document.querySelectorAll('.diff-toggle-file-tree-button .icon');
       toShow.classList.toggle('hide', visible);  // hide the toShow icon if the tree is visible
       toHide.classList.toggle('hide', !visible); // similarly
+
+      const diffTree = document.getElementById('diff-file-tree');
+      diffTree.classList.toggle('hide', !visible);
     },
     loadMoreData() {
       this.isLoadingNewData = true;
diff --git a/web_src/js/components/RepoBranchTagDropdown.js b/web_src/js/components/RepoBranchTagDropdown.js
index d55fa91b92..8bed305fca 100644
--- a/web_src/js/components/RepoBranchTagDropdown.js
+++ b/web_src/js/components/RepoBranchTagDropdown.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import {createApp, nextTick} from 'vue';
 import $ from 'jquery';
 import {vueDelimiters} from './VueComponentLoader.js';
 
@@ -37,10 +37,14 @@ export function initRepoBranchTagDropdown(selector) {
       });
     });
     $data.remove();
-    new Vue({
-      el: this,
+
+    // eslint-disable-next-line unicorn/no-this-assignment
+    const elRoot = this;
+    const view = createApp({
       delimiters: vueDelimiters,
-      data,
+      data() {
+        return data;
+      },
       computed: {
         filteredItems() {
           const items = this.items.filter((item) => {
@@ -73,10 +77,10 @@ export function initRepoBranchTagDropdown(selector) {
       },
 
       beforeMount() {
-        this.noResults = this.$el.getAttribute('data-no-results');
-        this.canCreateBranch = this.$el.getAttribute('data-can-create-branch') === 'true';
-        this.branchForm = this.$el.getAttribute('data-branch-form');
-        switch (this.$el.getAttribute('data-view-type')) {
+        this.noResults = elRoot.getAttribute('data-no-results');
+        this.canCreateBranch = elRoot.getAttribute('data-can-create-branch') === 'true';
+        this.branchForm = elRoot.getAttribute('data-branch-form');
+        switch (elRoot.getAttribute('data-view-type')) {
           case 'tree':
             this.isViewTree = true;
             break;
@@ -87,19 +91,19 @@ export function initRepoBranchTagDropdown(selector) {
             this.isViewBranch = true;
             break;
         }
-        this.refName = this.$el.getAttribute('data-ref-name');
-        this.branchURLPrefix = this.$el.getAttribute('data-branch-url-prefix');
-        this.branchURLSuffix = this.$el.getAttribute('data-branch-url-suffix');
-        this.tagURLPrefix = this.$el.getAttribute('data-tag-url-prefix');
-        this.tagURLSuffix = this.$el.getAttribute('data-tag-url-suffix');
-        this.setAction = this.$el.getAttribute('data-set-action') === 'true';
-        this.submitForm = this.$el.getAttribute('data-submit-form') === 'true';
+        this.refName = elRoot.getAttribute('data-ref-name');
+        this.branchURLPrefix = elRoot.getAttribute('data-branch-url-prefix');
+        this.branchURLSuffix = elRoot.getAttribute('data-branch-url-suffix');
+        this.tagURLPrefix = elRoot.getAttribute('data-tag-url-prefix');
+        this.tagURLSuffix = elRoot.getAttribute('data-tag-url-suffix');
+        this.setAction = elRoot.getAttribute('data-set-action') === 'true';
+        this.submitForm = elRoot.getAttribute('data-submit-form') === 'true';
 
 
         document.body.addEventListener('click', (event) => {
-          if (this.$el.contains(event.target)) return;
+          if (elRoot.contains(event.target)) return;
           if (this.menuVisible) {
-            Vue.set(this, 'menuVisible', false);
+            this.menuVisible = false;
           }
         });
       },
@@ -135,7 +139,7 @@ export function initRepoBranchTagDropdown(selector) {
             if (this.submitForm) {
               $(`#${this.branchForm}`).trigger('submit');
             }
-            Vue.set(this, 'menuVisible', false);
+            this.menuVisible = false;
           }
         },
         createNewBranch() {
@@ -143,7 +147,7 @@ export function initRepoBranchTagDropdown(selector) {
           $(this.$refs.newBranchForm).trigger('submit');
         },
         focusSearchField() {
-          Vue.nextTick(() => {
+          nextTick(() => {
             this.$refs.searchField.focus();
           });
         },
@@ -213,5 +217,6 @@ export function initRepoBranchTagDropdown(selector) {
         }
       }
     });
+    view.mount(this);
   });
 }
diff --git a/web_src/js/components/VueComponentLoader.js b/web_src/js/components/VueComponentLoader.js
index 2979cd6a86..f0555b21cc 100644
--- a/web_src/js/components/VueComponentLoader.js
+++ b/web_src/js/components/VueComponentLoader.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import {createApp} from 'vue';
 import {svgs} from '../svg.js';
 
 export const vueDelimiters = ['${', '}'];
@@ -8,13 +8,14 @@ export function initVueEnv() {
   if (vueEnvInited) return;
   vueEnvInited = true;
 
-  const isProd = window.config.runModeIsProd;
-  Vue.config.productionTip = false;
-  Vue.config.devtools = !isProd;
+  // As far as I could tell, this is no longer possible.
+  // But there seem not to be a guide what to do instead.
+  // const isProd = window.config.runModeIsProd;
+  // Vue.config.devtools = !isProd;
 }
 
 let vueSvgInited = false;
-export function initVueSvg() {
+export function initVueSvg(app) {
   if (vueSvgInited) return;
   vueSvgInited = true;
 
@@ -24,7 +25,7 @@ export function initVueSvg() {
       .replace(/height="[0-9]+"/, 'v-bind:height="size"')
       .replace(/width="[0-9]+"/, 'v-bind:width="size"');
 
-    Vue.component(name, {
+    app.component(name, {
       props: {
         size: {
           type: String,
@@ -42,8 +43,7 @@ export function initVueApp(el, opts = {}) {
   }
   if (!el) return null;
 
-  return new Vue(Object.assign({
-    el,
-    delimiters: vueDelimiters,
-  }, opts));
+  return createApp(
+    Object.assign({delimiters: vueDelimiters}, opts)
+  ).mount(el);
 }
diff --git a/web_src/js/features/contextpopup.js b/web_src/js/features/contextpopup.js
index f4e660be3f..d29da6d951 100644
--- a/web_src/js/features/contextpopup.js
+++ b/web_src/js/features/contextpopup.js
@@ -1,5 +1,5 @@
 import $ from 'jquery';
-import Vue from 'vue';
+import {createApp} from 'vue';
 import ContextPopup from '../components/ContextPopup.vue';
 import {parseIssueHref} from '../utils.js';
 import {createTippy} from '../modules/tippy.js';
@@ -17,17 +17,12 @@ export default function initContextPopups() {
     if (!owner) return;
 
     const el = document.createElement('div');
-    el.innerHTML = '<div></div>';
     this.parentNode.insertBefore(el, this.nextSibling);
 
-    const View = Vue.extend({
-      render: (createElement) => createElement(ContextPopup),
-    });
-
-    const view = new View();
+    const view = createApp(ContextPopup);
 
     try {
-      view.$mount(el.firstChild);
+      view.mount(el);
     } catch (err) {
       console.error(err);
       el.textContent = 'ContextPopup failed to load';
@@ -37,7 +32,7 @@ export default function initContextPopups() {
       content: el,
       interactive: true,
       onShow: () => {
-        view.$emit('load-context-popup', {owner, repo, index});
+        el.firstChild.dispatchEvent(new CustomEvent('us-load-context-popup', {detail: {owner, repo, index}}));
       }
     });
   });
diff --git a/web_src/js/features/heatmap.js b/web_src/js/features/heatmap.js
index 363be5ca75..6e6202e866 100644
--- a/web_src/js/features/heatmap.js
+++ b/web_src/js/features/heatmap.js
@@ -1,4 +1,4 @@
-import Vue from 'vue';
+import {createApp} from 'vue';
 import ActivityHeatmap from '../components/ActivityHeatmap.vue';
 
 export default function initHeatmap() {
@@ -17,11 +17,9 @@ export default function initHeatmap() {
       return {date: new Date(v), count: heatmap[v]};
     });
 
-    const View = Vue.extend({
-      render: (createElement) => createElement(ActivityHeatmap, {props: {values}}),
-    });
+    const View = createApp(ActivityHeatmap, {values});
 
-    new View().$mount(el);
+    View.mount(el);
   } catch (err) {
     console.error('Heatmap failed to load', err);
     el.textContent = 'Heatmap failed to load';
diff --git a/web_src/js/features/repo-diff-filetree.js b/web_src/js/features/repo-diff-filetree.js
index 9eba3cf887..6059dd82e7 100644
--- a/web_src/js/features/repo-diff-filetree.js
+++ b/web_src/js/features/repo-diff-filetree.js
@@ -1,21 +1,17 @@
-import Vue from 'vue';
+import {createApp} from 'vue';
 import DiffFileTree from '../components/DiffFileTree.vue';
 import DiffFileList from '../components/DiffFileList.vue';
 
 export default function initDiffFileTree() {
-  const el = document.getElementById('diff-file-tree-container');
+  const el = document.getElementById('diff-file-tree');
   if (!el) return;
 
-  const View = Vue.extend({
-    render: (createElement) => createElement(DiffFileTree),
-  });
-  new View().$mount(el);
+  const fileTreeView = createApp(DiffFileTree);
+  fileTreeView.mount(el);
 
-  const fileListElement = document.getElementById('diff-file-list-container');
+  const fileListElement = document.getElementById('diff-file-list');
   if (!fileListElement) return;
 
-  const fileListView = Vue.extend({
-    render: (createElement) => createElement(DiffFileList),
-  });
-  new fileListView().$mount(fileListElement);
+  const fileListView = createApp(DiffFileList);
+  fileListView.mount(fileListElement);
 }
diff --git a/web_src/js/features/repo-issue-pr-form.js b/web_src/js/features/repo-issue-pr-form.js
index 747e4f467e..59d4c7a3b4 100644
--- a/web_src/js/features/repo-issue-pr-form.js
+++ b/web_src/js/features/repo-issue-pr-form.js
@@ -1,12 +1,10 @@
-import Vue from 'vue';
+import {createApp} from 'vue';
 import PullRequestMergeForm from '../components/PullRequestMergeForm.vue';
 
 export default function initPullRequestMergeForm() {
   const el = document.getElementById('pull-request-merge-form');
   if (!el) return;
 
-  const View = Vue.extend({
-    render: (createElement) => createElement(PullRequestMergeForm),
-  });
-  new View().$mount(el);
+  const view = createApp(PullRequestMergeForm);
+  view.mount(el);
 }
diff --git a/web_src/js/svg.js b/web_src/js/svg.js
index 6677bf83cb..dedc126303 100644
--- a/web_src/js/svg.js
+++ b/web_src/js/svg.js
@@ -26,8 +26,6 @@ import octiconSidebarExpand from '../../public/img/svg/octicon-sidebar-expand.sv
 import octiconSidebarCollapse from '../../public/img/svg/octicon-sidebar-collapse.svg';
 
 
-import Vue from 'vue';
-
 export const svgs = {
   'octicon-chevron-down': octiconChevronDown,
   'octicon-chevron-right': octiconChevronRight,
@@ -74,7 +72,8 @@ export function svg(name, size = 16, className = '') {
   return serializer.serializeToString(svgNode);
 }
 
-export const SvgIcon = Vue.component('SvgIcon', {
+export const SvgIcon = {
+  name: 'SvgIcon',
   props: {
     name: {type: String, required: true},
     size: {type: Number, default: 16},
@@ -88,4 +87,4 @@ export const SvgIcon = Vue.component('SvgIcon', {
   },
 
   template: `<span v-html="svg" />`
-});
+};
diff --git a/web_src/less/features/heatmap.less b/web_src/less/features/heatmap.less
index f551daf333..952d6f35ed 100644
--- a/web_src/less/features/heatmap.less
+++ b/web_src/less/features/heatmap.less
@@ -3,10 +3,16 @@
   text-align: center;
   position: relative;
   min-height: 125px;
-  display: flex;
   align-items: center;
   justify-content: center;
 
+  // for the "Less" and "More" legend
+  .vch__legend .vch__legend div:first-child,
+  .vch__legend .vch__legend div:last-child {
+    display: inline-block;
+    padding: 0 5px;
+  }
+
   > svg {
     width: 100%;
   }
diff --git a/webpack.config.js b/webpack.config.js
index 3cc65d19d4..820c1359cf 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -4,14 +4,13 @@ import AddAssetPlugin from 'add-asset-webpack-plugin';
 import LicenseCheckerWebpackPlugin from 'license-checker-webpack-plugin';
 import MiniCssExtractPlugin from 'mini-css-extract-plugin';
 import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
-import VueLoader from 'vue-loader';
+import {VueLoaderPlugin} from 'vue-loader';
 import EsBuildLoader from 'esbuild-loader';
 import {parse, dirname} from 'path';
 import webpack from 'webpack';
 import {fileURLToPath} from 'url';
 import {readFileSync} from 'fs';
 
-const {VueLoaderPlugin} = VueLoader;
 const {ESBuildMinifyPlugin} = EsBuildLoader;
 const {SourceMapDevToolPlugin} = webpack;
 const formatLicenseText = (licenseText) => wrapAnsi(licenseText || '', 80).trim();
@@ -242,9 +241,6 @@ export default {
   },
   resolve: {
     symlinks: false,
-    alias: {
-      vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only
-    },
   },
   watchOptions: {
     ignored: [