diff --git a/.browserslistrc b/.browserslistrc
deleted file mode 100644
index 214388fe43cdfd7ce1c29cd3e401541ded620dba..0000000000000000000000000000000000000000
--- a/.browserslistrc
+++ /dev/null
@@ -1,3 +0,0 @@
-> 1%
-last 2 versions
-not dead
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000000000000000000000000000000000000..d6c028b8f0469ef64bb3db188fc0fa3e50df9b20
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.{html,js,vue,css,scss,json,yml,ts,md}]
+indent_style = space
+indent_size = 2
+
+[*.md]
+trim_trailing_whitespace = false
+
diff --git a/.gitignore b/.gitignore
index e37497bdf6e19a690a7653f117ecf23d06690718..45685c42c956402f703a9aa2cb6000895d1b81ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
 node_modules
 /dist
 /coverage
+junit.xml
 
 # local env files
 .env.local
@@ -22,5 +23,6 @@ pnpm-debug.log*
 *.sln
 *.sw?
 
+
 # vitepress
 public
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index fbb903550f307bb6d2f168590294995f2f85c344..4d90e25cfd7d9cc4d39b5a709afa9b31c1a36537 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,3 +1,7 @@
+include:
+  - project: 'funkwhale/gitlab-ci-templates'
+    file: '/static_pages/jobs.yaml'
+
 stages:
   - build
   - test
@@ -5,14 +9,14 @@ stages:
 
 default:
   image: node:16-slim
-  before_script: 
+  before_script:
     - yarn install
 
 build:
   stage: build
   script: yarn build
 
-pages:
+docs:
   stage: build
   script: yarn docs:build
   artifacts:
@@ -28,6 +32,14 @@ test:
         coverage_format: cobertura
         path: coverage/cobertura-coverage.xml
 
+deploy:
+  extends: .ssh_access
+  dependencies:
+    - docs
+  script:
+    - rsync -r -e "ssh -p 2281" $CI_PROJECT_DIR/public/ ui@ui.funkwhale.audio:/htdocs/
+  only:
+    - main@funkwhale/vui
 
 publish:
   stage: deploy
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ac0c2ba7ed0d0d98bf397f682a794685cebe5e55
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,10 @@
+
+tasks:
+  - name: Docs
+    init: yarn install
+    command: yarn docs:dev
+
+vscode:
+  extensions:
+    - ZixuanChen.vitest-explorer
+    - lukashass.volar
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..91152ef6e675788b24dfd40a0e0ddc025759257a
--- /dev/null
+++ b/docs/.vitepress/config.ts
@@ -0,0 +1,46 @@
+import { defineConfig } from 'vitepress'
+
+export default defineConfig({
+	title: "Funkwhale Vue Components",
+	outDir: "../public",
+  cleanUrls: 'with-subfolders',
+	themeConfig: {
+		nav: [
+			{ text: "Home", link: "https://funkwhale.audio" },
+			{ text: "Gitlab", link: "https://dev.funkwhale.audio/funkwhale/vui" }
+		],
+		sidebar: [
+			{
+        text: "Components",
+        items: [
+			    { text: "Pill", link: "/components/pill/" },
+			    { text: "Loader", link: "/components/loader/" },
+			    { text: "Activity", link: "/components/activity/" },
+			    { text: "Card", items: [
+            { text: "Basic Card", link: "/components/card/basic" },
+            { text: "Radio Card", link: "/components/card/radio" },
+            { text: "Artist Card", link: "/components/card/artist" },
+            { text: "Album Card", link: "/components/card/album" },
+            { text: "Playlist Card", link: "/components/card/playlist" },
+            { text: "Podcast Card", link: "/components/card/podcast" },
+          ] },
+			    { text: "Form", items: [
+            { text: "Input", link: "/components/input/" },
+            { text: "Textarea", link: "/components/textarea/" },
+            { text: "Toggle", link: "/components/toggle/" },
+            { text: "Popover", link: "/components/popover/" },
+            { text: "Button", items: [
+              { text: "Basic Button", link: "/components/button/basic" },
+              { text: "Play Button", link: "/components/button/play" },
+            ] },
+          ] },
+			    { text: "Content Navigation", items: [
+            { text: "Table of Contents", link: "/components/toc/" },
+            { text: "Pagination", link: "/components/pagination/" },
+            { text: "Tabs", link: "/components/tabs/" },
+          ] },
+        ]
+      }
+		]
+	}
+})
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..95be264b6981d2ac8e78c38c4e35655e5a143801
--- /dev/null
+++ b/docs/.vitepress/theme/index.ts
@@ -0,0 +1,20 @@
+import { createI18n } from 'vue-i18n'
+
+import DefaultTheme from 'vitepress/theme'
+import en from '~/locales/en.yaml'
+import Funkwhale from '~/main'
+
+export default {
+  ...DefaultTheme,
+  enhanceApp({ app }) {
+    const i18n = createI18n({
+      legacy: false,
+      locale: 'en',
+      fallbackLocale: 'en',
+      messages: { en }
+    })
+
+    app.use(Funkwhale)
+    app.use(i18n)
+  }
+}
diff --git a/docs/components/activity/index.md b/docs/components/activity/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..f742fc6d618ac7a2c4b9f1c00116a379a611c3df
--- /dev/null
+++ b/docs/components/activity/index.md
@@ -0,0 +1,32 @@
+<script setup lang="ts">
+const track = {
+  name: 'Some lovely track',
+  artist: {
+    name: 'Artist'
+  },
+  cover: {
+    urls: {
+      original: 'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb'
+    }
+  }
+}
+
+const user = {
+  username: 'username'
+}
+</script>
+
+# Activity
+```html
+<fw-activity :track="track" :user="user" />
+```
+<fw-activity :track="track" :user="user" />
+
+::: info
+Whenever there is more than 1 activity entry in a row, there would be a 1px border to separate the entries
+:::
+
+```html
+<fw-activity :track="track" :user="user" v-for="i in 3" :key="i" />
+```
+<fw-activity :track="track" :user="user" v-for="i in 3" :key="i" />
diff --git a/docs/components/button/basic.md b/docs/components/button/basic.md
new file mode 100644
index 0000000000000000000000000000000000000000..61cf036d4654e5354bc7329f2b1ca1ea0fa4f8ad
--- /dev/null
+++ b/docs/components/button/basic.md
@@ -0,0 +1,157 @@
+<script setup>
+const click = () => new Promise(resolve => setTimeout(resolve, 1000))
+</script>
+
+# Button
+## Button colors
+### Primary button
+```html
+<fw-button>
+  Primary button
+</fw-button>
+```
+<fw-button>
+  Primary button
+</fw-button>
+
+### Secondary button
+```html
+<fw-button secondary>
+  Secondary button
+</fw-button>
+```
+<fw-button secondary>
+  Secondary button
+</fw-button>
+
+### Destructive button
+```html
+<fw-button destructive>
+  Destructive button
+</fw-button>
+```
+<fw-button destructive>
+  Destructive button
+</fw-button>
+
+## Button types
+### Filled
+```html
+<fw-button>
+  Filled button
+</fw-button>
+```
+<fw-button>
+  Filled button
+</fw-button>
+
+### Outline
+```html
+<fw-button outline>
+  Outline button
+</fw-button>
+```
+<fw-button outline>
+  Outline button
+</fw-button>
+
+## Button shapes
+### Normal
+```html
+<fw-button>
+  Normal button
+</fw-button>
+```
+<fw-button>
+  Normal button
+</fw-button>
+
+### Round
+```html
+<fw-button round>
+  Round button
+</fw-button>
+```
+<fw-button round>
+  Round button
+</fw-button>
+
+## Button states
+### Disabled
+```html
+<fw-button disabled>
+  Disabled button
+</fw-button>
+```
+<fw-button disabled>
+  Disabled button
+</fw-button>
+
+### Active
+```html
+<fw-button is-active>
+  Active button
+</fw-button>
+```
+<fw-button is-active>
+  Active button
+</fw-button>
+
+### Loading
+```html
+<fw-button is-loading>
+  Loading button
+</fw-button>
+```
+<fw-button is-loading>
+  Loading button
+</fw-button>
+
+&nbsp;
+
+#### Promise handling in @click
+When a function passed to `@click` returns a promise, the button will automatically toggle it's loading state on click, and when promise resolves/rejects, it will toggle it off.
+
+::: danger
+The `<fw-button>` component does not have any promise rejection mechanism implemented. Make sure, that the `@click` handler never rejects.
+:::
+
+```vue
+<script setup lang="ts">
+const click = () => new Promise(resolve => setTimeout(resolve, 1000))
+</script>
+
+<template>
+  <fw-button @click="click">
+    Click me
+  </fw-button>
+</template>
+```
+<fw-button @click="click">
+  Click me
+</fw-button>
+
+## Icons
+You can use [Bootstrap Icons](https://icons.getbootstrap.com/) in your button component
+
+::: info
+When the button doesn't have any contents or it's contents are empty, it will shrink down to the icon size.
+To avoid this, you can add `&nbsp;`.
+:::
+```html
+<fw-button secondary icon="bi-three-dots-vertical" />
+
+<fw-button secondary is-round icon="bi-x" />
+
+<fw-button icon="bi-save">&nbsp;</fw-button>
+
+<fw-button destructive icon="bi-trash">
+  Delete
+</fw-button>
+```
+<fw-button secondary icon="bi-three-dots-vertical" />
+<fw-button secondary round icon="bi-x" />
+<fw-button icon="bi-save">&nbsp;</fw-button>
+<fw-button destructive icon="bi-trash">
+  Delete
+</fw-button>
diff --git a/docs/components/button/play.md b/docs/components/button/play.md
new file mode 100644
index 0000000000000000000000000000000000000000..2bbbeec1a4d7fc96f017ae367738b3b083b16dd9
--- /dev/null
+++ b/docs/components/button/play.md
@@ -0,0 +1,5 @@
+# Play Button
+```html
+<fw-play-button @play="play" />
+```
+<fw-play-button />
diff --git a/docs/components/card/album.md b/docs/components/card/album.md
new file mode 100644
index 0000000000000000000000000000000000000000..4af775980fabbdb11a9f959e0ed29e64bd5bfd0b
--- /dev/null
+++ b/docs/components/card/album.md
@@ -0,0 +1,21 @@
+<script setup lang="ts">
+const album = {
+  name: 'Relatively Long Album Name',
+  artist: {
+    name: 'Artist Name'
+  },
+  tracks: [{}, {}, {}, {}],
+  cover: {
+    urls: {
+      original: 'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb'
+    }
+  }
+}
+</script>
+
+# Album card
+## Normal card
+```html
+<fw-album-card :album="album" />
+```
+<fw-album-card :album="album" />
diff --git a/docs/components/card/artist.md b/docs/components/card/artist.md
new file mode 100644
index 0000000000000000000000000000000000000000..8cc4a30890c39c2dffe7d8643f913875d9465a2f
--- /dev/null
+++ b/docs/components/card/artist.md
@@ -0,0 +1,19 @@
+<script setup lang="ts">
+const artist = {
+  name: 'Artist Name',
+  tags: ['Electro'],
+  albums: [{}, {}, {}, {}],
+  cover: {
+    urls: {
+      original: 'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb'
+    }
+  }
+}
+</script>
+
+# Artist card
+## Normal card
+```html
+<fw-artist-card :artist="artist" />
+```
+<fw-artist-card :artist="artist" />
diff --git a/docs/components/card/basic.md b/docs/components/card/basic.md
new file mode 100644
index 0000000000000000000000000000000000000000..d28c2e5c4b419e4eb0defaf36d08832a03674124
--- /dev/null
+++ b/docs/components/card/basic.md
@@ -0,0 +1,80 @@
+<script setup lang="ts">
+const alert = (message: string) => window.alert(message)
+</script>
+
+# Card
+## Basic card
+```html
+<fw-card title="For music lovers">
+  Access your personnal music collection from anywhere. Beeing focused on the promotion of Free licensed content, Funkwhale sports advanced sharing features.
+</fw-card>
+```
+<fw-card title="For music lovers">
+  Access your personnal music collection from anywhere. Beeing focused on the promotion of Free licensed content, Funkwhale sports advanced sharing features.
+</fw-card>
+
+## Interactive card
+```html
+<fw-card 
+  title="Frequently Asked Questions" 
+  @click="alert('hello')"
+>
+  You have a question about Funkwhale? Get a quick answer!
+</fw-card>
+```
+<fw-card 
+  title="Frequently Asked Questions" 
+  @click="alert('hello')"
+>
+  You have a question about Funkwhale? Get a quick answer!
+</fw-card>
+
+## Category card
+```html
+<fw-card 
+  category 
+  title="Example Translations" 
+  @click="alert('hello')"
+/>
+```
+<fw-card 
+  category 
+  title="Example Translations" 
+  @click="alert('hello')"
+/>
+
+## Card with an image
+```html
+<fw-card 
+  title="For music lovers"
+  image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"
+>
+  Access your personnal music collection from anywhere. Beeing focused on the promotion of Free licensed content, Funkwhale sports advanced sharing features.
+</fw-card>
+```
+<fw-card 
+  title="For music lovers"
+  image="https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb"
+>
+  Access your personnal music collection from anywhere. Beeing focused on the promotion of Free licensed content, Funkwhale sports advanced sharing features.
+</fw-card>
+
+
+## Card with a button
+```html
+<fw-card 
+  title="Join an existing pod"
+  button-title="Find a pod"
+  @click="alert('hello')"
+>
+  The easiest way to start using Funkwhale is to register an account on one of the many available pods.
+</fw-card>
+
+```
+<fw-card 
+  title="Join an existing pod"
+  button-title="Find a pod"
+  @click="alert('hello')"
+>
+  The easiest way to start using Funkwhale is to register an account on one of the many available pods.
+</fw-card>
diff --git a/docs/components/card/playlist.md b/docs/components/card/playlist.md
new file mode 100644
index 0000000000000000000000000000000000000000..738fee60c18c865d5a1322bdb0703f1a03dccf03
--- /dev/null
+++ b/docs/components/card/playlist.md
@@ -0,0 +1,25 @@
+<script setup lang="ts">
+const playlist = {
+  name: 'Incredible Playlist',
+  user: {
+    full_username: '@username:example.com',
+    username: 'username'
+  },
+  tracks_count: 27,
+  album_covers: [
+    'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb',
+    'https://unsplash.com/photos/SVGan4GCopM/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8MTV8fG11c2ljaWFufGVufDB8Mnx8fDE2NjI5NzQwNjI&force=true&w=640',
+    'https://unsplash.com/photos/P4gXe-RsEXI/download?ixid=MnwxMjA3fDB8MXxzZWFyY2h8Nnx8cGVyZm9ybWVyfGVufDB8Mnx8fDE2NjMwMTM3OTE&force=true&w=640'
+  ]
+}
+</script>
+
+# Playlist card
+## Normal card
+```html
+<fw-playlist-card :playlist="playlist" />
+```
+<div style="display: grid; grid-template-columns: auto auto 1fr; grid-gap: 2rem;">
+<fw-playlist-card :playlist="playlist" />
+<fw-playlist-card :playlist="{ ...playlist, album_covers: [] }" />
+</div>
diff --git a/docs/components/card/podcast.md b/docs/components/card/podcast.md
new file mode 100644
index 0000000000000000000000000000000000000000..b86d1e43a186a71b3a01a2d87aa2a92af97418eb
--- /dev/null
+++ b/docs/components/card/podcast.md
@@ -0,0 +1,22 @@
+<script setup lang="ts">
+const podcast = {
+  name: 'Podcast title',
+  tracks_count: 8,
+  artist: {
+    name: 'Podcast owner',
+    modification_date: '2022-09-11',
+    cover: {
+      urls: {
+        original: 'https://images.unsplash.com/photo-1524650359799-842906ca1c06?ixlib=rb-1.2.1&dl=te-nguyen-Wt7XT1R6sjU-unsplash.jpg&w=640&q=80&fm=jpg&crop=entropy&cs=tinysrgb'
+      }
+    }
+  },
+}
+</script>
+
+# Podcast card
+## Normal card
+```html
+<fw-podcast-card :podcast="podcast" />
+```
+<fw-podcast-card :podcast="podcast" />
diff --git a/docs/components/card/radio.md b/docs/components/card/radio.md
new file mode 100644
index 0000000000000000000000000000000000000000..0a667291934fd9ef52aa1ad1fda13a88eb81cb3e
--- /dev/null
+++ b/docs/components/card/radio.md
@@ -0,0 +1,52 @@
+<script setup lang="ts">
+const radio = {
+  name: 'Less Listened',
+  description: 'First artist, Second Artist, One Other Artist, Yet Another One'
+}
+</script>
+
+# Radio card
+## Card colors
+### Blue
+This is the default color
+```html
+<fw-radio-card blue :radio="radio" />
+```
+<fw-radio-card blue :radio="radio" />
+
+### Red
+```html
+<fw-radio-card red :radio="radio" />
+```
+<fw-radio-card red :radio="radio" />
+
+### Purple
+```html
+<fw-radio-card purple :radio="radio" />
+```
+<fw-radio-card purple :radio="radio" />
+
+### Green
+```html
+<fw-radio-card green :radio="radio" />
+```
+<fw-radio-card green :radio="radio" />
+
+### Yellow
+```html
+<fw-radio-card yellow :radio="radio" />
+```
+<fw-radio-card yellow :radio="radio" />
+
+## Card sizes
+### Normal card
+```html
+<fw-radio-card :radio="radio" />
+```
+<fw-radio-card :radio="radio" />
+
+### Small card
+```html
+<fw-radio-card small :radio="radio" />
+```
+<fw-radio-card small :radio="radio" />
diff --git a/docs/components/input/index.md b/docs/components/input/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..6092d3e83eb8e57b592cd7fefb1a45f9e90f6404
--- /dev/null
+++ b/docs/components/input/index.md
@@ -0,0 +1,29 @@
+# Input
+## Input model
+```html
+<fw-input v-model="value" placeholder="Search" />
+```
+<fw-input placeholder="Search" />
+
+## Input icons
+You can use bootstrap icons
+```html
+<fw-input v-model="value" icon="bi-search" placeholder="Search" />
+```
+<fw-input icon="bi-search" placeholder="Search" />
+
+
+## Input-right slot
+```html
+<fw-input v-model="value" placeholder="Search">
+  <template #input-right>
+    suffix
+  </template>
+</fw-input>
+```
+<fw-input placeholder="Search">
+  <template #input-right>
+    suffix
+  </template>
+</fw-input>
+
diff --git a/docs/components/loader/index.md b/docs/components/loader/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..ef6ca1d48314b66cb55938cb412f7a9c91688c3f
--- /dev/null
+++ b/docs/components/loader/index.md
@@ -0,0 +1,35 @@
+<style>
+.docs-loader-container div[style^=width] {
+  border: 1px solid #666;
+  height: 2em;
+}
+</style>
+
+# Loader
+```html
+<div style="width: 50%">
+  <fw-loader />
+</div>
+```
+<div class="docs-loader-container">
+  <div style="width: 50%">
+    <fw-loader />
+  </div>
+</div>
+
+## No container
+By default the `<fw-loader />` component creates a container that is 100% height of component's parent. Sometimes we do not want such behavior. In those cases we can disable this using `:container="false"` property. The loader will then be centered in the middle of the first parent that has `position: relative` set.
+```html
+<div style="position: relative">
+  <div style="width: 50%">
+    <fw-loader :container="false" />
+  </div>
+</div>
+```
+<div class="docs-loader-container">
+  <div style="position: relative">
+    <div style="width: 50%">
+      <fw-loader :container="false" />
+    </div>
+  </div>
+</div>
diff --git a/docs/components/pagination/index.md b/docs/components/pagination/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..e9741c53aa76606802311995c09cafc78ce3cf50
--- /dev/null
+++ b/docs/components/pagination/index.md
@@ -0,0 +1,12 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const page = ref(1)
+</script>
+
+# Pagination
+## Pagination model
+```html
+<fw-pagination :pages="8" v-model:page="page" />
+```
+<fw-pagination :pages="8" v-model:page="page" />
diff --git a/docs/components/pill/index.md b/docs/components/pill/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..95e2f1828e5661c7f113341a2b2877c3496d0c90
--- /dev/null
+++ b/docs/components/pill/index.md
@@ -0,0 +1,100 @@
+# Pill
+## Pill colors
+### Primary pill
+```html
+<fw-pill primary>
+  Primary pill
+</fw-pill>
+```
+<fw-pill primary>
+  Primary pill
+</fw-pill>
+
+### Secondary pill
+This is the default pill color
+```html
+<fw-pill>
+  Secondary pill
+</fw-pill>
+```
+<fw-pill>
+  Secondary pill
+</fw-pill>
+
+### Destructive pill
+```html
+<fw-pill destructive>
+  Destructive pill
+</fw-pill>
+```
+<fw-pill destructive>
+  Destructive pill
+</fw-pill>
+
+### Blue
+```html
+<fw-pill blue>
+  Blue pill
+</fw-pill>
+```
+<fw-pill blue>
+  Blue pill
+</fw-pill>
+
+### Red
+```html
+<fw-pill red>
+  Red pill
+</fw-pill>
+```
+<fw-pill red>
+  Red pill
+</fw-pill>
+
+### Purple
+```html
+<fw-pill purple>
+  Purple pill
+</fw-pill>
+```
+<fw-pill purple>
+  Purple pill
+</fw-pill>
+
+### Green
+```html
+<fw-pill green>
+  Green pill
+</fw-pill>
+```
+<fw-pill green>
+  Green pill
+</fw-pill>
+
+### Yellow
+```html
+<fw-pill yellow>
+  Yellow pill
+</fw-pill>
+```
+<fw-pill yellow>
+  Yellow pill
+</fw-pill>
+
+
+## Images
+```html
+<fw-pill>
+  <template #image>
+    <img src="/images/awesome-artist.png" />
+  </template>
+  Awesome artist
+</fw-pill>
+```
+
+<fw-pill>
+  <template #image>
+    <div style="background-color: #0004" />
+  </template>
+  Awesome artist
+</fw-pill>
diff --git a/docs/components/popover/index.md b/docs/components/popover/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..f50f0dc3ddb5c8d9adf6bd8d84c40f9279883da0
--- /dev/null
+++ b/docs/components/popover/index.md
@@ -0,0 +1,131 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const bandcampPrivacy = ref('pod')
+const ccPrivacy = ref('public')
+
+const bandcamp = ref(false)
+const cc = ref(false)
+const share = ref(false)
+
+const items = [
+  {
+    type: 'button',
+    text: 'Play next',
+    icon: 'bi-arrow-up-right',
+    click: () => {}
+  },
+  {
+    type: 'button',
+    text: 'Append to queue',
+    icon: 'bi-arrow-down-right',
+    click: () => {}
+  },
+  {
+    type: 'button',
+    text: 'Add to playlist',
+    icon: 'bi-music-note-list',
+    click: () => {},
+    items: [
+      {
+        type: 'button',
+        text: 'Sample playlist',
+        icon: 'bi-music-note-list',
+        click: () => {}
+      },
+      { type: 'separator', },
+      {
+        type: 'button',
+        text: 'New playlist',
+        icon: 'bi-plus-lg',
+        click: () => {}
+      },
+    ]
+  },
+  { type: 'separator', },
+  {
+    type: 'button',
+    text: 'Add to favorites',
+    icon: 'bi-heart',
+    click: () => {}
+  },
+  {
+    type: 'button',
+    text: 'Organize and share',
+    icon: 'bi-collection',
+    click: () => {},
+    items: [
+      {
+        type: 'select',
+        text: 'Bandcamp',
+        model: bandcamp,
+        extraItems: [
+          { 
+            type: 'library-privacy-level', 
+            model: bandcampPrivacy
+          }
+        ]
+      },
+      {
+        type: 'select',
+        text: 'Creative Commons',
+        model: cc,
+        extraItems: [
+          { 
+            type: 'library-privacy-level', 
+            model: ccPrivacy
+          }
+        ]
+      },
+      { type: 'separator', },
+      {
+        type: 'button',
+        text: 'New library',
+        icon: 'bi-plus-lg',
+        click: () => {}
+      },
+      { type: 'separator', },
+      {
+        type: 'select',
+        text: 'Share by link',
+        model: share,
+        extraItems: [
+          { 
+            type: 'circle-button', 
+            icon: 'bi-link', 
+            click: () => {} 
+          },
+          { 
+            type: 'circle-button', 
+            icon: 'bi-code', 
+            click: () => {} 
+          },
+        ]
+      },
+    ]
+  },
+  {
+    type: 'button',
+    text: 'Download',
+    icon: 'bi-cloud-download',
+    click: () => {}
+  },
+  { type: 'separator', },
+  {
+    type: 'button',
+    text: 'Report',
+    icon: 'bi-exclamation',
+    click: () => {}
+  },
+]
+
+const open = ref(false)
+</script>
+
+# Popover
+```html
+<fw-popover :items="items" />
+```
+
+<fw-options-button @click="open = !open" />
+<fw-popover :items="items" v-model:open="open" />
diff --git a/docs/components/tabs/index.md b/docs/components/tabs/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..5f73ab9cf5b1a819c868b745eb04d85a0c9dd9cc
--- /dev/null
+++ b/docs/components/tabs/index.md
@@ -0,0 +1,49 @@
+# Tabs
+::: warning
+The `<fw-tab>` components won't render outside of the `<fw-tabs>` component.
+:::
+
+```html
+<fw-tabs>
+  <fw-tab title="Overview">Overview content</fw-tab>
+  <fw-tab title="Activity">Activity content</fw-tab>
+</fw-tabs>
+```
+<fw-tabs>
+  <fw-tab title="Overview">Overview content</fw-tab>
+  <fw-tab title="Activity">Activity content</fw-tab>
+</fw-tabs>
+
+## Tabs-right slot
+```html
+<fw-tabs>
+  <fw-tab title="Overview">Overview content</fw-tab>
+  <fw-tab title="Activity">Activity content</fw-tab>
+
+  <template #tabs-right>
+    <fw-input icon="bi-search" placeholder="Search" />
+  </template>
+</fw-tabs>
+```
+<fw-tabs>
+  <fw-tab title="Overview">Overview content</fw-tab>
+  <fw-tab title="Activity">Activity content</fw-tab>
+
+  <template #tabs-right>
+    <fw-input icon="bi-search" placeholder="Search" />
+  </template>
+</fw-tabs>
+
+## Duplicated tabs render as a single tab
+```html
+<fw-tabs>
+  <fw-tab title="Overview">Overview content</fw-tab>
+  <fw-tab title="Activity">Activity content</fw-tab>
+  <fw-tab title="Overview">More overview content</fw-tab>
+</fw-tabs>
+```
+<fw-tabs>
+  <fw-tab title="Overview">Overview content</fw-tab>
+  <fw-tab title="Activity">Activity content</fw-tab>
+  <fw-tab title="Overview">More overview content</fw-tab>
+</fw-tabs>
diff --git a/docs/components/textarea/index.md b/docs/components/textarea/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..64e3569eafce7823189b7b212df4542d1c4f8b3a
--- /dev/null
+++ b/docs/components/textarea/index.md
@@ -0,0 +1,33 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const text1 = ref('# Funk\nwhale')
+const text2 = ref('# Funk\nwhale')
+const text3 = ref('')
+</script>
+
+# Textarea
+## Textarea model
+```html
+<fw-textarea v-model="text" />
+```
+<ClientOnly>
+  <fw-textarea v-model="text1" />
+</ClientOnly>
+
+## Textarea max length
+```html
+<fw-textarea v-model="text" :max="20" />
+```
+<ClientOnly>
+  <fw-textarea v-model="text2" :max="20" />
+</ClientOnly>
+
+
+## Textarea placeholder
+```html
+<fw-textarea v-model="text" placeholder="Describe this track here…" />
+```
+<ClientOnly>
+  <fw-textarea v-model="text3" placeholder="Describe this track here…" />
+</ClientOnly>
diff --git a/docs/components/toc/index.md b/docs/components/toc/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..15ff012b38d6f2cd31bb3c707d62f13d4a565db9
--- /dev/null
+++ b/docs/components/toc/index.md
@@ -0,0 +1,71 @@
+# Table of Contents
+## Default
+By default table of contents is based on the `<h1>` tags
+```html
+<fw-toc>
+  <h1>This is a Table of Contents</h1>
+  Content...
+
+  <h1>It automatically generates from headings</h1>
+  More content...
+</fw-toc>
+```
+<ClientOnly>
+  <fw-toc>
+    <h1>This is a Table of Contents</h1>
+    <p>
+      In expedita ratione consequatur rerum et ullam architecto. Qui ut doloremque laboriosam perferendis corporis voluptatibus voluptates. Ad ducimus adipisci vitae mollitia quis. Aut placeat quaerat maxime velit et eius voluptas fugit. Omnis et et perspiciatis mollitia occaecati.
+    </p>
+    <p>
+      Unde praesentium voluptates esse in placeat. Quis qui sint illo tempore omnis sed. Ab dicta omnis aut dolor voluptate maxime repudiandae ea. Aspernatur alias et architecto asperiores in. A sunt necessitatibus voluptatem veniam at dolore. Dolorum saepe est eveniet dignissimos laborum.
+    </p>
+    <p>
+      Qui impedit dicta earum. Qui repudiandae est magnam. Illum sit ratione exercitationem fugiat aut tempore. Ut sit deserunt ratione ut architecto deleniti ea magnam. Voluptatibus dignissimos voluptatem rem fugiat.
+    </p>
+    <h1>It automatically generates from headings</h1>
+    <p>
+      In expedita ratione consequatur rerum et ullam architecto. Qui ut doloremque laboriosam perferendis corporis voluptatibus voluptates. Ad ducimus adipisci vitae mollitia quis. Aut placeat quaerat maxime velit et eius voluptas fugit. Omnis et et perspiciatis mollitia occaecati.
+    </p>
+    <p>
+      Unde praesentium voluptates esse in placeat. Quis qui sint illo tempore omnis sed. Ab dicta omnis aut dolor voluptate maxime repudiandae ea. Aspernatur alias et architecto asperiores in. A sunt necessitatibus voluptatem veniam at dolore. Dolorum saepe est eveniet dignissimos laborum.
+    </p>
+    <p>
+      Qui impedit dicta earum. Qui repudiandae est magnam. Illum sit ratione exercitationem fugiat aut tempore. Ut sit deserunt ratione ut architecto deleniti ea magnam. Voluptatibus dignissimos voluptatem rem fugiat.
+    </p>
+  </fw-toc>
+</ClientOnly>
+
+## Custom headings
+```html
+<fw-toc heading="h2">
+  <h1>This is a Table of Contents</h1>
+  Content...
+
+  <h2>It automatically generates from headings</h2>
+  More content...
+</fw-toc>
+```
+<ClientOnly>
+  <fw-toc heading="h2">
+    <h1>This is a Table of Contents</h1>
+    <p>
+      In expedita ratione consequatur rerum et ullam architecto. Qui ut doloremque laboriosam perferendis corporis voluptatibus voluptates. Ad ducimus adipisci vitae mollitia quis. Aut placeat quaerat maxime velit et eius voluptas fugit. Omnis et et perspiciatis mollitia occaecati.
+    </p>
+    <p>
+      Unde praesentium voluptates esse in placeat. Quis qui sint illo tempore omnis sed. Ab dicta omnis aut dolor voluptate maxime repudiandae ea. Aspernatur alias et architecto asperiores in. A sunt necessitatibus voluptatem veniam at dolore. Dolorum saepe est eveniet dignissimos laborum.
+    </p>
+    <p>
+      Qui impedit dicta earum. Qui repudiandae est magnam. Illum sit ratione exercitationem fugiat aut tempore. Ut sit deserunt ratione ut architecto deleniti ea magnam. Voluptatibus dignissimos voluptatem rem fugiat.
+    </p>
+    <h2>It automatically generates from headings</h2>
+    <p>
+      In expedita ratione consequatur rerum et ullam architecto. Qui ut doloremque laboriosam perferendis corporis voluptatibus voluptates. Ad ducimus adipisci vitae mollitia quis. Aut placeat quaerat maxime velit et eius voluptas fugit. Omnis et et perspiciatis mollitia occaecati.
+    </p>
+    <p>
+      Unde praesentium voluptates esse in placeat. Quis qui sint illo tempore omnis sed. Ab dicta omnis aut dolor voluptate maxime repudiandae ea. Aspernatur alias et architecto asperiores in. A sunt necessitatibus voluptatem veniam at dolore. Dolorum saepe est eveniet dignissimos laborum.
+    </p>
+    <p>
+      Qui impedit dicta earum. Qui repudiandae est magnam. Illum sit ratione exercitationem fugiat aut tempore. Ut sit deserunt ratione ut architecto deleniti ea magnam. Voluptatibus dignissimos voluptatem rem fugiat.
+    </p>
+  </fw-toc>
+</ClientOnly>
diff --git a/docs/components/toggle/index.md b/docs/components/toggle/index.md
new file mode 100644
index 0000000000000000000000000000000000000000..a10b78996abd3fb3a1a150c7242d550b554a7641
--- /dev/null
+++ b/docs/components/toggle/index.md
@@ -0,0 +1,18 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const toggle = ref(false)
+</script>
+
+# Toggle
+```html
+<fw-toggle v-model="toggle" />
+```
+<fw-toggle v-model="toggle" />
+
+## Big toggle
+```html
+<fw-toggle big v-model="toggle" />
+```
+<fw-toggle big v-model="toggle" />
+
diff --git a/src/index.md b/docs/index.md
similarity index 100%
rename from src/index.md
rename to docs/index.md
diff --git a/docs/vite.config.ts b/docs/vite.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2ffde2ba976cabf5498e6bc0094b607a937e855b
--- /dev/null
+++ b/docs/vite.config.ts
@@ -0,0 +1,18 @@
+import { fileURLToPath, URL } from 'url'
+import { defineConfig } from 'vite'
+
+import viteConfig from '../vite.config'
+import yaml from '@modyfi/vite-plugin-yaml'
+
+export default defineConfig((config) => ({
+  // @ts-expect-error UserConfigExport is not callable (only in theory)
+  css: viteConfig(config).css,
+  plugins: [yaml()],
+  publicDir: false,
+  resolve: {
+    alias: {
+      '~': fileURLToPath(new URL('../src', import.meta.url)),
+      '/node_modules': fileURLToPath(new URL('../node_modules', import.meta.url))
+    }
+  }
+}))
diff --git a/index.html b/index.html
deleted file mode 100644
index 11603f878f12266e2f1f1db14989f381cb578594..0000000000000000000000000000000000000000
--- a/index.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-  <head>
-    <meta charset="UTF-8" />
-    <link rel="icon" href="/favicon.ico" />
-    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-    <title>Vite App</title>
-  </head>
-  <body>
-    <div id="app"></div>
-    <script type="module" src="/src/main.ts"></script>
-  </body>
-</html>
diff --git a/package.json b/package.json
index 9af850eee5dad15d48514cff5685014b09b23125..60d19a3b5e69f1195135581e5e39a45adbbdfa94 100644
--- a/package.json
+++ b/package.json
@@ -1,30 +1,43 @@
 {
-	"name": "vui",
-	"version": "0.0.4-rc2",
+	"name": "@funkwhale/vui",
+	"version": "1.0.0",
 	"license": "AGPL-3.0-or-later",
+	"main": "./dist/vui.umd.js",
+	"module": "./dist/vui.es.js",
 	"scripts": {
 		"dev": "vite",
 		"build": "vue-tsc --noEmit && vite build",
 		"preview": "vite preview",
-		"test": "vitest run --coverage",
-		"docs:dev": "vitepress dev src",
-		"docs:build": "vitepress build src",
-		"docs:serve": "vitepress serve src"
+		"test": "vitest run --reporter junit --coverage",
+		"test:dev": "vitest",
+		"docs:dev": "vitepress dev docs",
+		"docs:build": "vitepress build docs",
+		"docs:serve": "vitepress serve docs"
 	},
 	"dependencies": {
-		"@funkwhale/ui": "0.1.2-dev1",
-		"vue": "^3.2.25"
+		"@vueuse/core": "^9.2.0",
+		"dompurify": "^2.4.0",
+		"showdown": "^2.1.0",
+		"transliteration": "^2.3.5",
+		"vue": "^3.2.38"
 	},
 	"devDependencies": {
-		"@testing-library/vue": "^6.4.2",
-		"@vitejs/plugin-vue": "^2.0.0",
-		"c8": "^7.11.0",
-		"happy-dom": "^2.31.1",
-		"sass": "^1.49.7",
-		"typescript": "^4.4.4",
-		"vite": "^2.7.2",
-		"vitepress": "^0.21.6",
-		"vitest": "^0.2.8",
-		"vue-tsc": "^0.29.8"
+		"@modyfi/vite-plugin-yaml": "^1.0.3",
+		"@types/dompurify": "^2.3.4",
+		"@types/showdown": "^2.0.0",
+		"@vitejs/plugin-vue": "^3.0.3",
+		"@vitest/coverage-c8": "^0.22.1",
+		"@vue/test-utils": "^2.0.2",
+		"@vue/tsconfig": "^0.1.3",
+		"bootstrap-icons": "^1.9.1",
+		"jsdom": "^20.0.0",
+		"sass": "^1.54.8",
+		"typescript": "^4.8.2",
+		"vite": "^3.0.9",
+		"vitepress": "^1.0.0-alpha.13",
+		"vitest": "^0.22.1",
+		"vue-i18n": "9",
+		"vue-router": "^4.1.5",
+		"vue-tsc": "^0.40.6"
 	}
 }
diff --git a/src/.vitepress/config.ts b/src/.vitepress/config.ts
deleted file mode 100644
index 9563bb9901605f6d2609ec481f53fdb2c63de4da..0000000000000000000000000000000000000000
--- a/src/.vitepress/config.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-module.exports = {
-	title: "Funkwhale Vue Components",
-	outDir: "../public",
-	base: "/vui/",
-	themeConfig: {
-		nav: [ 
-			{ text: "Home", link: "https://funkwhale.audio" },
-			{ text: "Gitlab", link: "https://dev.funkwhale.audio" },
-		],
-		sidebar: [
-			{ text: "Components", link: "/components/" },
-		],
-	},
-};
diff --git a/src/App.vue b/src/App.vue
deleted file mode 100644
index 89eafc650c0a36a2baabf2a7d0067d6a5b32e0fd..0000000000000000000000000000000000000000
--- a/src/App.vue
+++ /dev/null
@@ -1,11 +0,0 @@
-<script setup lang="ts">
-// This starter template is using Vue 3 <script setup> SFCs
-// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
-import FunkwhaleButton from './components/FunkwhaleButton/FunkwhaleButton.vue'
-</script>
-
-<template>
-  <FunkwhaleButton text="Button" />
-</template>
-
-<style></style>
diff --git a/src/assets/logo.png b/src/assets/logo.png
deleted file mode 100644
index f3d2503fc2a44b5053b0837ebea6e87a2d339a43..0000000000000000000000000000000000000000
Binary files a/src/assets/logo.png and /dev/null differ
diff --git a/src/components/FunkwhaleButton/FunkwhaleButton.test.ts b/src/components/FunkwhaleButton/FunkwhaleButton.test.ts
deleted file mode 100644
index 0aec3f63dd7e242899378cdc3c345f3d78821864..0000000000000000000000000000000000000000
--- a/src/components/FunkwhaleButton/FunkwhaleButton.test.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import {render} from '@testing-library/vue'
-import FunkwhaleButton from './FunkwhaleButton.vue'
-
-test('it should work', () => {
-	const {getByText} = render(FunkwhaleButton, {
-		props: {
-			text: 'Button Test'
-		}
-	})
-
-	getByText('Button Test')
-})
diff --git a/src/components/FunkwhaleButton/FunkwhaleButton.vue b/src/components/FunkwhaleButton/FunkwhaleButton.vue
deleted file mode 100644
index 427af8e3e90a1c5fed1ad3e84b719a07ec399000..0000000000000000000000000000000000000000
--- a/src/components/FunkwhaleButton/FunkwhaleButton.vue
+++ /dev/null
@@ -1,16 +0,0 @@
-<script setup lang="ts">
-import { ref } from 'vue'
-
-defineProps<{ text: string }>()
-
-const count = ref(0)
-</script>
-
-<template>
-
-	<button class="fw button is-primary">{{ text }}</button>
-</template>
-
-<style lang="scss">
-@import '@funkwhale/ui/src/elements/buttons.scss';
-</style>
diff --git a/src/components/FunkwhaleButton/index.md b/src/components/FunkwhaleButton/index.md
deleted file mode 100644
index fdb92412130deaf86828fd3f733694a885417432..0000000000000000000000000000000000000000
--- a/src/components/FunkwhaleButton/index.md
+++ /dev/null
@@ -1 +0,0 @@
-# Funkwhale Button
diff --git a/src/components/activity/Activity.vue b/src/components/activity/Activity.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8a08b34cde2f60b32f9f4ed09dddefc5af676242
--- /dev/null
+++ b/src/components/activity/Activity.vue
@@ -0,0 +1,95 @@
+<script setup lang="ts">
+import { FwOptionsButton, FwPlayButton } from '~/components'
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { computed } from 'vue'
+
+const { t } = useI18n()
+
+interface Track {
+  id: number
+  name: string
+
+  artist: {
+    id: number
+    name: string
+  }
+
+  cover: {
+    urls: {
+      original: string
+    }
+  }
+}
+
+interface User {
+  username: string
+  full_username: string
+}
+
+interface Events {
+  (e: 'play', track: Track): void
+}
+
+interface Props {
+  track: Track
+  user: User
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+const profileParams = computed(() => {
+  const [username, domain] = props.user.full_username.split('@')
+  return { username, domain }
+})
+
+let navigate = (to: 'track' | 'artist' | 'user') => {}
+
+if (import.meta.env.PROD) {
+  const router = useRouter()
+  navigate = (to: 'track' | 'artist' | 'user') => to === 'track'
+    ? router.push({ name: 'library.tracks.detail', params: { id: props.track.id } })
+    : to === 'artist'
+      ? router.push({ name: 'library.artists.detail', params: { id: props.track.artist.id } })
+      : router.push({ name: 'profile.full', params: profileParams.value })
+}
+</script>
+
+<template>
+	<div
+    class="funkwhale activity"
+    @click="navigate('track')"
+  >
+    <div class="activity-image">
+      <img :src="track.cover.urls.original" />
+      <fw-play-button
+        @play="emit('play', track)"
+        :round="false"
+        :shadow="false"
+      />
+    </div>
+    <div class="activity-content">
+      <div class="track-title">{{ track.name }}</div>
+      <a
+        @click.stop="navigate('artist')"
+        class="funkwhale link artist"
+      >
+        {{ track.artist.name }}
+      </a>
+      <a
+        @click.stop="navigate('user')"
+        class="funkwhale link user"
+      >
+        {{ t('vui.by-user', { username: user.username}) }}
+      </a>
+    </div>
+    <div>
+      <fw-options-button />
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/activity/style.scss b/src/components/activity/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..4ed1686f5496c7c1beb5f1d9feabbb81f4d63213
--- /dev/null
+++ b/src/components/activity/style.scss
@@ -0,0 +1,81 @@
+.funkwhale {
+  &.activity {
+    padding: 12px 6px;
+
+    color: var(--fw-gray-500);
+    cursor: pointer;
+
+    display: grid;
+    grid-template-columns: auto 1fr auto;
+    gap: 12px;
+
+    + .funkwhale.activity {
+      border-top: 1px solid var(--fw-gray-300);
+    }
+
+    > .activity-image {
+      position: relative;
+      width: 40px;
+      aspect-ratio: 1;
+      overflow: hidden;
+      border-radius: var(--fw-border-radius);
+
+      > img {
+        width: 100%;
+        aspect-ratio: 1;
+        object-fit: cover;
+      }
+
+      > .play-button {
+        position: absolute;
+        top: 0;
+        left: 0;
+        padding: 0 !important;
+        width: 100%;
+        aspect-ratio: 1;
+        margin: 0;
+        background: rgba(255, 255, 255, .5);
+        border: 0 !important;
+        opacity: 0;
+      }
+    }
+
+    &:hover {
+      .play-button {
+        opacity: 1;
+      }
+    }
+
+    > .activity-content {
+      display: flex;
+      flex-direction: column;
+      align-items: flex-start;
+
+      > .track-title {
+        color: var(--fw-gray-900);
+        font-weight: 700;
+        line-height: 1.5em;
+
+        html.dark & {
+          color: var(--fw-gray-300);
+        }
+      }
+
+      > .artist {
+        --fw-link-color: var(--fw-gray-900);
+        line-height: 1.5em;
+        font-size: 0.857rem;
+
+        html.dark & {
+          --fw-link-color: var(--fw-gray-300);
+        }
+      }
+
+      > .user {
+        --fw-link-color: var(--fw-gray-500);
+        line-height: 1.5em;
+        font-size: 0.8125rem;
+      }
+    }
+  }
+}
diff --git a/src/components/button/Button.vue b/src/components/button/Button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..71899f5cece7eadb8e23832a37816686c04a5bcf
--- /dev/null
+++ b/src/components/button/Button.vue
@@ -0,0 +1,68 @@
+<script setup lang="ts">
+import { type ColorProps, useColor } from '~/composables/useColor'
+import { FwLoader } from '~/components'
+import { ref, computed, useSlots } from 'vue'
+
+interface Props extends ColorProps {
+  outline?: boolean
+
+  isActive?: boolean
+  isLoading?: boolean
+
+  shadow?: boolean
+  round?: boolean
+  icon?: string
+
+  onClick?: (...args: any[]) => void | Promise<void>
+
+  // TODO (wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
+  primary?: boolean
+  secondary?: boolean
+  destructive?: boolean
+}
+
+const props = defineProps<Props>()
+
+const type = useColor(props)
+
+const slots = useSlots()
+const iconOnly = computed(() => !!props.icon && !slots.default)
+
+const internalLoader = ref(false)
+const isLoading = computed(() => props.isLoading || internalLoader.value)
+
+const click = async (...args: any[]) => {
+  internalLoader.value = true
+
+  try {
+    await props.onClick?.(...args)
+  } finally {
+    internalLoader.value = false
+  }
+}
+</script>
+
+<template>
+	<button
+    class="funkwhale is-colored button"
+    :class="[type, { 'is-active': isActive, 'is-outline': outline, 'is-loading': isLoading, 'icon-only': iconOnly, 'is-round': round, 'is-shadow': shadow }]"
+    @click="click"
+  >
+    <span>
+      <i
+        v-if="icon"
+        :class="['bi', icon]"
+      />
+      <slot />
+    </span>
+
+    <fw-loader
+      v-if="isLoading"
+      :container="false"
+    />
+  </button>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/button/options/Button.vue b/src/components/button/options/Button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f2009976dac867c499e70164c1c4519ddbb2e1f7
--- /dev/null
+++ b/src/components/button/options/Button.vue
@@ -0,0 +1,15 @@
+<script setup lang="ts">
+import { FwButton } from '~/components'
+</script>
+
+<template>
+  <fw-button
+    icon="bi-three-dots-vertical"
+    class="options-button"
+    secondary
+  />
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/button/options/style.scss b/src/components/button/options/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..9f38b013c5ea880a10690c6f7439170926eea67c
--- /dev/null
+++ b/src/components/button/options/style.scss
@@ -0,0 +1,17 @@
+.funkwhale {
+  &.options-button {
+
+    &:not(:hover):not(:active) {
+      --fw-bg-color: transparent !important;
+    }
+
+    will-change: transform;
+    transition: all .2s ease;
+    font-size: 0.6rem !important;
+    padding: 0.6em !important;
+
+    i {
+      font-size: 1.2rem;
+    }
+  }
+}
diff --git a/src/components/button/play/Button.vue b/src/components/button/play/Button.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e6d5cac2b625832bf66486206bac445fa5ca9818
--- /dev/null
+++ b/src/components/button/play/Button.vue
@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { FwButton } from '~/components'
+
+interface Events {
+  (e: 'play'): void
+}
+
+const emit = defineEmits<Events>()
+</script>
+
+<template>
+  <fw-button
+    @click="emit('play')"
+    icon="bi-play-fill"
+    class="play-button"
+    shadow
+    round
+  />
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/button/play/style.scss b/src/components/button/play/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..5c48813c08fc0462837425add28d79ce533437d7
--- /dev/null
+++ b/src/components/button/play/style.scss
@@ -0,0 +1,25 @@
+.funkwhale {
+  &.play-button {
+    --fw-bg-color: #fff !important;
+    --fw-text-color: var(--fw-gray-600) !important;
+
+    will-change: transform;
+    font-size: 0.6rem !important;
+    padding: 0.625em !important;
+    border: 0px !important;
+
+    i {
+      font-size: 2rem;
+
+      &::before {
+        transform: translateX(1px);
+        backface-visibility: hidden;
+      }
+    }
+
+    &:hover {
+      --fw-text-color: var(--fw-pastel-4, var(--fw-primary)) !important;
+      --fw-scale: 1.091;
+    }
+  }
+}
diff --git a/src/components/button/style.scss b/src/components/button/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..08094d5d92a7e31506bd836b1e599d75b6f7109f
--- /dev/null
+++ b/src/components/button/style.scss
@@ -0,0 +1,79 @@
+.funkwhale {
+  &.button {
+    position: relative;
+
+    background-color: var(--fw-bg-color);
+    color: var(--fw-text-color);
+
+    border: 1px solid var(--fw-bg-color);
+
+    font-family: $font-main;
+    font-weight: 900;
+    font-size: 0.875em;
+
+    line-height: 1em;
+
+    padding: 0.642857142857em;
+
+    border-radius: var(--fw-border-radius);
+    margin: 0 0.5ch;
+
+    transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
+    transition: all .2s ease;
+
+    &.is-shadow {
+      box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.2);
+    }
+
+    &:not(.icon-only) {
+      min-width: 8.5rem;
+    }
+
+    &.is-round {
+      border-radius: 100vh;
+    }
+
+    &[disabled] {
+      font-weight: normal;
+      cursor: default;
+    }
+
+    &.is-secondary.is-outline {
+      --fw-bg-color: var(--fw-gray-600);
+
+      &.is-hovered,
+      &:hover {
+        --fw-bg-color: var(--fw-gray-700);
+      }
+
+      &.is-active,
+      &:active {
+        --fw-text-color: var(--fw-gray-300);
+      }
+
+      &[disabled] {
+        --fw-bg-color: var(--fw-gray-400) !important;
+        background-color: transparent !important;
+
+        html.dark & {
+          --fw-bg-color: var(--fw-gray-800) !important;
+        }
+      }
+    }
+
+    &.is-outline {
+      &:not(:active):not(.is-active) {
+        background-color: transparent !important;
+        color: var(--fw-bg-color) !important;
+      }
+    }
+
+    &.is-loading {
+      @extend .is-active;
+
+      > span {
+        opacity: 0;
+      }
+    }
+  }
+}
diff --git a/src/components/card/Card.vue b/src/components/card/Card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c5ef9346b3c7fe25626b9db54b5d740e6e773cff
--- /dev/null
+++ b/src/components/card/Card.vue
@@ -0,0 +1,71 @@
+<script setup lang="ts">
+import { FwButton, FwPill } from '~/components'
+
+interface Props {
+  title: string
+  buttonTitle?: string
+  image?: string
+  onClick?: (event: Event) => void
+  category?: boolean
+  tags?: string[]
+}
+
+defineProps<Props>()
+</script>
+
+<template>
+	<div
+    @click="(event) => !buttonTitle ? onClick?.(event) : undefined"
+    class="funkwhale card"
+    :class="{ 'has-image': !!image || $slots.image, 'is-link': !!onClick, 'is-category': category, 'is-cta': !!buttonTitle }"
+  >
+    <div
+      v-if="$slots.image"
+      class="card-image"
+    >
+      <slot name="image" :src="image" />
+    </div>
+
+    <img
+      v-else-if="image"
+      :src="image"
+      class="card-image"
+    />
+
+    <div class="card-title">{{ title }}</div>
+
+    <div v-if="tags" class="card-tags">
+      <fw-pill
+        v-for="tag in tags"
+        :key="tag"
+      >
+        #{{ tag }}
+      </fw-pill>
+    </div>
+
+    <div
+      v-if="$slots.default"
+      class="card-content"
+    >
+      <slot />
+    </div>
+
+    <div
+      v-if="$slots.footer"
+      class="card-footer"
+    >
+      <slot name="footer" />
+    </div>
+
+    <div
+      v-if="buttonTitle"
+      class="card-button"
+    >
+      <fw-button outline secondary @click="onClick">{{ buttonTitle }}</fw-button>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/card/album/Card.vue b/src/components/card/album/Card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b99811d2e3493243d08bb659b1a686b16da1e3cc
--- /dev/null
+++ b/src/components/card/album/Card.vue
@@ -0,0 +1,69 @@
+<script setup lang="ts">
+import { FwCard, FwPlayButton, FwOptionsButton } from '~/components'
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
+
+interface Album {
+  id: number
+  name: string
+  artist: {
+    id: number
+    name: string
+  }
+  tracks: object[]
+  cover: {
+    urls: {
+      original: string
+    }
+  }
+}
+
+interface Events {
+  (e: 'play', album: Album): void
+}
+
+interface Props {
+  album: Album
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+let navigate = (to: 'artist' | 'album') => {}
+
+if (import.meta.env.PROD) {
+  const router = useRouter()
+  navigate = (to: 'artist' | 'album') => to === 'album'
+    ? router.push({ name: 'library.albums.detail', params: { id: props.album.id } })
+    : router.push({ name: 'library.artists.detail', params: { id: props.album.artist.id } })
+}
+</script>
+
+<template>
+  <fw-card
+    :title="album.name"
+    :image="album.cover.urls.original"
+    @click="navigate('album')"
+    class="album-card"
+  >
+    <fw-play-button @play="emit('play', album)" />
+
+    <a
+      @click.stop="navigate('artist')"
+      class="funkwhale link"
+    >
+      {{ album.artist.name }}
+    </a>
+
+    <template #footer>
+      {{ t('vui.tracks', album.tracks.length) }}
+      <fw-options-button />
+    </template>
+  </fw-card>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/card/album/style.scss b/src/components/card/album/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..4a3704529a3c4d428cac76a67796f132b7f856d3
--- /dev/null
+++ b/src/components/card/album/style.scss
@@ -0,0 +1,25 @@
+.funkwhale {
+  &.card.album-card {
+    --fw-border-radius: 12px;
+    --fw-card-width: 208px;
+    --fw-card-image-width: var(--fw-card-width);
+    --fw-card-padding: 16px;
+
+    > .card-image {
+      border-radius: 0 !important;
+      width: var(--fw-card-image-width);
+      margin: calc(-1 * var(--fw-card-padding)) calc(-1 * var(--fw-card-padding)) 0;
+    }
+
+    > .card-title {
+      font-size: 1rem;
+      text-align: left !important;
+      padding-top: 16px !important;
+    }
+
+    > .card-content {
+      padding-top: 0 !important;
+      text-align: left !important;
+    }
+  }
+}
diff --git a/src/components/card/artist/Card.vue b/src/components/card/artist/Card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2d793187acedc3dc88687523a5176bae9ea0ba9d
--- /dev/null
+++ b/src/components/card/artist/Card.vue
@@ -0,0 +1,58 @@
+<script setup lang="ts">
+import { FwCard, FwPlayButton, FwOptionsButton } from '~/components'
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
+
+interface Artist {
+  id: number
+  name: string
+  tags: string[]
+  albums: object[]
+  cover: {
+    urls: {
+      original: string
+    }
+  }
+}
+
+interface Events {
+  (e: 'play', artist: Artist): void
+}
+
+interface Props {
+  artist: Artist
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+let navigate = () => {}
+
+if (import.meta.env.PROD) {
+  const router = useRouter()
+  navigate = () => router.push({ name: 'library.artists.detail', params: { id: props.artist.id } })
+}
+</script>
+
+<template>
+  <fw-card
+    :title="artist.name"
+    :image="artist.cover.urls.original"
+    :tags="artist.tags"
+    @click="navigate"
+    class="artist-card"
+  >
+    <fw-play-button @play="emit('play', artist)" />
+
+    <template #footer>
+      {{ t('vui.albums', artist.albums.length) }}
+      <fw-options-button />
+    </template>
+  </fw-card>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/card/artist/style.scss b/src/components/card/artist/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6219103c6ca64cb5056433b97d29600e75938f00
--- /dev/null
+++ b/src/components/card/artist/style.scss
@@ -0,0 +1,28 @@
+.funkwhale {
+  &.card.artist-card {
+    --fw-border-radius: 12px;
+    --fw-card-width: 208px;
+    --fw-card-image-width: calc(var(--fw-card-width) - 16px);
+    --fw-card-padding: 16px;
+
+    > .card-image {
+      border-radius: 50% !important;
+      width: var(--fw-card-image-width);
+      margin: calc(-1 * var(--fw-card-padding) + 8px) calc(-1 * var(--fw-card-padding) + 8px) 0;
+    }
+
+    .play-button {
+      top: calc(var(--fw-card-width) - 44px - 8px) !important;
+    }
+
+    > .card-title {
+      font-size: 1rem;
+      text-align: left !important;
+    }
+
+    > .card-content {
+      padding-top: 6px !important;
+      text-align: left !important;
+    }
+  }
+}
diff --git a/src/components/card/playlist/Card.vue b/src/components/card/playlist/Card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..51f49985fa08c3d458d121b3964e47198f328dbf
--- /dev/null
+++ b/src/components/card/playlist/Card.vue
@@ -0,0 +1,87 @@
+<script setup lang="ts">
+import { FwCard, FwPlayButton, FwOptionsButton } from '~/components'
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+import { computed } from 'vue'
+
+const { t } = useI18n()
+
+interface Playlist {
+  id: number
+  name: string
+  user: {
+    full_username: string
+    username: string
+  }
+  tracks_count: number
+  album_covers: string[]
+}
+
+interface Events {
+  (e: 'play', playlist: Playlist): void
+}
+
+interface Props {
+  playlist: Playlist
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+const covers = computed(() => props.playlist.album_covers
+  .filter((src, index, array) => array.indexOf(src) === index)
+  .slice(0, 4)
+)
+
+const profileParams = computed(() => {
+  const [username, domain] = props.playlist.user.full_username.split('@')
+  return { username, domain }
+})
+
+let navigate = (to: 'playlist' | 'user') => {}
+
+if (import.meta.env.PROD) {
+  const router = useRouter()
+  navigate = (to: 'playlist' | 'user') => to === 'playlist'
+    ? router.push({ name: 'library.playlists.detail', params: { id: props.playlist.id } })
+    : router.push({ name: 'profile.full', params: profileParams.value })
+}
+</script>
+
+<template>
+  <fw-card
+    :title="playlist.name"
+    @click="navigate('playlist')"
+    class="playlist-card"
+  >
+    <template #image>
+      <img
+        v-for="src in covers"
+        :key="src"
+        :src="src"
+      />
+      <div
+        v-for="i in Math.max(0, 4 - covers.length)"
+        :key="i"
+      />
+    </template>
+
+    <fw-play-button @play="emit('play', playlist)" />
+
+    <a
+      @click.stop="navigate('user')"
+      class="funkwhale link"
+    >
+      {{ t('vui.by-user', playlist.user) }}
+    </a>
+
+    <template #footer>
+      {{ t('vui.tracks', playlist.tracks_count) }}
+      <fw-options-button />
+    </template>
+  </fw-card>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/card/playlist/style.scss b/src/components/card/playlist/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6cb04b0ff2dc4e3a39decf0caa41f697e0025203
--- /dev/null
+++ b/src/components/card/playlist/style.scss
@@ -0,0 +1,43 @@
+.funkwhale {
+  &.card.playlist-card {
+    --fw-border-radius: 12px;
+    --fw-card-width: 208px;
+    --fw-card-image-width: var(--fw-card-width);
+    --fw-card-padding: 16px;
+
+    > .card-image {
+      border-radius: 0 !important;
+      width: var(--fw-card-image-width);
+      margin: calc(-1 * var(--fw-card-padding)) calc(-1 * var(--fw-card-padding)) 0;
+
+      display: grid;
+      grid-template-columns: 1fr 1fr;
+      grid-template-rows: 1fr 1fr;
+
+      > div {
+        --fw-bg-color: var(--fw-pastel-blue-3);
+        background-color: var(--fw-bg-color);
+        &:nth-child(2) {
+          --fw-bg-color: var(--fw-pastel-blue-1);
+        }
+        &:nth-child(3) {
+          --fw-bg-color: var(--fw-pastel-blue-2);
+        }
+        &:nth-child(4) {
+          --fw-bg-color: var(--fw-pastel-blue-4);
+        }
+      }
+    }
+
+    > .card-title {
+      font-size: 1rem;
+      text-align: left !important;
+      padding-top: 16px !important;
+    }
+
+    > .card-content {
+      padding-top: 0 !important;
+      text-align: left !important;
+    }
+  }
+}
diff --git a/src/components/card/podcast/Card.vue b/src/components/card/podcast/Card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8c756102f103d26d162acfa7a57454f9e2ecfa9e
--- /dev/null
+++ b/src/components/card/podcast/Card.vue
@@ -0,0 +1,59 @@
+<script setup lang="ts">
+import { FwCard, FwOptionsButton } from '~/components'
+import { useTimeAgo } from '@vueuse/core'
+import { useRouter } from 'vue-router'
+
+interface Podcast {
+  name: string
+  artist: {
+    id: number
+    name: string
+    modification_date: '2022-09-11'
+    cover: {
+      urls: {
+        original: string
+      }
+    }
+  }
+}
+
+interface Props {
+  podcast: Podcast
+}
+
+const props = defineProps<Props>()
+
+const timeAgo = useTimeAgo(new Date(props.podcast.artist.modification_date))
+
+let navigate = () => {}
+
+if (import.meta.env.PROD) {
+  const router = useRouter()
+  navigate = () => router.push({ name: 'library.artists.detail', params: { id: props.podcast.artist.id } })
+}
+</script>
+
+<template>
+  <fw-card
+    :title="podcast.name"
+    :image="podcast.artist.cover.urls.original"
+    @click="navigate"
+    class="podcast-card"
+  >
+    <a
+      @click.stop="navigate"
+      class="funkwhale link"
+    >
+      {{ podcast.artist.name }}
+    </a>
+
+    <template #footer>
+      {{ timeAgo }}
+      <fw-options-button />
+    </template>
+  </fw-card>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/card/podcast/style.scss b/src/components/card/podcast/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..706a77ed19be56df0c74c0d141d16a22774bd6b3
--- /dev/null
+++ b/src/components/card/podcast/style.scss
@@ -0,0 +1,23 @@
+.funkwhale {
+  &.card.podcast-card {
+    --fw-border-radius: 12px;
+    --fw-card-width: 208px;
+    --fw-card-image-width: var(--fw-card-width);
+    --fw-card-padding: 16px;
+
+    > .card-image {
+      --fw-border-radius: 10px;
+    }
+
+    > .card-title {
+      font-size: 1rem;
+      text-align: left !important;
+      padding-top: 20px !important;
+    }
+
+    > .card-content {
+      padding-top: 6px !important;
+      text-align: left !important;
+    }
+  }
+}
diff --git a/src/components/card/radio/Card.vue b/src/components/card/radio/Card.vue
new file mode 100644
index 0000000000000000000000000000000000000000..34400f4e01b499b0fd8ac192788c21489668d5f1
--- /dev/null
+++ b/src/components/card/radio/Card.vue
@@ -0,0 +1,70 @@
+<script setup lang="ts">
+import { type PastelProps, usePastel } from '~/composables/usePastels'
+import { FwCard, FwPlayButton } from '~/components'
+import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
+
+interface Radio {
+  id: number
+  name: string
+  description: string
+}
+
+interface Events {
+  (e: 'play', radio: Radio): void
+}
+
+interface Props extends PastelProps {
+  radio: Radio
+  small?: boolean
+
+  // TODO (wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
+  red?: boolean
+  blue?: boolean
+  purple?: boolean
+  green?: boolean
+  yellow?: boolean
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+const pastel = usePastel(props)
+
+let navigate = () => {}
+
+if (import.meta.env.PROD) {
+  const router = useRouter()
+  navigate = () => router.push({ name: 'library.radios.detail', params: { id: props.radio.id } })
+}
+</script>
+
+<template>
+  <fw-card
+    :title="radio.name"
+    :class="pastel"
+    @click="navigate"
+    class="radio-card"
+  >
+    <template #image>
+      <div class="cover-name">
+        {{ t('vui.radio') }}
+      </div>
+    </template>
+
+    <fw-play-button @play="emit('play', props.radio)" />
+
+    <div
+      v-if="!small"
+      class="radio-description"
+    >
+      {{ radio.description }}
+    </div>
+  </fw-card>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/card/radio/style.scss b/src/components/card/radio/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..db3c96bea92755ad5b15bdc7f453aed502bbdb75
--- /dev/null
+++ b/src/components/card/radio/style.scss
@@ -0,0 +1,49 @@
+.funkwhale {
+  &.card.radio-card {
+    --fw-border-radius: 12px;
+    --fw-card-width: 208px;
+    --fw-card-image-width: var(--fw-card-width);
+    --fw-card-padding: 0;
+    --fw-bg-color: var(--fw-pastel-3);
+    --fw-text-color: var(--fw-gray-100);
+
+    text-align: left !important;
+    background: var(--fw-bg-color);
+
+    > .card-title {
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      padding: 16px 16px 0 !important;
+      font-size: 2rem;
+      white-space: initial;
+    }
+
+    .cover-name {
+      position: absolute;
+      top: calc(var(--fw-card-width) - 16px);
+      right: 16px;
+      left: 16px;
+      transform: translateY(-100%);
+
+      font-size: 1.5rem;
+      font-weight: 300;
+    }
+
+    > .card-content {
+      padding-top: 0 !important;
+    }
+
+    .radio-description {
+      --fw-bg-color: var(--fw-pastel-1);
+      --fw-text-color: var(--fw-gray-900);
+
+      text-align: center;
+      font-size: 0.875rem;
+      padding: 16px 16px 22px;
+      background: var(--fw-bg-color);
+      color: var(--fw-text-color);
+    }
+  }
+}
diff --git a/src/components/card/style.scss b/src/components/card/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..acd00fecbd8ff9c6339b46aa14629553293fbf1e
--- /dev/null
+++ b/src/components/card/style.scss
@@ -0,0 +1,139 @@
+.funkwhale {
+  &.card {
+    --fw-card-width: 320px;
+    --fw-card-image-width: 160px;
+    --fw-card-padding: 24px;
+    --fw-play-button-offset: 8px;
+
+    position: relative;
+    overflow: hidden;
+    border-radius: var(--fw-border-radius);
+    color: var(--fw-text-color);
+    width: var(--fw-card-width);
+    box-shadow: 0 3px 12px 2px rgb(0 0 0 / 20%);
+    padding: var(--fw-card-padding);
+    font-size: 1rem;
+
+    &.is-link {
+      cursor: pointer;
+    }
+
+    > .card-image {
+      overflow: hidden;
+      border-radius: var(--fw-border-radius);
+      object-fit: cover;
+      width: 100%;
+      max-width: var(--fw-card-image-width);
+      aspect-ratio: 1;
+      margin: 0 auto;
+    }
+
+    > .card-title {
+      line-height: 1.3em;
+      font-size: 1.125em;
+      font-weight: bold;
+      padding-top: 8px;
+
+      text-overflow: ellipsis;
+      white-space: pre;
+      overflow: hidden;
+
+      + .card-content {
+        padding-top: 16px;
+      }
+    }
+
+    &.is-category {
+      > .card-title {
+        font-size: 1.75em;
+        padding-top: 0;
+      }
+    }
+
+    &.has-image {
+      text-align: center;
+
+      > .card-title {
+        padding: 32px 0 0;
+      }
+    }
+
+    > .card-button {
+      padding-top: 30px;
+
+      > .funkwhale.button {
+        width: 100%;
+        font-size: 1em;
+      }
+    }
+
+    &.is-cta {
+      > .card-content,
+      > .card-title {
+        text-align: left !important;
+      }
+    }
+
+    // Tags
+    > .card-tags {
+      font-size: 0.875rem;
+      margin: 0 -8px;
+      width: 188px;
+      padding: 6px 0 0;
+      text-align: left;
+    }
+
+    // Content
+    > .card-content {
+      .funkwhale.link {
+        font-size: 0.875rem;
+        --fw-link-color: var(--fw-text-color);
+      }
+    }
+
+    // Footer
+    > .card-footer {
+      display: flex;
+      align-items: center;
+      font-size: 0.8125rem;
+      padding-top: 6px;
+
+      color: var(--fw-gray-700);
+      html.dark & {
+        color: var(--fw-gray-500);
+      }
+
+      > .options-button {
+        margin-left: auto;
+      }
+    }
+
+    // Buttons
+    .play-button {
+      --fw-translate-x: 6px;
+      position: absolute;
+      right: calc(var(--fw-play-button-offset) + 6px);
+      top: calc(var(--fw-card-image-width) - var(--fw-play-button-offset) - 44px);
+      opacity: 0;
+      margin: 0;
+    }
+
+    .options-button {
+      --fw-translate-x: 6px;
+      transition-delay: .1s;
+      opacity: 0;
+    }
+
+    &:hover {
+      .play-button {
+        opacity: 1 !important;
+        --fw-translate-x: 0;
+      }
+
+      .options-button {
+        opacity: 1 !important;
+        --fw-translate-x: 0;
+      }
+    }
+  }
+}
diff --git a/src/components/index.md b/src/components/index.md
deleted file mode 100644
index 7b765ae5fe18b9654dc59e6b85f620d08f592ac8..0000000000000000000000000000000000000000
--- a/src/components/index.md
+++ /dev/null
@@ -1 +0,0 @@
-[FunkwhaleButton](FunkwhaleButton/)
diff --git a/src/components/index.ts b/src/components/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d2be292d82915b1d2fe5f309a63625390713b920
--- /dev/null
+++ b/src/components/index.ts
@@ -0,0 +1,41 @@
+// Buttons
+export { default as FwOptionsButton } from './button/options/Button.vue'
+export { default as FwPlayButton } from './button/play/Button.vue'
+export { default as FwButton } from './button/Button.vue'
+
+// Cards
+export { default as FwPlaylistCard } from './card/playlist/Card.vue'
+export { default as FwPodcastCard } from './card/podcast/Card.vue'
+export { default as FwArtistCard } from './card/artist/Card.vue'
+export { default as FwAlbumCard } from './card/album/Card.vue'
+export { default as FwRadioCard } from './card/radio/Card.vue'
+export { default as FwCard } from './card/Card.vue'
+
+// Activity
+export { default as FwActivity } from './activity/Activity.vue'
+
+// Popover
+export { default as FwPopover } from './popover/Popover.vue'
+
+// Loader
+export { default as FwLoader } from './loader/Loader.vue'
+
+// Form
+export { default as FwPagination } from './pagination/Pagination.vue'
+export { default as FwTextarea } from './textarea/Textarea.vue'
+export { default as FwToggle } from './toggle/Toggle.vue'
+export { default as FwInput } from './input/Input.vue'
+
+// Pills
+export { default as FwPill } from './pill/Pill.vue'
+
+// Toc
+export { default as FwToc } from './toc/Toc.vue'
+
+// Tabs
+export { default as FwTabs } from './tabs/Tabs.vue'
+export { default as FwTab } from './tabs/Tab.vue'
+
+// Utils
+export { default as FwSanitizedHtml } from './utils/SanitizedHtml.vue'
+export { default as FwMarkdown } from './utils/Markdown.vue'
diff --git a/src/components/input/Input.vue b/src/components/input/Input.vue
new file mode 100644
index 0000000000000000000000000000000000000000..aa0facdf474e3987659e73691bcf915bcb6d7082
--- /dev/null
+++ b/src/components/input/Input.vue
@@ -0,0 +1,39 @@
+<script setup lang="ts">
+import { ref } from 'vue'
+
+interface Props {
+  modelValue: string
+  icon?: string
+  placeholder?: string
+}
+
+defineProps<Props>()
+
+const input = ref()
+</script>
+
+<template>
+	<div
+    :class="{ 'has-icon': !!icon }"
+    class="funkwhale input"
+    @click="input.focus()"
+  >
+    <div v-if="icon" class="prefix">
+      <i :class="['bi', icon]" />
+    </div>
+    <input
+      v-bind="$attrs"
+      v-model="modelValue"
+      ref="input"
+      :placeholder="placeholder"
+      @click.stop
+    />
+    <div v-if="$slots['input-right']" class="input-right">
+      <slot name="input-right" />
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/input/style.scss b/src/components/input/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3c030c637cf4b7e2c98bcba2231af8b2dad8b6b4
--- /dev/null
+++ b/src/components/input/style.scss
@@ -0,0 +1,69 @@
+.funkwhale {
+  &.input {
+    --fw-bg-color: var(--fw-gray-100);
+    --fw-border-color: var(--fw-bg-color);
+
+    html.dark & {
+      --fw-bg-color: var(--fw-gray-800);
+    }
+
+    position: relative;
+    display: flex;
+    align-items: center;
+    background-color: var(--fw-bg-color);
+    border-radius: var(--fw-border-radius);
+    box-shadow: inset 0 0 0 4px var(--fw-border-color);
+    overflow: hidden;
+    cursor: text;
+
+    &:focus-within {
+      --fw-border-color: var(--fw-primary);
+      --fw-bg-color: var(--fw-blue-010);
+    }
+
+    &.has-icon {
+      input {
+        padding-left: 36px;
+      }
+    }
+
+    input {
+      display: block;
+      width: 100%;
+      padding: 8px 12px;
+      font-size: 14px;
+      font-family: $font-main;
+      background-color: transparent;
+
+      &::placeholder {
+        color: var(--fw-gray-600);
+      }
+
+      &:hover {
+        --fw-border-color: var(--fw-gray-300);
+      }
+    }
+
+    .prefix,
+    .input-right {
+      display: flex;
+      align-items: center;
+      color: var(--fw-gray-600);
+      font-size: 14px;
+      pointer-events: none;
+    }
+
+    .prefix {
+      position: absolute;
+      top: 0;
+      left: 4px;
+      bottom: 0;
+      width: 32px;
+      justify-content: center;
+    }
+
+    .input-right {
+      padding-right: 12px;
+    }
+  }
+}
diff --git a/src/components/loader/Loader.vue b/src/components/loader/Loader.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0e84883e69ec097e8272e017529b0cacd331928d
--- /dev/null
+++ b/src/components/loader/Loader.vue
@@ -0,0 +1,25 @@
+<script setup lang="ts">
+interface Props {
+  container?: boolean
+}
+
+withDefaults(defineProps<Props>(), {
+  container: true
+})
+</script>
+
+<template>
+  <div v-if="container" class="funkwhale loader-container">
+    <div class="funkwhale loader">
+      <div class="loader" />
+    </div>
+  </div>
+
+  <div v-else class="funkwhale loader">
+    <div class="loader" />
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/loader/style.scss b/src/components/loader/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..6570880841d155bf0a4534d0239c105309a3591f
--- /dev/null
+++ b/src/components/loader/style.scss
@@ -0,0 +1,60 @@
+.funkwhale {
+  &.loader-container {
+    position: relative;
+    height: 100%;
+  }
+
+  &.loader {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+
+    display: flex;
+    justify-content: center;
+    align-items: center;
+
+    font-size: 1.2em;
+
+    // Modified version of https://github.com/lukehaas/css-loaders
+    > .loader,
+    > .loader:after {
+      border-radius: 50%;
+      width: 1em;
+      height: 1em;
+    }
+
+    > .loader:after {
+      content: '';
+      position: absolute;
+      display: block;
+      border: .11em solid currentColor;
+      opacity: 0.2;
+      margin-left: -0.11em;
+      margin-top: -0.11em;
+    }
+
+    > .loader {
+      position: relative;
+
+      text-indent: -9999em;
+      font-size: 1em;
+
+      border-top: .11em solid transparent;
+      border-right: .11em solid transparent;
+      border-bottom: .11em solid transparent;
+      border-left: .11em solid currentColor;
+
+      will-change: transform;
+      transform: rotate(0deg);
+      animation: rotate .5s infinite linear;
+
+      @keyframes rotate {
+        0% { transform: rotate(0deg); }
+        100% { transform: rotate(360deg); }
+      }
+    }
+  }
+
+}
diff --git a/src/components/pagination/Pagination.vue b/src/components/pagination/Pagination.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7cd6b7353c1ffa425bf2d9467277dfb0308c548d
--- /dev/null
+++ b/src/components/pagination/Pagination.vue
@@ -0,0 +1,133 @@
+<script setup lang="ts">
+import { useVModel, useElementSize } from '@vueuse/core'
+import { useI18n } from 'vue-i18n'
+import { ref, computed } from 'vue'
+
+const MOBILE_WIDTH = 640
+
+const { t } = useI18n()
+
+interface Events {
+  (e: 'update:page', page: number): void
+}
+
+interface Props {
+  pages: number
+  page: number
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+const goTo = ref('')
+const page = useVModel(props, 'page', emit)
+
+const range = (start: number, end: number) => Array.from({ length: end - start + 1 }, (_, i) => i + start)
+
+const pages = computed(() => {
+  const start = range(2, 5)
+  const end = range(props.pages - 4, props.pages - 1)
+
+  const pages = [1]
+
+  if (page.value < 5) pages.push(...start)
+  if (page.value >= 5 && page.value <= props.pages - 4) {
+    pages.push(page.value - 1)
+    pages.push(page.value)
+    pages.push(page.value + 1)
+  }
+  if (page.value > props.pages - 4) pages.push(...end)
+  pages.push(props.pages)
+
+  return pages.filter((page, index, pages) => pages.indexOf(page) === index)
+})
+
+const pagination = ref()
+const { width } = useElementSize(pagination)
+const isSmall = computed(() => width.value <= MOBILE_WIDTH)
+
+const setPage = () => {
+  if (!isNaN(+goTo.value) && goTo.value !== '') {
+    page.value = Math.min(props.pages, Math.max(1, +goTo.value))
+  }
+
+  goTo.value = ''
+}
+</script>
+
+<template>
+	<div
+    ref="pagination"
+    :class="{ 'is-small': isSmall }"
+    class="funkwhale pagination"
+  >
+    <div class="pages">
+      <fw-button
+        @click="page -= 1"
+        :disabled="page <= 1"
+        secondary
+        outline
+      >
+        <i class="bi bi-chevron-left" />
+        <span v-if="!isSmall">{{ t('vui.pagination.previous') }}</span>
+      </fw-button>
+
+      <template v-if="!isSmall">
+        <template v-for="(i, index) in pages" :key="i">
+          <fw-button
+            v-if="i <= props.pages && i > 0 && props.pages > 3"
+            @click="page = i"
+            secondary
+            :outline="page !== i"
+          >
+            {{ i }}
+          </fw-button>
+          <div v-if="i + 1 < pages[index + 1]">…</div>
+        </template>
+      </template>
+      <template v-else>
+        <fw-button
+          @click="page = 1"
+          secondary
+          :outline="page !== 1"
+        >
+          1
+        </fw-button>
+        <div v-if="page === 1 || page === props.pages">…</div>
+        <fw-button v-else secondary>
+          {{ page }}
+        </fw-button>
+        <fw-button
+          @click="page = props.pages"
+          secondary
+          :outline="page !== props.pages"
+        >
+          {{ props.pages }}
+        </fw-button>
+      </template>
+
+      <fw-button
+        @click="page += 1"
+        :disabled="page >= props.pages"
+        secondary
+        outline
+      >
+        <span v-if="!isSmall">{{ t('vui.pagination.next') }}</span>
+        <i class="bi bi-chevron-right" />
+      </fw-button>
+    </div>
+
+    <div class="goto">
+      {{ t('vui.go-to') }}
+      <fw-input
+        :placeholder="page.toString()"
+        @keyup.enter="setPage"
+        v-model="goTo"
+      />
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/pagination/style.scss b/src/components/pagination/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..97fd748f6decdafc1e2362898b060f70a358642c
--- /dev/null
+++ b/src/components/pagination/style.scss
@@ -0,0 +1,68 @@
+.funkwhale {
+  &.pagination {
+    height: 34px;
+    display: flex;
+
+    &.is-small {
+      > .pages {
+        width: auto;
+      }
+      > .goto {
+        margin-right: auto;
+      }
+    }
+
+    > .pages {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      width: 100%;
+
+      > .funkwhale.button {
+        min-width: 34px;
+        height: 34px;
+        padding-top: 0;
+        padding-bottom: 0;
+
+        &:first-child,
+        &:last-child {
+          min-width: 94px;
+          text-align: center;
+        }
+
+        &:first-child span > span {
+          padding-left: 0.5ch;
+        }
+
+        &:last-child span > span {
+          padding-right: 0.5ch;
+        }
+      }
+
+      > div {
+        width: 34px;
+        text-align: center;
+        margin: 0 0.5ch;
+      }
+    }
+
+    > .goto {
+      border-left: 1px solid var(--fw-gray-200);
+      margin-left: 16px;
+      padding-left: 16px;
+      display: flex;
+      align-items: center;
+      white-space: pre;
+
+      > .funkwhale.input {
+        margin-left: 16px;
+        width: calc(3ch + 32px);
+
+        input {
+          text-align: center;
+          padding: 6px 8px;
+        }
+      }
+    }
+  }
+}
diff --git a/src/components/pill/Pill.vue b/src/components/pill/Pill.vue
new file mode 100644
index 0000000000000000000000000000000000000000..074cebb11fd3050643668196d0c83dee8462b0ec
--- /dev/null
+++ b/src/components/pill/Pill.vue
@@ -0,0 +1,39 @@
+<script setup lang="ts">
+import { type ColorProps, useColor } from '~/composables/useColor'
+import { type PastelProps, usePastel } from '~/composables/usePastels'
+
+interface Props extends ColorProps, PastelProps {
+  // TODO (wvffle): Remove after https://github.com/vuejs/core/pull/4512 is merged
+  primary?: boolean
+  secondary?: boolean
+  destructive?: boolean
+  red?: boolean
+  blue?: boolean
+  purple?: boolean
+  green?: boolean
+  yellow?: boolean
+}
+
+const props = defineProps<Props>()
+
+const type = useColor(props, 'is-secondary')
+const color = usePastel(props, '')
+</script>
+
+<template>
+	<button
+    class="funkwhale is-colored pill"
+    :class="[type, color]"
+  >
+    <div v-if="!!$slots.image" class="pill-image">
+      <slot name="image" />
+    </div>
+    <div class="pill-content">
+      <slot />
+    </div>
+  </button>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/pill/style.scss b/src/components/pill/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..53205695443e0e041bb2daa18aba510a61c6e01a
--- /dev/null
+++ b/src/components/pill/style.scss
@@ -0,0 +1,58 @@
+.funkwhale {
+  &.pill {
+    position: relative;
+    display: inline-flex;
+
+    background-color: var(--fw-pastel-2, var(--fw-bg-color));
+    color: var(--fw-text-color);
+
+    font-family: $font-main;
+
+    line-height: 1em;
+    font-size: 0.8em;
+
+    border-radius: 100vh;
+    margin: 0 0.5ch;
+
+    > .pill-content {
+      padding: 0.5em 0.75em;
+      white-space: pre;
+    }
+
+    > .pill-image {
+      position: relative;
+      aspect-ratio: 1;
+      border-radius: 50%;
+      overflow: hidden;
+      height: calc(2em - 4px);
+      margin: 2px;
+
+      + .pill-content {
+        padding-left: 0.25em;
+      }
+
+      > * {
+        height: 100%;
+        width: 100%;
+      }
+
+      > img {
+        object-fit: cover;
+      }
+    }
+
+    &:hover {
+      text-decoration: underline;
+    }
+
+    &[disabled] {
+      font-weight: normal;
+      cursor: default;
+    }
+
+    &.is-focused,
+    &:focus {
+      box-shadow: none !important;
+    }
+  }
+}
diff --git a/src/components/popover/Popover.vue b/src/components/popover/Popover.vue
new file mode 100644
index 0000000000000000000000000000000000000000..661c5edddc766ec2a5e2ad1bca680fa2c42d5649
--- /dev/null
+++ b/src/components/popover/Popover.vue
@@ -0,0 +1,275 @@
+<script setup lang="ts">
+import { useVModel, onClickOutside, whenever } from '@vueuse/core'
+import { computed, ref, reactive, watch, type Ref } from 'vue'
+import { FwPopover, FwButton, FwPill } from '~/components'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
+
+interface Clickable {
+  click: (event: Event) => Promise<void> | void
+}
+
+interface HasExtras {
+  extraItems?: ExtraItem[]
+}
+
+interface Button extends Clickable, HasExtras {
+  type: 'button'
+  text: string
+  icon: string
+  items?: Item[]
+}
+
+interface Select extends HasExtras {
+  type: 'select'
+  text: string
+  model: Ref<boolean>
+}
+
+interface Separator {
+  type: 'separator'
+}
+
+type Item = Button | Separator | Select
+type EnumeratedItem = Item & { _id: number }
+
+interface ExtraCircleButton extends Clickable {
+  type: 'circle-button'
+  icon: string
+}
+
+type PrivacyLevel = 'pod' | 'public' | 'private'
+interface ExtraLibraryPrivacyLevel {
+  type: 'library-privacy-level'
+  model: Ref<PrivacyLevel>
+}
+
+type ExtraItem = (ExtraCircleButton | ExtraLibraryPrivacyLevel) & { _id?: number }
+
+interface Events {
+  (e: 'update:open', open: boolean): void
+}
+
+interface Props {
+  items: Item[]
+  open?: boolean
+  clickOutside?: boolean
+}
+
+const emit = defineEmits<Events>()
+const props = withDefaults(defineProps<Props>(), {
+  open: false,
+  clickOutside: true
+})
+
+const items = computed<EnumeratedItem[]>(() => props.items.map(item => ({
+  ...item,
+  extraItems: ('extraItems' in item ? item.extraItems : undefined)?.map(extra => ({ ...extra, _id: Math.random() })),
+  _id: Math.random()
+})))
+
+// Children
+const hovered = ref<EnumeratedItem>()
+const childOpen = reactive<Record<string, boolean>>({})
+watch(hovered, (to, from) => {
+  if (from?._id) {
+    childOpen[from?._id] = false
+  }
+
+  if (to?._id) {
+    childOpen[to?._id] = true
+  }
+})
+
+const popover = ref()
+const open = useVModel(props, 'open', emit)
+
+onClickOutside(popover, () => {
+  if (props.clickOutside) {
+    open.value = false
+  }
+})
+
+watch(open, (to) => {
+  if (!to) {
+    hovered.value = undefined
+  }
+})
+
+// Clickable
+const click = (item: Item | ExtraItem, event: Event) => {
+  if ('click' in item && item.click) {
+    return item.click(event)
+  }
+
+  if (item.type === 'select') {
+    item.model.value = !item.model.value
+    return
+  }
+}
+
+// Extras
+const extraOpen = reactive<Record<string, boolean>>({})
+
+// Privacy level
+const privacyLevel = ref<ExtraLibraryPrivacyLevel & { _id?: number }>()
+watch(privacyLevel, (to, from) => {
+  // @ts-expect-error Vue has already handled this...
+  const previousLevel: PrivacyLevel | undefined = from?.model
+  // @ts-expect-error Vue has already handled this...
+  const level: PrivacyLevel | undefined = to?.model
+
+  if (previousLevel) {
+    privacyLevelToggle[previousLevel].value = false
+  }
+
+  if (level) {
+    privacyLevelToggle[level].value = true
+  }
+
+  if (from?._id) {
+    extraOpen[from._id] = false
+  }
+
+  if (to?._id) {
+    extraOpen[to._id] = true
+  }
+}, { deep: false })
+
+const privacyLevelToggle: Record<PrivacyLevel, Ref<boolean>> = {
+  private: ref(false),
+  public: ref(false),
+  pod: ref(false)
+}
+
+whenever(privacyLevelToggle.private, () => {
+  // @ts-expect-error Vue has already handled this...
+  privacyLevel.value.model = 'private'
+
+  privacyLevelToggle.private.value = true
+  privacyLevelToggle.public.value = false
+  privacyLevelToggle.pod.value = false
+})
+
+whenever(privacyLevelToggle.public, () => {
+  // @ts-expect-error Vue has already handled this...
+  privacyLevel.value.model = 'public'
+
+  privacyLevelToggle.private.value = false
+  privacyLevelToggle.public.value = true
+  privacyLevelToggle.pod.value = false
+})
+
+whenever(privacyLevelToggle.pod, () => {
+  // @ts-expect-error Vue has already handled this...
+  privacyLevel.value.model = 'pod'
+
+  privacyLevelToggle.private.value = false
+  privacyLevelToggle.public.value = false
+  privacyLevelToggle.pod.value = true
+})
+
+
+const privacyLevelItems = computed(() => [
+  {
+    type: 'select',
+    text: privacyLevelLabels.value.private,
+    model: privacyLevelToggle.private
+  },
+  {
+    type: 'select',
+    text: privacyLevelLabels.value.public,
+    model: privacyLevelToggle.public
+  },
+  {
+    type: 'select',
+    text: privacyLevelLabels.value.pod,
+    model: privacyLevelToggle.pod
+  },
+] as Select[])
+
+const privacyLevelLabels = computed<Record<PrivacyLevel, string>>(() => ({
+  private: t('vui.privacy-level.private'),
+  public: t('vui.privacy-level.public'),
+  pod: t('vui.privacy-level.pod')
+}))
+</script>
+
+<template>
+	<div
+    :class="{ 'is-open': open }"
+    ref="popover"
+    class="funkwhale popover"
+  >
+    <div
+      v-for="item in items"
+      :key="item._id"
+      @mouseover="hovered = item"
+      @click="click(item, $event)"
+      :class="['item', item.type]"
+    >
+      <template v-if="item.type === 'button'">
+        <i :class="['bi', item.icon]" />
+        {{ item.text }}
+        <i
+          v-if="item.items?.length"
+          class="bi bi-chevron-right"
+        />
+
+        <fw-popover
+          v-if="item.items?.length"
+          :items="item.items"
+          :click-outside="false"
+          v-model:open="childOpen[item._id]"
+        />
+      </template>
+
+      <template v-else-if="item.type === 'select'">
+        <i :class="['bi', item.model.value ? 'bi-check-square' : 'bi-square']" />
+        {{ item.text }}
+      </template>
+
+      <div
+        v-if="'extraItems' in item && item.extraItems?.length"
+        class="extra-items"
+      >
+        <div
+          v-for="extra of item.extraItems"
+          :key="extra._id"
+        >
+          <fw-button
+            v-if="extra.type === 'circle-button'"
+            @click.stop="click(extra, $event)"
+            :icon="extra.icon"
+            secondary
+            round
+          />
+
+          <fw-pill
+            v-if="extra.type === 'library-privacy-level'"
+            @click.stop="privacyLevel = extra"
+            @mouseleave="privacyLevel = undefined"
+            :class="{ 'is-open': privacyLevel?._id === extra._id }"
+            :secondary="extra.model.value === 'private'"
+            :red="extra.model.value === 'public'"
+            :blue="extra.model.value === 'pod'"
+          >
+            {{ privacyLevelLabels[extra.model.value] }}
+            <i class="bi bi-chevron-down" />
+
+            <fw-popover
+              :items="privacyLevelItems"
+              :click-outside="false"
+              v-model:open="extraOpen[extra._id ?? '']"
+            />
+          </fw-pill>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/popover/style.scss b/src/components/popover/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..eace4eeb5298c72ed2c2db27ef1bee8569ddeab7
--- /dev/null
+++ b/src/components/popover/style.scss
@@ -0,0 +1,99 @@
+.funkwhale {
+  &.popover {
+    --fw-border-color: var(--fw-gray-500);
+
+    html.dark & {
+      --fw-border-color: var(--fw-gray-800);
+    }
+
+    position: absolute;
+    padding: 16px;
+    border: 1px solid var(--fw-border-color);
+    border-radius: var(--fw-border-radius);
+    min-width: 246px;
+    z-index: 999;
+    background: var(--fw-bg-color);
+
+    transition: all .2s ease;
+    will-change: transform;
+    transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));
+    pointer-events: none;
+    opacity: 0;
+    font-size: 0.875rem;
+    --fw-scale: 0.97;
+
+    &.is-open {
+      pointer-events: auto;
+      opacity: 1;
+      --fw-scale: 1;
+    }
+
+    > .item {
+      &.select,
+      &.button {
+        cursor: pointer;
+        padding: 0 8px;
+        height: 32px;
+        display: flex;
+        align-items: center;
+        border-radius: var(--fw-border-radius);
+        white-space: pre;
+
+        &:hover {
+          background-color: var(--fw-gray-100);
+
+          html.dark & {
+            background-color: var(--fw-gray-800);
+          }
+        }
+
+        > .bi:first-child {
+          margin-right: 16px;
+        }
+
+        > .bi-chevron-right {
+          margin-left: auto;
+        }
+
+        > .funkwhale.popover {
+          left: 90%;
+          align-self: flex-start;
+        }
+
+        > .extra-items {
+          margin-left: auto;
+          display: flex;
+          gap: 4px;
+
+          margin-right: -12px;
+
+          > :first-child {
+            margin-left: 2rem;
+          }
+
+          .funkwhale.pill {
+            font-size: 13px;
+
+            > .pill-content > .bi {
+              display: inline-block;
+              transition: transform .2s ease;
+              will-change: transform;
+            }
+
+            &.is-open {
+              > .pill-content > .bi {
+                transform: rotate(-180deg);
+              }
+            }
+          }
+        }
+      }
+
+      &.separator {
+        padding-top: 12px;
+        margin-bottom: 12px;
+        border-bottom: 1px solid var(--fw-border-color);
+      }
+    }
+  }
+}
diff --git a/src/components/tabs/Tab.vue b/src/components/tabs/Tab.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9c15f384eb8675a7cce415a803e52d1a8102ab39
--- /dev/null
+++ b/src/components/tabs/Tab.vue
@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { TABS_INJECTION_KEY } from '~/injection-keys'
+import { whenever } from '@vueuse/core'
+import { inject, ref } from 'vue'
+
+interface Props {
+  title: string
+}
+
+const props = defineProps<Props>()
+
+const { currentTab, tabs } = inject(TABS_INJECTION_KEY, {
+  currentTab: ref(props.title),
+  tabs: []
+})
+
+whenever(() => !tabs.includes(props.title), () => {
+  tabs.push(props.title)
+}, { immediate: true })
+</script>
+
+<template>
+	<div v-if="currentTab === title" class="tab-content">
+    <slot />
+  </div>
+</template>
diff --git a/src/components/tabs/Tabs.vue b/src/components/tabs/Tabs.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8b11f549a847271bb946f5f7ca4b888bf7b84349
--- /dev/null
+++ b/src/components/tabs/Tabs.vue
@@ -0,0 +1,46 @@
+<script setup lang="ts">
+import { TABS_INJECTION_KEY } from '~/injection-keys'
+import { provide, reactive, ref, watch } from 'vue'
+
+const tabs = reactive([] as string[])
+const currentTab = ref()
+
+provide(TABS_INJECTION_KEY, {
+  tabs,
+  currentTab
+})
+
+watch(() => tabs.length, (to, from) => {
+  if (from === 0) {
+    currentTab.value = tabs[0]
+  }
+})
+</script>
+
+<template>
+	<div class="funkwhale tabs">
+    <div class="tabs-header">
+      <div
+        v-for="tab in tabs"
+        :key="tab"
+        :class="{ 'is-active': currentTab === tab }"
+        @click="currentTab = tab"
+        class="tabs-item"
+      >
+        {{ tab }}
+      </div>
+
+      <div class="tabs-right">
+        <slot name="tabs-right" />
+      </div>
+    </div>
+
+    <hr />
+
+    <slot />
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/tabs/style.scss b/src/components/tabs/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..8ad244b8469cf0b5a604ab92fca14c4cb946812f
--- /dev/null
+++ b/src/components/tabs/style.scss
@@ -0,0 +1,24 @@
+.funkwhale {
+  &.tabs {
+    > .tabs-header {
+      display: flex;
+      align-items: center;
+
+      > .tabs-item {
+        font-size: 14px;
+        padding: 8px;
+        min-width: 96px;
+        text-align: center;
+        cursor: pointer;
+
+        &.is-active {
+          font-weight: 900;
+        }
+      }
+
+      > .tabs-right {
+        margin-left: auto;
+      }
+    }
+  }
+}
diff --git a/src/components/textarea/Textarea.vue b/src/components/textarea/Textarea.vue
new file mode 100644
index 0000000000000000000000000000000000000000..00324598f17ffaf6cb172e0345af420b3fd14a48
--- /dev/null
+++ b/src/components/textarea/Textarea.vue
@@ -0,0 +1,242 @@
+<script setup lang="ts">
+import { useVModel, useTextareaAutosize, computedWithControl, useManualRefHistory, watchDebounced } from '@vueuse/core'
+import { FwButton } from '~/components'
+import { nextTick, computed, ref, type ComputedRef } from 'vue'
+
+interface Events {
+  (e: 'update:modelValue', value: string): void
+}
+
+interface Props {
+  modelValue: string
+  max?: number
+  placeholder?: string
+}
+
+const emit = defineEmits<Events>()
+const props = withDefaults(defineProps<Props>(), { max: Infinity, placeholder: '' })
+
+const value = useVModel(props, 'modelValue', emit)
+const { undo, redo, commit: commitHistory, last } = useManualRefHistory(value)
+const { textarea, triggerResize } = useTextareaAutosize({ input: value })
+
+const commit = () => {
+  triggerResize()
+  commitHistory()
+}
+
+const preview = ref(false)
+
+watchDebounced(value, (value) => {
+  if (value !== last.value.snapshot) {
+    commit()
+  }
+}, { debounce: 300 })
+
+const lineNumber = computedWithControl(
+  () => [textarea.value, value.value],
+  () => {
+    const { selectionStart } = textarea.value ?? {}
+    return value.value.slice(0, selectionStart).split('\n').length - 1
+  }
+)
+
+const updateLineNumber = () => setTimeout(lineNumber.trigger, 0)
+
+const currentLine = computed({
+  get: () => value.value.split('\n')[lineNumber.value],
+  set: (line) => {
+    const content = value.value.split('\n')
+    content[lineNumber.value] = line
+    value.value = content.join('\n')
+  }
+})
+
+// Textarea manipulation
+const splice = async (start: number, deleteCount: number, items?: string) => {
+  let { selectionStart, selectionEnd } = textarea.value
+
+  const lineBeginning = value.value.slice(0, selectionStart).lastIndexOf('\n') + 1
+  let lineStart = selectionStart - lineBeginning
+  let lineEnd = selectionEnd - lineBeginning
+
+  const text = currentLine.value.split('')
+  text.splice(start, deleteCount, items ?? '')
+  currentLine.value = text.join('')
+
+  if (start <= lineStart) {
+    lineStart += items?.length ?? 0
+    lineStart -= deleteCount
+  }
+
+  if (start <= lineEnd) {
+    lineEnd += items?.length ?? 0
+    lineEnd -= deleteCount
+  }
+
+  selectionStart = lineBeginning + Math.max(0, lineStart)
+  selectionEnd = lineBeginning + Math.max(0, lineEnd)
+
+  textarea.value.focus()
+  await nextTick()
+  textarea.value.setSelectionRange(selectionStart, selectionEnd)
+}
+
+const newLineOperations = new Map<RegExp, (event: KeyboardEvent, line: string, groups: string[]) => void>()
+const newline = async (event: KeyboardEvent) => {
+  const line = currentLine.value
+  for (const regexp of newLineOperations.keys()) {
+    const matches = line.match(regexp) ?? []
+    if (matches.length > 0) {
+      newLineOperations.get(regexp)?.(event, line, matches.slice(1))
+    }
+  }
+}
+
+// Conditions
+const isHeading1 = computed(() => currentLine.value.startsWith('# '))
+const isHeading2 = computed(() => currentLine.value.startsWith('## '))
+const isQuote = computed(() => currentLine.value.startsWith('> '))
+const isUnorderedList = computed(() => currentLine.value.startsWith('- ') || currentLine.value.startsWith('* '))
+const isOrderedList = computed(() => /^\d+\. /.test(currentLine.value))
+
+const isParagraph = computed(() => !isHeading1.value && !isHeading2.value && !isQuote.value && !isUnorderedList.value && !isOrderedList.value)
+
+// Prefix operations
+const paragraph = async (shouldCommit = true) => {
+  if (isHeading1.value || isQuote.value || isUnorderedList.value) {
+    await splice(0, 2)
+    if (shouldCommit) commit()
+    return
+  }
+
+  if (isHeading2.value || isOrderedList.value) {
+    await splice(0, 3)
+    if (shouldCommit) commit()
+    return
+  }
+}
+
+const prefixOperation = (prefix: string, condition?: ComputedRef<boolean>) => async () => {
+  if (condition?.value) {
+    return paragraph()
+  }
+
+  await paragraph(false)
+  await splice(0, 0, prefix)
+  return commit()
+}
+
+const heading1 = prefixOperation('# ', isHeading1)
+const heading2 = prefixOperation('## ', isHeading2)
+const quote = prefixOperation('> ', isQuote)
+const orderedList = prefixOperation('1. ', isOrderedList)
+const unorderedList = prefixOperation('- ', isUnorderedList)
+
+// Newline operations
+const newlineOperation = (regexp: RegExp, newLineHandler: (line: string, groups: string[]) => Promise<void> | void) => {
+  newLineOperations.set(regexp, async (event, line, groups) => {
+    event.preventDefault()
+
+    if (new RegExp(regexp.toString().slice(1, -1) + '$').test(line)) {
+      return paragraph()
+    }
+
+    await newLineHandler(line, groups)
+    lineNumber.trigger()
+    return commit()
+  })
+}
+
+newlineOperation(/^(\d+)\. /, (line, [lastNumber]) => splice(line.length, 0, `\n${+lastNumber + 1}. `))
+newlineOperation(/^- /, (line) => splice(line.length, 0, `\n- `))
+newlineOperation(/^> /, (line) => splice(line.length, 0, `\n> `))
+newlineOperation(/^\* /, (line) => splice(line.length, 0, `\n* `))
+
+// Inline operations
+const inlineOperation = (chars: string) => async () => {
+  const { selectionStart, selectionEnd } = textarea.value
+  const lineBeginning = value.value.slice(0, selectionStart).lastIndexOf('\n') + 1
+  await splice(selectionStart - lineBeginning, 0, chars)
+  await splice(selectionEnd - lineBeginning + chars.length, 0, chars)
+
+  const start = selectionStart === selectionEnd
+    ? selectionStart + chars.length
+    : selectionEnd + chars.length * 2
+  textarea.value.setSelectionRange(start, start)
+  return commit()
+}
+
+const bold = inlineOperation('**')
+const italics = inlineOperation('_')
+const strikethrough = inlineOperation('~~')
+
+const link = async () => {
+  const { selectionStart, selectionEnd } = textarea.value
+  const lineBeginning = value.value.slice(0, selectionStart).lastIndexOf('\n') + 1
+  await splice(selectionStart - lineBeginning, 0, '[')
+  await splice(selectionEnd - lineBeginning + 1, 0, '](url)')
+  textarea.value.setSelectionRange(selectionEnd + 3, selectionEnd + 6)
+  return commit()
+}
+
+// Fix focus
+const focus = () => textarea.value.focus()
+</script>
+
+<template>
+	<div
+    :class="{ 'has-preview': preview }"
+    class="funkwhale textarea"
+    @mousedown.prevent="focus"
+    @mouseup.prevent="focus"
+  >
+    <fw-markdown :md="value" class="preview" />
+    <textarea
+      ref="textarea"
+      @click="updateLineNumber"
+      @mousedown.stop
+      @mouseup.stop
+      @keydown.left="updateLineNumber"
+      @keydown.right="updateLineNumber"
+      @keydown.up="updateLineNumber"
+      @keydown.down="updateLineNumber"
+      @keydown.enter="newline"
+      @keydown.ctrl.shift.z.exact.prevent="redo"
+      @keydown.ctrl.z.exact.prevent="undo"
+      @keydown.ctrl.b.exact.prevent="bold"
+      @keydown.ctrl.i.exact.prevent="italics"
+      @keydown.ctrl.shift.x.exact.prevent="strikethrough"
+      @keydown.ctrl.k.exact.prevent="link"
+      :maxlength="max"
+      :placeholder="placeholder"
+      v-model="value"
+      id="textarea_id"
+    />
+    <div class="textarea-buttons">
+      <fw-button @click="preview = !preview" icon="bi-eye" secondary :is-active="preview" />
+
+      <div class="separator" />
+
+      <fw-button @click="heading1" icon="bi-type-h1" secondary :is-active="isHeading1" :disabled="preview" />
+      <fw-button @click="heading2" icon="bi-type-h2" secondary :is-active="isHeading2" :disabled="preview" />
+      <fw-button @click="paragraph" icon="bi-paragraph" secondary :is-active="isParagraph" :disabled="preview" />
+      <fw-button @click="quote" icon="bi-quote" secondary :is-active="isQuote" :disabled="preview" />
+      <fw-button @click="orderedList" icon="bi-list-ol" secondary :is-active="isOrderedList" :disabled="preview" />
+      <fw-button @click="unorderedList" icon="bi-list-ul" secondary :is-active="isUnorderedList" :disabled="preview" />
+
+      <div class="separator" />
+
+      <fw-button @click="bold" icon="bi-type-bold" secondary :disabled="preview" />
+      <fw-button @click="italics" icon="bi-type-italic" secondary :disabled="preview" />
+      <fw-button @click="strikethrough" icon="bi-type-strikethrough" secondary :disabled="preview" />
+      <fw-button @click="link" icon="bi-link-45deg" secondary :disabled="preview" />
+
+      <span v-if="max !== Infinity" class="letter-count">{{ max - value.length }}</span>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/textarea/style.scss b/src/components/textarea/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..417e7f3b768710e2adfe594c708b5e87191842a8
--- /dev/null
+++ b/src/components/textarea/style.scss
@@ -0,0 +1,111 @@
+.funkwhale {
+  &.textarea {
+    --fw-border-color: var(--fw-bg-color);
+    --fw-buttons-border-color: var(--fw-gray-400);
+
+    position: relative;
+    padding: 8px;
+    background-color: var(--fw-gray-100);
+    border-radius: var(--fw-border-radius);
+    box-shadow: inset 0 0 0 4px var(--fw-border-color);
+
+    html.dark & {
+      background-color: var(--fw-gray-900);
+      --fw-border-color: var(--fw-gray-900);
+      --fw-buttons-border-color: var(--fw-gray-800);
+    }
+
+    &:hover {
+      --fw-border-color: var(--fw-gray-300);
+
+      html.dark & {
+        --fw-border-color: var(--fw-gray-700);
+      }
+    }
+
+    &.has-preview,
+    &:focus-within {
+      --fw-border-color: var(--fw-primary) !important;
+      background-color: var(--fw-bg-color) !important;
+
+      > .textarea-buttons {
+        opacity: 1;
+        transform: translateY(0rem);
+        pointer-events: auto;
+      }
+    }
+
+    &.has-preview > .preview {
+      opacity: 1;
+      transform: translateY(0rem);
+      pointer-events: auto;
+    }
+
+    > .preview {
+      position: absolute;
+      top: 8px;
+      left: 8px;
+      right: 8px;
+      bottom: 42px;
+      overflow-y: auto;
+      background-color: inherit;
+      opacity: 0;
+      transform: translateY(.5rem);
+      pointer-events: none;
+      transition: all .2s ease;
+    }
+
+    > textarea {
+      display: block;
+      width: 100%;
+      min-height: 240px;
+      padding: 8px 12px 40px;
+      font-family: monospace;
+      background: transparent;
+
+      &:placeholder-shown {
+        font-family: $font-main;
+      }
+    }
+
+    > .textarea-buttons {
+      display: flex;
+      align-items: center;
+
+      position: absolute;
+      bottom: 8px;
+      left: 8px;
+      right: 8px;
+
+      opacity: 0;
+      transform: translateY(.5rem);
+      pointer-events: none;
+      transition: all .2s ease;
+
+      padding-top: 4px;
+      border-top: 1px solid var(--fw-buttons-border-color);
+
+      > .funkwhale.button {
+        font-size: 1.2rem;
+        padding: 0.2em;
+
+        &:not(:hover):not(:active):not(.is-active) {
+          --fw-bg-color: transparent;
+        }
+      }
+
+      > .separator {
+        width: 1px;
+        height: 28px;
+        background-color: var(--fw-buttons-border-color);
+        margin: 0 8px;
+      }
+
+      > .letter-count {
+        margin-left: auto;
+        padding-right: 16px;
+        color: var(--fw-buttons-border-color);
+      }
+    }
+  }
+}
diff --git a/src/components/toc/Toc.vue b/src/components/toc/Toc.vue
new file mode 100644
index 0000000000000000000000000000000000000000..178f8bf745819d936a58bdeed34d1ffef5d44eb3
--- /dev/null
+++ b/src/components/toc/Toc.vue
@@ -0,0 +1,61 @@
+<script setup lang="ts">
+import { computed, ref, watchEffect } from 'vue'
+import { slugify } from 'transliteration'
+import { useScroll } from '@vueuse/core';
+
+interface Props {
+  heading?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
+}
+
+const props = withDefaults(defineProps<Props>(), { heading: 'h1' })
+
+const toc = ref()
+
+const headings = computed(() => toc.value?.querySelectorAll(props.heading) ?? [])
+watchEffect(() => {
+  for (const heading of headings.value) {
+    heading.id = slugify(heading.textContent)
+  }
+})
+
+const activeLink = ref()
+const { y } = useScroll(window)
+watchEffect(() => {
+  let lastActive = headings.value[0]
+  for (const heading of headings.value) {
+    if (y.value > heading.offsetTop) {
+      lastActive = heading
+    }
+  }
+
+  activeLink.value = lastActive?.id
+})
+</script>
+
+<template>
+	<div
+    ref="toc"
+    class="funkwhale toc"
+  >
+    <div class="toc-content">
+      <slot />
+    </div>
+
+    <div class="toc-toc">
+      <div class="toc-links">
+        <a
+          v-for="h of headings"
+          :key="h.id"
+          :class="{ 'is-active': activeLink === h.id }"
+          @click.prevent="h.scrollIntoView({ behavior: 'smooth' })"
+        >
+          {{ h.textContent }}
+      </a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/toc/style.scss b/src/components/toc/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..fd0d258e02e6df3273987c57a7e6bd20ba9c0289
--- /dev/null
+++ b/src/components/toc/style.scss
@@ -0,0 +1,42 @@
+.funkwhale {
+  &.toc {
+    display: grid;
+    grid-template-columns: 1fr 280px;
+    gap: 1rem;
+
+    > .toc-toc {
+      > .toc-links {
+        position: sticky;
+        top: 0;
+
+        .VPContent & {
+          top: 72px;
+        }
+
+        > a {
+          --fw-link-color: var(--fw-text-color);
+
+          &:hover {
+            --fw-link-color: var(--fw-text-color) !important;
+          }
+
+          padding: 4px 8px;
+          display: block;
+
+          box-shadow: inset 1px 0 0 var(--fw-gray-200);
+
+          &.is-active {
+            --fw-link-color: var(--fw-secondary);
+
+            &:hover {
+              --fw-link-color: var(--fw-secondary) !important;
+            }
+
+            box-shadow: inset 3px 0 0 var(--fw-secondary);
+            font-weight: bold
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/src/components/toggle/Toggle.vue b/src/components/toggle/Toggle.vue
new file mode 100644
index 0000000000000000000000000000000000000000..307f9b35e51e17a7cc1515d146cf8bb62b87ca98
--- /dev/null
+++ b/src/components/toggle/Toggle.vue
@@ -0,0 +1,30 @@
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core';
+
+
+interface Events {
+  (e: 'update:modelValue', enabled: boolean): void
+}
+interface Props {
+  modelValue: boolean
+  big?: boolean
+}
+
+const emit = defineEmits<Events>()
+const props = defineProps<Props>()
+
+const enabled = useVModel(props, 'modelValue', emit)
+</script>
+
+<template>
+	<div
+    class="funkwhale toggle"
+    :class="{ 'is-active': enabled, 'is-big': big }"
+  >
+    <input type="checkbox" v-model="enabled" />
+  </div>
+</template>
+
+<style lang="scss">
+@import './style.scss'
+</style>
diff --git a/src/components/toggle/style.scss b/src/components/toggle/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e7ced584014823eb0fdc4377c34b754c78776d2b
--- /dev/null
+++ b/src/components/toggle/style.scss
@@ -0,0 +1,60 @@
+.funkwhale {
+  &.toggle {
+    --fw-bg-color: var(--fw-gray-500);
+
+    position: relative;
+    border-radius: 100vw;
+    background-color: var(--fw-bg-color);
+    overflow: hidden;
+    height: 20px;
+    aspect-ratio: 2;
+
+    > input {
+      opacity: 0;
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      cursor: pointer;
+    }
+
+    &::before {
+      display: block;
+      content: '';
+      position: absolute;
+      top: 2px;
+      left: 2px;
+      height: calc(100% - 4px);
+      aspect-ratio: 1;
+      border-radius: 50%;
+      background: var(--fw-blue-010);
+      transition: transform .2s ease;
+    }
+
+    &:hover {
+      --fw-bg-color: var(--fw-gray-300);
+    }
+
+    &.is-big {
+      height: 28px;
+    }
+
+    &.is-active {
+      --fw-bg-color: var(--fw-primary);
+
+      &::before {
+        transform: translateX(calc(100% + 4px));
+      }
+
+      &:hover {
+        --fw-bg-color: var(--fw-blue-100);
+      }
+    }
+
+    &[disabled] {
+      opacity: 0;
+      pointer-events: none;
+    }
+  }
+}
diff --git a/src/components/utils/Markdown.vue b/src/components/utils/Markdown.vue
new file mode 100644
index 0000000000000000000000000000000000000000..09561cad98f82dcc4a71c67759ee8d2bc6b8071b
--- /dev/null
+++ b/src/components/utils/Markdown.vue
@@ -0,0 +1,67 @@
+<script setup lang="ts">
+import { FwSanitizedHtml } from '~/components'
+import { computed } from 'vue'
+
+import showdown from 'showdown'
+
+interface Props {
+  md: string
+}
+
+const props = defineProps<Props>()
+
+showdown.extension('openExternalInNewTab', {
+  type: 'output',
+  regex: /<a.+?href.+">/g,
+  replace (text: string) {
+    const matches = text.match(/href="(.+)">/) ?? []
+    const url = matches[1] ?? './'
+
+    if ((!url.startsWith('http://') && !url.startsWith('https://')) || url.startsWith('mailto:')) {
+      return text
+    }
+
+    try {
+      const { hostname } = new URL(url)
+      return hostname !== location.hostname
+        ? text.replace(matches[0], `href="${url}" target="_blank" rel="noopener noreferrer">`)
+        : text
+    } catch {
+      return text.replace(matches[0], `href="${url}" target="_blank" rel="noopener noreferrer">`)
+    }
+  }
+})
+
+showdown.extension('linkifyTags', {
+  type: 'language',
+  regex: /#[^\W]+/g,
+  replace (text: string) {
+    return `<a href="/library/tags/${text.slice(1)}">${text}</a>`
+  }
+})
+
+const markdown = new showdown.Converter({
+  extensions: ['openExternalInNewTab', 'linkifyTags'],
+  ghMentions: true,
+  ghMentionsLink: '/@{u}',
+  simplifiedAutoLink: true,
+  openLinksInNewWindow: false,
+  simpleLineBreaks: true,
+  strikethrough: true,
+  tables: true,
+  tasklists: true,
+  underline: true,
+  noHeaderId: true,
+  headerLevelStart: 3,
+  literalMidWordUnderscores: true,
+  excludeTrailingPunctuationFromURLs: true,
+  encodeEmails: true,
+  emoji: true
+})
+
+const html = computed(() => markdown.makeHtml(props.md))
+</script>
+
+<template>
+  <fw-sanitized-html :html="html" />
+</template>
diff --git a/src/components/utils/SanitizedHtml.vue b/src/components/utils/SanitizedHtml.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3c9dac151651d74eb7f995089106a09551615ac1
--- /dev/null
+++ b/src/components/utils/SanitizedHtml.vue
@@ -0,0 +1,27 @@
+<script setup lang="ts">
+import DOMPurify from 'dompurify'
+import { computed, h } from 'vue'
+
+interface Props {
+  tag?: string
+  html: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  tag: 'div'
+})
+
+DOMPurify.addHook('afterSanitizeAttributes', (node) => {
+  // set all elements owning target to target=_blank
+  if ('target' in node) {
+    node.setAttribute('target', '_blank')
+  }
+})
+
+const html = computed(() => DOMPurify.sanitize(props.html))
+const root = () => h(props.tag, { innerHTML: html.value })
+</script>
+
+<template>
+  <root />
+</template>
diff --git a/src/composables/useColor.ts b/src/composables/useColor.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6fde86fea99f663eed76ebf9f770b40e71f6c23c
--- /dev/null
+++ b/src/composables/useColor.ts
@@ -0,0 +1,17 @@
+import { computed } from "vue"
+
+export interface ColorProps {
+  primary?: boolean
+  secondary?: boolean
+  destructive?: boolean
+}
+
+type Color = 'is-primary' | 'is-secondary' | 'is-destructive'
+export const useColor = (props: ColorProps, defaultColor: Color = 'is-primary') => computed<Color>(() => props.primary
+  ? 'is-primary'
+  : props.secondary
+    ? 'is-secondary'
+    : props.destructive
+      ? 'is-destructive'
+      : defaultColor
+)
diff --git a/src/composables/usePastels.ts b/src/composables/usePastels.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf87cfc98e6f06c3b7f474daae464f511b7b1427
--- /dev/null
+++ b/src/composables/usePastels.ts
@@ -0,0 +1,23 @@
+import { computed } from "vue"
+
+export interface PastelProps {
+  red?: boolean
+  blue?: boolean
+  purple?: boolean
+  green?: boolean
+  yellow?: boolean
+}
+
+type Color = 'is-red' | 'is-blue' | 'is-purple' | 'is-green' | 'is-yellow'
+export const usePastel = (props: PastelProps, defaultColor: Color | '' = 'is-blue') => computed<Color | ''>(() => props.blue
+  ? 'is-blue'
+  : props.red
+    ? 'is-red'
+    : props.purple
+      ? 'is-purple'
+      : props.green
+        ? 'is-green'
+        : props.yellow
+          ? 'is-yellow'
+          : defaultColor
+)
diff --git a/src/injection-keys.ts b/src/injection-keys.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6d16d95e1089fc4de2522ad2c2bc82eca32500b
--- /dev/null
+++ b/src/injection-keys.ts
@@ -0,0 +1,6 @@
+import type { InjectionKey, Ref } from "vue"
+
+export const TABS_INJECTION_KEY = Symbol('tabs') as InjectionKey<{
+  tabs: string[]
+  currentTab: Ref<string>
+}>
diff --git a/src/locales/en.yaml b/src/locales/en.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1146c759f2f58a02e3a49f3ef629c1d2f82a8b27
--- /dev/null
+++ b/src/locales/en.yaml
@@ -0,0 +1,14 @@
+vui:
+  radio: Radio
+  albums: '{n} album | {n} albums'
+  tracks: '{n} track | {n} tracks'
+  episodes: '{n} episode | {n} episodes'
+  by-user: "by {'@'}{username}"
+  go-to: Go to
+  pagination:
+    previous: Previous
+    next: Next
+  privacy-level:
+    private: private
+    public: public
+    pod: pod
diff --git a/src/main.ts b/src/main.ts
index 01433bca2ac76590c48fabfee8d69d7b223f48bb..6b354846903b1a6ef4a2fe76bf399b499d5cbfae 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,4 +1,15 @@
-import { createApp } from 'vue'
-import App from './App.vue'
+import type { App } from 'vue'
 
-createApp(App).mount('#app')
+import '~/styles/funkwhale.scss'
+
+import * as components from '~/components'
+export * from '~/components'
+
+export default {
+  install (app: App) {
+    for (const prop in components) {
+      const component = components[prop as keyof typeof components]
+      app.component(prop, component)
+    }
+  }
+}
diff --git a/src/env.d.ts b/src/shims-vue.d.ts
similarity index 85%
rename from src/env.d.ts
rename to src/shims-vue.d.ts
index d27eb5a311f66a73c7f9db4b685c76222f293e4a..c458ab5a39cbeb465c60b4f6c9206d5927eeeb36 100644
--- a/src/env.d.ts
+++ b/src/shims-vue.d.ts
@@ -1,5 +1,3 @@
-/// <reference types="vite/client" />
-
 declare module '*.vue' {
   import { DefineComponent } from 'vue'
   // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
diff --git a/src/styles/base/generic.scss b/src/styles/base/generic.scss
new file mode 100644
index 0000000000000000000000000000000000000000..f0f051c3a65a49a13e74cc8e683b54561a10788b
--- /dev/null
+++ b/src/styles/base/generic.scss
@@ -0,0 +1,28 @@
+// Links
+.funkwhale.link,
+a {
+  --fw-link-color: var(--fw-blue-500);
+
+  &:not(.VPLink):not(.vp-doc .header-anchor):not(.VPDocAsideOutline .outline-link):not(.VPNavBarTitle .title):not(.VPDocFooter .pager-link) {
+    color: var(--fw-link-color);
+    cursor: pointer;
+
+    &:hover,
+    &.is-hovered {
+      --fw-link-color: var(--fw-blue-600);
+      text-decoration: underline;
+    }
+
+    &:visited {
+      --fw-link-color: var(--fw-blue-700);
+    }
+  }
+}
+
+.vp-doc hr,
+hr {
+  padding: 8px 0 0 0;
+  margin: 0 0 8px 0;
+  border: none;
+  border-bottom: 2px solid var(--fw-gray-200);
+}
diff --git a/src/styles/base/index.scss b/src/styles/base/index.scss
new file mode 100644
index 0000000000000000000000000000000000000000..e1448946dbd3eaa5a0b547fc4b177692bdc801d4
--- /dev/null
+++ b/src/styles/base/index.scss
@@ -0,0 +1 @@
+@import "generic"
diff --git a/src/styles/colors.scss b/src/styles/colors.scss
new file mode 100644
index 0000000000000000000000000000000000000000..1ff721ef95d8b86fa2548cc8cd9bd0b4e2ef7863
--- /dev/null
+++ b/src/styles/colors.scss
@@ -0,0 +1,159 @@
+:root {
+  // Blue
+  --fw-blue-010: #fdfdff;
+  --fw-blue-100: #65a2bb;
+  --fw-blue-500: #067cae;
+  --fw-blue-600: #05719e;
+  --fw-blue-700: #00638c;
+  --fw-blue-900: #1f1f2d;
+
+  // Red
+  --fw-red-010: #fffcfc;
+  --fw-red-100: #c4766e;
+  --fw-red-500: #cd2e1f;
+  --fw-red-600: #bb2a1c;
+  --fw-red-700: #9c1a0e;
+  --fw-red-900: #520600;
+
+  // gray
+  --fw-gray-100: #f2efef;
+  --fw-gray-200: #eee9e9;
+  --fw-gray-300: #ddd9d9;
+  --fw-gray-400: #cfcaca;
+  --fw-gray-500: #b3afaf;
+  --fw-gray-600: #908888;
+  --fw-gray-700: #605656;
+  --fw-gray-800: #5c4b4b;
+  --fw-gray-900: #292525;
+
+  // Beige
+  --fw-beige-100: #fafaf6;
+
+  // Pastels
+  --fw-pastel-blue-1: #afdde9;
+  --fw-pastel-blue-2: #87cdde;
+  --fw-pastel-blue-3: #5fbcd3;
+  --fw-pastel-blue-4: #37abc8;
+
+  --fw-pastel-red-1: #f4d7d7;
+  --fw-pastel-red-2: #e9afaf;
+  --fw-pastel-red-3: #de8787;
+  --fw-pastel-red-4: #d35f5f;
+
+  --fw-pastel-purple-1: #b37db1;
+  --fw-pastel-purple-2: #a0619b;
+  --fw-pastel-purple-3: #806b7e;
+  --fw-pastel-purple-4: #67535f;
+
+  --fw-pastel-green-1: #afcd7d;
+  --fw-pastel-green-2: #84a846;
+  --fw-pastel-green-3: #5aa546;
+  --fw-pastel-green-4: #5d944e;
+
+  --fw-pastel-yellow-1: #fdeb63;
+  --fw-pastel-yellow-2: #ffe32f;
+  --fw-pastel-yellow-3: #fed100;
+  --fw-pastel-yellow-4: #efa300;
+
+  // Background
+  --fw-bg-color: var(--fw-blue-010);
+  --fw-text-color: var(--fw-blue-900);
+
+  // Override Bulma
+  --fw-primary: var(--fw-blue-500);
+  --fw-secondary: #ff6600;
+  --fw-destructive: var(--fw-red-500);
+}
+
+html.dark {
+  --fw-bg-color: var(--fw-blue-900);
+  --fw-text-color: var(--fw-gray-100);
+}
+
+.funkwhale {
+  &.is-primary {
+    --fw-bg-color: var(--fw-blue-500);
+    --fw-text-color: var(--fw-gray-100);
+
+    &.is-colored {
+      &[disabled] {
+        --fw-bg-color: var(--fw-blue-100) !important;
+        --fw-text-color: var(--fw-blue-900) !important;
+      }
+
+      &.is-hovered,
+      &:hover {
+        --fw-bg-color: var(--fw-blue-600);
+      }
+
+      &.is-active,
+      &:active {
+        --fw-bg-color: var(--fw-blue-700);
+      }
+    }
+  }
+
+  &.is-secondary {
+    --fw-bg-color: var(--fw-gray-200);
+    --fw-text-color: var(--fw-gray-800);
+
+    html.dark & {
+      --fw-text-color: var(--fw-gray-300);
+    }
+
+    &.is-colored {
+      &[disabled] {
+        --fw-bg-color: var(--fw-gray-100) !important;
+      }
+
+      &.is-hovered,
+      &:hover {
+        --fw-bg-color: var(--fw-gray-300);
+        html.dark & {
+          --fw-text-color: var(--fw-gray-800);
+        }
+      }
+
+      &.is-active,
+      &:active {
+        --fw-bg-color: var(--fw-gray-400);
+      }
+    }
+  }
+
+  &.is-destructive {
+    --fw-bg-color: var(--fw-destructive);
+    --fw-text-color: var(--fw-gray-100);
+
+    &.is-colored {
+      &[disabled] {
+        --fw-bg-color: var(--fw-red-100) !important;
+        --fw-text-color: var(--fw-blue-900) !important;
+      }
+
+      &.is-hovered,
+      &:hover {
+        --fw-bg-color: var(--fw-red-600);
+      }
+
+      &.is-active,
+      &:active {
+        --fw-bg-color: var(--fw-red-700);
+      }
+    }
+  }
+}
+
+.funkwhale {
+  --fw-bg-color: var(--fw-blue-010);
+  --fw-text-color: var(--fw-blue-900);
+
+  @each $pastel in ("blue", "red", "green", "purple", "yellow") {
+    &.is-#{$pastel} {
+      --fw-pastel-1: var(--fw-pastel-#{$pastel}-1);
+      --fw-pastel-2: var(--fw-pastel-#{$pastel}-2);
+      --fw-pastel-3: var(--fw-pastel-#{$pastel}-3);
+      --fw-pastel-4: var(--fw-pastel-#{$pastel}-4);
+    }
+  }
+}
diff --git a/src/styles/font/Lato-Black.ttf b/src/styles/font/Lato-Black.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..4340502d93c09da91560c862cabf3f6055704149
Binary files /dev/null and b/src/styles/font/Lato-Black.ttf differ
diff --git a/src/styles/font/Lato-BlackItalic.ttf b/src/styles/font/Lato-BlackItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..4df1555e6f982bdefe51bb08f91494d7801cf0f0
Binary files /dev/null and b/src/styles/font/Lato-BlackItalic.ttf differ
diff --git a/src/styles/font/Lato-Bold.ttf b/src/styles/font/Lato-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..016068b486e1bd1d73743185c2752b7b1b843701
Binary files /dev/null and b/src/styles/font/Lato-Bold.ttf differ
diff --git a/src/styles/font/Lato-BoldItalic.ttf b/src/styles/font/Lato-BoldItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..a05d50320511f7ecb6076343707068c68d189981
Binary files /dev/null and b/src/styles/font/Lato-BoldItalic.ttf differ
diff --git a/src/styles/font/Lato-Italic.ttf b/src/styles/font/Lato-Italic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..0d0f69ea0399fa45ad6e5791dc402fedf3a1a151
Binary files /dev/null and b/src/styles/font/Lato-Italic.ttf differ
diff --git a/src/styles/font/Lato-Light.ttf b/src/styles/font/Lato-Light.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..dfa72ce808fbb783042da88ce1bae5d9adb54fb6
Binary files /dev/null and b/src/styles/font/Lato-Light.ttf differ
diff --git a/src/styles/font/Lato-LightItalic.ttf b/src/styles/font/Lato-LightItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..12f2b6c7d1475fe5a3506b8517f0cba76814f82c
Binary files /dev/null and b/src/styles/font/Lato-LightItalic.ttf differ
diff --git a/src/styles/font/Lato-Regular.ttf b/src/styles/font/Lato-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..bb2e8875a993f9c7d9e45d0eeffa839550cc6287
Binary files /dev/null and b/src/styles/font/Lato-Regular.ttf differ
diff --git a/src/styles/font/Lato-Thin.ttf b/src/styles/font/Lato-Thin.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..ba58da1a13d2259ee2d585d4d3cbd7657c03dabd
Binary files /dev/null and b/src/styles/font/Lato-Thin.ttf differ
diff --git a/src/styles/font/Lato-ThinItalic.ttf b/src/styles/font/Lato-ThinItalic.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..4d82766d190747a5d5438d288f936f5114764759
Binary files /dev/null and b/src/styles/font/Lato-ThinItalic.ttf differ
diff --git a/src/styles/font/OFL.txt b/src/styles/font/OFL.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dfca0da4bcdadf6fa32bed29ecc49ac0e6915b64
--- /dev/null
+++ b/src/styles/font/OFL.txt
@@ -0,0 +1,93 @@
+Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded, 
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/styles/funkwhale.scss b/src/styles/funkwhale.scss
new file mode 100644
index 0000000000000000000000000000000000000000..4cff967eaa7185ff2fd3214c5cd57e0ab50d5c4c
--- /dev/null
+++ b/src/styles/funkwhale.scss
@@ -0,0 +1,18 @@
+@charset "utf-8";
+@import "base/index";
+
+body {
+  font-family: $font-main;
+  font-size: 16px;
+  // background-color: var(--fw-bg-color);
+}
+
+.has-background-light {
+  background-color: var(--fw-gray-100);
+}
+
+* {
+  --fw-translate-x: 0;
+  --fw-translate-y: 0;
+  --fw-scale: 1;
+}
diff --git a/src/styles/inc/font.scss b/src/styles/inc/font.scss
new file mode 100644
index 0000000000000000000000000000000000000000..3a159ba21a3e6bb4a8ae9aeae4cf298ae84d5981
--- /dev/null
+++ b/src/styles/inc/font.scss
@@ -0,0 +1,73 @@
+@import url("/node_modules/bootstrap-icons/font/bootstrap-icons.css");
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-Black.ttf") format("truetype");
+  font-weight: 900;
+  font-style: normal;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-BlackItalic.ttf") format("truetype");
+  font-weight: 900;
+  font-style: italic;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-Bold.ttf") format("truetype");
+  font-weight: 700;
+  font-style: normal;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-BoldItalic.ttf") format("truetype");
+  font-weight: 700;
+  font-style: italic;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-Regular.ttf") format("truetype");
+  font-weight: 400;
+  font-style: normal;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-Italic.ttf") format("truetype");
+  font-weight: 400;
+  font-style: italic;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-Light.ttf") format("truetype");
+  font-weight: 300;
+  font-style: normal;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-LightItalic.ttf") format("truetype");
+  font-weight: 300;
+  font-style: italic;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-Thin.ttf") format("truetype");
+  font-weight: 100;
+  font-style: normal;
+}
+
+@font-face {
+  font-family: Lato;
+  src: url("~/styles/font/Lato-ThinItalic.ttf") format("truetype");
+  font-weight: 100;
+  font-style: italic;
+}
+
+$font-main: "Lato", sans-serif;
diff --git a/src/styles/inc/style.scss b/src/styles/inc/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..ab7a0f581acafbc01ae5e8e196adc7f3dcab870d
--- /dev/null
+++ b/src/styles/inc/style.scss
@@ -0,0 +1,4 @@
+.funkwhale,
+:root {
+  --fw-border-radius: 8px;
+}
diff --git a/test/button.test.ts b/test/button.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..115ee966ff031aff3e4d8d0800f173807b18eb8a
--- /dev/null
+++ b/test/button.test.ts
@@ -0,0 +1,14 @@
+import { FwButton } from '~/components'
+import { mount } from '@vue/test-utils'
+
+describe('FwButton', () => {
+  it('should display text', () => {
+    const wrapper = mount(FwButton, {
+      slots: {
+        default: 'Button Test'
+      }
+    })
+
+    expect(wrapper.text()).toBe('Button Test')
+  })
+})
diff --git a/tsconfig.json b/tsconfig.json
index 65b1613d455008923b21209abd295112ff3dfa93..6d1a7d1f14f3d8b9190694f96e7076093817ee3e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,16 +1,19 @@
 {
+  "extends": "@vue/tsconfig/tsconfig.web.json",
   "compilerOptions": {
-    "target": "esnext",
-    "useDefineForClassFields": true,
-    "module": "esnext",
-    "moduleResolution": "node",
-    "strict": true,
-    "jsx": "preserve",
+    "baseUrl": ".",
     "sourceMap": true,
-    "resolveJsonModule": true,
-    "esModuleInterop": true,
-    "lib": ["esnext", "dom"]
+    "noUnusedLocals": true,
+    "typeRoots": ["node_modules/@types"],
+    "types": [
+      "vitest/globals",
+      "vite/client",
+      "vue/ref-macros"
+    ],
+    "paths": {
+      "?/*": ["test/*"],
+      "~/*": ["src/*"]
+    }
   },
-  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
-	"exclude": ["src/**/*.test.ts"]
+  "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.vue", "vite.config.ts", "test/**/*.ts", "docs/vite.config.ts"]
 }
diff --git a/vite.config.ts b/vite.config.ts
index f1111d0827326bfdf8ce27b01d693127ee024b20..38f50d98f31b2ee5b7776bc2d4bdbc1e97f91374 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,16 +1,50 @@
 import { defineConfig } from 'vite'
+import { resolve } from 'path'
+
 import vue from '@vitejs/plugin-vue'
 
 // https://vitejs.dev/config/
-export default defineConfig({
+export default defineConfig(() => ({
   plugins: [vue()],
+  publicDir: false,
+  resolve: {
+    alias: {
+      '?': resolve(__dirname, './test'),
+      '~': resolve(__dirname, './src')
+    }
+  },
+  css: {
+    preprocessorOptions: {
+      scss: {
+        additionalData: `
+          @import "~/styles/inc/style";
+          @import "~/styles/inc/font";
+          @import "~/styles/colors";
+        `
+      }
+    }
+  },
+	build: {
+		lib: {
+		  entry: resolve(__dirname, 'src/main.ts'),
+		  name: 'vui',
+		},
+		rollupOptions: {
+		  external: ['vue', 'vue-i18n', '@vueuse/core'],
+		  output: {
+        exports: 'named',
+        globals: {
+          vue: 'Vue'
+        }
+		  }
+		}
+  },
 	test: {
-		global: true,
-		environment: 'happy-dom',
-		reporters: 'junit',
+		globals: true,
+		environment: 'jsdom',
 		outputFile: 'junit.xml',
 		coverage: {
 			reporter: 'cobertura'
 		}
 	}
-})
+}))
diff --git a/yarn.lock b/yarn.lock
index ba896a5041ddb64d527ecac32e0cc690b1abe4c1..77092670229a5e829e45730a6560308fcf04fbf8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,24 +2,24 @@
 # yarn lockfile v1
 
 
-"@algolia/autocomplete-core@1.5.2":
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.5.2.tgz#ec0178e07b44fd74a057728ac157291b26cecf37"
-  integrity sha512-DY0bhyczFSS1b/CqJlTE/nQRtnTAHl6IemIkBy0nEWnhDzRDdtdx4p5Uuk3vwAFxwEEgi1WqKwgSSMx6DpNL4A==
+"@algolia/autocomplete-core@1.7.1":
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/@algolia/autocomplete-core/-/autocomplete-core-1.7.1.tgz#025538b8a9564a9f3dd5bcf8a236d6951c76c7d1"
+  integrity sha512-eiZw+fxMzNQn01S8dA/hcCpoWCOCwcIIEUtHHdzN5TGB3IpzLbuhqFeTfh2OUhhgkE8Uo17+wH+QJ/wYyQmmzg==
   dependencies:
-    "@algolia/autocomplete-shared" "1.5.2"
+    "@algolia/autocomplete-shared" "1.7.1"
 
-"@algolia/autocomplete-preset-algolia@1.5.2":
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.5.2.tgz#36c5638cc6dba6ea46a86e5a0314637ca40a77ca"
-  integrity sha512-3MRYnYQFJyovANzSX2CToS6/5cfVjbLLqFsZTKcvF3abhQzxbqwwaMBlJtt620uBUOeMzhdfasKhCc40+RHiZw==
+"@algolia/autocomplete-preset-algolia@1.7.1":
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.1.tgz#7dadc5607097766478014ae2e9e1c9c4b3f957c8"
+  integrity sha512-pJwmIxeJCymU1M6cGujnaIYcY3QPOVYZOXhFkWVM7IxKzy272BwCvMFMyc5NpG/QmiObBxjo7myd060OeTNJXg==
   dependencies:
-    "@algolia/autocomplete-shared" "1.5.2"
+    "@algolia/autocomplete-shared" "1.7.1"
 
-"@algolia/autocomplete-shared@1.5.2":
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.5.2.tgz#e157f9ad624ab8fd940ff28bd2094cdf199cdd79"
-  integrity sha512-ylQAYv5H0YKMfHgVWX0j0NmL8XBcAeeeVQUmppnnMtzDbDnca6CzhKj3Q8eF9cHCgcdTDdb5K+3aKyGWA0obug==
+"@algolia/autocomplete-shared@1.7.1":
+  version "1.7.1"
+  resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.1.tgz#95c3a0b4b78858fed730cf9c755b7d1cd0c82c74"
+  integrity sha512-eTmGVqY3GeyBTT8IWiB2K5EuURAqhnumfktAEoHxfDY2o7vg2rSnO16ZtIG0fMgt3py28Vwgq42/bVEuaQV7pg==
 
 "@algolia/cache-browser-local-storage@4.12.1":
   version "4.12.1"
@@ -125,152 +125,126 @@
     "@algolia/logger-common" "4.12.1"
     "@algolia/requester-common" "4.12.1"
 
-"@babel/code-frame@^7.10.4":
-  version "7.16.7"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
-  integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
-  dependencies:
-    "@babel/highlight" "^7.16.7"
-
-"@babel/helper-validator-identifier@^7.16.7":
-  version "7.16.7"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
-  integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
-
-"@babel/highlight@^7.16.7":
-  version "7.16.10"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
-  integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.16.7"
-    chalk "^2.0.0"
-    js-tokens "^4.0.0"
-
-"@babel/parser@^7.16.4", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
+"@babel/parser@^7.16.4":
   version "7.17.0"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.0.tgz#f0ac33eddbe214e4105363bb17c3341c5ffcc43c"
   integrity sha512-VKXSCQx5D8S04ej+Dqsr1CzYvvWgf20jIw2D+YhQCrIlr2UZGaDds23Y0xg75/skOxpLCRpUZvk/1EAVkGoDOw==
 
-"@babel/runtime-corejs3@^7.10.2":
-  version "7.17.0"
-  resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.0.tgz#9de2f75b3ca4b68628c01bd76410b64faa4644f7"
-  integrity sha512-qeydncU80ravKzovVncW3EYaC1ji3GpntdPgNcJy9g7hHSY6KX+ne1cbV3ov7Zzm4F1z0+QreZPCuw1ynkmYNg==
-  dependencies:
-    core-js-pure "^3.20.2"
-    regenerator-runtime "^0.13.4"
-
-"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0":
-  version "7.17.0"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.0.tgz#b8d142fc0f7664fb3d9b5833fd40dcbab89276c0"
-  integrity sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==
-  dependencies:
-    regenerator-runtime "^0.13.4"
-
-"@babel/types@^7.6.1", "@babel/types@^7.9.6":
-  version "7.17.0"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
-  integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.16.7"
-    to-fast-properties "^2.0.0"
-
 "@bcoe/v8-coverage@^0.2.3":
   version "0.2.3"
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
-"@docsearch/css@3.0.0-alpha.50", "@docsearch/css@^3.0.0-alpha.41":
-  version "3.0.0-alpha.50"
-  resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.0.0-alpha.50.tgz#794c6a8d301840a49b55f5b331c7be84b9723643"
-  integrity sha512-QeWFCQOtS9D+Fi20liKsPXF2j/xWKh52e+P2Z1UATIdPMqmH6zoB2lcUz+cgv6PPVgWUtECeR6VSSUm71LT94w==
+"@docsearch/css@3.2.1", "@docsearch/css@^3.2.1":
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/@docsearch/css/-/css-3.2.1.tgz#c05d7818b0e43b42f9efa2d82a11c36606b37b27"
+  integrity sha512-gaP6TxxwQC+K8D6TRx5WULUWKrcbzECOPA2KCVMuI+6C7dNiGUk5yXXzVhc5sld79XKYLnO9DRTI4mjXDYkh+g==
 
-"@docsearch/js@^3.0.0-alpha.41":
-  version "3.0.0-alpha.50"
-  resolved "https://registry.yarnpkg.com/@docsearch/js/-/js-3.0.0-alpha.50.tgz#8d59834ceb4a27463851e7e7ce9968f3549cc8f2"
-  integrity sha512-1ap9Wz5oR/Z8yybaCZhsptXU43es3H52eEQUZtmzb8dUWyCW+3iXaKVB/qeMJOQWtggZ/WvZV3YknVIbCMR2dQ==
+"@docsearch/js@^3.2.1":
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/@docsearch/js/-/js-3.2.1.tgz#d6856fb6223c7a47091640264d5144d59806bc30"
+  integrity sha512-H1PekEtSeS0msetR2YGGey2w7jQ2wAKfGODJvQTygSwMgUZ+2DHpzUgeDyEBIXRIfaBcoQneqrzsljM62pm6Xg==
   dependencies:
-    "@docsearch/react" "3.0.0-alpha.50"
+    "@docsearch/react" "3.2.1"
     preact "^10.0.0"
 
-"@docsearch/react@3.0.0-alpha.50":
-  version "3.0.0-alpha.50"
-  resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.0.0-alpha.50.tgz#a7dc547836c2b221fd3aa8eb87bfb47a579ef141"
-  integrity sha512-oDGV1zZCRYv7MWsh6CyQVthYTRc3b4q+6kKwNYb1/g/Wf/4nJHutpxolFLHdEUDhrJ4Xi8wxwQG+lEwAVBTHPg==
+"@docsearch/react@3.2.1":
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/@docsearch/react/-/react-3.2.1.tgz#112ad88db07367fa6fd933d67d58421d8d8289aa"
+  integrity sha512-EzTQ/y82s14IQC5XVestiK/kFFMe2aagoYFuTAIfIb/e+4FU7kSMKonRtLwsCiLQHmjvNQq+HO+33giJ5YVtaQ==
   dependencies:
-    "@algolia/autocomplete-core" "1.5.2"
-    "@algolia/autocomplete-preset-algolia" "1.5.2"
-    "@docsearch/css" "3.0.0-alpha.50"
+    "@algolia/autocomplete-core" "1.7.1"
+    "@algolia/autocomplete-preset-algolia" "1.7.1"
+    "@docsearch/css" "3.2.1"
     algoliasearch "^4.0.0"
 
-"@emmetio/abbreviation@^2.2.2":
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/@emmetio/abbreviation/-/abbreviation-2.2.2.tgz#746762fd9e7a8c2ea604f580c62e3cfe250e6989"
-  integrity sha512-TtE/dBnkTCct8+LntkqVrwqQao6EnPAs1YN3cUgxOxTaBlesBCY37ROUAVZrRlG64GNnVShdl/b70RfAI3w5lw==
+"@esbuild/linux-loong64@0.14.54":
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz#de2a4be678bd4d0d1ffbb86e6de779cde5999028"
+  integrity sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==
+
+"@intlify/core-base@9.2.2":
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.2.2.tgz#5353369b05cc9fe35cab95fe20afeb8a4481f939"
+  integrity sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==
   dependencies:
-    "@emmetio/scanner" "^1.0.0"
+    "@intlify/devtools-if" "9.2.2"
+    "@intlify/message-compiler" "9.2.2"
+    "@intlify/shared" "9.2.2"
+    "@intlify/vue-devtools" "9.2.2"
 
-"@emmetio/css-abbreviation@^2.1.4":
-  version "2.1.4"
-  resolved "https://registry.yarnpkg.com/@emmetio/css-abbreviation/-/css-abbreviation-2.1.4.tgz#90362e8a1122ce3b76f6c3157907d30182f53f54"
-  integrity sha512-qk9L60Y+uRtM5CPbB0y+QNl/1XKE09mSO+AhhSauIfr2YOx/ta3NJw2d8RtCFxgzHeRqFRr8jgyzThbu+MZ4Uw==
+"@intlify/devtools-if@9.2.2":
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/@intlify/devtools-if/-/devtools-if-9.2.2.tgz#b13d9ac4b4e2fe6d2e7daa556517a8061fe8bd39"
+  integrity sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==
   dependencies:
-    "@emmetio/scanner" "^1.0.0"
+    "@intlify/shared" "9.2.2"
 
-"@emmetio/scanner@^1.0.0":
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/@emmetio/scanner/-/scanner-1.0.0.tgz#065b2af6233fe7474d44823e3deb89724af42b5f"
-  integrity sha512-8HqW8EVqjnCmWXVpqAOZf+EGESdkR27odcMMMGefgKXtar00SoYNSryGv//TELI4T3QFsECo78p+0lmalk/CFA==
+"@intlify/message-compiler@9.2.2":
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.2.2.tgz#e42ab6939b8ae5b3d21faf6a44045667a18bba1c"
+  integrity sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==
+  dependencies:
+    "@intlify/shared" "9.2.2"
+    source-map "0.6.1"
+
+"@intlify/shared@9.2.2":
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.2.2.tgz#5011be9ca2b4ab86f8660739286e2707f9abb4a5"
+  integrity sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==
 
-"@funkwhale/ui@0.1.2-dev1":
-  version "0.1.2-dev1"
-  resolved "https://dev.funkwhale.audio/api/v4/projects/527/packages/npm/@funkwhale/ui/-/@funkwhale/ui-0.1.2-dev1.tgz#91b806e81314c620f43ebbff3907e43aafacb940"
-  integrity sha1-kbgG6BMUxiD0Prv/OQfkOq+suUA=
+"@intlify/vue-devtools@9.2.2":
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/@intlify/vue-devtools/-/vue-devtools-9.2.2.tgz#b95701556daf7ebb3a2d45aa3ae9e6415aed8317"
+  integrity sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==
   dependencies:
-    bulma "^0.9.3"
+    "@intlify/core-base" "9.2.2"
+    "@intlify/shared" "9.2.2"
 
-"@istanbuljs/schema@^0.1.2":
+"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3":
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
   integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
 
-"@jest/types@^26.6.2":
-  version "26.6.2"
-  resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e"
-  integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==
+"@jridgewell/resolve-uri@^3.0.3":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+  integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+  version "1.4.14"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+  integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/trace-mapping@^0.3.12":
+  version "0.3.15"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774"
+  integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==
   dependencies:
-    "@types/istanbul-lib-coverage" "^2.0.0"
-    "@types/istanbul-reports" "^3.0.0"
-    "@types/node" "*"
-    "@types/yargs" "^15.0.0"
-    chalk "^4.0.0"
-
-"@testing-library/dom@^7.30.4":
-  version "7.31.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
-  integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==
-  dependencies:
-    "@babel/code-frame" "^7.10.4"
-    "@babel/runtime" "^7.12.5"
-    "@types/aria-query" "^4.2.0"
-    aria-query "^4.2.2"
-    chalk "^4.1.0"
-    dom-accessibility-api "^0.5.6"
-    lz-string "^1.4.4"
-    pretty-format "^26.6.2"
-
-"@testing-library/vue@^6.4.2":
-  version "6.4.2"
-  resolved "https://registry.yarnpkg.com/@testing-library/vue/-/vue-6.4.2.tgz#4286d9b6495fc8869b9e8e1937687c0755412aab"
-  integrity sha512-L+yFRVu+zc/kwYWRaEF3gkEwUcoxhKNz74aya1NmAni2xrJORMqpY7URKxjfiJb4YchLgA47qEZ3irE11/x/+A==
-  dependencies:
-    "@babel/runtime" "^7.14.0"
-    "@testing-library/dom" "^7.30.4"
-    "@vue/test-utils" "^2.0.0-rc.0"
-
-"@types/aria-query@^4.2.0":
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
-  integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
+    "@jridgewell/resolve-uri" "^3.0.3"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+
+"@modyfi/vite-plugin-yaml@^1.0.3":
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/@modyfi/vite-plugin-yaml/-/vite-plugin-yaml-1.0.3.tgz#5d6a4713b14995b73ef60a7a473c2663e2c21fa9"
+  integrity sha512-UB9A9b5h9v4fruyZPeLQh4k7ZF7KPrOuMnTfSIJD2WYXx9rHE0U19/RzLjFhDzWnTBPZWu6RoT4wxsaDkxXUzw==
+  dependencies:
+    "@rollup/pluginutils" "4.2.1"
+    js-yaml "4.1.0"
+    tosource "2.0.0-alpha.3"
+
+"@rollup/pluginutils@4.2.1":
+  version "4.2.1"
+  resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
+  integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
+  dependencies:
+    estree-walker "^2.0.1"
+    picomatch "^2.2.2"
+
+"@tootallnate/once@2":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
+  integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
 
 "@types/chai-subset@^1.3.3":
   version "1.3.3"
@@ -279,338 +253,300 @@
   dependencies:
     "@types/chai" "*"
 
-"@types/chai@*", "@types/chai@^4.3.0":
+"@types/chai@*":
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc"
   integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==
 
-"@types/concat-stream@^1.6.0":
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/@types/concat-stream/-/concat-stream-1.6.1.tgz#24bcfc101ecf68e886aaedce60dfd74b632a1b74"
-  integrity sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==
-  dependencies:
-    "@types/node" "*"
+"@types/chai@^4.3.3":
+  version "4.3.3"
+  resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07"
+  integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==
 
-"@types/form-data@0.0.33":
-  version "0.0.33"
-  resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-0.0.33.tgz#c9ac85b2a5fd18435b8c85d9ecb50e6d6c893ff8"
-  integrity sha1-yayFsqX9GENbjIXZ7LUObWyJP/g=
+"@types/dompurify@^2.3.4":
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.4.tgz#94e997e30338ea24d4c8d08beca91ce4dd17a1b4"
+  integrity sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==
   dependencies:
-    "@types/node" "*"
+    "@types/trusted-types" "*"
 
-"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
+"@types/istanbul-lib-coverage@^2.0.1":
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
   integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==
 
-"@types/istanbul-lib-report@*":
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
-  integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
-  dependencies:
-    "@types/istanbul-lib-coverage" "*"
-
-"@types/istanbul-reports@^3.0.0":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff"
-  integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==
-  dependencies:
-    "@types/istanbul-lib-report" "*"
-
 "@types/node@*":
   version "17.0.16"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.16.tgz#e3733f46797b9df9e853ca9f719c8a6f7b84cd26"
   integrity sha512-ydLaGVfQOQ6hI1xK2A5nVh8bl0OGoIfYMxPWHqqYe9bTkWCfqiVvZoh2I/QF2sNSkZzZyROBoTefIEI+PB6iIA==
 
-"@types/node@^10.0.3":
-  version "10.17.60"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.60.tgz#35f3d6213daed95da7f0f73e75bcc6980e90597b"
-  integrity sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==
+"@types/showdown@^2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-2.0.0.tgz#3e800eca8573848cac4e5555f4377ba3a0e7b1f2"
+  integrity sha512-70xBJoLv+oXjB5PhtA8vo7erjLDp9/qqI63SRHm4REKrwuPOLs8HhXwlZJBJaB4kC18cCZ1UUZ6Fb/PLFW4TCA==
 
-"@types/node@^8.0.0":
-  version "8.10.66"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.66.tgz#dd035d409df322acc83dff62a602f12a5783bbb3"
-  integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==
+"@types/trusted-types@*":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
+  integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
 
-"@types/qs@^6.2.31":
-  version "6.9.7"
-  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
-  integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
+"@types/web-bluetooth@^0.0.15":
+  version "0.0.15"
+  resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.15.tgz#d60330046a6ed8a13b4a53df3813c44942ebdf72"
+  integrity sha512-w7hEHXnPMEZ+4nGKl/KDRVpxkwYxYExuHOYXyzIzCDzEZ9ZCGMAewulr9IqJu2LR4N37fcnb1XVeuZ09qgOxhA==
 
-"@types/yargs-parser@*":
-  version "20.2.1"
-  resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129"
-  integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==
+"@vitejs/plugin-vue@^3.0.3":
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-3.0.3.tgz#7e3e401ccb30b4380d2279d9849281413f1791ef"
+  integrity sha512-U4zNBlz9mg+TA+i+5QPc3N5lQvdUXENZLO2h0Wdzp56gI1MWhqJOv+6R+d4kOzoaSSq6TnGPBdZAXKOe4lXy6g==
 
-"@types/yargs@^15.0.0":
-  version "15.0.14"
-  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06"
-  integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==
+"@vitest/coverage-c8@^0.22.1":
+  version "0.22.1"
+  resolved "https://registry.yarnpkg.com/@vitest/coverage-c8/-/coverage-c8-0.22.1.tgz#d91b3487caca570a815899943c1939fe421fc114"
+  integrity sha512-KOOYpO7EGpaF+nD8GD+Y05D0JtZp12NUu6DdLXvBPqSOPo2HkZ7KNBtfR0rb6gOy3NLtGiWTYTzCwhajgb2HlA==
   dependencies:
-    "@types/yargs-parser" "*"
+    c8 "^7.12.0"
+    vitest "0.22.1"
 
-"@vitejs/plugin-vue@^2.0.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.1.0.tgz#ddf5e0059f84f2ff649afc25ce5a59211e670542"
-  integrity sha512-AZ78WxvFMYd8JmM/GBV6a6SGGTU0GgN/0/4T+FnMMsLzFEzTeAUwuraapy50ifHZsC+G5SvWs86bvaCPTneFlA==
-
-"@volar/code-gen@0.29.8":
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/@volar/code-gen/-/code-gen-0.29.8.tgz#db1a4bf29caeba131265bed9dbe96a1a0b66ea35"
-  integrity sha512-eohLLUqPChHRPDFT5gXn4V6pr/CeTri7Ou5GI26lUvBRRAbP8p+oYfQRcbMPGeKmVkYjfVj0chsxQGx6T8PQ4Q==
-  dependencies:
-    "@volar/shared" "0.29.8"
-    "@volar/source-map" "0.29.8"
-
-"@volar/html2pug@0.29.8":
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/@volar/html2pug/-/html2pug-0.29.8.tgz#2e97fa2968dcdfe0dbbc67b0cd2ab4c440018738"
-  integrity sha512-bhSNXg8A2aD3w0B+CwmHjqCAaKtj5rORbE5C/q/UdGqptJbC6STCmi30KuRTdfPhR++Xb18Hauf3s/WCmtNAPA==
-  dependencies:
-    domelementtype "^2.2.0"
-    domhandler "^4.2.2"
-    htmlparser2 "^7.1.2"
-    pug "^3.0.2"
-
-"@volar/shared@0.29.8":
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/@volar/shared/-/shared-0.29.8.tgz#e635ddf2cbcf307da932eb4b98e33c320d3d2991"
-  integrity sha512-Y1NN6irkIukD+T0wf4p/dHWYL90sacN2e2lYoDXxRlvoYxwANnHgw0J0Rcp+yw58ElWRScdG7/YntEIuZWeJsw==
-  dependencies:
-    upath "^2.0.1"
-    vscode-jsonrpc "^8.0.0-next.2"
-    vscode-uri "^3.0.2"
-
-"@volar/source-map@0.29.8":
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-0.29.8.tgz#3299a0ae86ae0b72b4db3e50499d8bb285d8e9b2"
-  integrity sha512-7w+UoYtnc6UQu30CgMVvx0YN4dzDgP4TIsSmUaW62AGmxU9Lxwp3Kkn/4N8efi91z8ma5Z78v/HddyJPwAC3LA==
-  dependencies:
-    "@volar/shared" "0.29.8"
-
-"@volar/transforms@0.29.8":
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/@volar/transforms/-/transforms-0.29.8.tgz#ef807010ac90772a065e7cf50509b6433b53e355"
-  integrity sha512-o2hRa8CoDwYTO1Mu5KA47+1elUnYUjDaVhCvbyKlRfd8qpHea2llotArq7B6OORSL2M9DVs1IRJ5NGURBFeZ3Q==
-  dependencies:
-    "@volar/shared" "0.29.8"
-    vscode-languageserver "^8.0.0-next.2"
-
-"@volar/vue-code-gen@0.29.8":
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/@volar/vue-code-gen/-/vue-code-gen-0.29.8.tgz#32401d52e2570d775fcc6cbc83abefeef65c48cd"
-  integrity sha512-E1e7P2oktNC/DzgDBditfla4s8+HlUlluZ+BtcLvEdbkl3QEjujkB0x1wxguWzXmpWgLIDPtrS3Jzll5cCOkTg==
-  dependencies:
-    "@volar/code-gen" "0.29.8"
-    "@volar/shared" "0.29.8"
-    "@volar/source-map" "0.29.8"
-    "@vue/compiler-core" "^3.2.21"
-    "@vue/compiler-dom" "^3.2.21"
-    "@vue/shared" "^3.2.21"
-    upath "^2.0.1"
-
-"@vscode/emmet-helper@^2.8.0":
-  version "2.8.3"
-  resolved "https://registry.yarnpkg.com/@vscode/emmet-helper/-/emmet-helper-2.8.3.tgz#f7c2b4a4751d03bcf2b421f0fce5b521a0f64a18"
-  integrity sha512-dkTSL+BaBBS8gFgPm/GMOU+XfxaMyI+Fl1IUYxEi8Iv24RfHf9/q2eCpV2hs7sncLcoKWEbMYe5gv4Ppmp2Oxw==
-  dependencies:
-    emmet "^2.3.0"
-    jsonc-parser "^2.3.0"
-    vscode-languageserver-textdocument "^1.0.1"
-    vscode-languageserver-types "^3.15.1"
-    vscode-nls "^5.0.0"
-    vscode-uri "^2.1.2"
-
-"@vue/compiler-core@3.2.29", "@vue/compiler-core@^3.2.21":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.29.tgz#b06097ab8ff0493177c68c5ea5b63d379a061097"
-  integrity sha512-RePZ/J4Ub3sb7atQw6V6Rez+/5LCRHGFlSetT3N4VMrejqJnNPXKUt5AVm/9F5MJriy2w/VudEIvgscCfCWqxw==
+"@volar/code-gen@0.40.6":
+  version "0.40.6"
+  resolved "https://registry.yarnpkg.com/@volar/code-gen/-/code-gen-0.40.6.tgz#87db08d525476738b7a12dad82cb1d41c960a21e"
+  integrity sha512-TEi6tSQPeKfig07d+C7uPTVnG7Vob6x8S/NOCU0TlnMNtEmmMlZJlKJz3xhZt96Hz2rNEmBCcuscmwSSoDdxlw==
   dependencies:
-    "@babel/parser" "^7.16.4"
-    "@vue/shared" "3.2.29"
-    estree-walker "^2.0.2"
-    source-map "^0.6.1"
+    "@volar/source-map" "0.40.6"
 
-"@vue/compiler-core@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.30.tgz#6c5b362490930e72de8d033270a145e3830ae5c4"
-  integrity sha512-64fq1KfcR+k3Vlw+IsBM2VhV5B+2IP3YxvKU8LWCDLrkmlXtbf2eMK6+0IwX5KP41D0f1gzryIiXR7P8cB9O5Q==
+"@volar/source-map@0.40.6":
+  version "0.40.6"
+  resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-0.40.6.tgz#e0282497b2f05268e14dcbe9e184df5237e08ec5"
+  integrity sha512-NTUBzxwcr8YkGmbxpAe2Wa9fDifqVdVd4piVBi8xcTRECOIy8z/tzR5m41I2a/B0V1sc+fb5e8Ino4M4c3bZUA==
   dependencies:
-    "@babel/parser" "^7.16.4"
-    "@vue/shared" "3.2.30"
-    estree-walker "^2.0.2"
-    source-map "^0.6.1"
+    "@vue/reactivity" "3.2.37"
+
+"@volar/typescript-faster@0.40.6":
+  version "0.40.6"
+  resolved "https://registry.yarnpkg.com/@volar/typescript-faster/-/typescript-faster-0.40.6.tgz#1e42344e900df1f6a04714eb1979f52c71119af8"
+  integrity sha512-h3EG/HIInqYiw1CL1TGD9XJY0OXATuSwuan3KCN211eb5OnL+Ap9LUmzQIZpAs7+TzY9EMSEu4/sA14aoIhW9A==
+  dependencies:
+    semver "^7.3.7"
 
-"@vue/compiler-dom@3.2.29", "@vue/compiler-dom@^3.2.21":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.29.tgz#ad0ead405bd2f2754161335aad9758aa12430715"
-  integrity sha512-y26vK5khdNS9L3ckvkqJk/78qXwWb75Ci8iYLb67AkJuIgyKhIOcR1E8RIt4mswlVCIeI9gQ+fmtdhaiTAtrBQ==
+"@volar/vue-language-core@0.40.6":
+  version "0.40.6"
+  resolved "https://registry.yarnpkg.com/@volar/vue-language-core/-/vue-language-core-0.40.6.tgz#21f974d75a2e95bf461ed4ef4248eec92fc78303"
+  integrity sha512-XfcGw7KlrrroYHUmhkOwCY8idDxVWMebCcAX1fwP0G24ooQsWBi1LyJQGBi1NdwSsUK1pmd8pIsqVdSUAbr0+g==
   dependencies:
-    "@vue/compiler-core" "3.2.29"
-    "@vue/shared" "3.2.29"
+    "@volar/code-gen" "0.40.6"
+    "@volar/source-map" "0.40.6"
+    "@vue/compiler-core" "^3.2.37"
+    "@vue/compiler-dom" "^3.2.37"
+    "@vue/compiler-sfc" "^3.2.37"
+    "@vue/reactivity" "^3.2.37"
+    "@vue/shared" "^3.2.37"
 
-"@vue/compiler-dom@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.30.tgz#ed15e6243227baeaa445d04df804aee6e4926eab"
-  integrity sha512-t7arHz2SXLCXlF2fdGDFVbhENbGMez254Z5edUqb//6WXJU1lC7GvSkUE7i5x8WSjgfqt60i0V8zdmk16rvLdw==
+"@volar/vue-typescript@0.40.6":
+  version "0.40.6"
+  resolved "https://registry.yarnpkg.com/@volar/vue-typescript/-/vue-typescript-0.40.6.tgz#10850e7b352a79e1728c764a8d7dc5b13a179f20"
+  integrity sha512-qna2mRb1aiylxsdob+V+AMJylfm0f+Yr1lzfnlE9A6g4bcg/eED+9PrQNwLqAHO3RSezbyFJVF43BA1KnCBrgg==
   dependencies:
-    "@vue/compiler-core" "3.2.30"
-    "@vue/shared" "3.2.30"
+    "@volar/code-gen" "0.40.6"
+    "@volar/typescript-faster" "0.40.6"
+    "@volar/vue-language-core" "0.40.6"
 
-"@vue/compiler-sfc@3.2.29":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.29.tgz#f76d556cd5fca6a55a3ea84c88db1a2a53a36ead"
-  integrity sha512-X9+0dwsag2u6hSOP/XsMYqFti/edvYvxamgBgCcbSYuXx1xLZN+dS/GvQKM4AgGS4djqo0jQvWfIXdfZ2ET68g==
+"@vue/compiler-core@3.2.38", "@vue/compiler-core@^3.2.37":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.38.tgz#0a2a7bffd2280ac19a96baf5301838a2cf1964d7"
+  integrity sha512-/FsvnSu7Z+lkd/8KXMa4yYNUiqQrI22135gfsQYVGuh5tqEgOB0XqrUdb/KnCLa5+TmQLPwvyUnKMyCpu+SX3Q==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.29"
-    "@vue/compiler-dom" "3.2.29"
-    "@vue/compiler-ssr" "3.2.29"
-    "@vue/reactivity-transform" "3.2.29"
-    "@vue/shared" "3.2.29"
+    "@vue/shared" "3.2.38"
     estree-walker "^2.0.2"
-    magic-string "^0.25.7"
-    postcss "^8.1.10"
     source-map "^0.6.1"
 
-"@vue/compiler-sfc@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.30.tgz#9d2e56adb859059551fc1204bc37503f168c4d0c"
-  integrity sha512-P/5YpILtcQY92z72gxhkyOUPHVskEzhSrvYi91Xcr+csOxaDaYU5OqOxCzZKcf3Og70Tat404vO1OHrwprN90A==
+"@vue/compiler-dom@3.2.38", "@vue/compiler-dom@^3.2.37":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.38.tgz#53d04ed0c0c62d1ef259bf82f9b28100a880b6fd"
+  integrity sha512-zqX4FgUbw56kzHlgYuEEJR8mefFiiyR3u96498+zWPsLeh1WKvgIReoNE+U7gG8bCUdvsrJ0JRmev0Ky6n2O0g==
+  dependencies:
+    "@vue/compiler-core" "3.2.38"
+    "@vue/shared" "3.2.38"
+
+"@vue/compiler-sfc@3.2.38", "@vue/compiler-sfc@^3.2.37":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.38.tgz#9e763019471a535eb1fceeaac9d4d18a83f0940f"
+  integrity sha512-KZjrW32KloMYtTcHAFuw3CqsyWc5X6seb8KbkANSWt3Cz9p2qA8c1GJpSkksFP9ABb6an0FLCFl46ZFXx3kKpg==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.30"
-    "@vue/compiler-dom" "3.2.30"
-    "@vue/compiler-ssr" "3.2.30"
-    "@vue/reactivity-transform" "3.2.30"
-    "@vue/shared" "3.2.30"
+    "@vue/compiler-core" "3.2.38"
+    "@vue/compiler-dom" "3.2.38"
+    "@vue/compiler-ssr" "3.2.38"
+    "@vue/reactivity-transform" "3.2.38"
+    "@vue/shared" "3.2.38"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
     postcss "^8.1.10"
     source-map "^0.6.1"
 
-"@vue/compiler-ssr@3.2.29":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.29.tgz#37b15b32dcd2f6b410bb61fca3f37b1a92b7eb1e"
-  integrity sha512-LrvQwXlx66uWsB9/VydaaqEpae9xtmlUkeSKF6aPDbzx8M1h7ukxaPjNCAXuFd3fUHblcri8k42lfimHfzMICA==
+"@vue/compiler-ssr@3.2.38":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.38.tgz#933b23bf99e667e5078eefc6ba94cb95fd765dfe"
+  integrity sha512-bm9jOeyv1H3UskNm4S6IfueKjUNFmi2kRweFIGnqaGkkRePjwEcfCVqyS3roe7HvF4ugsEkhf4+kIvDhip6XzQ==
   dependencies:
-    "@vue/compiler-dom" "3.2.29"
-    "@vue/shared" "3.2.29"
+    "@vue/compiler-dom" "3.2.38"
+    "@vue/shared" "3.2.38"
 
-"@vue/compiler-ssr@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.30.tgz#fc2bc13a9cdfd70fcffab3f0bc7de141cd9c3411"
-  integrity sha512-OUh3MwAu/PsD7VN3UOdBbTkltkrUCNouSht47+CMRzpUR5+ta7+xyMAVHeq8wg4YZenWaJimbR5TL35Ka4Vk6g==
-  dependencies:
-    "@vue/compiler-dom" "3.2.30"
-    "@vue/shared" "3.2.30"
+"@vue/devtools-api@^6.1.4", "@vue/devtools-api@^6.2.1":
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.2.1.tgz#6f2948ff002ec46df01420dfeff91de16c5b4092"
+  integrity sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==
 
-"@vue/reactivity-transform@3.2.29":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.29.tgz#a08d606e10016b7cf588d1a43dae4db2953f9354"
-  integrity sha512-YF6HdOuhdOw6KyRm59+3rML8USb9o8mYM1q+SH0G41K3/q/G7uhPnHGKvspzceD7h9J3VR1waOQ93CUZj7J7OA==
+"@vue/reactivity-transform@3.2.38":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.38.tgz#a856c217b2ead99eefb6fddb1d61119b2cb67984"
+  integrity sha512-3SD3Jmi1yXrDwiNJqQ6fs1x61WsDLqVk4NyKVz78mkaIRh6d3IqtRnptgRfXn+Fzf+m6B1KxBYWq1APj6h4qeA==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.29"
-    "@vue/shared" "3.2.29"
+    "@vue/compiler-core" "3.2.38"
+    "@vue/shared" "3.2.38"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
-"@vue/reactivity-transform@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.30.tgz#2006e9f4645777a481b78ae77fc486159afa8480"
-  integrity sha512-Le5XzCJyK3qTjoTnvQG/Ehu8fYjayauMNFyMaEnwFlm/avDofpuibpS9u+/6AgzsGnVWN+i0Jgf25bJd9DIwMw==
+"@vue/reactivity@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.37.tgz#5bc3847ac58828e2b78526e08219e0a1089f8848"
+  integrity sha512-/7WRafBOshOc6m3F7plwzPeCu/RCVv9uMpOwa/5PiY1Zz+WLVRWiy0MYKwmg19KBdGtFWsmZ4cD+LOdVPcs52A==
   dependencies:
-    "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.30"
-    "@vue/shared" "3.2.30"
-    estree-walker "^2.0.2"
-    magic-string "^0.25.7"
+    "@vue/shared" "3.2.37"
 
-"@vue/reactivity@3.2.29", "@vue/reactivity@^3.2.21":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.29.tgz#afdc9c111d4139b14600be17ad80267212af6052"
-  integrity sha512-Ryhb6Gy62YolKXH1gv42pEqwx7zs3n8gacRVZICSgjQz8Qr8QeCcFygBKYfJm3o1SccR7U+bVBQDWZGOyG1k4g==
+"@vue/reactivity@3.2.38", "@vue/reactivity@^3.2.37":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.38.tgz#d576fdcea98eefb96a1f1ad456e289263e87292e"
+  integrity sha512-6L4myYcH9HG2M25co7/BSo0skKFHpAN8PhkNPM4xRVkyGl1K5M3Jx4rp5bsYhvYze2K4+l+pioN4e6ZwFLUVtw==
   dependencies:
-    "@vue/shared" "3.2.29"
+    "@vue/shared" "3.2.38"
 
-"@vue/reactivity@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.30.tgz#fdae2bb66d075c34593ea7e15c6831300a1ad39e"
-  integrity sha512-qlNKbkRn2JiGxVUEdoXbLAy+vcuHUCcq+YH2uXWz0BNMvXY2plmz+oqsw+694llwmYLkke5lbdYF4DIupisIkg==
+"@vue/runtime-core@3.2.38":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.38.tgz#d19cf591c210713f80e6a94ffbfef307c27aea06"
+  integrity sha512-kk0qiSiXUU/IKxZw31824rxmFzrLr3TL6ZcbrxWTKivadoKupdlzbQM4SlGo4MU6Zzrqv4fzyUasTU1jDoEnzg==
   dependencies:
-    "@vue/shared" "3.2.30"
+    "@vue/reactivity" "3.2.38"
+    "@vue/shared" "3.2.38"
 
-"@vue/runtime-core@3.2.29":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.29.tgz#fb8577b2fcf52e8d967bd91cdf49ab9fb91f9417"
-  integrity sha512-VMvQuLdzoTGmCwIKTKVwKmIL0qcODIqe74JtK1pVr5lnaE0l25hopodmPag3RcnIcIXe+Ye3B2olRCn7fTCgig==
+"@vue/runtime-dom@3.2.38":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.38.tgz#fec711f65c2485991289fd4798780aa506469b48"
+  integrity sha512-4PKAb/ck2TjxdMSzMsnHViOrrwpudk4/A56uZjhzvusoEU9xqa5dygksbzYepdZeB5NqtRw5fRhWIiQlRVK45A==
   dependencies:
-    "@vue/reactivity" "3.2.29"
-    "@vue/shared" "3.2.29"
-
-"@vue/runtime-core@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.30.tgz#1acc119ff8a49c06af6b03611bc4e03f464ca8a2"
-  integrity sha512-RTi7xH0Ht/6wfbo2WFBMJTEiyWFTqGhrksJm8lz6E+auO6lXZ6Eq3gPNfLt47GDWCm4xyrv+rs5R4UbarPEQ1Q==
-  dependencies:
-    "@vue/reactivity" "3.2.30"
-    "@vue/shared" "3.2.30"
-
-"@vue/runtime-dom@3.2.29":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.29.tgz#35e9a2bf04ef80b86ac2ca0e7b2ceaccf1e18f01"
-  integrity sha512-YJgLQLwr+SQyORzTsBQLL5TT/5UiV83tEotqjL7F9aFDIQdFBTCwpkCFvX9jqwHoyi9sJqM9XtTrMcc8z/OjPA==
-  dependencies:
-    "@vue/runtime-core" "3.2.29"
-    "@vue/shared" "3.2.29"
+    "@vue/runtime-core" "3.2.38"
+    "@vue/shared" "3.2.38"
     csstype "^2.6.8"
 
-"@vue/runtime-dom@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.30.tgz#16a85b359ea1fff9b1dd61e9d00e93f4652aba5e"
-  integrity sha512-a3+jrncDvEFQmB+v9k0VyT4/Y3XO6OAueCroXXY4yLyr6PJeyxljweV5TzvW0rvVzH9sZO0QAvG76Lo+6C92Qw==
+"@vue/server-renderer@3.2.38":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.38.tgz#01a4c0f218e90b8ad1815074208a1974ded109aa"
+  integrity sha512-pg+JanpbOZ5kEfOZzO2bt02YHd+ELhYP8zPeLU1H0e7lg079NtuuSB8fjLdn58c4Ou8UQ6C1/P+528nXnLPAhA==
   dependencies:
-    "@vue/runtime-core" "3.2.30"
-    "@vue/shared" "3.2.30"
-    csstype "^2.6.8"
+    "@vue/compiler-ssr" "3.2.38"
+    "@vue/shared" "3.2.38"
 
-"@vue/server-renderer@3.2.29":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.29.tgz#ea6afa361b9c781a868c8da18c761f9b7bc89102"
-  integrity sha512-lpiYx7ciV7rWfJ0tPkoSOlLmwqBZ9FTmQm33S+T4g0j1fO/LmhJ9b9Ctl1o5xvIFVDk9QkSUWANZn7H2pXuxVw==
-  dependencies:
-    "@vue/compiler-ssr" "3.2.29"
-    "@vue/shared" "3.2.29"
+"@vue/shared@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.37.tgz#8e6adc3f2759af52f0e85863dfb0b711ecc5c702"
+  integrity sha512-4rSJemR2NQIo9Klm1vabqWjD8rs/ZaJSzMxkMNeJS6lHiUjjUeYFbooN19NgFjztubEKh3WlZUeOLVdbbUWHsw==
 
-"@vue/server-renderer@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.30.tgz#4acccad3933475d07b94560c6cb205363975b969"
-  integrity sha512-pzb8J/w+JdZVOtuKFlirGqrs4GP60FXGDJySw3WV2pCetuFstaacDrnymEeSo3ohAD+Qjv7zAG+Y7OvkdxQxmQ==
-  dependencies:
-    "@vue/compiler-ssr" "3.2.30"
-    "@vue/shared" "3.2.30"
+"@vue/shared@3.2.38", "@vue/shared@^3.2.37":
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.38.tgz#e823f0cb2e85b6bf43430c0d6811b1441c300f3c"
+  integrity sha512-dTyhTIRmGXBjxJE+skC8tTWCGLCVc4wQgRRLt8+O9p5ewBAjoBwtCAkLPrtToSr1xltoe3st21Pv953aOZ7alg==
 
-"@vue/shared@3.2.29", "@vue/shared@^3.2.21":
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925"
-  integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw==
+"@vue/test-utils@^2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.0.2.tgz#0b5edd683366153d5bc5a91edc62f292118710eb"
+  integrity sha512-E2P4oXSaWDqTZNbmKZFVLrNN/siVN78YkEqs7pHryWerrlZR9bBFLWdJwRoguX45Ru6HxIflzKl4vQvwRMwm5g==
 
-"@vue/shared@3.2.30":
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.30.tgz#e2ba8f6692399c27c81c668ecd3f1a4e13ee2f5e"
-  integrity sha512-B3HouBtUxcfu2w2d+VhdLcVBXKYYhXiFMAfQ+hoe8NUhKkPRkWDIqhpuehCZxVQ3S2dN1P1WfKGlxGC+pfmxGg==
+"@vue/tsconfig@^0.1.3":
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/@vue/tsconfig/-/tsconfig-0.1.3.tgz#4a61dbd29783d01ddab504276dcf0c2b6988654f"
+  integrity sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg==
+
+"@vueuse/core@^9.1.0":
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.1.1.tgz#a5c09c33ccee58cfd53bc3ec2d5a0d304155529e"
+  integrity sha512-QfuaNWRDMQcCUwXylCyYhPC3ScS9Tiiz4J0chdwr3vOemBwRToSywq8MP+ZegKYFnbETzRY8G/5zC+ca30wrRQ==
+  dependencies:
+    "@types/web-bluetooth" "^0.0.15"
+    "@vueuse/metadata" "9.1.1"
+    "@vueuse/shared" "9.1.1"
+    vue-demi "*"
+
+"@vueuse/core@^9.2.0":
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.2.0.tgz#58e3588b9bc5a69010aa9104b00056ee9ebff738"
+  integrity sha512-/MZ6qpz6uSyaXrtoeBWQzAKRG3N7CvfVWvQxiM3ei3Xe5ydOjjtVbo7lGl9p8dECV93j7W8s63A8H0kFLpLyxg==
+  dependencies:
+    "@types/web-bluetooth" "^0.0.15"
+    "@vueuse/metadata" "9.2.0"
+    "@vueuse/shared" "9.2.0"
+    vue-demi "*"
+
+"@vueuse/metadata@9.1.1":
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.1.1.tgz#b3fe4b97e62096f7566cd8eb107c503998b2c9a6"
+  integrity sha512-XZ2KtSW+85LLHB/IdGILPAtbIVHasPsAW7aqz3BRMzJdAQWRiM/FGa1OKBwLbXtUw/AmjKYFlZJo7eOFIBXRog==
+
+"@vueuse/metadata@9.2.0":
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-9.2.0.tgz#6bf7c9c44b9f5ece405837226a0e04a997994458"
+  integrity sha512-exN4KE6iquxDCdt72BgEhb3tlOpECtD61AUdXnUqBTIUCl70x1Ar/QXo3bYcvxmdMS2/peQyfeTzBjRTpvL5xw==
+
+"@vueuse/shared@9.1.1":
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.1.1.tgz#811f47629e281a19013ae6dcdf11ed3e1e91e023"
+  integrity sha512-c+IfcOYmHiHqoEa3ED1Tbpue5GHmoUmTp8PtO4YbczthtY155Rt6DmWhjxMLXBF1Bcidagxljmp/7xtAzEHXLw==
+  dependencies:
+    vue-demi "*"
+
+"@vueuse/shared@9.2.0":
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-9.2.0.tgz#7831051b2c1d01c3413c749468ee53a86024510e"
+  integrity sha512-NnRp/noSWuXW0dKhZK5D0YLrDi0nmZ18UeEgwXQq7Ul5TTP93lcNnKjrHtd68j2xFB/l59yPGFlCryL692bnrA==
+  dependencies:
+    vue-demi "*"
+
+abab@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
+  integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
 
-"@vue/test-utils@^2.0.0-rc.0":
-  version "2.0.0-rc.17"
-  resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.0.0-rc.17.tgz#e6dcf5b5bd3ae23595bdb154b9b578ebcdffd698"
-  integrity sha512-7LHZKsFRV/HqDoMVY+cJamFzgHgsrmQFalROHC5FMWrzPzd+utG5e11krj1tVsnxYufGA2ABShX4nlcHXED+zQ==
+acorn-globals@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
+  integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
+  dependencies:
+    acorn "^7.1.1"
+    acorn-walk "^7.1.1"
+
+acorn-walk@^7.1.1:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
+  integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
 
 acorn@^7.1.1:
   version "7.4.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
+acorn@^8.7.1:
+  version "8.8.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
+  integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
+
+agent-base@6:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+  integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+  dependencies:
+    debug "4"
+
 algoliasearch@^4.0.0:
   version "4.12.1"
   resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.12.1.tgz#574a2c5424c4b6681c026928fb810be2d2ec3924"
@@ -631,19 +567,12 @@ algoliasearch@^4.0.0:
     "@algolia/requester-node-http" "4.12.1"
     "@algolia/transporter" "4.12.1"
 
-ansi-regex@^5.0.0, ansi-regex@^5.0.1:
+ansi-regex@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
   integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
 
-ansi-styles@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
-  integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
-  dependencies:
-    color-convert "^1.9.0"
-
-ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ansi-styles@^4.0.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
   integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
@@ -658,23 +587,10 @@ anymatch@~3.1.2:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-aria-query@^4.2.2:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
-  integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==
-  dependencies:
-    "@babel/runtime" "^7.10.2"
-    "@babel/runtime-corejs3" "^7.10.2"
-
-asap@~2.0.3, asap@~2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
-  integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=
-
-assert-never@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe"
-  integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw==
+argparse@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
 
 assertion-error@^1.1.0:
   version "1.1.0"
@@ -686,13 +602,6 @@ asynckit@^0.4.0:
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
 
-babel-walk@3.0.0-canary-5:
-  version "3.0.0-canary-5"
-  resolved "https://registry.yarnpkg.com/babel-walk/-/babel-walk-3.0.0-canary-5.tgz#f66ecd7298357aee44955f235a6ef54219104b11"
-  integrity sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==
-  dependencies:
-    "@babel/types" "^7.9.6"
-
 balanced-match@^1.0.0:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -703,6 +612,16 @@ binary-extensions@^2.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
+body-scroll-lock@^4.0.0-beta.0:
+  version "4.0.0-beta.0"
+  resolved "https://registry.yarnpkg.com/body-scroll-lock/-/body-scroll-lock-4.0.0-beta.0.tgz#4f78789d10e6388115c0460cd6d7d4dd2bbc4f7e"
+  integrity sha512-a7tP5+0Mw3YlUJcGAKUqIBkYYGlYxk2fnCasq/FUph1hadxlTRjF+gAcZksxANnaMnALjxEddmSi/H3OR8ugcQ==
+
+bootstrap-icons@^1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/bootstrap-icons/-/bootstrap-icons-1.9.1.tgz#cf22d91a25447645e45c49ebde4e56eafdfe761b"
+  integrity sha512-d4ZkO30MIkAhQ2nNRJqKXJVEQorALGbLWTuRxyCTJF96lRIV6imcgMehWGJUiJMJhglN0o2tqLIeDnMdiQEE9g==
+
 brace-expansion@^1.1.7:
   version "1.1.11"
   resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -718,46 +637,28 @@ braces@~3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
-buffer-from@^1.0.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
-  integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
-
-bulma@^0.9.3:
-  version "0.9.3"
-  resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.9.3.tgz#ddccb7436ebe3e21bf47afe01d3c43a296b70243"
-  integrity sha512-0d7GNW1PY4ud8TWxdNcP6Cc8Bu7MxcntD/RRLGWuiw/s0a9P+XlH/6QoOIrmbj6o8WWJzJYhytiu9nFjTszk1g==
+browser-process-hrtime@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
+  integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
 
-c8@^7.11.0:
-  version "7.11.0"
-  resolved "https://registry.yarnpkg.com/c8/-/c8-7.11.0.tgz#b3ab4e9e03295a102c47ce11d4ef6d735d9a9ac9"
-  integrity sha512-XqPyj1uvlHMr+Y1IeRndC2X5P7iJzJlEJwBpCdBbq2JocXOgJfr+JVfJkyNMGROke5LfKrhSFXGFXnwnRJAUJw==
+c8@^7.12.0:
+  version "7.12.0"
+  resolved "https://registry.yarnpkg.com/c8/-/c8-7.12.0.tgz#402db1c1af4af5249153535d1c84ad70c5c96b14"
+  integrity sha512-CtgQrHOkyxr5koX1wEUmN/5cfDa2ckbHRA4Gy5LAL0zaCFtVWJS5++n+w4/sr2GWGerBxgTjpKeDclk/Qk6W/A==
   dependencies:
     "@bcoe/v8-coverage" "^0.2.3"
-    "@istanbuljs/schema" "^0.1.2"
+    "@istanbuljs/schema" "^0.1.3"
     find-up "^5.0.0"
     foreground-child "^2.0.0"
-    istanbul-lib-coverage "^3.0.1"
+    istanbul-lib-coverage "^3.2.0"
     istanbul-lib-report "^3.0.0"
-    istanbul-reports "^3.0.2"
-    rimraf "^3.0.0"
+    istanbul-reports "^3.1.4"
+    rimraf "^3.0.2"
     test-exclude "^6.0.0"
-    v8-to-istanbul "^8.0.0"
+    v8-to-istanbul "^9.0.0"
     yargs "^16.2.0"
-    yargs-parser "^20.2.7"
-
-call-bind@^1.0.0, call-bind@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
-  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
-  dependencies:
-    function-bind "^1.1.1"
-    get-intrinsic "^1.0.2"
-
-caseless@^0.12.0, caseless@~0.12.0:
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-  integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+    yargs-parser "^20.2.9"
 
 chai@^4.3.6:
   version "4.3.6"
@@ -772,30 +673,6 @@ chai@^4.3.6:
     pathval "^1.1.1"
     type-detect "^4.0.5"
 
-chalk@^2.0.0:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
-  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
-chalk@^4.0.0, chalk@^4.1.0:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
-  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
-  dependencies:
-    ansi-styles "^4.1.0"
-    supports-color "^7.1.0"
-
-character-parser@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/character-parser/-/character-parser-2.2.0.tgz#c7ce28f36d4bcd9744e5ffc2c5fcde1c73261fc0"
-  integrity sha1-x84o821LzZdE5f/CxfzeHHMmH8A=
-  dependencies:
-    is-regex "^1.0.3"
-
 check-error@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
@@ -825,13 +702,6 @@ cliui@^7.0.2:
     strip-ansi "^6.0.0"
     wrap-ansi "^7.0.0"
 
-color-convert@^1.9.0:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
-  integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
-  dependencies:
-    color-name "1.1.3"
-
 color-convert@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@@ -839,45 +709,27 @@ color-convert@^2.0.1:
   dependencies:
     color-name "~1.1.4"
 
-color-name@1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-  integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-
 color-name@~1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
   integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
 
-combined-stream@^1.0.6:
+combined-stream@^1.0.8:
   version "1.0.8"
   resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
   integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
   dependencies:
     delayed-stream "~1.0.0"
 
+commander@^9.0.0:
+  version "9.4.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-9.4.0.tgz#bc4a40918fefe52e22450c111ecd6b7acce6f11c"
+  integrity sha512-sRPT+umqkz90UA8M1yqYfnHlZA7fF6nSphDtxeywPZ49ysjxDQybzk13CL+mXekDRG92skbcqCLVovuCusNmFw==
+
 concat-map@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-
-concat-stream@^1.6.0, concat-stream@^1.6.2:
-  version "1.6.2"
-  resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
-  integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
-  dependencies:
-    buffer-from "^1.0.0"
-    inherits "^2.0.3"
-    readable-stream "^2.2.2"
-    typedarray "^0.0.6"
-
-constantinople@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/constantinople/-/constantinople-4.0.1.tgz#0def113fa0e4dc8de83331a5cf79c8b325213151"
-  integrity sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==
-  dependencies:
-    "@babel/parser" "^7.6.0"
-    "@babel/types" "^7.6.1"
+  integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
 
 convert-source-map@^1.6.0:
   version "1.8.0"
@@ -886,16 +738,6 @@ convert-source-map@^1.6.0:
   dependencies:
     safe-buffer "~5.1.1"
 
-core-js-pure@^3.20.2:
-  version "3.21.0"
-  resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.0.tgz#819adc8dfb808205ce25b51d50591becd615db7e"
-  integrity sha512-VaJUunCZLnxuDbo1rNOzwbet9E1K9joiXS5+DQMPtgxd24wfsZbJZMMfQLGYMlCUvSxLfsRUUhoOR2x28mFfeg==
-
-core-util-is@~1.0.0:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
-  integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
-
 cross-spawn@^7.0.0:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -905,11 +747,49 @@ cross-spawn@^7.0.0:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+cssom@^0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
+  integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==
+
+cssom@~0.3.6:
+  version "0.3.8"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+  integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
+cssstyle@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+  integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
+  dependencies:
+    cssom "~0.3.6"
+
 csstype@^2.6.8:
   version "2.6.19"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa"
   integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==
 
+data-urls@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143"
+  integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==
+  dependencies:
+    abab "^2.0.6"
+    whatwg-mimetype "^3.0.0"
+    whatwg-url "^11.0.0"
+
+debug@4, debug@^4.3.4:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+  dependencies:
+    ms "2.1.2"
+
+decimal.js@^10.3.1:
+  version "10.4.0"
+  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.0.tgz#97a7448873b01e92e5ff9117d89a7bca8e63e0fe"
+  integrity sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==
+
 deep-eql@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
@@ -917,317 +797,207 @@ deep-eql@^3.0.1:
   dependencies:
     type-detect "^4.0.0"
 
+deep-is@~0.1.3:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
+  integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
 delayed-stream@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
   integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
 
-doctypes@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
-  integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=
-
-dom-accessibility-api@^0.5.6:
-  version "0.5.11"
-  resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.11.tgz#79d5846c4f90eba3e617d9031e921de9324f84ed"
-  integrity sha512-7X6GvzjYf4yTdRKuCVScV+aA9Fvh5r8WzWrXBH9w82ZWB/eYDMGCnazoC/YAqAzUJWHzLOnZqr46K3iEyUhUvw==
-
-dom-serializer@^1.0.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91"
-  integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig==
-  dependencies:
-    domelementtype "^2.0.1"
-    domhandler "^4.2.0"
-    entities "^2.0.0"
-
-domelementtype@^2.0.1, domelementtype@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
-  integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
-
-domhandler@^4.2.0, domhandler@^4.2.2:
-  version "4.3.0"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626"
-  integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g==
-  dependencies:
-    domelementtype "^2.2.0"
-
-domutils@^2.8.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
-  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+domexception@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673"
+  integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==
   dependencies:
-    dom-serializer "^1.0.1"
-    domelementtype "^2.2.0"
-    domhandler "^4.2.0"
+    webidl-conversions "^7.0.0"
 
-emmet@^2.3.0:
-  version "2.3.5"
-  resolved "https://registry.yarnpkg.com/emmet/-/emmet-2.3.5.tgz#7f80f9c3db6831d1ee2b458717b9c36a074b1a47"
-  integrity sha512-LcWfTamJnXIdMfLvJEC5Ld3hY5/KHXgv1L1bp6I7eEvB0ZhacHZ1kX0BYovJ8FroEsreLcq7n7kZhRMsf6jkXQ==
-  dependencies:
-    "@emmetio/abbreviation" "^2.2.2"
-    "@emmetio/css-abbreviation" "^2.1.4"
+dompurify@^2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.0.tgz#c9c88390f024c2823332615c9e20a453cf3825dd"
+  integrity sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==
 
 emoji-regex@^8.0.0:
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
   integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
 
-entities@^2.0.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
-  integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
-
-entities@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
-  integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
-
-esbuild-android-arm64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.13.15.tgz#3fc3ff0bab76fe35dd237476b5d2b32bb20a3d44"
-  integrity sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==
-
-esbuild-android-arm64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.21.tgz#8842d0c3b7c81fbe2dc46ddb416ffd6eb822184b"
-  integrity sha512-Bqgld1TY0wZv8TqiQmVxQFgYzz8ZmyzT7clXBDZFkOOdRybzsnj8AZuK1pwcLVA7Ya6XncHgJqIao7NFd3s0RQ==
-
-esbuild-darwin-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.13.15.tgz#8e9169c16baf444eacec60d09b24d11b255a8e72"
-  integrity sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==
-
-esbuild-darwin-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.21.tgz#ec7df02ad88ecf7f8fc23a3ed7917e07dea0c9c9"
-  integrity sha512-j+Eg+e13djzyYINVvAbOo2/zvZ2DivuJJTaBrJnJHSD7kUNuGHRkHoSfFjbI80KHkn091w350wdmXDNSgRjfYQ==
-
-esbuild-darwin-arm64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.13.15.tgz#1b07f893b632114f805e188ddfca41b2b778229a"
-  integrity sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==
-
-esbuild-darwin-arm64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.21.tgz#0c2a977edec1ef54097ee56a911518c820d4e5e4"
-  integrity sha512-nDNTKWDPI0RuoPj5BhcSB2z5EmZJJAyRtZLIjyXSqSpAyoB8eyAKXl4lB8U2P78Fnh4Lh1le/fmpewXE04JhBQ==
-
-esbuild-freebsd-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.13.15.tgz#0b8b7eca1690c8ec94c75680c38c07269c1f4a85"
-  integrity sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==
-
-esbuild-freebsd-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.21.tgz#f5b5fc1d031286c3a0949d1bda7db774b7d0404e"
-  integrity sha512-zIurkCHXhxELiDZtLGiexi8t8onQc2LtuE+S7457H/pP0g0MLRKMrsn/IN4LDkNe6lvBjuoZZi2OfelOHn831g==
-
-esbuild-freebsd-arm64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.13.15.tgz#2e1a6c696bfdcd20a99578b76350b41db1934e52"
-  integrity sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==
-
-esbuild-freebsd-arm64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.21.tgz#a05cab908013e4992b31a675850b8c44eb468c0c"
-  integrity sha512-wdxMmkJfbwcN+q85MpeUEamVZ40FNsBa9mPq8tAszDn8TRT2HoJvVRADPIIBa9SWWwlDChIMjkDKAnS3KS/sPA==
-
-esbuild-linux-32@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.13.15.tgz#6fd39f36fc66dd45b6b5f515728c7bbebc342a69"
-  integrity sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==
-
-esbuild-linux-32@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.21.tgz#638d244cc58b951f447addb4bade628d126ef84b"
-  integrity sha512-fmxvyzOPPh2xiEHojpCeIQP6pXcoKsWbz3ryDDIKLOsk4xp3GbpHIEAWP0xTeuhEbendmvBDVKbAVv3PnODXLg==
-
-esbuild-linux-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.13.15.tgz#9cb8e4bcd7574e67946e4ee5f1f1e12386bb6dd3"
-  integrity sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==
-
-esbuild-linux-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.21.tgz#8eb634abee928be7e35b985fafbfef2f2e31397f"
-  integrity sha512-edZyNOv1ql+kpmlzdqzzDjRQYls+tSyi4QFi+PdBhATJFUqHsnNELWA9vMSzAaInPOEaVUTA5Ml28XFChcy4DA==
-
-esbuild-linux-arm64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.13.15.tgz#3891aa3704ec579a1b92d2a586122e5b6a2bfba1"
-  integrity sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==
-
-esbuild-linux-arm64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.21.tgz#e05599ea6253b58394157da162d856f3ead62f9e"
-  integrity sha512-t5qxRkq4zdQC0zXpzSB2bTtfLgOvR0C6BXYaRE/6/k8/4SrkZcTZBeNu+xGvwCU4b5dU9ST9pwIWkK6T1grS8g==
-
-esbuild-linux-arm@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.13.15.tgz#8a00e99e6a0c6c9a6b7f334841364d8a2b4aecfe"
-  integrity sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==
-
-esbuild-linux-arm@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.21.tgz#1ae1078231cf689d3ba894a32d3723c0be9b91fd"
-  integrity sha512-aSU5pUueK6afqmLQsbU+QcFBT62L+4G9hHMJDHWfxgid6hzhSmfRH9U/f+ymvxsSTr/HFRU4y7ox8ZyhlVl98w==
-
-esbuild-linux-mips64le@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.13.15.tgz#36b07cc47c3d21e48db3bb1f4d9ef8f46aead4f7"
-  integrity sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==
-
-esbuild-linux-mips64le@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.21.tgz#f05be62d126764e99b37edcac5bb49b78c7a8890"
-  integrity sha512-jLZLQGCNlUsmIHtGqNvBs3zN+7a4D9ckf0JZ+jQTwHdZJ1SgV9mAjbB980OFo66LoY+WeM7t3WEnq3FjI1zw4A==
-
-esbuild-linux-ppc64le@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.13.15.tgz#f7e6bba40b9a11eb9dcae5b01550ea04670edad2"
-  integrity sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==
-
-esbuild-linux-ppc64le@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.21.tgz#592c98d82dad7982268ef8deed858c4566f07ab1"
-  integrity sha512-4TWxpK391en2UBUw6GSrukToTDu6lL9vkm3Ll40HrI08WG3qcnJu7bl8e1+GzelDsiw1QmfAY/nNvJ6iaHRpCQ==
-
-esbuild-linux-riscv64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.21.tgz#0db7bd6f10d8f9afea973a7d6bf87b449b864b7b"
-  integrity sha512-fElngqOaOfTsF+u+oetDLHsPG74vB2ZaGZUqmGefAJn3a5z9Z2pNa4WpVbbKgHpaAAy5tWM1m1sbGohj6Ki6+Q==
-
-esbuild-linux-s390x@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.21.tgz#254a9354d34c9d1b41a3e21d2ec9269cbbb2c5df"
-  integrity sha512-brleZ6R5fYv0qQ7ZBwenQmP6i9TdvJCB092c/3D3pTLQHBGHJb5zWgKxOeS7bdHzmLy6a6W7GbFk6QKpjyD6QA==
-
-esbuild-netbsd-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.13.15.tgz#a2fedc549c2b629d580a732d840712b08d440038"
-  integrity sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==
-
-esbuild-netbsd-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.21.tgz#4cb783d060b02bf3b897a9a12cce2b3b547726f8"
-  integrity sha512-nCEgsLCQ8RoFWVV8pVI+kX66ICwbPP/M9vEa0NJGIEB/Vs5sVGMqkf67oln90XNSkbc0bPBDuo4G6FxlF7PN8g==
-
-esbuild-openbsd-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.13.15.tgz#b22c0e5806d3a1fbf0325872037f885306b05cd7"
-  integrity sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==
-
-esbuild-openbsd-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.21.tgz#f886b93feefddbe573528fa4b421c9c6e2bc969b"
-  integrity sha512-h9zLMyVD0T73MDTVYIb/qUTokwI6EJH9O6wESuTNq6+XpMSr6C5aYZ4fvFKdNELW+Xsod+yDS2hV2JTUAbFrLA==
-
-esbuild-sunos-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.13.15.tgz#d0b6454a88375ee8d3964daeff55c85c91c7cef4"
-  integrity sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==
-
-esbuild-sunos-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.21.tgz#3829e4d57d4cb6950837fe90b0b67cdfb37cf13a"
-  integrity sha512-Kl+7Cot32qd9oqpLdB1tEGXEkjBlijrIxMJ0+vlDFaqsODutif25on0IZlFxEBtL2Gosd4p5WCV1U7UskNQfXA==
-
-esbuild-windows-32@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.13.15.tgz#c96d0b9bbb52f3303322582ef8e4847c5ad375a7"
-  integrity sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==
-
-esbuild-windows-32@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.21.tgz#b858a22d1a82e53cdc59310cd56294133f7a95e7"
-  integrity sha512-V7vnTq67xPBUCk/9UtlolmQ798Ecjdr1ZoI1vcSgw7M82aSSt0eZdP6bh5KAFZU8pxDcx3qoHyWQfHYr11f22A==
-
-esbuild-windows-64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.13.15.tgz#1f79cb9b1e1bb02fb25cd414cb90d4ea2892c294"
-  integrity sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==
-
-esbuild-windows-64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.21.tgz#7bb5a027d5720cf9caf18a4bedd11327208f1f12"
-  integrity sha512-kDgHjKOHwjfJDCyRGELzVxiP/RBJBTA+wyspf78MTTJQkyPuxH2vChReNdWc+dU2S4gIZFHMdP1Qrl/k22ZmaA==
-
-esbuild-windows-arm64@0.13.15:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.13.15.tgz#482173070810df22a752c686509c370c3be3b3c3"
-  integrity sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==
-
-esbuild-windows-arm64@0.14.21:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.21.tgz#25df54521ad602c826b262ea2e7cc1fe80f5c2f5"
-  integrity sha512-8Sbo0zpzgwWrwjQYLmHF78f7E2xg5Ve63bjB2ng3V2aManilnnTGaliq2snYg+NOX60+hEvJHRdVnuIAHW0lVw==
-
-esbuild@^0.13.12:
-  version "0.13.15"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.13.15.tgz#db56a88166ee373f87dbb2d8798ff449e0450cdf"
-  integrity sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==
-  optionalDependencies:
-    esbuild-android-arm64 "0.13.15"
-    esbuild-darwin-64 "0.13.15"
-    esbuild-darwin-arm64 "0.13.15"
-    esbuild-freebsd-64 "0.13.15"
-    esbuild-freebsd-arm64 "0.13.15"
-    esbuild-linux-32 "0.13.15"
-    esbuild-linux-64 "0.13.15"
-    esbuild-linux-arm "0.13.15"
-    esbuild-linux-arm64 "0.13.15"
-    esbuild-linux-mips64le "0.13.15"
-    esbuild-linux-ppc64le "0.13.15"
-    esbuild-netbsd-64 "0.13.15"
-    esbuild-openbsd-64 "0.13.15"
-    esbuild-sunos-64 "0.13.15"
-    esbuild-windows-32 "0.13.15"
-    esbuild-windows-64 "0.13.15"
-    esbuild-windows-arm64 "0.13.15"
-
-esbuild@^0.14.14:
-  version "0.14.21"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.21.tgz#b3e05f900f1c4394f596d60d63d9816468f0f671"
-  integrity sha512-7WEoNMBJdLN993dr9h0CpFHPRc3yFZD+EAVY9lg6syJJ12gc5fHq8d75QRExuhnMkT2DaRiIKFThRvDWP+fO+A==
+entities@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174"
+  integrity sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==
+
+esbuild-android-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz#505f41832884313bbaffb27704b8bcaa2d8616be"
+  integrity sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==
+
+esbuild-android-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz#8ce69d7caba49646e009968fe5754a21a9871771"
+  integrity sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==
+
+esbuild-darwin-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz#24ba67b9a8cb890a3c08d9018f887cc221cdda25"
+  integrity sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==
+
+esbuild-darwin-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz#3f7cdb78888ee05e488d250a2bdaab1fa671bf73"
+  integrity sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==
+
+esbuild-freebsd-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz#09250f997a56ed4650f3e1979c905ffc40bbe94d"
+  integrity sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==
+
+esbuild-freebsd-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz#bafb46ed04fc5f97cbdb016d86947a79579f8e48"
+  integrity sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==
+
+esbuild-linux-32@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz#e2a8c4a8efdc355405325033fcebeb941f781fe5"
+  integrity sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==
+
+esbuild-linux-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz#de5fdba1c95666cf72369f52b40b03be71226652"
+  integrity sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==
+
+esbuild-linux-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz#dae4cd42ae9787468b6a5c158da4c84e83b0ce8b"
+  integrity sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==
+
+esbuild-linux-arm@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz#a2c1dff6d0f21dbe8fc6998a122675533ddfcd59"
+  integrity sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==
+
+esbuild-linux-mips64le@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz#d9918e9e4cb972f8d6dae8e8655bf9ee131eda34"
+  integrity sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==
+
+esbuild-linux-ppc64le@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz#3f9a0f6d41073fb1a640680845c7de52995f137e"
+  integrity sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==
+
+esbuild-linux-riscv64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz#618853c028178a61837bc799d2013d4695e451c8"
+  integrity sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==
+
+esbuild-linux-s390x@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz#d1885c4c5a76bbb5a0fe182e2c8c60eb9e29f2a6"
+  integrity sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==
+
+esbuild-netbsd-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz#69ae917a2ff241b7df1dbf22baf04bd330349e81"
+  integrity sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==
+
+esbuild-openbsd-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz#db4c8495287a350a6790de22edea247a57c5d47b"
+  integrity sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==
+
+esbuild-sunos-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz#54287ee3da73d3844b721c21bc80c1dc7e1bf7da"
+  integrity sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==
+
+esbuild-windows-32@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz#f8aaf9a5667630b40f0fb3aa37bf01bbd340ce31"
+  integrity sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==
+
+esbuild-windows-64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz#bf54b51bd3e9b0f1886ffdb224a4176031ea0af4"
+  integrity sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==
+
+esbuild-windows-arm64@0.14.54:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz#937d15675a15e4b0e4fafdbaa3a01a776a2be982"
+  integrity sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==
+
+esbuild@^0.14.47:
+  version "0.14.54"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.54.tgz#8b44dcf2b0f1a66fc22459943dccf477535e9aa2"
+  integrity sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==
   optionalDependencies:
-    esbuild-android-arm64 "0.14.21"
-    esbuild-darwin-64 "0.14.21"
-    esbuild-darwin-arm64 "0.14.21"
-    esbuild-freebsd-64 "0.14.21"
-    esbuild-freebsd-arm64 "0.14.21"
-    esbuild-linux-32 "0.14.21"
-    esbuild-linux-64 "0.14.21"
-    esbuild-linux-arm "0.14.21"
-    esbuild-linux-arm64 "0.14.21"
-    esbuild-linux-mips64le "0.14.21"
-    esbuild-linux-ppc64le "0.14.21"
-    esbuild-linux-riscv64 "0.14.21"
-    esbuild-linux-s390x "0.14.21"
-    esbuild-netbsd-64 "0.14.21"
-    esbuild-openbsd-64 "0.14.21"
-    esbuild-sunos-64 "0.14.21"
-    esbuild-windows-32 "0.14.21"
-    esbuild-windows-64 "0.14.21"
-    esbuild-windows-arm64 "0.14.21"
+    "@esbuild/linux-loong64" "0.14.54"
+    esbuild-android-64 "0.14.54"
+    esbuild-android-arm64 "0.14.54"
+    esbuild-darwin-64 "0.14.54"
+    esbuild-darwin-arm64 "0.14.54"
+    esbuild-freebsd-64 "0.14.54"
+    esbuild-freebsd-arm64 "0.14.54"
+    esbuild-linux-32 "0.14.54"
+    esbuild-linux-64 "0.14.54"
+    esbuild-linux-arm "0.14.54"
+    esbuild-linux-arm64 "0.14.54"
+    esbuild-linux-mips64le "0.14.54"
+    esbuild-linux-ppc64le "0.14.54"
+    esbuild-linux-riscv64 "0.14.54"
+    esbuild-linux-s390x "0.14.54"
+    esbuild-netbsd-64 "0.14.54"
+    esbuild-openbsd-64 "0.14.54"
+    esbuild-sunos-64 "0.14.54"
+    esbuild-windows-32 "0.14.54"
+    esbuild-windows-64 "0.14.54"
+    esbuild-windows-arm64 "0.14.54"
 
 escalade@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
   integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
 
-escape-string-regexp@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-  integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escodegen@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
+  integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==
+  dependencies:
+    esprima "^4.0.1"
+    estraverse "^5.2.0"
+    esutils "^2.0.2"
+    optionator "^0.8.1"
+  optionalDependencies:
+    source-map "~0.6.1"
 
-estree-walker@^2.0.2:
+esprima@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+  integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+estraverse@^5.2.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+  integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-walker@^2.0.1, estree-walker@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
   integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
 
+esutils@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+  integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+fast-levenshtein@~2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+  integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
 fill-range@^7.0.1:
   version "7.0.1"
   resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
@@ -1251,19 +1021,19 @@ foreground-child@^2.0.0:
     cross-spawn "^7.0.0"
     signal-exit "^3.0.2"
 
-form-data@^2.2.0:
-  version "2.5.1"
-  resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
-  integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
+form-data@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
+  integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
   dependencies:
     asynckit "^0.4.0"
-    combined-stream "^1.0.6"
+    combined-stream "^1.0.8"
     mime-types "^2.1.12"
 
 fs.realpath@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-  integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+  integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
 
 fsevents@~2.3.2:
   version "2.3.2"
@@ -1285,20 +1055,6 @@ get-func-name@^2.0.0:
   resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
   integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=
 
-get-intrinsic@^1.0.2:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
-  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
-  dependencies:
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.1"
-
-get-port@^3.1.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc"
-  integrity sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=
-
 glob-parent@~5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -1307,51 +1063,22 @@ glob-parent@~5.1.2:
     is-glob "^4.0.1"
 
 glob@^7.1.3, glob@^7.1.4:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
-  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+  version "7.2.3"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+  integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
     inherits "2"
-    minimatch "^3.0.4"
+    minimatch "^3.1.1"
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-happy-dom@^2.31.1:
-  version "2.31.1"
-  resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-2.31.1.tgz#7bc30b61048839d1237e3adac413edb51da642f0"
-  integrity sha512-hbTLxMqyluLT06nRN4TDGLjjKni73tZlvLdF6qGfdv5U4EnrSYSwcZK3ESmv0LEEa5St7NY7e62rhISotH8O3Q==
-  dependencies:
-    he "^1.1.1"
-    node-fetch "^2.6.1"
-    sync-request "^6.1.0"
-    webidl-conversions "^7.0.0"
-    whatwg-encoding "^1.0.5"
-    whatwg-mimetype "^2.3.0"
-
-has-flag@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
-  integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
-
 has-flag@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
   integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
 
-has-symbols@^1.0.1, has-symbols@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
-  integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
-
-has-tostringtag@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
-  integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
-  dependencies:
-    has-symbols "^1.0.2"
-
 has@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -1359,49 +1086,41 @@ has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
-he@^1.1.1:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
-  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+html-encoding-sniffer@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9"
+  integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==
+  dependencies:
+    whatwg-encoding "^2.0.0"
 
 html-escaper@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
   integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
 
-htmlparser2@^7.1.2:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5"
-  integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==
-  dependencies:
-    domelementtype "^2.0.1"
-    domhandler "^4.2.2"
-    domutils "^2.8.0"
-    entities "^3.0.1"
-
-http-basic@^8.1.1:
-  version "8.1.3"
-  resolved "https://registry.yarnpkg.com/http-basic/-/http-basic-8.1.3.tgz#a7cabee7526869b9b710136970805b1004261bbf"
-  integrity sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==
+http-proxy-agent@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
+  integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==
   dependencies:
-    caseless "^0.12.0"
-    concat-stream "^1.6.2"
-    http-response-object "^3.0.1"
-    parse-cache-control "^1.0.1"
+    "@tootallnate/once" "2"
+    agent-base "6"
+    debug "4"
 
-http-response-object@^3.0.1:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/http-response-object/-/http-response-object-3.0.2.tgz#7f435bb210454e4360d074ef1f989d5ea8aa9810"
-  integrity sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==
+https-proxy-agent@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+  integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
   dependencies:
-    "@types/node" "^10.0.3"
+    agent-base "6"
+    debug "4"
 
-iconv-lite@0.4.24:
-  version "0.4.24"
-  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
-  integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+iconv-lite@0.6.3:
+  version "0.6.3"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
+  integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
   dependencies:
-    safer-buffer ">= 2.1.2 < 3"
+    safer-buffer ">= 2.1.2 < 3.0.0"
 
 immutable@^4.0.0:
   version "4.0.0"
@@ -1411,12 +1130,12 @@ immutable@^4.0.0:
 inflight@^1.0.4:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
-  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
   dependencies:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -1428,21 +1147,13 @@ is-binary-path@~2.1.0:
   dependencies:
     binary-extensions "^2.0.0"
 
-is-core-module@^2.8.1:
-  version "2.8.1"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
-  integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
+is-core-module@^2.9.0:
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed"
+  integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==
   dependencies:
     has "^1.0.3"
 
-is-expression@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/is-expression/-/is-expression-4.0.0.tgz#c33155962abf21d0afd2552514d67d2ec16fd2ab"
-  integrity sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==
-  dependencies:
-    acorn "^7.1.1"
-    object-assign "^4.1.1"
-
 is-extglob@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -1465,30 +1176,17 @@ is-number@^7.0.0:
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-is-promise@^2.0.0:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
-  integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==
-
-is-regex@^1.0.3:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
-  integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
-  dependencies:
-    call-bind "^1.0.2"
-    has-tostringtag "^1.0.0"
-
-isarray@~1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
-  integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+is-potential-custom-element-name@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
+  integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
 
 isexe@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-  integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+  integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
 
-istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.1:
+istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
   integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
@@ -1502,46 +1200,71 @@ istanbul-lib-report@^3.0.0:
     make-dir "^3.0.0"
     supports-color "^7.1.0"
 
-istanbul-reports@^3.0.2:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c"
-  integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==
+istanbul-reports@^3.1.4:
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae"
+  integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==
   dependencies:
     html-escaper "^2.0.0"
     istanbul-lib-report "^3.0.0"
 
-js-stringify@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/js-stringify/-/js-stringify-1.0.2.tgz#1736fddfd9724f28a3682adc6230ae7e4e9679db"
-  integrity sha1-Fzb939lyTyijaCrcYjCufk6Weds=
-
-js-tokens@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-jsonc-parser@^2.3.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.1.tgz#59549150b133f2efacca48fe9ce1ec0659af2342"
-  integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==
+js-yaml@4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+  integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+  dependencies:
+    argparse "^2.0.1"
+
+jsdom@^20.0.0:
+  version "20.0.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.0.tgz#882825ac9cc5e5bbee704ba16143e1fa78361ebf"
+  integrity sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==
+  dependencies:
+    abab "^2.0.6"
+    acorn "^8.7.1"
+    acorn-globals "^6.0.0"
+    cssom "^0.5.0"
+    cssstyle "^2.3.0"
+    data-urls "^3.0.2"
+    decimal.js "^10.3.1"
+    domexception "^4.0.0"
+    escodegen "^2.0.0"
+    form-data "^4.0.0"
+    html-encoding-sniffer "^3.0.0"
+    http-proxy-agent "^5.0.0"
+    https-proxy-agent "^5.0.1"
+    is-potential-custom-element-name "^1.0.1"
+    nwsapi "^2.2.0"
+    parse5 "^7.0.0"
+    saxes "^6.0.0"
+    symbol-tree "^3.2.4"
+    tough-cookie "^4.0.0"
+    w3c-hr-time "^1.0.2"
+    w3c-xmlserializer "^3.0.0"
+    webidl-conversions "^7.0.0"
+    whatwg-encoding "^2.0.0"
+    whatwg-mimetype "^3.0.0"
+    whatwg-url "^11.0.0"
+    ws "^8.8.0"
+    xml-name-validator "^4.0.0"
 
 jsonc-parser@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22"
   integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==
 
-jstransformer@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/jstransformer/-/jstransformer-1.0.0.tgz#ed8bf0921e2f3f1ed4d5c1a44f68709ed24722c3"
-  integrity sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=
+levn@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
+  integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==
   dependencies:
-    is-promise "^2.0.0"
-    promise "^7.0.1"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
 
-local-pkg@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.1.tgz#e7b0d7aa0b9c498a1110a5ac5b00ba66ef38cfff"
-  integrity sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==
+local-pkg@^0.4.2:
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f"
+  integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg==
 
 locate-path@^6.0.0:
   version "6.0.0"
@@ -1564,11 +1287,6 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
-lz-string@^1.4.4:
-  version "1.4.4"
-  resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
-  integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
-
 magic-string@^0.25.7:
   version "0.25.7"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
@@ -1595,47 +1313,57 @@ mime-types@^2.1.12:
   dependencies:
     mime-db "1.51.0"
 
-minimatch@^3.0.4:
-  version "3.0.5"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.5.tgz#4da8f1290ee0f0f8e83d60ca69f8f134068604a3"
-  integrity sha512-tUpxzX0VAzJHjLu0xUfFv1gwVp9ba3IOuRAVH2EGuRW8a5emA2FlACLqiT/lDVtS1W+TGNwqz3sWaNyLgDJWuw==
+minimatch@^3.0.4, minimatch@^3.1.1:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
   dependencies:
     brace-expansion "^1.1.7"
 
+ms@2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
 nanoid@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c"
   integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==
 
-node-fetch@^2.6.1:
-  version "2.6.7"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
-  integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
-  dependencies:
-    whatwg-url "^5.0.0"
+nanoid@^3.3.4:
+  version "3.3.4"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
+  integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
 
 normalize-path@^3.0.0, normalize-path@~3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
   integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
 
-object-assign@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
-  integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
-
-object-inspect@^1.9.0:
-  version "1.12.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
-  integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==
+nwsapi@^2.2.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.2.tgz#e5418863e7905df67d51ec95938d67bf801f0bb0"
+  integrity sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==
 
 once@^1.3.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
-  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
   dependencies:
     wrappy "1"
 
+optionator@^0.8.1:
+  version "0.8.3"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
+  integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
+  dependencies:
+    deep-is "~0.1.3"
+    fast-levenshtein "~2.0.6"
+    levn "~0.3.0"
+    prelude-ls "~1.1.2"
+    type-check "~0.3.2"
+    word-wrap "~1.2.3"
+
 p-limit@^3.0.2:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
@@ -1650,10 +1378,12 @@ p-locate@^5.0.0:
   dependencies:
     p-limit "^3.0.2"
 
-parse-cache-control@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/parse-cache-control/-/parse-cache-control-1.0.1.tgz#8eeab3e54fa56920fe16ba38f77fa21aacc2d74e"
-  integrity sha1-juqz5U+laSD+Fro493+iGqzC104=
+parse5@^7.0.0:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746"
+  integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==
+  dependencies:
+    entities "^4.4.0"
 
 path-exists@^4.0.0:
   version "4.0.0"
@@ -1663,7 +1393,7 @@ path-exists@^4.0.0:
 path-is-absolute@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
-  integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+  integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
 
 path-key@^3.1.0:
   version "3.1.1"
@@ -1685,12 +1415,12 @@ picocolors@^1.0.0:
   resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
   integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
 
-picomatch@^2.0.4, picomatch@^2.2.1:
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2:
   version "2.3.1"
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
   integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
 
-postcss@^8.1.10, postcss@^8.4.5:
+postcss@^8.1.10:
   version "8.4.6"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
   integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==
@@ -1699,172 +1429,39 @@ postcss@^8.1.10, postcss@^8.4.5:
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
+postcss@^8.4.16:
+  version "8.4.16"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c"
+  integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==
+  dependencies:
+    nanoid "^3.3.4"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
 preact@^10.0.0:
   version "10.6.5"
   resolved "https://registry.yarnpkg.com/preact/-/preact-10.6.5.tgz#726d8bd12903a0d51cdd17e2e1b90cc539403e0c"
   integrity sha512-i+LXM6JiVjQXSt2jG2vZZFapGpCuk1fl8o6ii3G84MA3xgj686FKjs4JFDkmUVhtxyq21+4ay74zqPykz9hU6w==
 
-pretty-format@^26.6.2:
-  version "26.6.2"
-  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
-  integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==
-  dependencies:
-    "@jest/types" "^26.6.2"
-    ansi-regex "^5.0.0"
-    ansi-styles "^4.0.0"
-    react-is "^17.0.1"
-
-prismjs@^1.25.0:
-  version "1.26.0"
-  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.26.0.tgz#16881b594828bb6b45296083a8cbab46b0accd47"
-  integrity sha512-HUoH9C5Z3jKkl3UunCyiD5jwk0+Hz0fIgQ2nbwU2Oo/ceuTAQAg+pPVnfdt2TJWRVLcxKh9iuoYDUSc8clb5UQ==
-
-process-nextick-args@~2.0.0:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
-  integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
-
-promise@^7.0.1:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"
-  integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==
-  dependencies:
-    asap "~2.0.3"
-
-promise@^8.0.0:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e"
-  integrity sha512-W04AqnILOL/sPRXziNicCjSNRruLAuIHEOVBazepu0545DDNGYHz7ar9ZgZ1fMU8/MA4mVxp5rkBWRi6OXIy3Q==
-  dependencies:
-    asap "~2.0.6"
-
-pug-attrs@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pug-attrs/-/pug-attrs-3.0.0.tgz#b10451e0348165e31fad1cc23ebddd9dc7347c41"
-  integrity sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==
-  dependencies:
-    constantinople "^4.0.1"
-    js-stringify "^1.0.2"
-    pug-runtime "^3.0.0"
-
-pug-code-gen@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/pug-code-gen/-/pug-code-gen-3.0.2.tgz#ad190f4943133bf186b60b80de483100e132e2ce"
-  integrity sha512-nJMhW16MbiGRiyR4miDTQMRWDgKplnHyeLvioEJYbk1RsPI3FuA3saEP8uwnTb2nTJEKBU90NFVWJBk4OU5qyg==
-  dependencies:
-    constantinople "^4.0.1"
-    doctypes "^1.1.0"
-    js-stringify "^1.0.2"
-    pug-attrs "^3.0.0"
-    pug-error "^2.0.0"
-    pug-runtime "^3.0.0"
-    void-elements "^3.1.0"
-    with "^7.0.0"
-
-pug-error@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pug-error/-/pug-error-2.0.0.tgz#5c62173cb09c34de2a2ce04f17b8adfec74d8ca5"
-  integrity sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==
-
-pug-filters@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/pug-filters/-/pug-filters-4.0.0.tgz#d3e49af5ba8472e9b7a66d980e707ce9d2cc9b5e"
-  integrity sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==
-  dependencies:
-    constantinople "^4.0.1"
-    jstransformer "1.0.0"
-    pug-error "^2.0.0"
-    pug-walk "^2.0.0"
-    resolve "^1.15.1"
-
-pug-lexer@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/pug-lexer/-/pug-lexer-5.0.1.tgz#ae44628c5bef9b190b665683b288ca9024b8b0d5"
-  integrity sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==
-  dependencies:
-    character-parser "^2.2.0"
-    is-expression "^4.0.0"
-    pug-error "^2.0.0"
-
-pug-linker@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/pug-linker/-/pug-linker-4.0.0.tgz#12cbc0594fc5a3e06b9fc59e6f93c146962a7708"
-  integrity sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==
-  dependencies:
-    pug-error "^2.0.0"
-    pug-walk "^2.0.0"
-
-pug-load@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pug-load/-/pug-load-3.0.0.tgz#9fd9cda52202b08adb11d25681fb9f34bd41b662"
-  integrity sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==
-  dependencies:
-    object-assign "^4.1.1"
-    pug-walk "^2.0.0"
-
-pug-parser@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/pug-parser/-/pug-parser-6.0.0.tgz#a8fdc035863a95b2c1dc5ebf4ecf80b4e76a1260"
-  integrity sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==
-  dependencies:
-    pug-error "^2.0.0"
-    token-stream "1.0.0"
-
-pug-runtime@^3.0.0, pug-runtime@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/pug-runtime/-/pug-runtime-3.0.1.tgz#f636976204723f35a8c5f6fad6acda2a191b83d7"
-  integrity sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==
+prelude-ls@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
+  integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
 
-pug-strip-comments@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz#f94b07fd6b495523330f490a7f554b4ff876303e"
-  integrity sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==
-  dependencies:
-    pug-error "^2.0.0"
+psl@^1.1.33:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
+  integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
 
-pug-walk@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/pug-walk/-/pug-walk-2.0.0.tgz#417aabc29232bb4499b5b5069a2b2d2a24d5f5fe"
-  integrity sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==
+punycode@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-pug@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/pug/-/pug-3.0.2.tgz#f35c7107343454e43bc27ae0ff76c731b78ea535"
-  integrity sha512-bp0I/hiK1D1vChHh6EfDxtndHji55XP/ZJKwsRqrz6lRia6ZC2OZbdAymlxdVFwd1L70ebrVJw4/eZ79skrIaw==
-  dependencies:
-    pug-code-gen "^3.0.2"
-    pug-filters "^4.0.0"
-    pug-lexer "^5.0.1"
-    pug-linker "^4.0.0"
-    pug-load "^3.0.0"
-    pug-parser "^6.0.0"
-    pug-runtime "^3.0.1"
-    pug-strip-comments "^2.0.0"
-
-qs@^6.4.0:
-  version "6.10.3"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
-  integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
-  dependencies:
-    side-channel "^1.0.4"
-
-react-is@^17.0.1:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
-  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-
-readable-stream@^2.2.2:
-  version "2.3.7"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
-  integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
-  dependencies:
-    core-util-is "~1.0.0"
-    inherits "~2.0.3"
-    isarray "~1.0.0"
-    process-nextick-args "~2.0.0"
-    safe-buffer "~5.1.1"
-    string_decoder "~1.1.1"
-    util-deprecate "~1.0.1"
+querystringify@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+  integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
 
 readdirp@~3.6.0:
   version "3.6.0"
@@ -1873,72 +1470,74 @@ readdirp@~3.6.0:
   dependencies:
     picomatch "^2.2.1"
 
-regenerator-runtime@^0.13.4:
-  version "0.13.9"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
-  integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
-
-request-light@^0.5.4:
-  version "0.5.7"
-  resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.7.tgz#1c448c22153b55d2cd278eb414df24a5ad6e6d5e"
-  integrity sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw==
-
 require-directory@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-  integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+  integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
 
-resolve@^1.15.1, resolve@^1.20.0, resolve@^1.22.0:
-  version "1.22.0"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
-  integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
+requires-port@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+  integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
+resolve@^1.22.1:
+  version "1.22.1"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+  integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
   dependencies:
-    is-core-module "^2.8.1"
+    is-core-module "^2.9.0"
     path-parse "^1.0.7"
     supports-preserve-symlinks-flag "^1.0.0"
 
-rimraf@^3.0.0:
+rimraf@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
   integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
   dependencies:
     glob "^7.1.3"
 
-rollup@^2.59.0:
-  version "2.67.0"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.67.0.tgz#496de7e641dbe39f681c5a82419cb5013917d406"
-  integrity sha512-W83AaERwvDiHwHEF/dfAfS3z1Be5wf7n+pO3ZAO5IQadCT2lBTr7WQ2MwZZe+nodbD+n3HtC4OCOAdsOPPcKZQ==
+"rollup@>=2.75.6 <2.77.0 || ~2.77.0":
+  version "2.77.3"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.77.3.tgz#8f00418d3a2740036e15deb653bed1a90ee0cc12"
+  integrity sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==
   optionalDependencies:
     fsevents "~2.3.2"
 
-safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-"safer-buffer@>= 2.1.2 < 3":
+"safer-buffer@>= 2.1.2 < 3.0.0":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
-sass@^1.49.7:
-  version "1.49.7"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.7.tgz#22a86a50552b9b11f71404dfad1b9ff44c6b0c49"
-  integrity sha512-13dml55EMIR2rS4d/RDHHP0sXMY3+30e1TKsyXaSz3iLWVoDWEoboY8WzJd5JMnxrRHffKO3wq2mpJ0jxRJiEQ==
+sass@^1.54.8:
+  version "1.54.8"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.8.tgz#4adef0dd86ea2b1e4074f551eeda4fc5f812a996"
+  integrity sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
     source-map-js ">=0.6.2 <2.0.0"
 
+saxes@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5"
+  integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==
+  dependencies:
+    xmlchars "^2.2.0"
+
 semver@^6.0.0:
   version "6.3.0"
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.3.5:
-  version "7.3.5"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
-  integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+semver@^7.3.7:
+  version "7.3.7"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+  integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
   dependencies:
     lru-cache "^6.0.0"
 
@@ -1954,14 +1553,21 @@ shebang-regex@^3.0.0:
   resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
   integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
 
-side-channel@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
-  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+shiki@^0.11.1:
+  version "0.11.1"
+  resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.11.1.tgz#df0f719e7ab592c484d8b73ec10e215a503ab8cc"
+  integrity sha512-EugY9VASFuDqOexOgXR18ZV+TbFrQHeCpEYaXamO+SZlsnT/2LxuLBX25GGtIrwaEVFXUAbUQ601SWE2rMwWHA==
+  dependencies:
+    jsonc-parser "^3.0.0"
+    vscode-oniguruma "^1.6.1"
+    vscode-textmate "^6.0.0"
+
+showdown@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/showdown/-/showdown-2.1.0.tgz#1251f5ed8f773f0c0c7bfc8e6fd23581f9e545c5"
+  integrity sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==
   dependencies:
-    call-bind "^1.0.0"
-    get-intrinsic "^1.0.2"
-    object-inspect "^1.9.0"
+    commander "^9.0.0"
 
 signal-exit@^3.0.2:
   version "3.0.7"
@@ -1973,22 +1579,17 @@ signal-exit@^3.0.2:
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 
-source-map@^0.6.1:
+source-map@0.6.1, source-map@^0.6.1, source-map@~0.6.1:
   version "0.6.1"
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
   integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
 
-source-map@^0.7.3:
-  version "0.7.3"
-  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
-  integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
-
 sourcemap-codec@^1.4.4:
   version "1.4.8"
   resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
   integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
 
-string-width@^4.1.0, string-width@^4.2.0:
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -1997,13 +1598,6 @@ string-width@^4.1.0, string-width@^4.2.0:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.1"
 
-string_decoder@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
-  integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
-  dependencies:
-    safe-buffer "~5.1.0"
-
 strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
@@ -2011,13 +1605,6 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   dependencies:
     ansi-regex "^5.0.1"
 
-supports-color@^5.3.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
-  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
-  dependencies:
-    has-flag "^3.0.0"
-
 supports-color@^7.1.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -2030,21 +1617,10 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
-sync-request@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/sync-request/-/sync-request-6.1.0.tgz#e96217565b5e50bbffe179868ba75532fb597e68"
-  integrity sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==
-  dependencies:
-    http-response-object "^3.0.1"
-    sync-rpc "^1.2.1"
-    then-request "^6.0.0"
-
-sync-rpc@^1.2.1:
-  version "1.3.6"
-  resolved "https://registry.yarnpkg.com/sync-rpc/-/sync-rpc-1.3.6.tgz#b2e8b2550a12ccbc71df8644810529deb68665a7"
-  integrity sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==
-  dependencies:
-    get-port "^3.1.0"
+symbol-tree@^3.2.4:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+  integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
 test-exclude@^6.0.0:
   version "6.0.0"
@@ -2055,37 +1631,15 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
-then-request@^6.0.0:
-  version "6.0.2"
-  resolved "https://registry.yarnpkg.com/then-request/-/then-request-6.0.2.tgz#ec18dd8b5ca43aaee5cb92f7e4c1630e950d4f0c"
-  integrity sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==
-  dependencies:
-    "@types/concat-stream" "^1.6.0"
-    "@types/form-data" "0.0.33"
-    "@types/node" "^8.0.0"
-    "@types/qs" "^6.2.31"
-    caseless "~0.12.0"
-    concat-stream "^1.6.0"
-    form-data "^2.2.0"
-    http-basic "^8.1.1"
-    http-response-object "^3.0.1"
-    promise "^8.0.0"
-    qs "^6.4.0"
-
-tinypool@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.1.1.tgz#99eaf29d030feeca2da6c1d6b33f90fc18093bc7"
-  integrity sha512-sW2fQZ2BRb/GX5v55NkHiTrbMLx0eX0xNpP+VGhOe2f7Oo04+LeClDyM19zCE/WCy7jJ8kzIJ0Ojrxj3UhN9Sg==
-
-tinyspy@^0.2.10:
-  version "0.2.10"
-  resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-0.2.10.tgz#7f684504bda345620f7a6a8462c618ef3d055517"
-  integrity sha512-Qij6rGWCDjWIejxCXXVi6bNgvrYBp3PbqC4cBP/0fD6WHDOHCw09Zd13CsxrDqSR5PFq01WeqDws8t5lz5sH0A==
-
-to-fast-properties@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
-  integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
+tinypool@^0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c"
+  integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==
+
+tinyspy@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d"
+  integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q==
 
 to-regex-range@^5.0.1:
   version "5.0.1"
@@ -2094,292 +1648,205 @@ to-regex-range@^5.0.1:
   dependencies:
     is-number "^7.0.0"
 
-token-stream@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-1.0.0.tgz#cc200eab2613f4166d27ff9afc7ca56d49df6eb4"
-  integrity sha1-zCAOqyYT9BZtJ/+a/HylbUnfbrQ=
+tosource@2.0.0-alpha.3:
+  version "2.0.0-alpha.3"
+  resolved "https://registry.yarnpkg.com/tosource/-/tosource-2.0.0-alpha.3.tgz#ef385dac9092e009bf25c018838ddaae436daeb6"
+  integrity sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==
 
-tr46@~0.0.3:
-  version "0.0.3"
-  resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
-  integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
+tough-cookie@^4.0.0:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
+  integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
+  dependencies:
+    psl "^1.1.33"
+    punycode "^2.1.1"
+    universalify "^0.2.0"
+    url-parse "^1.5.3"
+
+tr46@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9"
+  integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==
+  dependencies:
+    punycode "^2.1.1"
+
+transliteration@^2.3.5:
+  version "2.3.5"
+  resolved "https://registry.yarnpkg.com/transliteration/-/transliteration-2.3.5.tgz#8f92309575f69e4a8a525dab4ff705ebcf961c45"
+  integrity sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==
+  dependencies:
+    yargs "^17.5.1"
+
+type-check@~0.3.2:
+  version "0.3.2"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
+  integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==
+  dependencies:
+    prelude-ls "~1.1.2"
 
 type-detect@^4.0.0, type-detect@^4.0.5:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
   integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
 
-typedarray@^0.0.6:
-  version "0.0.6"
-  resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-  integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript@^4.8.2:
+  version "4.8.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
+  integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
 
-typescript@^4.4.4:
-  version "4.5.5"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
-  integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
-
-upath@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b"
-  integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w==
+universalify@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
+  integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
 
-util-deprecate@~1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-  integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+url-parse@^1.5.3:
+  version "1.5.10"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
+  integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
+  dependencies:
+    querystringify "^2.1.1"
+    requires-port "^1.0.0"
 
-v8-to-istanbul@^8.0.0:
-  version "8.1.1"
-  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed"
-  integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==
+v8-to-istanbul@^9.0.0:
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4"
+  integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==
   dependencies:
+    "@jridgewell/trace-mapping" "^0.3.12"
     "@types/istanbul-lib-coverage" "^2.0.1"
     convert-source-map "^1.6.0"
-    source-map "^0.7.3"
-
-vite@>=2.7.13, vite@^2.7.2:
-  version "2.7.13"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-2.7.13.tgz#99b56e27dfb1e4399e407cf94648f5c7fb9d77f5"
-  integrity sha512-Mq8et7f3aK0SgSxjDNfOAimZGW9XryfHRa/uV0jseQSilg+KhYDSoNb9h1rknOy6SuMkvNDLKCYAYYUMCE+IgQ==
-  dependencies:
-    esbuild "^0.13.12"
-    postcss "^8.4.5"
-    resolve "^1.20.0"
-    rollup "^2.59.0"
-  optionalDependencies:
-    fsevents "~2.3.2"
 
-vite@^2.7.12:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-2.8.0.tgz#0646ab9eee805fb24b667889644ac04bc516d0d3"
-  integrity sha512-ed5rjyeysttuPJX/aKSA0gTB/8ZKLM5xF6FtEuKy1B9DiQbDNFMVMQxnb9JesgBPUMMIJxC8w5KZ/KNWLKFXoA==
+"vite@^2.9.12 || ^3.0.0-0", vite@^3.0.8, vite@^3.0.9:
+  version "3.0.9"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30"
+  integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==
   dependencies:
-    esbuild "^0.14.14"
-    postcss "^8.4.5"
-    resolve "^1.22.0"
-    rollup "^2.59.0"
+    esbuild "^0.14.47"
+    postcss "^8.4.16"
+    resolve "^1.22.1"
+    rollup ">=2.75.6 <2.77.0 || ~2.77.0"
   optionalDependencies:
     fsevents "~2.3.2"
 
-vitepress@^0.21.6:
-  version "0.21.6"
-  resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-0.21.6.tgz#aa9e7a597efad607a08a79a020417771bec081c7"
-  integrity sha512-OzwD2cTfvoK5VKV0UWSqu4XvUOz4vWFJ4Bdi0z8GoVkTFXkfmbFawEDVXAZwzu0Hn4/VLopgmEyooc91iGKFlg==
-  dependencies:
-    "@docsearch/css" "^3.0.0-alpha.41"
-    "@docsearch/js" "^3.0.0-alpha.41"
-    "@vitejs/plugin-vue" "^2.0.0"
-    prismjs "^1.25.0"
-    vite "^2.7.12"
-    vue "^3.2.27"
-
-vitest@^0.2.8:
-  version "0.2.8"
-  resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.2.8.tgz#1beb95cafe6ab05e00fa0f4925341407a314574e"
-  integrity sha512-shzN00EkvUNzP8cSykhrOCwB7MlGxcwJNjOtHhbayvexGnqX6oLOfEp3OQixWdDJpEaqNiE3Lcie0WsOnoQ3Og==
-  dependencies:
-    "@types/chai" "^4.3.0"
+vitepress@^1.0.0-alpha.13:
+  version "1.0.0-alpha.13"
+  resolved "https://registry.yarnpkg.com/vitepress/-/vitepress-1.0.0-alpha.13.tgz#dfc0af0c624fbb81496ced9095b3f5ecb7a3a8ce"
+  integrity sha512-gCbKb+6o0g5wHt2yyqBPk7FcvrB+MfwGtg1JMS5p99GTQR87l3b7symCl8o1ecv7MDXwJ2mPB8ZrYNLnQAJxLQ==
+  dependencies:
+    "@docsearch/css" "^3.2.1"
+    "@docsearch/js" "^3.2.1"
+    "@vitejs/plugin-vue" "^3.0.3"
+    "@vue/devtools-api" "^6.2.1"
+    "@vueuse/core" "^9.1.0"
+    body-scroll-lock "^4.0.0-beta.0"
+    shiki "^0.11.1"
+    vite "^3.0.8"
+    vue "^3.2.37"
+
+vitest@0.22.1, vitest@^0.22.1:
+  version "0.22.1"
+  resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.22.1.tgz#3122e6024bf782ee9aca53034017af7adb009c32"
+  integrity sha512-+x28YTnSLth4KbXg7MCzoDAzPJlJex7YgiZbUh6YLp0/4PqVZ7q7/zyfdL0OaPtKTpNiQFPpMC8Y2MSzk8F7dw==
+  dependencies:
+    "@types/chai" "^4.3.3"
     "@types/chai-subset" "^1.3.3"
+    "@types/node" "*"
     chai "^4.3.6"
-    local-pkg "^0.4.1"
-    tinypool "^0.1.1"
-    tinyspy "^0.2.10"
-    vite ">=2.7.13"
-
-void-elements@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
-  integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
+    debug "^4.3.4"
+    local-pkg "^0.4.2"
+    tinypool "^0.2.4"
+    tinyspy "^1.0.2"
+    vite "^2.9.12 || ^3.0.0-0"
 
-vscode-css-languageservice@^5.1.7:
-  version "5.1.12"
-  resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.1.12.tgz#eed3a6a1bb8a7a70636ef07cd742a034c71635a1"
-  integrity sha512-293C5C2732Rbhh3opTs+nQBpC5Dd+oYrEA8lc0OWdyt40oYmJ331FV7NMF1SLFSIcOFB5XveLiWUZak2oyc49Q==
-  dependencies:
-    vscode-languageserver-textdocument "^1.0.1"
-    vscode-languageserver-types "^3.16.0"
-    vscode-nls "^5.0.0"
-    vscode-uri "^3.0.2"
+vscode-oniguruma@^1.6.1:
+  version "1.6.2"
+  resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.6.2.tgz#aeb9771a2f1dbfc9083c8a7fdd9cccaa3f386607"
+  integrity sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==
 
-vscode-html-languageservice@^4.1.0:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.2.1.tgz#b95077cffd19bf187e53c7bf79e3e0dd7edbc7cf"
-  integrity sha512-PgaToZVXJ44nFWEBuSINdDgVV6EnpC3MnXBsysR3O5TKcAfywbYeRGRy+Y4dVR7YeUgDvtb+JkJoSkaYC0mxXQ==
+vscode-textmate@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-6.0.0.tgz#a3777197235036814ac9a92451492f2748589210"
+  integrity sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==
+
+vue-demi@*:
+  version "0.13.11"
+  resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.13.11.tgz#7d90369bdae8974d87b1973564ad390182410d99"
+  integrity sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==
+
+vue-i18n@9:
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.2.2.tgz#aeb49d9424923c77e0d6441e3f21dafcecd0e666"
+  integrity sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==
+  dependencies:
+    "@intlify/core-base" "9.2.2"
+    "@intlify/shared" "9.2.2"
+    "@intlify/vue-devtools" "9.2.2"
+    "@vue/devtools-api" "^6.2.1"
+
+vue-router@^4.1.5:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.5.tgz#256f597e3f5a281a23352a6193aa6e342c8d9f9a"
+  integrity sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ==
+  dependencies:
+    "@vue/devtools-api" "^6.1.4"
+
+vue-tsc@^0.40.6:
+  version "0.40.6"
+  resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-0.40.6.tgz#156340663c2afb59a1d707f54d1a3e1e9559ff95"
+  integrity sha512-qAl/MJdcFKDRIlm/4g8hQ+fnHAva5i1+cCBbhGISM3o3UHqLOkdiwUch+T++9G2Di5IDsWj6Pn0YpLWSdoTlvQ==
+  dependencies:
+    "@volar/vue-language-core" "0.40.6"
+    "@volar/vue-typescript" "0.40.6"
+
+vue@^3.2.37, vue@^3.2.38:
+  version "3.2.38"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.38.tgz#cda3a414631745b194971219318a792dbbccdec0"
+  integrity sha512-hHrScEFSmDAWL0cwO4B6WO7D3sALZPbfuThDsGBebthrNlDxdJZpGR3WB87VbjpPh96mep1+KzukYEhpHDFa8Q==
+  dependencies:
+    "@vue/compiler-dom" "3.2.38"
+    "@vue/compiler-sfc" "3.2.38"
+    "@vue/runtime-dom" "3.2.38"
+    "@vue/server-renderer" "3.2.38"
+    "@vue/shared" "3.2.38"
+
+w3c-hr-time@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
+  integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
   dependencies:
-    vscode-languageserver-textdocument "^1.0.1"
-    vscode-languageserver-types "^3.16.0"
-    vscode-nls "^5.0.0"
-    vscode-uri "^3.0.2"
+    browser-process-hrtime "^1.0.0"
 
-vscode-json-languageservice@^4.1.8:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.0.tgz#df0693b69ba2fbf0a6add896087b6f1c9c38f06a"
-  integrity sha512-XNawv0Vdy/sUK0S+hGf7cq/qsVAbIniGJr89TvZOqMCNJmpgKTy1e8PL1aWW0uy6BfWMG7vxa5lZb3ypuFtuGQ==
+w3c-xmlserializer@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923"
+  integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==
   dependencies:
-    jsonc-parser "^3.0.0"
-    vscode-languageserver-textdocument "^1.0.3"
-    vscode-languageserver-types "^3.16.0"
-    vscode-nls "^5.0.0"
-    vscode-uri "^3.0.3"
-
-vscode-jsonrpc@8.0.0-next.6, vscode-jsonrpc@^8.0.0-next.2:
-  version "8.0.0-next.6"
-  resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.0-next.6.tgz#981f7c065ecc7e7e8595f9da6d073ac592b34114"
-  integrity sha512-6Ld3RYjygn5Ih7CkAtcAwiDQC+rakj2O+PnASfNyYv3sLmm44eJpEKzuPUN30Iy2UB09AZg8T6LBKWTJTEJDVw==
-
-vscode-languageserver-protocol@3.17.0-next.14:
-  version "3.17.0-next.14"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.0-next.14.tgz#d3500bef2ad2889385cda4802acfe6549842164d"
-  integrity sha512-iangobY8dL6sFZkOx4OhRPJM9gN0I1caUsOVR+MnPozsqQUtwMXmbIcfaIf0Akp0pd3KhJDPf/tdwRX68QGeeA==
-  dependencies:
-    vscode-jsonrpc "8.0.0-next.6"
-    vscode-languageserver-types "3.17.0-next.7"
-
-vscode-languageserver-textdocument@^1.0.1, vscode-languageserver-textdocument@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157"
-  integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==
-
-vscode-languageserver-types@3.17.0-next.7:
-  version "3.17.0-next.7"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.0-next.7.tgz#3e41ebb290c95bb38595f568a9963212626290cc"
-  integrity sha512-KH4zdG1qBXxoso61ChgpeoZYyHGJo8bV7Jv4I+fwQ1Ryy59JAxoZ9GAbhR5TeeafHctLcg6RFvY3m8Jqfu17cg==
-
-vscode-languageserver-types@^3.15.1, vscode-languageserver-types@^3.16.0:
-  version "3.16.0"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
-  integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
-
-vscode-languageserver@^8.0.0-next.2:
-  version "8.0.0-next.8"
-  resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.0-next.8.tgz#86ef42fb296ab338b800e83e478d0f687805c170"
-  integrity sha512-Gq0uqKbOgw7YNwPxMoNCeh7mHKMhG5j6EuoSh+w5pnKd7Eu9a74cagqf9aZDVFWW6GRpqR/Z+1o6EXqDK+g2Tg==
-  dependencies:
-    vscode-languageserver-protocol "3.17.0-next.14"
-
-vscode-nls@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
-  integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==
-
-vscode-pug-languageservice@0.29.8:
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/vscode-pug-languageservice/-/vscode-pug-languageservice-0.29.8.tgz#3d95f974bc273ddd8f5a8fb5d3ac8064b2dc63d1"
-  integrity sha512-QHYAzDSJLg7GOLxCZ12qsM0dAM0dPeMSS1t4kKfzLsfpErmZpFzkAIXbidVrNMdMffGZMtTuIlcpEyWHbx96Iw==
-  dependencies:
-    "@volar/code-gen" "0.29.8"
-    "@volar/shared" "0.29.8"
-    "@volar/source-map" "0.29.8"
-    "@volar/transforms" "0.29.8"
-    pug-lexer "^5.0.1"
-    pug-parser "^6.0.0"
-    vscode-languageserver "^8.0.0-next.2"
-
-vscode-typescript-languageservice@0.29.8:
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/vscode-typescript-languageservice/-/vscode-typescript-languageservice-0.29.8.tgz#370572e8c99c8b8190733a4bfc1b45c5f91aa044"
-  integrity sha512-eecDqHk4WjEvy6VHQ6teHczppQ9yJO2wExCy7yu7WiFj35qbw0h4G6Erv46MvP3ClL8FggFzD7s1qM6vdqJUfw==
-  dependencies:
-    "@volar/shared" "0.29.8"
-    semver "^7.3.5"
-    upath "^2.0.1"
-    vscode-languageserver "^8.0.0-next.2"
-    vscode-languageserver-textdocument "^1.0.1"
-
-vscode-uri@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-2.1.2.tgz#c8d40de93eb57af31f3c715dd650e2ca2c096f1c"
-  integrity sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==
-
-vscode-uri@^3.0.2, vscode-uri@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.3.tgz#a95c1ce2e6f41b7549f86279d19f47951e4f4d84"
-  integrity sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==
-
-vscode-vue-languageservice@0.29.8:
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/vscode-vue-languageservice/-/vscode-vue-languageservice-0.29.8.tgz#6d59aab4fb94215b99b6f7d0e2ab73babd398d05"
-  integrity sha512-qSJdvW5ttyGUB/8uWDKgo8vnIoFnXYlBP4Z/cn54btsRn6ZMw7IJGJU1381e7p/yGvMTLeGbugD53SghbnSa6g==
-  dependencies:
-    "@volar/code-gen" "0.29.8"
-    "@volar/html2pug" "0.29.8"
-    "@volar/shared" "0.29.8"
-    "@volar/source-map" "0.29.8"
-    "@volar/transforms" "0.29.8"
-    "@volar/vue-code-gen" "0.29.8"
-    "@vscode/emmet-helper" "^2.8.0"
-    "@vue/reactivity" "^3.2.21"
-    "@vue/shared" "^3.2.21"
-    request-light "^0.5.4"
-    upath "^2.0.1"
-    vscode-css-languageservice "^5.1.7"
-    vscode-html-languageservice "^4.1.0"
-    vscode-json-languageservice "^4.1.8"
-    vscode-languageserver "^8.0.0-next.2"
-    vscode-languageserver-textdocument "^1.0.1"
-    vscode-pug-languageservice "0.29.8"
-    vscode-typescript-languageservice "0.29.8"
-
-vue-tsc@^0.29.8:
-  version "0.29.8"
-  resolved "https://registry.yarnpkg.com/vue-tsc/-/vue-tsc-0.29.8.tgz#f4d8de5dd8756107c878489ccf9178d2d72fff47"
-  integrity sha512-pT0wLRjvRuSmB+J4WJT6uuV9mO0KtSSXEAtaVXZQzyk5+DJdbLIQTbRce/TXSkfqt1l1WogO78RjtOJFiMCgfQ==
-  dependencies:
-    "@volar/shared" "0.29.8"
-    vscode-vue-languageservice "0.29.8"
-
-vue@^3.2.25:
-  version "3.2.29"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.29.tgz#3571b65dbd796d3a6347e2fd45a8e6e11c13d56a"
-  integrity sha512-cFIwr7LkbtCRanjNvh6r7wp2yUxfxeM2yPpDQpAfaaLIGZSrUmLbNiSze9nhBJt5MrZ68Iqt0O5scwAMEVxF+Q==
-  dependencies:
-    "@vue/compiler-dom" "3.2.29"
-    "@vue/compiler-sfc" "3.2.29"
-    "@vue/runtime-dom" "3.2.29"
-    "@vue/server-renderer" "3.2.29"
-    "@vue/shared" "3.2.29"
-
-vue@^3.2.27:
-  version "3.2.30"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.30.tgz#47de3039631ac22cab2fd26b427575260199b8bb"
-  integrity sha512-ZmTFWVJUX2XADkuOB8GcLTuxnBLogjJBTNVrM7WsTnjqRQ+VR8bLNrvNsbn8vj/LaP5+0WFAPrpngOYE2x+e+Q==
-  dependencies:
-    "@vue/compiler-dom" "3.2.30"
-    "@vue/compiler-sfc" "3.2.30"
-    "@vue/runtime-dom" "3.2.30"
-    "@vue/server-renderer" "3.2.30"
-    "@vue/shared" "3.2.30"
-
-webidl-conversions@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
-  integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
+    xml-name-validator "^4.0.0"
 
 webidl-conversions@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
   integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
 
-whatwg-encoding@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
-  integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
+whatwg-encoding@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"
+  integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==
   dependencies:
-    iconv-lite "0.4.24"
+    iconv-lite "0.6.3"
 
-whatwg-mimetype@^2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
-  integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+whatwg-mimetype@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7"
+  integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==
 
-whatwg-url@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
-  integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
+whatwg-url@^11.0.0:
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018"
+  integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==
   dependencies:
-    tr46 "~0.0.3"
-    webidl-conversions "^3.0.0"
+    tr46 "^3.0.0"
+    webidl-conversions "^7.0.0"
 
 which@^2.0.1:
   version "2.0.2"
@@ -2388,15 +1855,10 @@ which@^2.0.1:
   dependencies:
     isexe "^2.0.0"
 
-with@^7.0.0:
-  version "7.0.2"
-  resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac"
-  integrity sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==
-  dependencies:
-    "@babel/parser" "^7.9.6"
-    "@babel/types" "^7.9.6"
-    assert-never "^1.2.1"
-    babel-walk "3.0.0-canary-5"
+word-wrap@~1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
+  integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
 
 wrap-ansi@^7.0.0:
   version "7.0.0"
@@ -2410,7 +1872,22 @@ wrap-ansi@^7.0.0:
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
-  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+  integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+ws@^8.8.0:
+  version "8.8.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0"
+  integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==
+
+xml-name-validator@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
+  integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
+xmlchars@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+  integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
 
 y18n@^5.0.5:
   version "5.0.8"
@@ -2422,11 +1899,16 @@ yallist@^4.0.0:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
   integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
 
-yargs-parser@^20.2.2, yargs-parser@^20.2.7:
+yargs-parser@^20.2.2, yargs-parser@^20.2.9:
   version "20.2.9"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
   integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
 
+yargs-parser@^21.0.0:
+  version "21.1.1"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
+  integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
+
 yargs@^16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
@@ -2440,6 +1922,19 @@ yargs@^16.2.0:
     y18n "^5.0.5"
     yargs-parser "^20.2.2"
 
+yargs@^17.5.1:
+  version "17.5.1"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
+  integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
+  dependencies:
+    cliui "^7.0.2"
+    escalade "^3.1.1"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.3"
+    y18n "^5.0.5"
+    yargs-parser "^21.0.0"
+
 yocto-queue@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"