diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md
index 8734fc0c36..0fecce2ee8 100644
--- a/.github/ISSUE_TEMPLATE/01_bug-report.md
+++ b/.github/ISSUE_TEMPLATE/01_bug-report.md
@@ -22,7 +22,10 @@ First, in order to avoid duplicate Issues, please search to see if the problem y
 
 ## ๐Ÿคฌ Actual Behavior
 
-<!--- Tell us what happens instead of the expected behavior -->
+<!--
+Tell us what happens instead of the expected behavior.
+Please include errors from the developer console and/or server log files if you have access to them.
+-->
 
 ## ๐Ÿ“ Steps to Reproduce
 
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000000..98f1d2e383
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,12 @@
+'โš™๏ธServer':
+- packages/backend/**/*
+
+'๐Ÿ–ฅ๏ธClient':
+- packages/client/**/*
+
+'๐ŸงชTest':
+- cypress/**/*
+- packages/backend/test/**/*
+
+'โ€ผ๏ธ wrong locales':
+- any: ['locales/*.yml', '!locales/ja-JP.yml']
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
new file mode 100644
index 0000000000..fa4a58c3a9
--- /dev/null
+++ b/.github/workflows/labeler.yml
@@ -0,0 +1,16 @@
+name: "Pull Request Labeler"
+on:
+  pull_request_target:
+    branches-ignore:
+      - 'l10n_develop'
+
+jobs:
+  triage:
+    permissions:
+      contents: read
+      pull-requests: write
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/labeler@v4
+      with:
+        repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index da2c73a656..4e42fa9314 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,25 +1,39 @@
-name: Lint
-
-on:
-  push:
-    branches:
-      - master
-      - develop
-  pull_request:
-
-jobs:
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v2
-      with:
-        submodules: true
-    - uses: actions/setup-node@v3
-      with:
-        node-version: 16.x
-        cache: 'yarn'
-        cache-dependency-path: |
-          packages/backend/yarn.lock
-          packages/client/yarn.lock
-    - run: yarn install
-    - run: yarn lint
+name: Lint
+
+on:
+  push:
+    branches:
+      - master
+      - develop
+  pull_request:
+
+jobs:
+  backend:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - uses: actions/setup-node@v3
+      with:
+        node-version: 18.x
+        cache: 'yarn'
+        cache-dependency-path: |
+          packages/backend/yarn.lock
+    - run: yarn install
+    - run: yarn --cwd ./packages/backend lint
+
+  client:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - uses: actions/setup-node@v3
+      with:
+        node-version: 18.x
+        cache: 'yarn'
+        cache-dependency-path: |
+          packages/client/yarn.lock
+    - run: yarn install
+    - run: yarn --cwd ./packages/client lint
diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml
new file mode 100644
index 0000000000..87af3a6ba6
--- /dev/null
+++ b/.github/workflows/ok-to-test.yml
@@ -0,0 +1,36 @@
+# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event
+name: Ok To Test
+
+on:
+  issue_comment:
+    types: [created]
+
+jobs:
+  ok-to-test:
+    runs-on: ubuntu-latest
+    # Only run for PRs, not issue comments
+    if: ${{ github.event.issue.pull_request }}
+    steps:
+    # Generate a GitHub App installation access token from an App ID and private key
+    # To create a new GitHub App:
+    #   https://developer.github.com/apps/building-github-apps/creating-a-github-app/
+    # See app.yml for an example app manifest
+    - name: Generate token
+      id: generate_token
+      uses: tibdex/github-app-token@v1
+      with:
+        app_id: ${{ secrets.DEPLOYBOT_APP_ID }}
+        private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }}
+
+    - name: Slash Command Dispatch
+      uses: peter-evans/slash-command-dispatch@v1
+      env:
+        TOKEN: ${{ steps.generate_token.outputs.token }}
+      with:
+        token: ${{ env.TOKEN }} # GitHub App installation access token
+        # token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work
+        reaction-token: ${{ secrets.GITHUB_TOKEN }}
+        issue-type: pull-request
+        commands: deploy
+        named-args: true
+        permission: write
diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml
new file mode 100644
index 0000000000..fd43bce9e6
--- /dev/null
+++ b/.github/workflows/pr-preview-deploy.yml
@@ -0,0 +1,95 @@
+# Run secret-dependent integration tests only after /deploy approval
+on:
+  pull_request:
+    types: [opened, reopened, synchronize]
+  repository_dispatch:
+    types: [deploy-command]
+
+name: Deploy preview environment
+
+jobs:
+  # Repo owner has commented /deploy on a (fork-based) pull request
+  deploy-preview-environment:
+    runs-on: ubuntu-latest
+    if:
+      github.event_name == 'repository_dispatch' &&
+      github.event.client_payload.slash_command.sha != '' &&
+      contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha)
+    steps:
+    - uses: actions/github-script@v5
+      id: check-id
+      env:
+        number: ${{ github.event.client_payload.pull_request.number }}
+        job: ${{ github.job }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        result-encoding: string
+        script: |
+          const { data: pull } = await github.rest.pulls.get({
+            ...context.repo,
+            pull_number: process.env.number
+          });
+          const ref = pull.head.sha;
+
+          const { data: checks } = await github.rest.checks.listForRef({
+            ...context.repo,
+            ref
+          });
+
+          const check = checks.check_runs.filter(c => c.name === process.env.job);
+
+          return check[0].id;
+
+    - uses: actions/github-script@v5
+      env:
+        check_id: ${{ steps.check-id.outputs.result }}
+        details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        script: |
+          await github.rest.checks.update({
+            ...context.repo,
+            check_run_id: process.env.check_id,
+            status: 'in_progress',
+            details_url: process.env.details_url
+          });
+
+    # Check out merge commit
+    - name: Fork based /deploy checkout
+      uses: actions/checkout@v2
+      with:
+        ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge'
+
+    # <insert integration tests needing secrets>
+    - name: Context
+      uses: okteto/context@latest
+      with:
+        token: ${{ secrets.OKTETO_TOKEN }}
+
+    - name: Deploy preview environment
+      uses: ikuradon/deploy-preview@latest
+      env:
+        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+      with:
+        name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo
+        timeout: 15m
+
+    # Update check run called "integration-fork"
+    - uses: actions/github-script@v5
+      id: update-check-run
+      if: ${{ always() }}
+      env:
+        # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run
+        conclusion: ${{ job.status }}
+        check_id: ${{ steps.check-id.outputs.result }}
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        script: |
+          const { data: result } = await github.rest.checks.update({
+            ...context.repo,
+            check_run_id: process.env.check_id,
+            status: 'completed',
+            conclusion: process.env.conclusion
+          });
+
+          return result;
diff --git a/.github/workflows/pr-preview-destroy.yml b/.github/workflows/pr-preview-destroy.yml
new file mode 100644
index 0000000000..c14c3db5c5
--- /dev/null
+++ b/.github/workflows/pr-preview-destroy.yml
@@ -0,0 +1,21 @@
+# file: .github/workflows/preview-closed.yaml
+on:
+  pull_request:
+    types:
+      - closed
+
+name: Destroy preview environment
+
+jobs:
+  destroy-preview-environment:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Context
+        uses: okteto/context@latest
+        with:
+          token: ${{ secrets.OKTETO_TOKEN }}
+
+      - name: Destroy preview environment
+        uses: okteto/destroy-preview@latest
+        with:
+          name: pr-${{ github.event.number }}-syuilo
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index d57d85c874..c32c82e2a1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -13,7 +13,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [16.x]
+        node-version: [18.x]
 
     services:
       postgres:
@@ -57,7 +57,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        node-version: [16.x]
+        node-version: [18.x]
         browser: [chrome]
 
     services:
@@ -103,7 +103,7 @@ jobs:
     - name: ALSA Env
       run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc
     - name: Cypress run
-      uses: cypress-io/github-action@v2
+      uses: cypress-io/github-action@v4
       with:
         install: false
         start: npm run start:test
diff --git a/.node-version b/.node-version
index bf79505bb8..7fd023741b 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v16.14.0
+v16.15.0
diff --git a/.okteto/okteto-pipeline.yml b/.okteto/okteto-pipeline.yml
new file mode 100644
index 0000000000..e2996fbbc9
--- /dev/null
+++ b/.okteto/okteto-pipeline.yml
@@ -0,0 +1,6 @@
+build:
+  misskey:
+    args:
+      - NODE_ENV=development
+deploy:
+  - helm upgrade --install misskey chart --set image=${OKTETO_BUILD_MISSKEY_IMAGE} --set url="https://misskey-$(kubectl config view --minify -o jsonpath='{..namespace}').cloud.okteto.net" --set environment=development
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 9adb0d0697..42264548ea 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -3,6 +3,7 @@
 		"editorconfig.editorconfig",
 		"eg2.vscode-npm-script",
 		"dbaeumer.vscode-eslint",
-		"johnsoncodehk.volar",
+		"Vue.volar",
+		"Vue.vscode-typescript-vue-plugin"
 	]
 }
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dff67b40dc..eb16a1675d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,6 @@
 ## 12.x.x (unreleased)
 
 ### Improvements
-- 
 
 ### Bugfixes
 - 
@@ -10,11 +9,59 @@
 You should also include the user name that made the change.
 -->
 
+## 12.111.0 (2022/06/11)
+### Improvements
+- Supports Unicode Emoji 14.0 @mei23
+- ใƒ—ใƒƒใ‚ทใƒฅ้€š็Ÿฅใ‚’่ค‡ๆ•ฐใ‚ขใ‚ซใ‚ฆใƒณใƒˆๅฏพๅฟœใซ #7667 @tamaina
+- ใƒ—ใƒƒใ‚ทใƒฅ้€š็Ÿฅใซใ‚ฏใƒชใƒƒใ‚ฏใ‚„actionใ‚’่จญๅฎš #7667 @tamaina
+- ใƒ‰ใƒฉใ‚คใƒ–ใซ็”ปๅƒใƒ•ใ‚กใ‚คใƒซใ‚’ใ‚ขใƒƒใƒ—ใƒญใƒผใƒ‰ใ™ใ‚‹ใจใใ‚ชใƒชใ‚ธใƒŠใƒซ็”ปๅƒใ‚’็ ดๆฃ„ใ—ใฆwebpublicใฎใฟไฟๆŒใ™ใ‚‹ใ‚ชใƒ—ใ‚ทใƒงใƒณ @tamaina
+- Server: always remove completed tasks of job queue @Johann150
+- Client: ใ‚ขใƒใ‚ฟใƒผใฎ่จญๅฎšใง็”ปๅƒใ‚’ใ‚ฏใƒญใƒƒใƒ—ใงใใ‚‹ใ‚ˆใ†ใซ @syuilo
+- Client: make emoji stand out more on reaction button @Johann150
+- Client: display URL of QR code for TOTP registration @tamaina
+- Client: render quote renote CWs as MFM @pixeldesu
+- API: notifications/readใฏ้…ๅˆ—ใงใ‚‚ๅ—ใ‘ไป˜ใ‘ใ‚‹ใ‚ˆใ†ใซ #7667 @tamaina
+- API: ใƒฆใƒผใ‚ถใƒผๆคœ็ดขใงใ€ใ‚ฏใ‚จใƒชใŒusernameใฎๆกไปถใ‚’ๆบ€ใŸใ™ๅ ดๅˆใฏusernameใ‚‚LIKEๆคœ็ดขใ™ใ‚‹ใ‚ˆใ†ใซ @tamaina
+- MFM: Allow speed changes in all animated MFMs @Johann150
+- The theme color is now better validated. @Johann150
+  Your own theme color may be unset if it was in an invalid format.
+  Admins should check their instance settings if in doubt.
+- Perform port diagnosis at startup only when Listen fails @mei23
+- Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23
+  Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
+
+### Bugfixes
+- Server: keep file order of note attachement @Johann150
+- Server: fix caching @Johann150
+- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
+- Server: fix internal in-memory caching @Johann150
+- Server: use correct order of attachments on notes @Johann150
+- Server: prevent crash when processing certain PNGs @syuilo
+- Server: Fix unable to generate video thumbnails @mei23
+- Server: Fix `Cannot find module` issue @mei23
+- Federation: Add rel attribute to host-meta @mei23
+- Federation: add id for activitypub follows @Johann150
+- Federation: use `source` instead of `_misskey_content` @Johann150
+- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
+- Federation: correctly render empty note text @Johann150
+- Federation: Fix quote renotes containing no text being federated correctly @Johann150
+- Federation: remove duplicate br tag/newline @Johann150
+- Federation: add missing authorization checks @Johann150
+- Client: fix profile picture height in mentions @tamaina
+- Client: fix abuse reports page to be able to show all reports @Johann150
+- Client: fix settings page @tamaina
+- Client: fix profile tabs @futchitwo
+- Client: fix popout URL @futchitwo
+- Client: correctly handle MiAuth URLs with query string @sn0w
+- Client: ใƒŽใƒผใƒˆ่ฉณ็ดฐใƒšใƒผใ‚ธใฎๆ–ฐใ—ใ„ใƒŽใƒผใƒˆใ‚’่กจ็คบใ™ใ‚‹ๆฉŸ่ƒฝใฎๅ‹•ไฝœใŒๆญฃใ—ใใชใ‚‹ใ‚ˆใ†ใซไฟฎๆญฃใ™ใ‚‹ @xianonn
+- MFM: more animated functions support `speed` parameter @futchitwo
+- MFM: limit large MFM @Johann150
+
 ## 12.110.1 (2022/04/23)
 
 ### Bugfixes
 - Fix GOP rendering @syuilo
-- Improve performance of antenna, clip, and list @xianon
+- Improve performance of antenna, clip, and list @xianonn
 
 ## 12.110.0 (2022/04/11)
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a696bc5ceb..a37df3bdee 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,10 +1,11 @@
 # Contribution guide
 We're glad you're interested in contributing Misskey! In this document you will find the information you need to contribute to the project.
 
-**โ„น๏ธ Important:** This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
-Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
-The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
-It will also allow the reader to use the translation tool of their preference if necessary.
+> **Note**
+> This project uses Japanese as its major language, **but you do not need to translate and write the Issues/PRs in Japanese.**
+> Also, you might receive comments on your Issue/PR in Japanese, but you do not need to reply to them in Japanese as well.\
+> The accuracy of machine translation into Japanese is not high, so it will be easier for us to understand if you write it in the original language.
+> It will also allow the reader to use the translation tool of their preference if necessary.
 
 ## Roadmap
 See [ROADMAP.md](./ROADMAP.md)
@@ -16,6 +17,9 @@ Before creating an issue, please check the following:
 	- Issues should only be used to feature requests, suggestions, and bug tracking.
 	- Please ask questions or troubleshooting in the [Misskey Forum](https://forum.misskey.io/) or [Discord](https://discord.gg/Wp8gVStHW3).
 
+> **Warning**
+> Do not close issues that are about to be resolved. It should remain open until a commit that actually resolves it is merged.
+
 ## Before implementation
 When you want to add a feature or fix a bug, **first have the design and policy reviewed in an Issue** (if it is not there, please make one). Without this step, there is a high possibility that the PR will not be merged even if it is implemented.
 
@@ -62,6 +66,30 @@ Be willing to comment on the good points and not just the things you want fixed
 	- Are there any omissions or gaps?
 	- Does it check for anomalies?
 
+## Deploy
+The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment.
+```
+/deploy sha=<commit hash>
+```
+An actual domain will be assigned so you can test the federation.
+
+## Merge
+For now, basically only @syuilo has the authority to merge PRs into develop because he is most familiar with the codebase.
+However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor.
+
+## Release
+### Release Instructions
+1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
+2. Create a release PR.
+	- Into `master` from `develop` branch.
+	- The title must be in the format `Release: x.y.z`.
+		- `x.y.z` is the new version you are trying to release.
+3. Deploy and perform a simple QA check. Also verify that the tests passed.
+4. Merge it.
+5. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
+	- The target branch must be `master`
+	- The tag name must be the version
+
 ## Localization (l10n)
 Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
 You can improve our translations with your Crowdin account.
diff --git a/Dockerfile b/Dockerfile
index e4959756e8..33d5faad12 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
-FROM node:16.14.0-alpine3.15 AS base
+FROM node:18.0.0-alpine3.15 AS base
 
-ENV NODE_ENV=production
+ARG NODE_ENV=production
 
 WORKDIR /misskey
 
@@ -11,16 +11,16 @@ FROM base AS builder
 COPY . ./
 
 RUN apk add --no-cache $BUILD_DEPS && \
-    git submodule update --init && \
-    yarn install && \
-    yarn build && \
-    rm -rf .git
+	git submodule update --init && \
+	yarn install && \
+	yarn build && \
+	rm -rf .git
 
 FROM base AS runner
 
 RUN apk add --no-cache \
-    ffmpeg \
-    tini
+	ffmpeg \
+	tini
 
 ENTRYPOINT ["/sbin/tini", "--"]
 
@@ -31,5 +31,6 @@ COPY --from=builder /misskey/packages/backend/built ./packages/backend/built
 COPY --from=builder /misskey/packages/client/node_modules ./packages/client/node_modules
 COPY . ./
 
+ENV NODE_ENV=production
 CMD ["npm", "run", "migrateandstart"]
 
diff --git a/README.md b/README.md
index c7bc9ef219..c273270644 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,29 @@
-[![Misskey](https://github.com/misskey-dev/assets/blob/main/banner.png?raw=true)](https://join.misskey.page/)
-
 <div align="center">
-
-**๐ŸŒŽ A forever evolving, interplanetary microblogging platform. ๐Ÿš€**
-
-**Misskey** is a distributed microblogging platform with advanced features such as Reactions and a highly customizable UI.
-
-[Learn more](https://misskey-hub.net/)
-
+<a href="https://misskey-hub.net">
+	<img src="./assets/title_float.svg" alt="Misskey logo" style="border-radius:50%" width="400"/>
+</a>
+	
+**๐ŸŒŽ **[Misskey](https://misskey-hub.net/)** is an open source, decentralized social media platform that's free forever! ๐Ÿš€**
+	
 ---
 
-[โœจ Find an instance](https://misskey-hub.net/instances.html)
-โ€ข
-[๐Ÿ“ฆ Create your own instance](https://misskey-hub.net/docs/install.html)
-โ€ข
-[๐Ÿ› ๏ธ Contribute](./CONTRIBUTING.md)
-โ€ข
-[๐Ÿš€ Join the community](https://discord.gg/Wp8gVStHW3)
+<a href="https://misskey-hub.net/instances.html">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=misskey&labelColor=363B40" alt="find an instance"/></a>
 
+<a href="https://misskey-hub.net/docs/install.html">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/create_an-instance-FBD53C?logoColor=FBD53C&style=for-the-badge&logo=server&labelColor=363B40" alt="create an instance"/></a>
+
+<a href="./CONTRIBUTING.md">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-contributor-A371F7?logoColor=A371F7&style=for-the-badge&logo=git-merge&labelColor=363B40" alt="become a contributor"/></a>
+
+<a href="https://discord.gg/Wp8gVStHW3">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/join_the-community-5865F2?logoColor=5865F2&style=for-the-badge&logo=discord&labelColor=363B40" alt="join the community"/></a>
+
+<a href="https://www.patreon.com/syuilo">
+		<img src="https://custom-icon-badges.herokuapp.com/badge/become_a-patron-F96854?logoColor=F96854&style=for-the-badge&logo=patreon&labelColor=363B40" alt="become a patron"/></a>
+	
 ---
 
-<a href="https://www.patreon.com/syuilo"><img src="https://c5.patreon.com/external/logo/become_a_patron_button@2x.png" alt="Become a Patron!" width="160" /></a>
-
 </div>
 
 <div>
@@ -30,139 +32,26 @@
 
 ## โœจ Features
 - **ActivityPub support**\
-	It is possible to interact with other software.
+Not on Misskey? No problem! Not only can Misskey instances talk to each other, but you can make friends with people on other networks like Mastodon and Pixelfed!
 - **Reactions**\
-	You can add "reactions" to each post, making it easy for you to express your feelings.
+You can add emoji reactions to any post! No longer are you bound by a like button, show everyone exactly how you feel with the tap of a button.
 - **Drive**\
-	An interface to manage uploaded files such as images, videos, sounds, etc.
-	You can also organize your favorite content into folders, making it easy to share again.
+With Misskey's built in drive, you get cloud storage right in your social media, where you can upload any files, make folders, and find media from posts you've made!
 - **Rich Web UI**\
-	Misskey has a rich WebUI by default.
-	It is highly customizable by flexibly changing the layout and installing various widgets and themes.
-	Furthermore, plug-ins can be created using AiScript, a original programming language.
-- and more...
+	Misskey has a rich and easy to use Web UI!
+	It is highly customizable, from changing the layout and adding widgets to making custom themes.
+	Furthermore, plugins can be created using AiScript, an original programming language.
+- And much more...
 
 </div>
 
 <div style="clear: both;"></div>
 
+## Documentation
+
+Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it.
+
 ## Sponsors
 <div align="center">
 	<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>
 </div>
-
-## Backers
-<!-- PATREON_START -->
-<table><tr>
-<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan " width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/27648259" alt="ใฟใชใ—ใพ " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td>
-<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
-<td><a href="https://www.patreon.com/weepjp">weepjp </a></td>
-<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td>
-<td><a href="https://www.patreon.com/user?u=27648259">ใฟใชใ—ใพ </a></td>
-<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/4.jpg?token-time=2145916800&token-hash=BslMqDjTjz8KYANLvxL87agHTugHa0dMPUzT-hwR6Vk%3D" alt="Nesakko" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/776209" alt="Demogrognard" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon " width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/16869916" alt="่ฆ‹ๅฝ“ใ‹ใชใฟ " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/36813045/29876ea679d443bcbba3c3f16edab8c2/2.jpeg?token-time=2145916800&token-hash=YCKWnIhrV9rjUCV9KqtJnEqjy_uGYF3WMXftjUdpi7o%3D" alt="Wataru Manji (manji0)" width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
-<td><a href="https://www.patreon.com/user?u=776209">Demogrognard</a></td>
-<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
-<td><a href="https://www.patreon.com/user?u=557245">mkatze </a></td>
-<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y </a></td>
-<td><a href="https://www.patreon.com/AureoleArk">AureoleArk </a></td>
-<td><a href="https://www.patreon.com/osapon">osapon </a></td>
-<td><a href="https://www.patreon.com/user?u=16869916">่ฆ‹ๅฝ“ใ‹ใชใฟ </a></td>
-<td><a href="https://www.patreon.com/user?u=36813045">Wataru Manji (manji0)</a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61 " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/38837364/9421361c54c645ac8f5fc442a40c32e9/1.png?token-time=2145916800&token-hash=TUZB48Nem3BeUPLBH6s3P6WyKBnQOy0xKaDSTBBUNzA%3D" alt="xianon" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td>
-<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
-<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
-<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
-<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin </a></td>
-<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
-<td><a href="https://www.patreon.com/user?u=38837364">xianon</a></td>
-<td><a href="https://www.patreon.com/user?u=26340354">totokoro </a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26144593/9514b10a5c1b42a3af58621aee213d1d/1.png?token-time=2145916800&token-hash=v1PYRsjzu4c_mndN4Hvi_dlispZJsuGRCQeNS82pUSM%3D" alt="EBISUME" width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s </a></td>
-<td><a href="https://www.patreon.com/user?u=5827393">motcha </a></td>
-<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </a></td>
-<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
-<td><a href="https://www.patreon.com/takimura">takimura </a></td>
-<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
-<td><a href="https://www.patreon.com/user?u=9109588">nafuchoco </a></td>
-<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
-<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
-<td><a href="https://www.patreon.com/user?u=26144593">EBISUME</a></td>
-<td><a href="https://www.patreon.com/noellabo">noellabo </a></td>
-</tr></table>
-<table><tr>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/14661394" alt="Chandler " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
-<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone " width="100"></td>
-<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
-</tr><tr>
-<td><a href="https://www.patreon.com/Corset">CG </a></td>
-<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td>
-<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
-<td><a href="https://www.patreon.com/user?u=14661394">Chandler </a></td>
-<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
-<td><a href="https://www.patreon.com/user?u=23932002">nenohi </a></td>
-<td><a href="https://www.patreon.com/efertone">Efertone </a></td>
-<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
-</tr></table>
-
-**Last updated:** Sun, 26 Jul 2020 07:00:10 UTC
-<!-- PATREON_END -->
-
-[backer-url]: #backers
-[backer-badge]: https://opencollective.com/misskey/backers/badge.svg
-[backers-image]: https://opencollective.com/misskey/backers.svg
-[sponsor-url]: #sponsors
-[sponsor-badge]: https://opencollective.com/misskey/sponsors/badge.svg
-[sponsors-image]: https://opencollective.com/misskey/sponsors.svg
-[support-url]: https://opencollective.com/misskey#support
-
-[syuilo-link]:      https://syuilo.com
-[syuilo-icon]:      https://avatars2.githubusercontent.com/u/4439005?v=3&s=70
diff --git a/assets/title_float.svg b/assets/title_float.svg
new file mode 100644
index 0000000000..43205ac1c4
--- /dev/null
+++ b/assets/title_float.svg
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   id="svg10"
+   version="1.1"
+   viewBox="0 0 162.642 54.261"
+   height="205.08"
+   width="614.71">
+  <metadata
+     id="metadata16">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+	<style>
+		#g8 { 
+    	animation-name: floating;
+    	animation-duration: 3s;
+    	animation-iteration-count: infinite;
+   	 	animation-timing-function: ease-in-out;
+		}
+ 
+		@keyframes floating {
+    	0% { transform: translate(0,  0px); }
+    	50% { transform: translate(0, -5px); }
+    	100% { transform: translate(0, 0px); }
+		}
+	</style>
+	<linearGradient id="myGradient" gradientTransform="rotate(90)">
+      <stop offset="5%"  stop-color="#A1CA03" />
+      <stop offset="95%" stop-color="#91BA03" />
+    </linearGradient>
+  <defs
+     id="defs14" />
+  <g
+     id="g8"
+     fill="url('#myGradient')"
+     word-spacing="0"
+     letter-spacing="0"
+     font-family="OTADESIGN Rounded"
+     font-weight="400">
+    <g
+       id="g4"
+       style="line-height:476.69509888px;-inkscape-font-specification:'OTADESIGN Rounded'">
+      <path
+         id="path2"
+         font-size="141.034"
+         aria-label="Mi"
+         d="m 27.595,34.59 c -1.676,0.006 -3.115,-1.004 -3.793,-2.179 -0.363,-0.513 -1.08,-0.696 -1.09,0 v 3.214 c 0,1.291 -0.47,2.408 -1.412,3.35 -0.915,0.914 -2.031,1.371 -3.35,1.371 -1.29,0 -2.407,-0.457 -3.349,-1.372 -0.914,-0.941 -1.372,-2.058 -1.372,-3.349 V 17.95 c 0,-0.995 0.283,-1.896 0.848,-2.703 0.591,-0.834 1.345,-1.413 2.26,-1.735 0.516591,-0.189385 1.062793,-0.285215 1.613,-0.283 1.453,0 2.664,0.565 3.632,1.695 l 4.832,5.608 c 0.108,0.08 0.424,0.697 1.18,0.697 0.758,0 1.115,-0.617 1.222,-0.698 l 4.791,-5.607 c 0.996,-1.13 2.22,-1.695 3.673,-1.695 0.538,0 1.076,0.094 1.614,0.283 0.914,0.322 1.654,0.9 2.22,1.735 0.591,0.807 0.887,1.708 0.887,2.703 v 17.675 c 0,1.291 -0.47,2.408 -1.412,3.35 -0.915,0.914 -2.032,1.371 -3.35,1.371 -1.291,0 -2.407,-0.457 -3.35,-1.372 -0.914,-0.941 -1.371,-2.058 -1.371,-3.349 v -3.214 c -0.08,-0.877 -0.855,-0.324 -1.13,0 -0.726,1.345 -2.118,2.173 -3.793,2.18 z M 47.806,21.38 c -1.13,0 -2.098333,-0.39 -2.905,-1.17 -0.78,-0.806667 -1.17,-1.775 -1.17,-2.905 0,-1.13 0.39,-2.085 1.17,-2.865 0.806667,-0.806667 1.775,-1.21 2.905,-1.21 1.13,0 2.098667,0.403333 2.906,1.21 0.806667,0.78 1.21,1.735 1.21,2.865 0,1.13 -0.403333,2.098333 -1.21,2.905 -0.807333,0.78 -1.776,1.17 -2.906,1.17 z m 0.04,0.808 c 1.13,0 2.085333,0.403333 2.866,1.21 0.806667,0.806667 1.21,1.775333 1.21,2.906 v 9.967 c 0,1.13 -0.403333,2.098333 -1.21,2.905 -0.78,0.78 -1.735333,1.17 -2.866,1.17 -1.129333,0 -2.097667,-0.39 -2.905,-1.17 -0.806667,-0.806667 -1.21,-1.775 -1.21,-2.905 v -9.967 c 0,-1.13 0.403333,-2.098667 1.21,-2.906 0.806667,-0.806667 1.775,-1.21 2.905,-1.21 z"
+         style="font-size:141.03399658px;-inkscape-font-specification:'OTADESIGN Rounded'" />
+    </g>
+    <path
+       id="path6"
+       d="M60.925 27.24q.968.243 2.42.525 2.42.403 3.792 1.29 2.582 1.695 2.582 5.083 0 2.743-1.815 4.478-2.098 2.017-5.85 2.017-2.742 0-6.13-.767-1.09-.242-1.776-1.089-.645-.847-.645-1.896 0-1.29.887-2.178.928-.928 2.179-.928.363 0 .685.081 1.17.242 4.478.605.444 0 .968-.04.202 0 .202-.242.04-.202-.242-.283-1.372-.242-2.542-.524-1.33-.282-1.896-.484-1.129-.323-1.895-.847-2.582-1.694-2.622-5.083 0-2.702 1.855-4.477 2.26-2.179 6.414-1.977 2.783.121 5.567.726 1.048.242 1.734 1.09.686.846.686 1.936 0 1.25-.928 2.178-.887.887-2.178.887-.323 0-.645-.08-1.17-.242-4.518-.565-.404-.04-.767 0-.323.04-.323.242.04.242.323.323zm17.555 0q.968.243 2.42.525 2.42.403 3.792 1.29 2.581 1.695 2.581 5.083 0 2.743-1.815 4.478-2.098 2.017-5.849 2.017-2.743 0-6.131-.767-1.09-.242-1.775-1.089-.646-.847-.646-1.896 0-1.29.888-2.178.927-.928 2.178-.928.363 0 .686.081 1.17.242 4.477.605.444 0 .968-.04.202 0 .202-.242.04-.202-.242-.283-1.371-.242-2.541-.524-1.331-.282-1.896-.484-1.13-.323-1.896-.847-2.582-1.694-2.622-5.083 0-2.702 1.855-4.477 2.26-2.179 6.414-1.977 2.784.121 5.567.726 1.049.242 1.735 1.09.685.846.685 1.936 0 1.25-.927 2.178-.888.887-2.179.887-.322 0-.645-.08-1.17-.242-4.518-.565-.403-.04-.767 0-.322.04-.322.242.04.242.322.323zm26.075 3.335q.12.08 2.864 2.783 1.25 1.21 1.25 2.945 0 1.613-1.17 2.864-1.17 1.21-2.904 1.21-1.654 0-2.864-1.17l-4.034-3.913q-.161-.12-.323-.12-.322 0-.322 1.21 0 1.694-1.21 2.904-1.21 1.17-2.905 1.17-1.694 0-2.904-1.17-1.17-1.21-1.17-2.905V17.586q0-1.694 1.17-2.864 1.21-1.21 2.904-1.21t2.904 1.21q1.21 1.17 1.21 2.864v6.293q0 .403.283.524.242.121.524-.08.162-.081 4.841-3.188 1.049-.645 2.259-.645 2.219 0 3.429 1.815.645 1.05.645 2.26 0 2.218-1.815 3.428l-2.541 1.614v.04l-.081.04q-.565.363-.04.888zm15.599 10.058q-4.195 0-7.18-2.945-2.945-2.985-2.945-7.18 0-4.155 2.945-7.1 2.985-2.985 7.18-2.985 4.155 0 6.979 2.784.928.927.928 2.259 0 1.33-.928 2.259l-4.68 4.639q-1.008 1.008-2.016 1.008-1.453 0-2.26-.807-.806-.807-.806-2.138 0-1.29.928-2.218l.806-.847q.162-.121.081-.243-.12-.08-.323-.04-.806.202-1.371.807-1.13 1.09-1.13 2.622 0 1.573 1.09 2.703 1.13 1.089 2.702 1.089 1.533 0 2.622-1.13.928-.927 2.26-.927 1.33 0 2.258.927.928.928.928 2.26 0 1.33-.928 2.258-2.985 2.945-7.14 2.945zm29.259-15.786v5.607q0 .564-.08 1.21v7.382q0 4.518-2.744 7.22-2.702 2.703-7.301 2.703-2.662 0-4.8-1.008-2.138-.968-2.138-3.348 0-.807.363-1.533.968-2.179 3.348-2.179.565 0 1.573.323 1.009.323 1.654.323 1.694 0 2.219-.726.201-.283.08-.444-.161-.242-.564-.161-.686.12-1.493.12-4.074 0-6.979-2.904-2.904-2.904-2.904-6.978v-5.607q0-1.695 1.17-2.864 1.21-1.21 2.904-1.21t2.905 1.21q1.21 1.17 1.21 2.864v5.607q0 .685.484 1.21.524.484 1.21.484.726 0 1.21-.484.484-.525.484-1.21v-5.607q0-1.695 1.21-2.864 1.21-1.21 2.905-1.21 1.694 0 2.864 1.21 1.21 1.17 1.21 2.864z"
+       style="line-height:136.34428406px;-inkscape-font-specification:'OTADESIGN Rounded'" />
+  </g>
+</svg>
diff --git a/chart/Chart.yaml b/chart/Chart.yaml
new file mode 100644
index 0000000000..8f31cf7fb4
--- /dev/null
+++ b/chart/Chart.yaml
@@ -0,0 +1,3 @@
+apiVersion: v2
+name: misskey
+version: 0.0.0
diff --git a/chart/files/default.yml b/chart/files/default.yml
new file mode 100644
index 0000000000..a9ef22f424
--- /dev/null
+++ b/chart/files/default.yml
@@ -0,0 +1,165 @@
+#โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
+# Misskey configuration
+#โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
+
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ URL โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+# Final accessible URL seen by a user.
+# url: https://example.tld/
+
+# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
+# URL SETTINGS AFTER THAT!
+
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ Port and TLS settings โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+#
+# Misskey supports two deployment options for public.
+#
+
+# Option 1: With Reverse Proxy
+#
+#                 +----- https://example.tld/ ------------+
+#   +------+      |+-------------+      +----------------+|
+#   | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
+#   +------+      |+-------------+      +----------------+|
+#                 +---------------------------------------+
+#
+#   You need to setup reverse proxy. (eg. nginx)
+#   You do not define 'https' section.
+
+# Option 2: Standalone
+#
+#                 +- https://example.tld/ -+
+#   +------+      |   +---------------+    |
+#   | User | ---> |   | Misskey (443) |    |
+#   +------+      |   +---------------+    |
+#                 +------------------------+
+#
+#   You need to run Misskey as root.
+#   You need to set Certificate in 'https' section.
+
+# To use option 1, uncomment below line.
+port: 3000 # A port that your Misskey server should listen.
+
+# To use option 2, uncomment below lines.
+#port: 443
+
+#https:
+#  # path for certification
+#  key: /etc/letsencrypt/live/example.tld/privkey.pem
+#  cert: /etc/letsencrypt/live/example.tld/fullchain.pem
+
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ PostgreSQL configuration โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+db:
+  host: localhost
+  port: 5432
+
+  # Database name
+  db: misskey
+
+  # Auth
+  user: example-misskey-user
+  pass: example-misskey-pass
+
+  # Whether disable Caching queries
+  #disableCache: true
+
+  # Extra Connection options
+  #extra:
+  #  ssl: true
+
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ Redis configuration โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+redis:
+  host: localhost
+  port: 6379
+  #pass: example-pass
+  #prefix: example-prefix
+  #db: 1
+
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ Elasticsearch configuration โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+#elasticsearch:
+#  host: localhost
+#  port: 9200
+#  ssl: false
+#  user:
+#  pass:
+
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ ID generation โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+# You can select the ID generation method.
+# You don't usually need to change this setting, but you can
+# change it according to your preferences.
+
+# Available methods:
+# aid ... Short, Millisecond accuracy
+# meid ... Similar to ObjectID, Millisecond accuracy
+# ulid ... Millisecond accuracy
+# objectid ... This is left for backward compatibility
+
+# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
+# ID SETTINGS AFTER THAT!
+
+id: "aid"
+#   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
+#โ”€โ”€โ”€โ”˜ Other configuration โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+
+# Whether disable HSTS
+#disableHsts: true
+
+# Number of worker processes
+#clusterLimit: 1
+
+# Job concurrency per worker
+# deliverJobConcurrency: 128
+# inboxJobConcurrency: 16
+
+# Job rate limiter
+# deliverJobPerSec: 128
+# inboxJobPerSec: 16
+
+# Job attempts
+# deliverJobMaxAttempts: 12
+# inboxJobMaxAttempts: 8
+
+# IP address family used for outgoing request (ipv4, ipv6 or dual)
+#outgoingAddressFamily: ipv4
+
+# Syslog option
+#syslog:
+#  host: localhost
+#  port: 514
+
+# Proxy for HTTP/HTTPS
+#proxy: http://127.0.0.1:3128
+
+#proxyBypassHosts: [
+#  'example.com',
+#  '192.0.2.8'
+#]
+
+# Proxy for SMTP/SMTPS
+#proxySmtp: http://127.0.0.1:3128   # use HTTP/1.1 CONNECT
+#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
+#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
+
+# Media Proxy
+#mediaProxy: https://example.com/proxy
+
+# Sign to ActivityPub GET request (default: false)
+#signToActivityPubGet: true
+
+#allowedPrivateNetworks: [
+#  '127.0.0.1/32'
+#]
+
+# Upload or download file size limits (bytes)
+#maxFileSize: 262144000
diff --git a/chart/templates/ConfigMap.yml b/chart/templates/ConfigMap.yml
new file mode 100644
index 0000000000..37c25e0864
--- /dev/null
+++ b/chart/templates/ConfigMap.yml
@@ -0,0 +1,8 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ include "misskey.fullname" . }}-configuration
+data:
+  default.yml: |-
+    {{ .Files.Get "files/default.yml"|nindent 4 }}
+    url: {{ .Values.url }}
diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml
new file mode 100644
index 0000000000..d16aece915
--- /dev/null
+++ b/chart/templates/Deployment.yml
@@ -0,0 +1,47 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "misskey.fullname" . }}
+  labels:
+		{{- include "misskey.labels" . | nindent 4 }}
+spec:
+  selector:
+    matchLabels:
+			{{- include "misskey.selectorLabels" . | nindent 6 }}
+  replicas: 1
+  template:
+    metadata:
+      labels:
+				{{- include "misskey.selectorLabels" . | nindent 8 }}
+    spec:
+      containers:
+        - name: misskey
+          image: {{ .Values.image }}
+          env:
+            - name: NODE_ENV
+              value: {{ .Values.environment }}
+          volumeMounts:
+            - name: {{ include "misskey.fullname" . }}-configuration
+              mountPath: /misskey/.config
+              readOnly: true
+          ports:
+            - containerPort: 3000
+        - name: postgres
+          image: postgres:14-alpine
+          env:
+            - name: POSTGRES_USER
+              value: "example-misskey-user"
+            - name: POSTGRES_PASSWORD
+              value: "example-misskey-pass"
+            - name: POSTGRES_DB
+              value: "misskey"
+          ports:
+            - containerPort: 5432
+        - name: redis
+          image: redis:alpine
+          ports:
+            - containerPort: 6379
+      volumes:
+        - name: {{ include "misskey.fullname" . }}-configuration
+          configMap:
+            name: {{ include "misskey.fullname" . }}-configuration
diff --git a/chart/templates/Service.yml b/chart/templates/Service.yml
new file mode 100644
index 0000000000..3209581298
--- /dev/null
+++ b/chart/templates/Service.yml
@@ -0,0 +1,14 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "misskey.fullname" . }}
+  annotations:
+    dev.okteto.com/auto-ingress: "true"
+spec:
+  type: ClusterIP
+  ports:
+    - port: 3000
+      protocol: TCP
+      name: http
+  selector:
+		{{- include "misskey.selectorLabels" . | nindent 4 }}
diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl
new file mode 100644
index 0000000000..a5a2499f3f
--- /dev/null
+++ b/chart/templates/_helpers.tpl
@@ -0,0 +1,62 @@
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "misskey.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "misskey.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "misskey.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "misskey.labels" -}}
+helm.sh/chart: {{ include "misskey.chart" . }}
+{{ include "misskey.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "misskey.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "misskey.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "misskey.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "misskey.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}
diff --git a/chart/values.yml b/chart/values.yml
new file mode 100644
index 0000000000..a7031538a9
--- /dev/null
+++ b/chart/values.yml
@@ -0,0 +1,3 @@
+url: https://example.tld/
+image: okteto.dev/misskey
+environment: production
diff --git a/cypress.config.ts b/cypress.config.ts
new file mode 100644
index 0000000000..e390c41a54
--- /dev/null
+++ b/cypress.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from 'cypress'
+
+export default defineConfig({
+  e2e: {
+    // We've imported your old cypress plugins here.
+    // You may want to clean this up later by importing these.
+    setupNodeEvents(on, config) {
+      return require('./cypress/plugins/index.js')(on, config)
+    },
+    baseUrl: 'http://localhost:61812',
+  },
+})
diff --git a/cypress.json b/cypress.json
deleted file mode 100644
index e858e480b0..0000000000
--- a/cypress.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-	"baseUrl": "http://localhost:61812"
-}
diff --git a/cypress/integration/basic.js b/cypress/e2e/basic.cy.js
similarity index 75%
rename from cypress/integration/basic.js
rename to cypress/e2e/basic.cy.js
index 7d27b649f4..eb5195c4b2 100644
--- a/cypress/integration/basic.js
+++ b/cypress/e2e/basic.cy.js
@@ -1,8 +1,6 @@
 describe('Before setup instance', () => {
 	beforeEach(() => {
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 	});
 
 	afterEach(() => {
@@ -32,15 +30,10 @@ describe('Before setup instance', () => {
 
 describe('After setup instance', () => {
 	beforeEach(() => {
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 
 		// ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นๅˆๆœŸใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 	});
 
 	afterEach(() => {
@@ -70,21 +63,13 @@ describe('After setup instance', () => {
 
 describe('After user signup', () => {
 	beforeEach(() => {
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 
 		// ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นๅˆๆœŸใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 
 		// ใƒฆใƒผใ‚ถใƒผไฝœๆˆ
-		cy.request('POST', '/api/signup', {
-			username: 'alice',
-			password: 'alice1234',
-		}).its('body').as('alice');
+		cy.registerUser('alice', 'alice1234');
 	});
 
 	afterEach(() => {
@@ -129,31 +114,15 @@ describe('After user signup', () => {
 
 describe('After user singed in', () => {
 	beforeEach(() => {
-		cy.request('POST', '/api/reset-db').as('reset');
-		cy.get('@reset').its('status').should('equal', 204);
-		cy.reload(true);
+		cy.resetState();
 
 		// ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นๅˆๆœŸใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—
-		cy.request('POST', '/api/admin/accounts/create', {
-			username: 'admin',
-			password: 'pass',
-		}).its('body').as('admin');
+		cy.registerUser('admin', 'pass', true);
 
 		// ใƒฆใƒผใ‚ถใƒผไฝœๆˆ
-		cy.request('POST', '/api/signup', {
-			username: 'alice',
-			password: 'alice1234',
-		}).its('body').as('alice');
+		cy.registerUser('alice', 'alice1234');
 
-		cy.visit('/');
-
-		cy.intercept('POST', '/api/signin').as('signin');
-
-		cy.get('[data-cy-signin]').click();
-		cy.get('[data-cy-signin-username] input').type('alice');
-		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
-
-		cy.wait('@signin').as('signedIn');
+		cy.login('alice', 'alice1234');
 	});
 
 	afterEach(() => {
@@ -163,12 +132,10 @@ describe('After user singed in', () => {
 	});
 
   it('successfully loads', () => {
-    cy.visit('/');
+		cy.get('[data-cy-open-post-form]').should('be.visible');
   });
 
 	it('note', () => {
-    cy.visit('/');
-
 		cy.get('[data-cy-open-post-form]').click();
 		cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
 		cy.get('[data-cy-open-post-form-submit]').click();
diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js
new file mode 100644
index 0000000000..56ad95ee94
--- /dev/null
+++ b/cypress/e2e/widgets.cy.js
@@ -0,0 +1,65 @@
+describe('After user signed in', () => {
+	beforeEach(() => {
+		cy.resetState();
+		cy.viewport('macbook-16');
+
+		// ใ‚คใƒณใ‚นใ‚ฟใƒณใ‚นๅˆๆœŸใ‚ปใƒƒใƒˆใ‚ขใƒƒใƒ—
+		cy.registerUser('admin', 'pass', true);
+
+		// ใƒฆใƒผใ‚ถใƒผไฝœๆˆ
+		cy.registerUser('alice', 'alice1234');
+
+		cy.login('alice', 'alice1234');
+	});
+
+	afterEach(() => {
+		// ใƒ†ใ‚นใƒˆ็ต‚ไบ†็›ดๅ‰ใซใƒšใƒผใ‚ธ้ท็งปใ™ใ‚‹ใ‚ˆใ†ใชใƒ†ใ‚นใƒˆใ‚ฑใƒผใ‚น(ไพ‹ใˆใฐใ‚ขใ‚ซใ‚ฆใƒณใƒˆไฝœๆˆ)ใ ใจใ€ใŸใถใ‚“Cypressใฎใƒใ‚ฐใงใƒ–ใƒฉใ‚ฆใ‚ถใฎๅ†…ๅฎนใŒๆฌกใฎใƒ†ใ‚นใƒˆใ‚ฑใƒผใ‚นใซๅผ•ใ็ถ™ใŒใ‚Œใฆใ—ใพใ†(ไพ‹ใˆใฐใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒไฝœๆˆใ—็ต‚ใ‚ใฃใŸๆฎต้šŽใ‹ใ‚‰ใƒ†ใ‚นใƒˆใŒๅง‹ใพใ‚‹)ใ€‚
+		// waitใ‚’ๅ…ฅใ‚Œใ‚‹ใ“ใจใงใใ‚Œใ‚’้˜ฒๆญขใงใใ‚‹
+		cy.wait(1000);
+	});
+
+  it('widget edit toggle is visible', () => {
+		cy.get('.mk-widget-edit').should('be.visible');
+  });
+
+	it('widget select should be visible in edit mode', () => {
+		cy.get('.mk-widget-edit').click();
+		cy.get('.mk-widget-select').should('be.visible');
+  });
+
+	it('first widget should be removed', () => {
+		cy.get('.mk-widget-edit').click();
+		cy.get('.customize-container:first-child .remove._button').click();
+		cy.get('.customize-container').should('have.length', 2);
+	});
+
+	function buildWidgetTest(widgetName) {
+		it(`${widgetName} widget should get added`, () => {
+			cy.get('.mk-widget-edit').click();
+			cy.get('.mk-widget-select select').select(widgetName, { force: true });
+			cy.get('.bg._modalBg.transparent').click({ multiple: true, force: true });
+			cy.get('.mk-widget-add').click({ force: true });
+			cy.get(`.mkw-${widgetName}`).should('exist');
+		});
+	}
+
+	buildWidgetTest('memo');
+	buildWidgetTest('notifications');
+	buildWidgetTest('timeline');
+	buildWidgetTest('calendar');
+	buildWidgetTest('rss');
+	buildWidgetTest('trends');
+	buildWidgetTest('clock');
+	buildWidgetTest('activity');
+	buildWidgetTest('photos');
+	buildWidgetTest('digitalClock');
+	buildWidgetTest('federation');
+	buildWidgetTest('postForm');
+	buildWidgetTest('slideshow');
+	buildWidgetTest('serverMetric');
+	buildWidgetTest('onlineUsers');
+	buildWidgetTest('jobQueue');
+	buildWidgetTest('button');
+	buildWidgetTest('aiscript');
+	buildWidgetTest('aichan');
+});
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 119ab03f7c..95bfcf6855 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -23,3 +23,33 @@
 //
 // -- This will overwrite an existing command --
 // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
+
+Cypress.Commands.add('resetState', () => {
+	cy.window(win => {
+		win.indexedDB.deleteDatabase('keyval-store');
+	});
+	cy.request('POST', '/api/reset-db').as('reset');
+	cy.get('@reset').its('status').should('equal', 204);
+	cy.reload(true);
+});
+
+Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
+	const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup';
+
+	cy.request('POST', route, {
+		username: username,
+		password: password,
+	}).its('body').as(username);
+});
+
+Cypress.Commands.add('login', (username, password) => {
+	cy.visit('/');
+
+	cy.intercept('POST', '/api/signin').as('signin');
+
+	cy.get('[data-cy-signin]').click();
+	cy.get('[data-cy-signin-username] input').type(username);
+	cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
+
+	cy.wait('@signin').as('signedIn');
+});
diff --git a/cypress/support/index.js b/cypress/support/e2e.js
similarity index 100%
rename from cypress/support/index.js
rename to cypress/support/e2e.js
diff --git a/docker-compose.yml b/docker-compose.yml
index e1d51668a7..0bf17a5557 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -9,7 +9,7 @@ services:
       - redis
 #      - es
     ports:
-      - "127.0.0.1:3000:3000"
+      - "3000:3000"
     networks:
       - internal_network
       - external_network
diff --git a/gulpfile.js b/gulpfile.js
index b7aa4e328e..90f8ebaabe 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -37,7 +37,6 @@ gulp.task('copy:client:locales', cb => {
 
 gulp.task('build:backend:script', () => {
 	return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
-		.pipe(replace('VERSION', JSON.stringify(meta.version)))
 		.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
 		.pipe(terser({
 			toplevel: true
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index f3f8b45777..3bd8f1e506 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -141,7 +141,7 @@ flagAsBotDescription: "ูุนู‘ู„ ู‡ุฐุง ุงู„ุฎูŠุงุฑ ุฅุฐุง ูƒุงู† ู‡ุฐุง ุงู„ุญ
 flagAsCat: "ุนู„ู‘ู… ู‡ุฐุง ุงู„ุญุณุงุจ ูƒุญุณุงุจ ู‚ุท"
 flagAsCatDescription: "ูุนู‘ู„ ู‡ุฐุง ุงู„ุฎูŠุงุฑ ู„ูˆุถุน ุนู„ุงู…ุฉ ุนู„ู‰ ุงู„ุญุณุงุจ ู„ุชูˆุถูŠุญ ุฃู†ู‡ ุญุณุงุจ ู‚ุท."
 flagShowTimelineReplies: "ุฃุธู‡ุฑ ุงู„ุชุนู„ูŠู‚ุงุช ููŠ ุงู„ุฎูŠุท ุงู„ุฒู…ู†ูŠ"
-flagShowTimelineRepliesDescription: "ูŠุธู‡ุฑ ุงู„ุฑุฏูˆุฏ ููŠ ุงู„ุฎุท ุงู„ุฒู…ู†ูŠ"
+flagShowTimelineRepliesDescription: "ูŠุธู‡ุฑ ุงู„ุฑุฏูˆุฏ ููŠ ุงู„ุฎูŠุท ุงู„ุฒู…ู†ูŠ"
 autoAcceptFollowed: "ุงู‚ุจู„ ุทู„ุจุงุช ุงู„ู…ุชุงุจุนุฉ ุชู„ู‚ุงุฆูŠุง ู…ู† ุงู„ุญุณุงุจุงุช ุงู„ู…ุชุงุจูŽุนุฉ"
 addAccount: "ุฃุถู ุญุณุงุจุงู‹"
 loginFailed: "ูุดู„ ุงู„ูˆู„ูˆุฌ"
@@ -312,12 +312,12 @@ dayX: "{day}"
 monthX: "{month}"
 yearX: "{year}"
 pages: "ุงู„ุตูุญุงุช"
-integration: "ุฏู…ุฌ"
+integration: "ุงู„ุชูƒุงู…ู„"
 connectService: "ุงุชุตู„"
 disconnectService: "ุงู‚ุทุน ุงู„ุงุชุตุงู„"
 enableLocalTimeline: "ุชูุนูŠู„ ุงู„ุฎูŠุท ุงู„ู…ุญู„ูŠ"
 enableGlobalTimeline: "ุชูุนูŠู„ ุงู„ุฎูŠุท ุงู„ุฒู…ู†ูŠ ุงู„ุดุงู…ู„"
-disablingTimelinesInfo: "ุณูŠุชู…ูƒู† ุงู„ู…ุฏูŠุฑูˆู† ูˆุงู„ู…ุดุฑููˆู† ู…ู† ุงู„ูˆุตูˆู„ ุฅู„ู‰ ูƒู„ ุงู„ุฎุทูˆุท ุงู„ุฒู…ู†ูŠุฉ ุญุชู‰ ูˆุฅู† ู„ู… ุชูุนู‘ู„."
+disablingTimelinesInfo: "ุณูŠุชู…ูƒู† ุงู„ู…ุฏูŠุฑูˆู† ูˆุงู„ู…ุดุฑููˆู† ู…ู† ุงู„ูˆุตูˆู„ ุฅู„ู‰ ูƒู„ ุงู„ุฎูŠูˆุท ุงู„ุฒู…ู†ูŠุฉ ุญุชู‰ ูˆุฅู† ู„ู… ุชูุนู‘ู„."
 registration: "ุฅู†ุดุงุก ุญุณุงุจ"
 enableRegistration: "ุชูุนูŠู„ ุฅู†ุดุงุก ุงู„ุญุณุงุจุงุช ุงู„ุฌุฏูŠุฏุฉ"
 invite: "ุฏุนูˆุฉ"
@@ -532,6 +532,7 @@ poll: "ุงุณุชุทู„ุงุน ุฑุฃูŠ"
 useCw: "ุฅุฎูุงุก ุงู„ู…ุญุชูˆู‰"
 enablePlayer: "ุงูุชุญ ู…ุดุบู„ ุงู„ููŠุฏูŠูˆ"
 disablePlayer: "ุฃุบู„ู‚ ู…ุดุบู„ ุงู„ููŠุฏูŠูˆ"
+expandTweet: "ูˆุณู‘ุน ุงู„ุชุบุฑูŠุฏุฉ"
 themeEditor: "ู…ุตู…ู… ุงู„ู‚ูˆุงู„ุจ"
 description: "ุงู„ูˆุตู"
 describeFile: "ุฃุถู ุชุนู„ูŠู‚ู‹ุง ุชูˆุถูŠุญูŠู‹ุง"
@@ -635,6 +636,7 @@ yes: "ู†ุนู…"
 no: "ู„ุง"
 driveFilesCount: "ุนุฏุฏ ุงู„ู…ู„ูุงุช ููŠ ู‚ุฑุต ุงู„ุชุฎุฒูŠู†"
 driveUsage: "ุงู„ู…ุณุชุบู„ ู…ู† ู‚ุฑุต ุงู„ุชุฎุฒูŠู†"
+noCrawle: "ุงุฑูุถ ูู‡ุฑุณุฉ ุฒุงุญู ุงู„ูˆูŠุจ"
 noCrawleDescription: "ูŠุทู„ุจ ู…ู† ู…ุญุฑูƒุงุช ุงู„ุจุญุซ ุฃู„ู‘ุง ูŠููู‡ุฑุณูˆุง ู…ู„ููƒ ุงู„ุดุฎุตูŠ ูˆู…ู„ุงุญุธุงุช ูˆุตูุญุงุชูƒ ูˆู…ุง ุดุงุจู‡."
 alwaysMarkSensitive: "ุนู„ู‘ู… ุงูุชุฑุงุถูŠู‹ุง ุฌู…ูŠุน ู…ู„ุงุญุธุงุชูŠ ูƒุฐุงุช ู…ุญุชูˆู‰ ุญุณุงุณ"
 loadRawImages: "ุญู…ู‘ู„ ุงู„ุตูˆุฑ ุงู„ุฃุตู„ูŠุฉ ุจุฏู„ู‹ุง ู…ู† ุงู„ู…ุตุบุฑุงุช"
@@ -878,9 +880,11 @@ _mfm:
   center: "ูˆุณุท"
   centerDescription: "ูŠู…ุฑูƒุฒ ุงู„ู…ุญุชูˆู‰ ููŠ ุงู„ูˆูŽุณูŽุท."
   quote: "ุงู‚ุชุจุณ"
+  quoteDescription: "ูŠุนุฑุถ ุงู„ู…ุญุชูˆู‰ ูƒุงู‚ุชุจุงุณ"
   emoji: "ุฅูŠู…ูˆุฌูŠ ู…ุฎุตุต"
   emojiDescription: "ุฅุญุงุทุฉ ุงุณู… ุงู„ุฅูŠู…ูˆุฌูŠ ุจู†ู‚ุทุชูŠ ุชูุณูŠุฑ ุณูŠุณุชุจุฏู„ู‡ ุจุตูˆุฑุฉ ุงู„ุฅูŠู…ูˆุฌูŠ."
   search: "ุงู„ุจุญุซ"
+  searchDescription: "ูŠุนุฑุถ ู†ุตู‹ุง ููŠ ุตู†ุฏูˆู‚ ุงู„ุจุญุซ"
   flip: "ุงู‚ู„ุจ"
   flipDescription: "ูŠู‚ู„ุจ ุงู„ู…ุญุชูˆู‰ ุนู…ูˆุฏูŠู‹ุง ุฃูˆ ุฃูู‚ูŠู‹ุง"
   jelly: "ุชุฃุซูŠุฑ (ู‡ู„ุงู…)"
@@ -1003,7 +1007,6 @@ _sfx:
   antenna: "ุงู„ู‡ูˆุงุฆูŠุงุช"
   channel: "ุฅุดุนุงุฑุงุช ุงู„ู‚ู†ุงุช"
 _ago:
-  unknown: "ู…ุฌู‡ูˆู„"
   future: "ุงู„ู…ุณุชู‚ุจูŽู„"
   justNow: "ุงู„ู„ุญุธุฉ"
   secondsAgo: "ู…ู†ุฐ {n} ุซูˆุงู†ู"
@@ -1030,12 +1033,12 @@ _tutorial:
   step3_3: "ุงู…ู„ุฃ ุงู„ู†ู…ูˆุฐุฌ ูˆุงู†ู‚ุฑ ุงู„ุฒุฑู‘ ุงู„ู…ูˆุฌูˆุฏ ููŠ ุฃุนู„ู‰ ุงู„ูŠู…ูŠู† ู„ู„ุฅุฑุณุงู„."
   step3_4: "ู„ูŠุณ ู„ุฏูŠูƒ ู…ุง ุชู‚ูˆู„ู‡ุŸ ุฅุฐุง ุงูƒุชุจ \"ุจุฏุฃุชู ุงุณุชุฎุฏู… ู…ูŠุณูƒูŠ\"."
   step4_1: "ู‡ู„ ู†ุดุฑุช ู…ู„ุงุญุธุชูƒ ุงู„ุฃูˆู„ู‰ุŸ"
-  step4_2: "ู…ุฑุญู‰! ูŠู…ูƒู†ูƒ ุงู„ุขู† ุฑุคูŠุฉ ู…ู„ุงุญุธุชูƒ ููŠ ุงู„ุฎุท ุงู„ุฒู…ู†ูŠ."
-  step5_1: "ูˆุงู„ุขู†ุŒ ู„ู†ุฌุนู„ ุงู„ุฎุท ุงู„ุฒู…ู†ูŠ ุฃูƒุซุฑ ุญูŠูˆูŠุฉ ูˆุฐู„ูƒ ุจู…ุชุงุจุนุฉ ุจุนุถ ุงู„ู…ุณุชุฎุฏู…ูŠู†."
+  step4_2: "ู…ุฑุญู‰! ูŠู…ูƒู†ูƒ ุงู„ุขู† ุฑุคูŠุฉ ู…ู„ุงุญุธุชูƒ ููŠ ุงู„ุฎูŠุท ุงู„ุฒู…ู†ูŠ."
+  step5_1: "ูˆุงู„ุขู†ุŒ ู„ู†ุฌุนู„ ุงู„ุฎูŠุท ุงู„ุฒู…ู†ูŠ ุฃูƒุซุฑ ุญูŠูˆูŠุฉ ูˆุฐู„ูƒ ุจู…ุชุงุจุนุฉ ุจุนุถ ุงู„ู…ุณุชุฎุฏู…ูŠู†."
   step5_2: "ุชุนุฑุถ ุตูุญุฉ {features} ุงู„ู…ู„ุงุญุธุงุช ุงู„ู…ุชุฏุงูˆู„ุฉ ููŠ ู‡ุฐุง ุงู„ู…ุซูŠู„ ูˆูŠุชูŠุญ ู„ูƒ {Explore} ุงู„ุนุซูˆุฑ ุนู„ู‰ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุฑุงุฆุฏูŠู†. ุงุนุซุฑ ุนู„ู‰ ุงู„ุฃุดุฎุงุต ุงู„ุฐูŠู† ูŠุซูŠุฑูˆู† ุฅู‡ุชู…ุงู…ูƒ ูˆุชุงุจุนู‡ู…!"
   step5_3: "ู„ู…ุชุงุจุนุฉ ู…ุณุชุฎุฏู…ูŠู† ุงุฏุฎู„ ู…ู„ูู‡ู… ุงู„ุดุฎุตูŠ ุจุงู„ู†ู‚ุฑ ุนู„ู‰ ุตูˆุฑุชู‡ู… ุงู„ุดุฎุตูŠุฉ ุซู… ุงุถุบุท ุฒุฑ 'ุชุงุจุน'."
   step5_4: "ุฅุฐุง ูƒุงู† ู„ุฏู‰ ุงู„ู…ุณุชุฎุฏู… ุฑู…ุฒ ู‚ูู„ ุจุฌูˆุงุฑ ุงุณู…ู‡ ุŒ ูˆุฌุจ ุนู„ูŠูƒ ุงู†ุชุธุงุฑู‡ ู„ูŠู‚ุจู„ ุทู„ุจ ุงู„ู…ุชุงุจุนุฉ ูŠุฏูˆูŠู‹ุง."
-  step6_1: "ุงู„ุขู† ุณุชุชู…ูƒู† ู…ู† ุฑุคูŠุฉ ู…ู„ุงุญุธุงุช ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู…ุชุงุจูŽุนูŠู† ููŠ ุงู„ุฎุท ุงู„ุฒู…ู†ูŠ."
+  step6_1: "ุงู„ุขู† ุณุชุชู…ูƒู† ู…ู† ุฑุคูŠุฉ ู…ู„ุงุญุธุงุช ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู…ุชุงุจูŽุนูŠู† ููŠ ุงู„ุฎูŠุท ุงู„ุฒู…ู†ูŠ."
   step6_2: "ูŠู…ูƒู†ูƒ ุงู„ุชูุงุนู„ ุจุณุฑุนุฉ ู…ุน ุงู„ู…ู„ุงุญุธุงุช ุนู† ุทุฑูŠู‚ ุฅุถุงูุฉ \"ุชูุงุนู„\"."
   step6_3: "ู„ุฅุถุงูุฉ ุชูุงุนู„ ู„ู…ู„ุงุญุธุฉ ุŒ ุงู†ู‚ุฑ ููˆู‚ ุนู„ุงู…ุฉ \"+\" ุฃุณูู„ ู„ู„ู…ู„ุงุญุธุฉ ูˆุงุฎุชุฑ ุงู„ุฅูŠู…ูˆุฌูŠ ุงู„ู…ุทู„ูˆุจ."
   step7_1: "ู…ุจุงุฑูƒ ! ุฃู†ู‡ูŠุช ุงู„ุฏูˆุฑุฉ ุงู„ุชุนู„ูŠู…ูŠุฉ ุงู„ุฃุณุงุณูŠุฉ ู„ุงุณุชุฎุฏุงู… ู…ูŠุณูƒูŠ."
@@ -1201,8 +1204,13 @@ _charts:
 _instanceCharts:
   requests: "ุงู„ุทู„ุจุงุช"
   users: "ุชุจุงูŠู† ุนุฏุฏ ุงู„ู…ุณุชุฎุฏู…ูŠู†"
+  usersTotal: "ุชุจุงูŠู† ุนุฏุฏ ุงู„ู…ุณุชุฎุฏู…ูŠู†"
   notes: "ุชุจุงูŠู† ุนุฏุฏ ุงู„ู…ู„ุงุญุธุงุช"
+  notesTotal: "ุชุจุงูŠู† ุนุฏุฏ ุงู„ู…ู„ุงุญุธุงุช"
+  ff: "ุชุจุงูŠู† ุนุฏุฏ ุญุณุงุจุงุช ุงู„ู…ุชุงุจูŽุนุฉ/ุงู„ู…ุชุงุจูุนุฉ"
+  ffTotal: "ุชุจุงูŠู† ุนุฏุฏ ุญุณุงุจุงุช ุงู„ู…ุชุงุจูŽุนุฉ/ุงู„ู…ุชุงุจูุนุฉ"
   files: "ุชุจุงูŠู† ุนุฏุฏ ุงู„ู…ู„ูุงุช"
+  filesTotal: "ุชุจุงูŠู† ุนุฏุฏ ุงู„ู…ู„ูุงุช"
 _timelines:
   home: "ุงู„ุฑุฆูŠุณูŠ"
   local: "ุงู„ู…ุญู„ูŠ"
@@ -1321,6 +1329,7 @@ _pages:
       random: "ุนุดูˆุงุฆูŠ"
       value: "ุงู„ู‚ูŠู…"
       fn: "ุฏูˆุงู„"
+      text: "ุฅุฌุฑุงุกุงุช ุนู„ู‰ ุงู„ู†ุตูˆุต"
       convert: "ุชุญูˆูŠู„"
       list: "ุงู„ู‚ูˆุงุฆู…"
     blocks:
@@ -1501,6 +1510,10 @@ _notification:
     followRequestAccepted: "ุทู„ุจุงุช ุงู„ู…ุชุงุจุนุฉ ุงู„ู…ู‚ุจูˆู„ุฉ"
     groupInvited: "ุฏุนูˆุงุช ุงู„ูุฑูŠู‚"
     app: "ุฅุดุนุงุฑุงุช ุงู„ุชุทุจูŠู‚ุงุช ุงู„ู…ุฑุชุจุทุฉ"
+  _actions:
+    followBack: "ุชุงุจุนูƒ ุจุงู„ู…ุซู„"
+    reply: "ุฑุฏ"
+    renote: "ุฃุนุฏ ุงู„ู†ุดุฑ"
 _deck:
   alwaysShowMainColumn: "ุฃุธู‡ุฑ ุงู„ุนู…ูˆุฏ ุงู„ุฑุฆูŠุณูŠ ุฏุงุฆู…ู‹ุง"
   columnAlign: "ุญุงุฐู ุงู„ุฃุนู…ุฏุฉ"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index b2ba236fd5..d7753b6dcf 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -831,11 +831,18 @@ themeColor: "เฆฅเฆฟเฆฎเง‡เฆฐ เฆฐเฆ‚"
 size: "เฆ†เฆ•เฆพเฆฐ"
 numberOfColumn: "เฆ•เฆฒเฆพเฆฎเง‡เฆฐ เฆธเฆ‚เฆ–เงเฆฏเฆพ"
 searchByGoogle: "เฆ—เงเฆ—เฆฒ"
+instanceDefaultLightTheme: "เฆ‡เฆจเงเฆธเฆŸเงเฆฏเฆพเฆจเงเฆธเง‡เฆฐ เฆกเฆฟเฆซเฆฒเงเฆŸ เฆฒเฆพเฆ‡เฆŸ เฆฅเฆฟเฆฎ"
+instanceDefaultDarkTheme: "เฆ‡เฆจเงเฆธเฆŸเงเฆฏเฆพเฆจเงเฆธเง‡เฆฐ เฆกเฆฟเฆซเฆฒเงเฆŸ เฆกเฆพเฆฐเงเฆ• เฆฅเฆฟเฆฎ"
+instanceDefaultThemeDescription: "เฆ…เฆฌเฆœเง‡เฆ•เงเฆŸ เฆซเฆฐเฆฎเงเฆฏเฆพเฆŸเง‡ เฆฅเฆฟเฆฎ เฆ•เง‹เฆก เฆฒเฆฟเฆ–เงเฆจ"
+mutePeriod: "เฆฎเฆฟเฆ‰เฆŸเง‡เฆฐ เฆธเฆฎเงŸเฆ•เฆพเฆฒ"
 indefinitely: "เฆ…เฆจเฆฟเฆฐเงเฆฆเฆฟเฆทเงเฆŸ"
 tenMinutes: "เงงเงฆ เฆฎเฆฟเฆจเฆฟเฆŸ"
 oneHour: "เงง เฆ˜เฆฃเงเฆŸเฆพ"
 oneDay: "เฆเฆ•เฆฆเฆฟเฆจ"
 oneWeek: "เฆเฆ• เฆธเฆชเงเฆคเฆพเฆน"
+reflectMayTakeTime: "เฆเฆŸเฆฟเฆฐ เฆ•เฆพเฆœ เฆฆเง‡เฆ–เฆพ เฆฏเง‡เฆคเง‡ เฆ•เฆฟเฆ›เงเฆŸเฆพ เฆธเฆฎเฆฏเฆผ เฆฒเฆพเฆ—เฆคเง‡ เฆชเฆพเฆฐเง‡เฅค"
+failedToFetchAccountInformation: "เฆ…เงเฆฏเฆพเฆ•เฆพเฆ‰เฆจเงเฆŸเง‡เฆฐ เฆคเฆฅเงเฆฏ เฆ‰เฆฆเงเฆงเฆพเฆฐ เฆ•เฆฐเฆพ เฆฏเฆพเงŸเฆจเฆฟ"
+rateLimitExceeded: "เฆฐเง‡เฆŸ เฆฒเฆฟเฆฎเฆฟเฆŸ เฆ›เฆพเงœเฆฟเงŸเง‡ เฆ—เง‡เฆ›เง‡ "
 _emailUnavailable:
   used: "เฆเฆ‡ เฆ‡เฆฎเง‡เฆ‡เฆฒ เฆ เฆฟเฆ•เฆพเฆจเฆพเฆŸเฆฟ เฆ‡เฆคเง‹เฆฎเฆงเงเฆฏเง‡ เฆฌเงเฆฏเฆฌเฆนเงƒเฆค เฆนเงŸเง‡เฆ›เง‡"
   format: "เฆเฆ‡ เฆ‡เฆฎเง‡เฆฒ เฆ เฆฟเฆ•เฆพเฆจเฆพเฆŸเฆฟ เฆธเฆ เฆฟเฆ•เฆญเฆพเฆฌเง‡ เฆฒเฆฟเฆ–เฆพ เฆนเงŸเฆจเฆฟ"
@@ -1081,7 +1088,6 @@ _sfx:
   antenna: "เฆ…เงเฆฏเฆพเฆจเงเฆŸเง‡เฆจเฆพเฆ—เงเฆฒเฆฟ"
   channel: "เฆšเงเฆฏเฆพเฆจเง‡เฆฒเง‡เฆฐ เฆฌเฆฟเฆœเงเฆžเฆชเงเฆคเฆฟ"
 _ago:
-  unknown: "เฆ…เฆœเฆพเฆจเฆพ"
   future: "เฆญเฆฌเฆฟเฆทเงเฆฏเงŽ"
   justNow: "เฆเฆ‡เฆฎเฆพเฆคเงเฆฐ"
   secondsAgo: "{n} เฆธเง‡เฆ•เง‡เฆจเงเฆก เฆ†เฆ—เง‡"
@@ -1125,6 +1131,7 @@ _2fa:
   registerKey: "เฆธเฆฟเฆ•เฆฟเฆ‰เฆฐเฆฟเฆŸเฆฟ เฆ•เง€ เฆจเฆฟเฆฌเฆจเงเฆงเฆจ เฆ•เฆฐเงเฆจ"
   step1: "เฆชเงเฆฐเฆฅเฆฎเง‡, เฆ†เฆชเฆจเฆพเฆฐ เฆกเฆฟเฆญเฆพเฆ‡เฆธเง‡ {a} เฆฌเฆพ {b} เฆเฆฐ เฆฎเฆคเง‹ เฆเฆ•เฆŸเฆฟ เฆ…เฆฅเง‡เฆจเฆŸเฆฟเฆ•เง‡เฆถเฆจ เฆ…เงเฆฏเฆพเฆช เฆ‡เฆจเฆธเงเฆŸเฆฒ เฆ•เฆฐเงเฆจเงท"
   step2: "เฆเฆฐเฆชเฆฐเง‡, เฆ…เงเฆฏเฆพเฆชเง‡เฆฐ เฆธเฆพเฆนเฆพเฆฏเงเฆฏเง‡ เฆชเงเฆฐเฆฆเฆฐเงเฆถเฆฟเฆค QR เฆ•เง‹เฆกเฆŸเฆฟ เฆธเงเฆ•เงเฆฏเฆพเฆจ เฆ•เฆฐเงเฆจเฅค"
+  step2Url: "เฆกเง‡เฆธเงเฆ•เฆŸเฆช เฆ…เงเฆฏเฆพเฆชเง‡, เฆจเฆฟเฆฎเงเฆจเฆฒเฆฟเฆ–เฆฟเฆค URL เฆฒเฆฟเฆ–เงเฆจ:"
   step3: "เฆ…เงเฆฏเฆพเฆชเง‡ เฆชเงเฆฐเฆฆเฆฐเงเฆถเฆฟเฆค เฆŸเง‹เฆ•เง‡เฆจเฆŸเฆฟ เฆฒเฆฟเฆ–เงเฆจ เฆเฆฌเฆ‚ เฆ†เฆชเฆจเฆพเฆฐ เฆ•เฆพเฆœ เฆถเง‡เฆทเฅค"
   step4: "เฆ†เฆชเฆจเฆพเฆ•เง‡ เฆเฆ–เฆจ เฆฅเง‡เฆ•เง‡ เฆฒเฆ— เฆ‡เฆจ เฆ•เฆฐเฆพเฆฐ เฆธเฆฎเฆฏเฆผ, เฆเฆ‡เฆญเฆพเฆฌเง‡ เฆŸเง‹เฆ•เง‡เฆจ เฆฒเฆฟเฆ–เฆคเง‡ เฆนเฆฌเง‡เฅค"
   securityKeyInfo: "เฆ†เฆชเฆจเฆฟ เฆเฆ•เฆŸเฆฟ เฆนเฆพเฆฐเงเฆกเฆ“เฆฏเฆผเงเฆฏเฆพเฆฐ เฆธเฆฟเฆ•เฆฟเฆ‰เฆฐเฆฟเฆŸเฆฟ เฆ•เง€ เฆฌเงเฆฏเฆฌเฆนเฆพเฆฐ เฆ•เฆฐเง‡ เฆฒเฆ— เฆ‡เฆจ เฆ•เฆฐเฆคเง‡ เฆชเฆพเฆฐเง‡เฆจ เฆฏเฆพ FIDO2 เฆฌเฆพ เฆกเฆฟเฆญเฆพเฆ‡เฆธเง‡เฆฐ เฆซเฆฟเฆ™เงเฆ—เฆพเฆฐเฆชเงเฆฐเฆฟเฆจเงเฆŸ เฆธเง‡เฆจเงเฆธเฆฐ เฆฌเฆพ เฆชเฆฟเฆจ เฆธเฆฎเฆฐเงเฆฅเฆจ เฆ•เฆฐเง‡เงท"
@@ -1608,6 +1615,8 @@ _notification:
   youReceivedFollowRequest: "เฆ…เฆจเงเฆธเฆฐเฆฃ เฆ•เฆฐเฆพเฆฐ เฆœเฆจเงเฆฏ เฆ…เฆจเงเฆฐเง‹เฆง เฆชเฆพเฆ“เงŸเฆพ เฆ—เง‡เฆ›เง‡"
   yourFollowRequestAccepted: "เฆ†เฆชเฆจเฆพเฆฐ เฆ…เฆจเงเฆธเฆฐเฆฃ เฆ•เฆฐเฆพเฆฐ เฆ…เฆจเงเฆฐเง‹เฆง เฆ—เงƒเฆนเง€เฆค เฆนเงŸเง‡เฆ›เง‡"
   youWereInvitedToGroup: "เฆ†เฆชเฆจเฆฟ เฆเฆ•เฆŸเฆฟ เฆ—เงเฆฐเงเฆชเง‡ เฆ†เฆฎเฆจเงเฆคเงเฆฐเฆฟเฆค เฆนเงŸเง‡เฆ›เง‡เฆจ"
+  pollEnded: "เฆชเง‹เฆฒเง‡เฆฐ เฆซเฆฒเฆพเฆซเฆฒ เฆฆเง‡เฆ–เฆพ เฆฏเฆพเฆฌเง‡"
+  emptyPushNotificationMessage: "เฆ†เฆชเฆกเง‡เฆŸ เฆ•เฆฐเฆพ เฆชเงเฆถ เฆฌเฆฟเฆœเงเฆžเฆชเงเฆคเฆฟ"
   _types:
     all: "เฆธเฆ•เฆฒ"
     follow: "เฆ…เฆจเงเฆธเฆฐเฆฃ เฆ•เฆฐเฆพ เฆนเฆšเงเฆ›เง‡"
@@ -1617,10 +1626,15 @@ _notification:
     quote: "เฆ‰เฆฆเงเฆงเงƒเฆคเฆฟ"
     reaction: "เฆชเงเฆฐเฆคเฆฟเฆ•เงเฆฐเฆฟเฆฏเฆผเฆพ"
     pollVote: "เฆชเง‹เฆฒเง‡ เฆญเง‹เฆŸ เฆ†เฆ›เง‡"
+    pollEnded: "เฆชเง‹เฆฒ เฆถเง‡เฆท"
     receiveFollowRequest: "เฆชเงเฆฐเฆพเฆชเงเฆค เฆ…เฆจเงเฆธเฆฐเฆฃเง‡เฆฐ เฆ…เฆจเงเฆฐเง‹เฆงเฆธเฆฎเง‚เฆน"
     followRequestAccepted: "เฆ—เงƒเฆนเง€เฆค เฆ…เฆจเงเฆธเฆฐเฆฃเง‡เฆฐ เฆ…เฆจเงเฆฐเง‹เฆงเฆธเฆฎเง‚เฆน"
     groupInvited: "เฆ—เงเฆฐเงเฆชเง‡เฆฐ เฆ†เฆฎเฆจเงเฆคเงเฆฐเฆจเฆธเฆฎเง‚เฆน"
     app: "เฆฒเฆฟเฆ™เงเฆ• เฆ•เฆฐเฆพ เฆ…เงเฆฏเฆพเฆช เฆฅเง‡เฆ•เง‡ เฆฌเฆฟเฆœเงเฆžเฆชเงเฆคเฆฟ"
+  _actions:
+    followBack: "เฆซเฆฒเง‹ เฆฌเงเฆฏเฆพเฆ• เฆ•เฆฐเง‡เฆ›เง‡"
+    reply: "เฆœเฆฌเฆพเฆฌ"
+    renote: "เฆฐเฆฟเฆจเง‹เฆŸ"
 _deck:
   alwaysShowMainColumn: "เฆธเฆฐเงเฆฌเฆฆเฆพ เฆฎเง‡เฆ‡เฆจ เฆ•เฆฒเฆพเฆฎ เฆฆเง‡เฆ–เฆพเฆจ"
   columnAlign: "เฆ•เฆฒเฆพเฆฎ เฆธเฆพเฆœเฆพเฆจ"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 5f74cb6bef..74eab3603b 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -1,6 +1,8 @@
 ---
 _lang_: "Catalร "
 headlineMisskey: "Una xarxa connectada per notes"
+introMisskey: "Benvingut! Misskey รฉs un servei de microblogging descentralitzat de codi obert.\nCrea \"notes\" per compartir els teus pensaments amb tots els que t'envolten. ๐Ÿ“ก\nAmb \"reaccions\", tambรฉ pots expressar rร pidament els teus sentiments sobre les notes de tothom. ๐Ÿ‘\nExplorem un mรณn nou! ๐Ÿš€"
+monthAndDay: "{day}/{month}"
 search: "Cercar"
 notifications: "Notificacions"
 username: "Nom d'usuari"
@@ -10,17 +12,175 @@ fetchingAsApObject: "Cercant en el Fediverse..."
 ok: "OK"
 gotIt: "Ho he entรจs!"
 cancel: "Cancelยทlar"
+enterUsername: "Introdueix el teu nom d'usuari"
+renotedBy: "Resignat per {usuari}"
+noNotes: "Cap nota"
+noNotifications: "Cap notificaciรณ"
+instance: "Instร ncies"
+settings: "Preferรจncies"
+basicSettings: "Configuraciรณ bร sica"
+otherSettings: "Configuraciรณ avanรงada"
+openInWindow: "Obrir en una nova finestra"
+profile: "Perfil"
+timeline: "Lรญnia de temps"
+noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia."
+login: "Iniciar sessiรณ"
+loggingIn: "Identificant-se"
+logout: "Tancar la sessiรณ"
+signup: "Registrar-se"
+uploading: "Pujant..."
+save: "Desar"
+users: "Usuaris"
+addUser: "Afegir un usuari"
+favorite: "Afegir a preferits"
+favorites: "Favorits"
+unfavorite: "Eliminar dels preferits"
+favorited: "Afegit als preferits."
+alreadyFavorited: "Ja s'ha afegit als preferits."
+cantFavorite: "No s'ha pogut afegir als preferits."
+pin: "Fixar al perfil"
+unpin: "Para de fixar del perfil"
+copyContent: "Copiar el contingut"
+copyLink: "Copiar l'enllaรง"
+delete: "Eliminar"
+deleteAndEdit: "Esborrar i editar"
+deleteAndEditConfirm: "Estร s segur que vols suprimir aquesta nota i editar-la? Perdrร s totes les reaccions, notes i respostes."
+addToList: "Afegir a una llista"
+sendMessage: "Enviar un missatge"
+copyUsername: "Copiar nom d'usuari"
+searchUser: "Cercar usuaris"
+reply: "Respondre"
+loadMore: "Carregar mรฉs"
+showMore: "Veure mรฉs"
+youGotNewFollower: "t'ha seguit"
+receiveFollowRequest: "Solยทlicitud de seguiment rebuda"
+followRequestAccepted: "Solยทlicitud de seguiment acceptada"
+mention: "Menciรณ"
+mentions: "Mencions"
+directNotes: "Notes directes"
+importAndExport: "Importar / Exportar"
+import: "Importar"
+export: "Exportar"
+files: "Fitxers"
+download: "Baixar"
+driveFileDeleteConfirm: "Estร s segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer adjunt tambรฉ se suprimiran."
+unfollowConfirm: "Estร s segur que vols deixar de seguir {name}?"
+exportRequested: "Has solยทlicitat una exportaciรณ. Aixรฒ pot trigar una estona. S'afegirร  a la teva unitat un cop completat."
+importRequested: "Has solยทlicitat una importaciรณ. Aixรฒ pot trigar una estona."
+lists: "Llistes"
+noLists: "No tens cap llista"
+note: "Nota"
+notes: "Notes"
+following: "Seguint"
+followers: "Seguidors"
+followsYou: "Et segueix"
+createList: "Crear llista"
+manageLists: "Gestionar les llistes"
+error: "Error"
+somethingHappened: "S'ha produรฏt un error"
+retry: "Torna-ho a intentar"
+pageLoadError: "S'ha produรฏt un error en carregar la pร gina"
+pageLoadErrorDescription: "Aixรฒ normalment es deu a errors de xarxa o a la memรฒria cau del navegador. Prova d'esborrar la memรฒria cau i torna-ho a provar desprรฉs d'esperar una estona."
+serverIsDead: "Aquest servidor no respon. Espera una estona i torna-ho a provar."
+youShouldUpgradeClient: "Per veure aquesta pร gina, actualitzeu-la per actualitzar el vostre client."
+enterListName: "Introdueix un nom per a la llista"
+privacy: "Privadesa"
+makeFollowManuallyApprove: "Les solยทlicituds de seguiment requereixen aprovaciรณ"
+defaultNoteVisibility: "Visibilitat per defecte"
+follow: "Seguint"
+followRequest: "Enviar la solยทlicitud de seguiment"
+followRequests: "Solยทlicituds de seguiment"
+unfollow: "Deixar de seguir"
+followRequestPending: "Solยทlicituds de seguiment pendents"
+enterEmoji: "Introduir un emoji"
+renote: "Renotar"
+unrenote: "Anulยทlar renota"
+renoted: "Renotat."
+cantRenote: "Aquesta publicaciรณ no pot ser renotada."
+cantReRenote: "Impossible renotar una renota."
+quote: "Citar"
+pinnedNote: "Nota fixada"
+pinned: "Fixar al perfil"
+you: "Tu"
+clickToShow: "Fes clic per mostrar"
+sensitive: "NSFW"
+add: "Afegir"
+reaction: "Reaccions"
+reactionSetting: "Reaccions a mostrar al selector de reaccions"
+reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
+rememberNoteVisibility: "Recorda la configuraciรณ de visibilitat de les notes"
+attachCancel: "Eliminar el fitxer adjunt"
+markAsSensitive: "Marcar com a NSFW"
+instances: "Instร ncies"
+remove: "Eliminar"
+nsfw: "NSFW"
+pinnedNotes: "Nota fixada"
+userList: "Llistes"
 smtpUser: "Nom d'usuari"
 smtpPass: "Contrasenya"
+user: "Usuaris"
 searchByGoogle: "Cercar"
+_email:
+  _follow:
+    title: "t'ha seguit"
 _mfm:
+  mention: "Menciรณ"
+  quote: "Citar"
   search: "Cercar"
+_theme:
+  keys:
+    mention: "Menciรณ"
+    renote: "Renotar"
 _sfx:
+  note: "Notes"
   notification: "Notificacions"
+_2fa:
+  step2Url: "Tambรฉ pots inserir aquest enllaรง i utilitzes una aplicaciรณ d'escriptori:"
 _widgets:
   notifications: "Notificacions"
+  timeline: "Lรญnia de temps"
+_cw:
+  show: "Carregar mรฉs"
+_visibility:
+  followers: "Seguidors"
 _profile:
   username: "Nom d'usuari"
+_exportOrImport:
+  followingList: "Seguint"
+  userLists: "Llistes"
+_pages:
+  script:
+    categories:
+      list: "Llistes"
+    blocks:
+      _join:
+        arg1: "Llistes"
+      _randomPick:
+        arg1: "Llistes"
+      _dailyRandomPick:
+        arg1: "Llistes"
+      _seedRandomPick:
+        arg2: "Llistes"
+      _pick:
+        arg1: "Llistes"
+      _listLen:
+        arg1: "Llistes"
+    types:
+      array: "Llistes"
+_notification:
+  youWereFollowed: "t'ha seguit"
+  _types:
+    follow: "Seguint"
+    mention: "Menciรณ"
+    renote: "Renotar"
+    quote: "Citar"
+    reaction: "Reaccions"
+  _actions:
+    reply: "Respondre"
+    renote: "Renotar"
 _deck:
   _columns:
     notifications: "Notificacions"
+    tl: "Lรญnia de temps"
+    list: "Llistes"
+    mentions: "Mencions"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 2f5e375372..4b20340df1 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -53,6 +53,8 @@ reply: "Odpovฤ›dฤ›t"
 loadMore: "Zobrazit vรญce"
 showMore: "Zobrazit vรญce"
 youGotNewFollower: "Mรกte novรฉho nรกsledovnรญka"
+receiveFollowRequest: "ลฝรกdost o sledovรกnรญ pล™ijata"
+followRequestAccepted: "ลฝรกdost o sledovรกnรญ pล™ijata"
 mention: "Zmรญnฤ›nรญ"
 mentions: "Zmรญnฤ›nรญ"
 importAndExport: "Import a export"
@@ -60,7 +62,9 @@ import: "Importovat"
 export: "Exportovat"
 files: "Soubor(ลฏ)"
 download: "Stรกhnout"
+driveFileDeleteConfirm: "Opravdu chcete smazat soubor \"{name}\"? Poznรกmky, ke kterรฝm je tento soubor pล™ipojen, budou takรฉ smazรกny."
 unfollowConfirm: "Jste si jisti ลพe uลพ nechcete sledovat {name}?"
+exportRequested: "Poลพรกdali jste o export. To mลฏลพe chvรญli trvat. Pล™idรกme ho na vรกลก Disk aลพ bude dokonฤen."
 importRequested: "Poลพรกdali jste o export. To mลฏลพe chvilku trvat."
 lists: "Seznamy"
 noLists: "Nemรกte ลพรกdnรฉ seznamy"
@@ -75,13 +79,25 @@ error: "Chyba"
 somethingHappened: "Jejda. Nฤ›co se nepovedlo."
 retry: "Opakovat"
 pageLoadError: "Nepodaล™ilo se naฤรญst strรกnku"
+serverIsDead: "Server neodpovรญdรก. Poฤkejte chvรญli a zkuste to znovu."
+youShouldUpgradeClient: "Pro zobrazenรญ tรฉto strรกnky obnovte strรกnku pro aktualizaci klienta."
 enterListName: "Jmรฉno seznamu"
 privacy: "Soukromรญ"
+makeFollowManuallyApprove: "ลฝรกdosti o sledovรกnรญ vyลพadujรญ potvrzenรญ"
+defaultNoteVisibility: "Vรฝchozรญ viditelnost"
 follow: "Sledovanรญ"
+followRequest: "Odeslat ลพรกdost o sledovรกnรญ"
+followRequests: "ลฝรกdosti o sledovรกnรญ"
 unfollow: "Pล™estat sledovat"
+followRequestPending: "ฤŒekajรญcรญ ลพรกdosti o sledovรกnรญ"
+enterEmoji: "Vloลพte emoji"
 renote: "Pล™eposlat"
+unrenote: "Zruลกit pล™eposlรกnรญ"
+renoted: "Pล™eposlรกno"
+cantRenote: "Tento pล™รญspฤ›vek nelze pล™eposlat."
 cantReRenote: "Odpovฤ›ฤ nemลฏลพe bรฝt odstranฤ›na."
 quote: "Citovat"
+pinnedNote: "Pล™ipnutรก poznรกmka"
 pinned: "Pล™ipnout"
 you: "Vy"
 clickToShow: "Kliknฤ›te pro zobrazenรญ"
@@ -122,6 +138,8 @@ flagAsBot: "Tento รบฤet je bot"
 flagAsBotDescription: "Pokud je tento รบฤet kontrolovรกn programem zaลกkrtnฤ›te tuto moลพnost. To oznaฤรญ tento รบฤet jako bot pro ostatnรญ vรฝvojรกล™e a zabrรกnรญ tak nekoneฤnรฝm interakcรญm s ostatnรญmi boty a upravรญ Misskey systรฉm aby se choval k tomuhle รบฤtu jako bot."
 flagAsCat: "Tenhle รบฤet je koฤka"
 flagAsCatDescription: "Vyberte tuto moลพnost aby tento รบฤet byl oznaฤen jako koฤka."
+flagShowTimelineReplies: "Zobrazovat odpovฤ›di na ฤasovรฉ ose"
+flagShowTimelineRepliesDescription: "Je-li zapnuto, zobrazรญ odpovฤ›di uลพivatelลฏ na poznรกmky jinรฝch uลพivatelลฏ na vaลกรญ ฤasovรฉ ose."
 autoAcceptFollowed: "Automaticky akceptovat nรกsledovรกnรญ od รบฤtลฏ kterรฉ sledujete"
 addAccount: "Pล™idat รบฤet"
 loginFailed: "Pล™ihlรกลกenรญ se nezdaล™ilo."
@@ -130,13 +148,16 @@ general: "Obecnฤ›"
 wallpaper: "Obrรกzek na pozadรญ"
 setWallpaper: "Nastavenรญ obrรกzku na pozadรญ"
 removeWallpaper: "Odstranit pozadรญ"
+searchWith: "Hledat: {q}"
 youHaveNoLists: "Nemรกte ลพรกdnรฉ seznamy"
+followConfirm: "Jste si jisti, ลพe chcete sledovat {name}?"
 proxyAccount: "Proxy รบฤet"
 proxyAccountDescription: "Proxy รบฤet je รบฤet, kterรฝ za urฤitรฝch podmรญnek sleduje uลพivatele na dรกlku vaลกรญm jmรฉnem. Napล™รญklad kdyลพ uลพivatel zaล™adรญ vzdรกlenรฉho uลพivatele do seznamu, pokud nikdo nesleduje uลพivatele na seznamu, aktivita nebude doruฤena instanci, takลพe mรญsto toho bude uลพivatele sledovat รบฤet proxy."
 host: "Hostitel"
 selectUser: "Vyberte uลพivatele"
 recipient: "Pro"
 annotation: "Komentรกล™e"
+federation: "Federace"
 instances: "Instance"
 registeredAt: "Registrovรกn"
 latestRequestSentAt: "Poslednรญ poลพadavek poslรกn"
@@ -146,6 +167,7 @@ storageUsage: "Vyuลพitรญ รบloลพiลกtฤ›"
 charts: "Grafy"
 perHour: "za hodinu"
 perDay: "za den"
+stopActivityDelivery: "Pล™estat zasรญlat aktivitu"
 blockThisInstance: "Blokovat tuto instanci"
 operations: "Operace"
 software: "Software"
@@ -283,6 +305,8 @@ iconUrl: "Favicon URL"
 bannerUrl: "Baner URL"
 backgroundImageUrl: "Adresa URL obrรกzku pozadรญ"
 basicInfo: "Zรกkladnรญ informace"
+pinnedUsers: "Pล™ipnutรญ uลพivatelรฉ"
+pinnedNotes: "Pล™ipnutรก poznรกmka"
 hcaptcha: "hCaptcha"
 enableHcaptcha: "Aktivovat hCaptchu"
 hcaptchaSecretKey: "Tajnรฝ Klรญฤ (Secret Key)"
@@ -471,6 +495,7 @@ _widgets:
   notifications: "Oznรกmenรญ"
   timeline: "ฤŒasovรก osa"
   activity: "Aktivita"
+  federation: "Federace"
   jobQueue: "Fronta รบloh"
 _cw:
   show: "Zobrazit vรญce"
@@ -485,6 +510,8 @@ _exportOrImport:
   muteList: "Ztlumit"
   blockingList: "Zablokovat"
   userLists: "Seznamy"
+_charts:
+  federation: "Federace"
 _timelines:
   home: "Domลฏ"
 _pages:
@@ -517,6 +544,9 @@ _notification:
     renote: "Pล™eposlat"
     quote: "Citovat"
     reaction: "Reakce"
+  _actions:
+    reply: "Odpovฤ›dฤ›t"
+    renote: "Pล™eposlat"
 _deck:
   _columns:
     notifications: "Oznรกmenรญ"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 1f558787ab..5dfce28002 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -842,6 +842,9 @@ oneDay: "Einen Tag"
 oneWeek: "Eine Woche"
 reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt."
 failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden"
+rateLimitExceeded: "Versuchsanzahl รผberschritten"
+cropImage: "Bild zuschneiden"
+cropImageAsk: "Mรถchtest du das Bild zuschneiden?"
 _emailUnavailable:
   used: "Diese Email-Adresse wird bereits verwendet"
   format: "Das Format dieser Email-Adresse ist ungรผltig"
@@ -1006,7 +1009,7 @@ _instanceMute:
   heading: "Liste der stummzuschaltenden Instanzen"
 _theme:
   explore: "Farbschemata erforschen"
-  install: "Farbschmata installieren"
+  install: "Farbschemata installieren"
   manage: "Farbschemaverwaltung"
   code: "Farbschemencode"
   description: "Beschreibung"
@@ -1087,7 +1090,6 @@ _sfx:
   antenna: "Antennen"
   channel: "Kanalbenachrichtigung"
 _ago:
-  unknown: "Unbekannt"
   future: "Zukunft"
   justNow: "Gerade eben"
   secondsAgo: "vor {n} Sekunde(n)"
@@ -1131,6 +1133,7 @@ _2fa:
   registerKey: "Neuen Sicherheitsschlรผssel registrieren"
   step1: "Installiere zuerst eine Authentifizierungsapp (z.B. {a} oder {b}) auf deinem Gerรคt."
   step2: "Dann, scanne den angezeigten QR-Code mit deinem Gerรคt."
+  step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:"
   step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird."
   step4: "Alle folgenden Anmeldungsversuche werden ab sofort die Eingabe eines solchen Tokens benรถtigen."
   securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerรคt auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlรผssels einrichten."
@@ -1613,8 +1616,9 @@ _notification:
   youWereFollowed: "ist dir gefolgt"
   youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
   yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
-  youWereInvitedToGroup: "Du wurdest in eine Gruppe eingeladen"
+  youWereInvitedToGroup: "{userName} hat dich in eine Gruppe eingeladen"
   pollEnded: "Umfrageergebnisse sind verfรผgbar"
+  emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert"
   _types:
     all: "Alle"
     follow: "Neue Follower"
@@ -1629,6 +1633,10 @@ _notification:
     followRequestAccepted: "Akzeptierte Follow-Anfragen"
     groupInvited: "Erhaltene Gruppeneinladungen"
     app: "Benachrichtigungen von Apps"
+  _actions:
+    followBack: "folgt dir nun auch"
+    reply: "Antworten"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Hauptspalte immer zeigen"
   columnAlign: "Spaltenausrichtung"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 99fe05375b..8bfea26b0f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -842,6 +842,9 @@ oneDay: "One day"
 oneWeek: "One week"
 reflectMayTakeTime: "It may take some time for this to be reflected."
 failedToFetchAccountInformation: "Could not fetch account information"
+rateLimitExceeded: "Rate limit exceeded"
+cropImage: "Crop image"
+cropImageAsk: "Do you want to crop this image?"
 _emailUnavailable:
   used: "This email address is already being used"
   format: "The format of this email address is invalid"
@@ -1087,7 +1090,6 @@ _sfx:
   antenna: "Antennas"
   channel: "Channel notifications"
 _ago:
-  unknown: "Unknown"
   future: "Future"
   justNow: "Just now"
   secondsAgo: "{n} second(s) ago"
@@ -1131,6 +1133,7 @@ _2fa:
   registerKey: "Register a security key"
   step1: "First, install an authentication app (such as {a} or {b}) on your device."
   step2: "Then, scan the QR code displayed on this screen."
+  step2Url: "You can also enter this URL if you're using a desktop program:"
   step3: "Enter the token provided by your app to finish setup."
   step4: "From now on, any future login attempts will ask for such a login token."
   securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account."
@@ -1613,8 +1616,9 @@ _notification:
   youWereFollowed: "followed you"
   youReceivedFollowRequest: "You've received a follow request"
   yourFollowRequestAccepted: "Your follow request was accepted"
-  youWereInvitedToGroup: "You've been invited to a group"
+  youWereInvitedToGroup: "{userName} invited you to a group"
   pollEnded: "Poll results have become available"
+  emptyPushNotificationMessage: "Push notifications have been updated"
   _types:
     all: "All"
     follow: "New followers"
@@ -1629,6 +1633,10 @@ _notification:
     followRequestAccepted: "Accepted follow requests"
     groupInvited: "Group invitations"
     app: "Notifications from linked apps"
+  _actions:
+    followBack: "followed you back"
+    reply: "Reply"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Always show main column"
   columnAlign: "Align columns"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index fd69f62ff5..6c10942b48 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -141,6 +141,8 @@ flagAsBot: "Esta cuenta es un bot"
 flagAsBotDescription: "En caso de que esta cuenta fuera usada por un programa, active esta opciรณn. Al hacerlo, esta opciรณn servirรก para otros desarrolladores para evitar cadenas infinitas de reacciones, y ajustarรก los sistemas internos de Misskey para que trate a esta cuenta como un bot."
 flagAsCat: "Esta cuenta es un gato"
 flagAsCatDescription: "En caso de que declare que esta cuenta es de un gato, active esta opciรณn."
+flagShowTimelineReplies: "Mostrar respuestas a las notas en la biografรญa"
+flagShowTimelineRepliesDescription: "Cuando se marca, la lรญnea de tiempo muestra respuestas a otras notas ademรกs de las notas del usuario"
 autoAcceptFollowed: "Aceptar automรกticamente las solicitudes de seguimiento de los usuarios que sigues"
 addAccount: "Agregar Cuenta"
 loginFailed: "Error al iniciar sesiรณn."
@@ -235,6 +237,8 @@ resetAreYouSure: "ยฟDesea reestablecer?"
 saved: "Guardado"
 messaging: "Chat"
 upload: "Subir"
+keepOriginalUploading: "Mantener la imagen original"
+keepOriginalUploadingDescription: "Mantener la versiรณn original al cargar imรกgenes. Si estรก desactivado, el navegador generarรก imรกgenes para la publicaciรณn web en el momento de recargar la pรกgina"
 fromDrive: "Desde el drive"
 fromUrl: "Desde la URL"
 uploadFromUrl: "Subir desde una URL"
@@ -444,6 +448,7 @@ uiLanguage: "Idioma de visualizaciรณn de la interfaz"
 groupInvited: "Invitado al grupo"
 aboutX: "Acerca de {x}"
 useOsNativeEmojis: "Usa los emojis nativos de la plataforma"
+disableDrawer: "No mostrar los menรบs en cajones"
 youHaveNoGroups: "Sin grupos"
 joinOrCreateGroup: "Obtenga una invitaciรณn para unirse al grupos o puede crear su propio grupo."
 noHistory: "No hay datos en el historial"
@@ -615,6 +620,10 @@ reportAbuse: "Reportar"
 reportAbuseOf: "Reportar a {name}"
 fillAbuseReportDescription: "Ingrese los detalles del reporte. Si hay una nota en particular, ingrese la URL de esta."
 abuseReported: "Se ha enviado el reporte. Muchas gracias."
+reporteeOrigin: "Informar a"
+reporterOrigin: "Origen del informe"
+forwardReport: "Transferir un informe a una instancia remota"
+forwardReportIsAnonymous: "No puede ver su informaciรณn de la instancia remota y aparecerรก como una cuenta anรณnima del sistema"
 send: "Enviar"
 abuseMarkAsResolved: "Marcar reporte como resuelto"
 openInNewTab: "Abrir en una Nueva Pestaรฑa"
@@ -676,6 +685,7 @@ center: "Centrar"
 wide: "Ancho"
 narrow: "Estrecho"
 reloadToApplySetting: "Esta configuraciรณn sรณlo se aplicarรก despuรฉs de recargar la pรกgina. ยฟRecargar ahora?"
+needReloadToApply: "Se requiere un reinicio para la aplicar los cambios"
 showTitlebar: "Mostrar la barra de tรญtulo"
 clearCache: "Limpiar cachรฉ"
 onlineUsersCount: "{n} usuarios en lรญnea"
@@ -706,19 +716,55 @@ capacity: "Capacidad"
 inUse: "Usado"
 editCode: "Editar cรณdigo"
 apply: "Aplicar"
+receiveAnnouncementFromInstance: "Recibir notificaciones de la instancia"
+emailNotification: "Notificaciones por correo electrรณnico"
 publish: "Publicar"
 inChannelSearch: "Buscar en el canal"
+useReactionPickerForContextMenu: "Haga clic con el botรณn derecho para abrir el menu de reacciones"
+typingUsers: "{users} estรก escribiendo"
+jumpToSpecifiedDate: "Saltar a una fecha especรญfica"
+showingPastTimeline: "Mostrar lรญneas de tiempo antiguas"
+clear: "Limpiar"
 markAllAsRead: "Marcar todo como leรญdo"
 goBack: "Deseleccionar"
+fullView: "Vista completa"
+quitFullView: "quitar vista completa"
+addDescription: "Agregar descripciรณn"
+userPagePinTip: "Puede mantener sus notas visibles aquรญ seleccionando Pin en el menรบ de notas individuales"
+notSpecifiedMentionWarning: "Algunas menciones no estรกn incluidas en el destino"
 info: "Informaciรณn"
+userInfo: "Informaciรณn del usuario"
+unknown: "Desconocido"
+onlineStatus: "En lรญnea"
+hideOnlineStatus: "mostrarse como desconectado"
+hideOnlineStatusDescription: "Ocultar su estado en lรญnea puede reducir la eficacia de algunas funciones, como la bรบsqueda"
 online: "En lรญnea"
+active: "Activo"
 offline: "Sin conexiรณn"
+notRecommended: "obsoleto"
+botProtection: "Protecciรณn contra bots"
+instanceBlocking: "Instancias bloqueadas"
+selectAccount: "Elija una cuenta"
+switchAccount: "Cambiar de cuenta"
+enabled: "Activado"
+disabled: "Desactivado"
+quickAction: "Acciones rรกpidas"
 user: "Usuarios"
 administration: "Administrar"
+accounts: "Cuentas"
+switch: "Cambiar"
+noMaintainerInformationWarning: "No se ha establecido la informaciรณn del administrador"
+noBotProtectionWarning: "La protecciรณn contra los bots no estรก configurada"
+configure: "Configurar"
+postToGallery: "Crear una nueva publicaciรณn en la galerรญa"
 gallery: "Galerรญa"
 recentPosts: "Posts recientes"
 popularPosts: "Mรกs vistos"
+shareWithNote: "Compartir con una nota"
+ads: "Anuncios"
 expiration: "Termina el"
+memo: "Notas"
+priority: "Prioridad"
 high: "Alta"
 middle: "Mediano"
 low: "Baja"
@@ -770,22 +816,50 @@ _accountDelete:
   accountDelete: "Eliminar Cuenta"
 _ad:
   back: "Deseleccionar"
+_forgotPassword:
+  contactAdmin: "Esta instancia no admite el uso de direcciones de correo electrรณnico, pรณngase en contacto con el administrador de la instancia para restablecer su contraseรฑa"
 _gallery:
   my: "Mi galerรญa"
+  liked: "Publicaciones que me gustan"
+  like: "ยกMuy bien!"
   unlike: "Quitar me gusta"
 _email:
   _follow:
     title: "te ha seguido"
+  _receiveFollowRequest:
+    title: "Has recibido una solicitud de seguimiento"
+_plugin:
+  install: "Instalar plugins"
+  installWarn: "Por favor no instale plugins que no son de confianza"
+  manage: "Gestionar plugins"
 _registry:
+  scope: "Alcance"
   key: "Clave"
   keys: "Clave"
+  domain: "Dominio"
+  createKey: "Crear una llave"
+_aboutMisskey:
+  about: "Misskey es un software de cรณdigo abierto, desarrollado por syuilo desde el 2014"
+  contributors: "Principales colaboradores"
+  allContributors: "Todos los colaboradores"
+  source: "Cรณdigo fuente"
+  translation: "Traducir Misskey"
+  donate: "Donar a Misskey"
+  morePatrons: "Muchas mรกs personas nos apoyan. Muchas gracias๐Ÿฅฐ"
+  patrons: "Patrocinadores"
+_nsfw:
+  respect: "Ocultar medios NSFW"
+  ignore: "No esconder medios NSFW "
+  force: "Ocultar todos los medios"
 _mfm:
   cheatSheet: "Hoja de referencia de MFM"
   intro: "MFM es un lenguaje de marcado dedicado que se puede usar en varios lugares dentro de Misskey. Aquรญ puede ver una lista de sintaxis disponibles en MFM."
+  dummy: "Misskey expande el mundo de la Fediverso"
   mention: "Menciones"
   mentionDescription: "El signo @ seguido de un nombre de usuario se puede utilizar para notificar a un usuario en particular."
   hashtag: "Hashtag"
   url: "URL"
+  urlDescription: "Se pueden mostrar las URL"
   link: "Vรญnculo"
   bold: "Negrita"
   center: "Centrar"
@@ -915,7 +989,6 @@ _sfx:
   antenna: "Antena receptora"
   channel: "Notificaciones del canal"
 _ago:
-  unknown: "Desconocido"
   future: "Futuro"
   justNow: "Reciรฉn ahora"
   secondsAgo: "Hace {n} segundos"
@@ -1432,6 +1505,9 @@ _notification:
     followRequestAccepted: "El seguimiento fue aceptado"
     groupInvited: "Invitado al grupo"
     app: "Notificaciones desde aplicaciones"
+  _actions:
+    reply: "Responder"
+    renote: "Renotar"
 _deck:
   alwaysShowMainColumn: "Siempre mostrar la columna principal"
   columnAlign: "Alinear columnas"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index 1fe74fa9ab..7e225c2992 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -804,7 +804,7 @@ manageAccounts: "Gรฉrer les comptes"
 makeReactionsPublic: "Rendre les rรฉactions publiques"
 makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos rรฉactions donnรฉes publique."
 classic: "Classique"
-muteThread: "Mettre ce thread en sourdine"
+muteThread: "Masquer cette discussion"
 unmuteThread: "Ne plus masquer le fil"
 ffVisibility: "Visibilitรฉ des abonnรฉs/abonnements"
 ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu suis et les personnes qui te suivent."
@@ -1075,7 +1075,6 @@ _sfx:
   antenna: "Rรฉception de lโ€™antenne"
   channel: "Notifications de canal"
 _ago:
-  unknown: "Inconnu"
   future: "Futur"
   justNow: "ร  lโ€™instant"
   secondsAgo: "Il y a {n}s"
@@ -1615,6 +1614,9 @@ _notification:
     followRequestAccepted: "Demande d'abonnement acceptรฉe"
     groupInvited: "Invitation ร  un groupe"
     app: "Notifications provenant des apps"
+  _actions:
+    reply: "Rรฉpondre"
+    renote: "Renoter"
 _deck:
   alwaysShowMainColumn: "Toujours afficher la colonne principale"
   columnAlign: "Aligner les colonnes"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 11dff184cd..39e2c1f661 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -593,6 +593,7 @@ smtpSecureInfo: "Matikan ini ketika menggunakan STARTTLS"
 testEmail: "Tes pengiriman surel"
 wordMute: "Bisukan kata"
 regexpError: "Kesalahan ekspresi reguler"
+regexpErrorDescription: "Galat terjadi pada baris {line} ekspresi reguler dari {tab} kata yang dibisukan:"
 instanceMute: "Bisuka instansi"
 userSaysSomething: "{name} mengatakan sesuatu"
 makeActive: "Aktifkan"
@@ -839,7 +840,11 @@ tenMinutes: "10 Menit"
 oneHour: "1 Jam"
 oneDay: "1 Hari"
 oneWeek: "1 Bulan"
+reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan."
 failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
+rateLimitExceeded: "Batas sudah terlampaui"
+cropImage: "potong gambar"
+cropImageAsk: "Ingin memotong gambar?"
 _emailUnavailable:
   used: "Alamat surel ini telah digunakan"
   format: "Format tidak valid."
@@ -1085,7 +1090,6 @@ _sfx:
   antenna: "Penerimaan Antenna"
   channel: "Pemberitahuan saluran"
 _ago:
-  unknown: "Tidak diketahui"
   future: "Masa depan"
   justNow: "Baru saja"
   secondsAgo: "{n} detik lalu"
@@ -1129,6 +1133,7 @@ _2fa:
   registerKey: "Daftarkan kunci keamanan baru"
   step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu."
   step2: "Lalu, pindai kode QR yang ada di layar."
+  step2Url: "Di aplikasi desktop, masukkan URL berikut:"
   step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan."
   step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu."
   securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu."
@@ -1613,6 +1618,7 @@ _notification:
   yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima"
   youWereInvitedToGroup: "Telah diundang ke grup"
   pollEnded: "Hasil Kuesioner telah keluar"
+  emptyPushNotificationMessage: "Pembaruan notifikasi dorong"
   _types:
     all: "Semua"
     follow: "Ikuti"
@@ -1627,6 +1633,10 @@ _notification:
     followRequestAccepted: "Permintaan mengikuti disetujui"
     groupInvited: "Diundang ke grup"
     app: "Pemberitahuan dari aplikasi"
+  _actions:
+    followBack: "Ikuti Kembali"
+    reply: "Balas"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Selalu tampilkan kolom utama"
   columnAlign: "Luruskan kolom"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 1eaa78b646..8584ed6a8e 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -10,7 +10,7 @@ password: "Password"
 forgotPassword: "Hai dimenticato la tua password?"
 fetchingAsApObject: "Recuperando dal Fediverso..."
 ok: "OK"
-gotIt: "Capito!"
+gotIt: "Ho capito"
 cancel: "Annulla"
 enterUsername: "Inserisci un nome utente"
 renotedBy: "Rinotato da {user}"
@@ -767,6 +767,7 @@ customCss: "CSS personalizzato"
 global: "Federata"
 squareAvatars: "Mostra l'immagine del profilo come quadrato"
 sent: "Inviare"
+received: "Ricevuto"
 searchResult: "Risultati della Ricerca"
 hashtags: "Hashtag"
 troubleshooting: "Risoluzione problemi"
@@ -804,6 +805,10 @@ welcomeBackWithName: "Bentornato/a, {name}"
 clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email."
 searchByGoogle: "Cerca"
 indefinitely: "Non scade"
+tenMinutes: "10 minuti"
+oneHour: "1 ora"
+oneDay: "1 giorno"
+oneWeek: "1 settimana"
 _emailUnavailable:
   used: "Email giร  in uso"
   format: "Formato email non valido"
@@ -999,7 +1004,6 @@ _sfx:
   antenna: "Ricezione dell'antenna"
   channel: "Notifiche di canale"
 _ago:
-  unknown: "Sconosciuto"
   future: "Futuro"
   justNow: "Ora"
   secondsAgo: "{n}s fa"
@@ -1433,6 +1437,9 @@ _notification:
     followRequestAccepted: "Richiesta di follow accettata"
     groupInvited: "Invito a un gruppo"
     app: "Notifiche da applicazioni"
+  _actions:
+    reply: "Rispondi"
+    renote: "Rinota"
 _deck:
   alwaysShowMainColumn: "Mostra sempre la colonna principale"
   columnAlign: "Allineare colonne"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 6326094dd8..43ab7f2d69 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -356,7 +356,7 @@ antennaExcludeKeywords: "้™คๅค–ใ‚ญใƒผใƒฏใƒผใƒ‰"
 antennaKeywordsDescription: "ใ‚นใƒšใƒผใ‚นใงๅŒบๅˆ‡ใ‚‹ใจANDๆŒ‡ๅฎšใซใชใ‚Šใ€ๆ”น่กŒใงๅŒบๅˆ‡ใ‚‹ใจORๆŒ‡ๅฎšใซใชใ‚Šใพใ™"
 notifyAntenna: "ๆ–ฐใ—ใ„ใƒŽใƒผใƒˆใ‚’้€š็Ÿฅใ™ใ‚‹"
 withFileAntenna: "ใƒ•ใ‚กใ‚คใƒซใŒๆทปไป˜ใ•ใ‚ŒใŸใƒŽใƒผใƒˆใฎใฟ"
-enableServiceworker: "ServiceWorkerใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹"
+enableServiceworker: "ใƒ–ใƒฉใ‚ฆใ‚ถใธใฎใƒ—ใƒƒใ‚ทใƒฅ้€š็Ÿฅใ‚’ๆœ‰ๅŠนใซใ™ใ‚‹"
 antennaUsersDescription: "ใƒฆใƒผใ‚ถใƒผๅใ‚’ๆ”น่กŒใงๅŒบๅˆ‡ใฃใฆๆŒ‡ๅฎšใ—ใพใ™"
 caseSensitive: "ๅคงๆ–‡ๅญ—ๅฐๆ–‡ๅญ—ใ‚’ๅŒบๅˆฅใ™ใ‚‹"
 withReplies: "่ฟ”ไฟกใ‚’ๅซใ‚€"
@@ -425,7 +425,7 @@ quoteQuestion: "ๅผ•็”จใจใ—ใฆๆทปไป˜ใ—ใพใ™ใ‹๏ผŸ"
 noMessagesYet: "ใพใ ใƒใƒฃใƒƒใƒˆใฏใ‚ใ‚Šใพใ›ใ‚“"
 newMessageExists: "ๆ–ฐใ—ใ„ใƒกใƒƒใ‚ปใƒผใ‚ธใŒใ‚ใ‚Šใพใ™"
 onlyOneFileCanBeAttached: "ใƒกใƒƒใ‚ปใƒผใ‚ธใซๆทปไป˜ใงใใ‚‹ใƒ•ใ‚กใ‚คใƒซใฏใฒใจใคใงใ™"
-signinRequired: "ใƒญใ‚ฐใ‚คใƒณใ—ใฆใใ ใ•ใ„"
+signinRequired: "็ถš่กŒใ™ใ‚‹ๅ‰ใซใ€ใ‚ตใ‚คใƒณใ‚ขใƒƒใƒ—ใพใŸใฏใ‚ตใ‚คใƒณใ‚คใƒณใŒๅฟ…่ฆใงใ™"
 invitations: "ๆ‹›ๅพ…"
 invitationCode: "ๆ‹›ๅพ…ใ‚ณใƒผใƒ‰"
 checking: "็ขบ่ชใ—ใฆใ„ใพใ™"
@@ -842,6 +842,9 @@ oneDay: "1ๆ—ฅ"
 oneWeek: "1้€ฑ้–“"
 reflectMayTakeTime: "ๅๆ˜ ใ•ใ‚Œใ‚‹ใพใงๆ™‚้–“ใŒใ‹ใ‹ใ‚‹ๅ ดๅˆใŒใ‚ใ‚Šใพใ™ใ€‚"
 failedToFetchAccountInformation: "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆๆƒ…ๅ ฑใฎๅ–ๅพ—ใซๅคฑๆ•—ใ—ใพใ—ใŸ"
+rateLimitExceeded: "ใƒฌใƒผใƒˆๅˆถ้™ใ‚’่ถ…ใˆใพใ—ใŸ"
+cropImage: "็”ปๅƒใฎใ‚ฏใƒญใƒƒใƒ—"
+cropImageAsk: "็”ปๅƒใ‚’ใ‚ฏใƒญใƒƒใƒ—ใ—ใพใ™ใ‹๏ผŸ"
 
 _emailUnavailable:
   used: "ๆ—ขใซไฝฟ็”จใ•ใ‚Œใฆใ„ใพใ™"
@@ -1110,7 +1113,6 @@ _sfx:
   channel: "ใƒใƒฃใƒณใƒใƒซ้€š็Ÿฅ"
 
 _ago:
-  unknown: "่ฌŽ"
   future: "ๆœชๆฅ"
   justNow: "ใŸใฃใŸไปŠ"
   secondsAgo: "{n}็ง’ๅ‰"
@@ -1157,6 +1159,7 @@ _2fa:
   registerKey: "ใ‚ญใƒผใ‚’็™ป้Œฒ"
   step1: "ใพใšใ€{a}ใ‚„{b}ใชใฉใฎ่ช่จผใ‚ขใƒ—ใƒชใ‚’ใŠไฝฟใ„ใฎใƒ‡ใƒใ‚คใ‚นใซใ‚คใƒณใ‚นใƒˆใƒผใƒซใ—ใพใ™ใ€‚"
   step2: "ๆฌกใซใ€่กจ็คบใ•ใ‚Œใฆใ„ใ‚‹QRใ‚ณใƒผใƒ‰ใ‚’ใ‚ขใƒ—ใƒชใงใ‚นใ‚ญใƒฃใƒณใ—ใพใ™ใ€‚"
+  step2Url: "ใƒ‡ใ‚นใ‚ฏใƒˆใƒƒใƒ—ใ‚ขใƒ—ใƒชใงใฏๆฌกใฎURLใ‚’ๅ…ฅๅŠ›ใ—ใพใ™:"
   step3: "ใ‚ขใƒ—ใƒชใซ่กจ็คบใ•ใ‚Œใฆใ„ใ‚‹ใƒˆใƒผใ‚ฏใƒณใ‚’ๅ…ฅๅŠ›ใ—ใฆๅฎŒไบ†ใงใ™ใ€‚"
   step4: "ใ“ใ‚Œใ‹ใ‚‰ใƒญใ‚ฐใ‚คใƒณใ™ใ‚‹ใจใใ‚‚ใ€ๅŒใ˜ใ‚ˆใ†ใซใƒˆใƒผใ‚ฏใƒณใ‚’ๅ…ฅๅŠ›ใ—ใพใ™ใ€‚"
   securityKeyInfo: "FIDO2ใ‚’ใ‚ตใƒใƒผใƒˆใ™ใ‚‹ใƒใƒผใƒ‰ใ‚ฆใ‚งใ‚ขใ‚ปใ‚ญใƒฅใƒชใƒ†ใ‚ฃใ‚ญใƒผใ‚‚ใ—ใใฏ็ซฏๆœซใฎๆŒ‡็ด‹่ช่จผใ‚„PINใ‚’ไฝฟ็”จใ—ใฆใƒญใ‚ฐใ‚คใƒณใ™ใ‚‹ใ‚ˆใ†ใซ่จญๅฎšใงใใพใ™ใ€‚"
@@ -1668,8 +1671,9 @@ _notification:
   youWereFollowed: "ใƒ•ใ‚ฉใƒญใƒผใ•ใ‚Œใพใ—ใŸ"
   youReceivedFollowRequest: "ใƒ•ใ‚ฉใƒญใƒผใƒชใ‚ฏใ‚จใ‚นใƒˆใŒๆฅใพใ—ใŸ"
   yourFollowRequestAccepted: "ใƒ•ใ‚ฉใƒญใƒผใƒชใ‚ฏใ‚จใ‚นใƒˆใŒๆ‰ฟ่ชใ•ใ‚Œใพใ—ใŸ"
-  youWereInvitedToGroup: "ใ‚ฐใƒซใƒผใƒ—ใซๆ‹›ๅพ…ใ•ใ‚Œใพใ—ใŸ"
+  youWereInvitedToGroup: "{userName}ใŒใ‚ใชใŸใ‚’ใ‚ฐใƒซใƒผใƒ—ใซๆ‹›ๅพ…ใ—ใพใ—ใŸ"
   pollEnded: "ใ‚ขใƒณใ‚ฑใƒผใƒˆใฎ็ตๆžœใŒๅ‡บใพใ—ใŸ"
+  emptyPushNotificationMessage: "ใƒ—ใƒƒใ‚ทใƒฅ้€š็Ÿฅใฎๆ›ดๆ–ฐใ‚’ใ—ใพใ—ใŸ"
 
   _types:
     all: "ใ™ในใฆ"
@@ -1686,6 +1690,11 @@ _notification:
     groupInvited: "ใ‚ฐใƒซใƒผใƒ—ใซๆ‹›ๅพ…ใ•ใ‚ŒใŸ"
     app: "้€ฃๆบใ‚ขใƒ—ใƒชใ‹ใ‚‰ใฎ้€š็Ÿฅ"
 
+  _actions:
+    followBack: "ใƒ•ใ‚ฉใƒญใƒผใƒใƒƒใ‚ฏ"
+    reply: "่ฟ”ไฟก"
+    renote: "Renote"
+
 _deck:
   alwaysShowMainColumn: "ๅธธใซใƒกใ‚คใƒณใ‚ซใƒฉใƒ ใ‚’่กจ็คบ"
   columnAlign: "ใ‚ซใƒฉใƒ ใฎๅฏ„ใ›"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 52ecd8c24e..5458152dda 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -799,7 +799,6 @@ _sfx:
   notification: "้€š็Ÿฅ"
   chat: "ใƒใƒฃใƒƒใƒˆ"
 _ago:
-  unknown: "ใ‚ใ‹ใ‚‰ใ‚“"
   future: "ๆœชๆฅ"
   justNow: "ใŸใฃใŸไปŠ"
   secondsAgo: "{n}็ง’ๅ‰"
@@ -1202,6 +1201,9 @@ _notification:
     reaction: "ใƒชใ‚ขใ‚ฏใ‚ทใƒงใƒณ"
     receiveFollowRequest: "ใƒ•ใ‚ฉใƒญใƒผ่จฑๅฏใ—ใฆใปใ—ใ„ใฟใŸใ„ใ‚„ใง"
     followRequestAccepted: "ใƒ•ใ‚ฉใƒญใƒผใŒๅ—็†ใ•ใ‚ŒใŸใง"
+  _actions:
+    reply: "่ฟ”ไบ‹"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "ใ„ใคใ‚‚ใƒกใ‚คใƒณใ‚ซใƒฉใƒ ใ‚’่กจ็คบ"
   columnAlign: "ใ‚ซใƒฉใƒ ใฎๅฏ„ใ›"
diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml
index 6a14cbe1ba..77ca824528 100644
--- a/locales/kab-KAB.yml
+++ b/locales/kab-KAB.yml
@@ -116,6 +116,8 @@ _notification:
   _types:
     follow: "Ig แนญแนญafaแน›"
     mention: "Bder"
+  _actions:
+    reply: "Err"
 _deck:
   _columns:
     notifications: "Ilษฃuyen"
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index 3111c90dd5..3682277175 100644
--- a/locales/kn-IN.yml
+++ b/locales/kn-IN.yml
@@ -76,6 +76,8 @@ _profile:
   username: "เฒฌเฒณเฒ•เณ†เฒนเณ†เฒธเฒฐเณ"
 _notification:
   youWereFollowed: "เฒนเฒฟเฒ‚เฒฌเฒพเฒฒเฒฟเฒธเฒฟเฒฆเฒฐเณ"
+  _actions:
+    reply: "เฒ‰เฒคเณเฒคเฒฐเฒฟเฒธเณ"
 _deck:
   _columns:
     notifications: "เฒ…เฒงเฒฟเฒธเณ‚เฒšเฒจเณ†เฒ—เฒณเณ"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index e1ad77cbc9..4e7369a5ef 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -592,6 +592,8 @@ smtpSecure: "SMTP ์—ฐ๊ฒฐ์— Implicit SSL/TTS ์‚ฌ์šฉ"
 smtpSecureInfo: "STARTTLS ์‚ฌ์šฉ ์‹œ์—๋Š” ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค."
 testEmail: "์ด๋ฉ”์ผ ์ „์†ก ํ…Œ์ŠคํŠธ"
 wordMute: "๋‹จ์–ด ๋ฎคํŠธ"
+regexpError: "์ •๊ทœ ํ‘œํ˜„์‹ ์˜ค๋ฅ˜"
+regexpErrorDescription: "{tab}๋‹จ์–ด ๋ฎคํŠธ {line}ํ–‰์˜ ์ •๊ทœ ํ‘œํ˜„์‹์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:"
 instanceMute: "์ธ์Šคํ„ด์Šค ๋ฎคํŠธ"
 userSaysSomething: "{name}๋‹˜์ด ๋ฌด์–ธ๊ฐ€๋ฅผ ๋งํ–ˆ์Šต๋‹ˆ๋‹ค"
 makeActive: "ํ™œ์„ฑํ™”"
@@ -825,8 +827,21 @@ overridedDeviceKind: "์žฅ์น˜ ์œ ํ˜•"
 smartphone: "์Šค๋งˆํŠธํฐ"
 tablet: "ํƒœ๋ธ”๋ฆฟ"
 auto: "์ž๋™"
+themeColor: "ํ…Œ๋งˆ ์ปฌ๋Ÿฌ"
+size: "ํฌ๊ธฐ"
+numberOfColumn: "ํ•œ ์ค„์— ๋ณด์ผ ๋ฆฌ์•ก์…˜์˜ ์ˆ˜"
 searchByGoogle: "๊ฒ€์ƒ‰"
+instanceDefaultLightTheme: "์ธ์Šคํ„ด์Šค ๊ธฐ๋ณธ ๋ผ์ดํŠธ ํ…Œ๋งˆ"
+instanceDefaultDarkTheme: "์ธ์Šคํ„ด์Šค ๊ธฐ๋ณธ ๋‹คํฌ ํ…Œ๋งˆ"
+instanceDefaultThemeDescription: "๊ฐ์ฒด ํ˜•์‹์˜ ํ…Œ๋งˆ ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."
+mutePeriod: "๋ฎคํŠธํ•  ๊ธฐ๊ฐ„"
 indefinitely: "๋ฌด๊ธฐํ•œ"
+tenMinutes: "10๋ถ„"
+oneHour: "1์‹œ๊ฐ„"
+oneDay: "1์ผ"
+oneWeek: "์ผ์ฃผ์ผ"
+reflectMayTakeTime: "๋ฐ˜์˜๋˜๊ธฐ๊นŒ์ง€ ์‹œ๊ฐ„์ด ๊ฑธ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
+failedToFetchAccountInformation: "๊ณ„์ • ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค"
 _emailUnavailable:
   used: "์ด ๋ฉ”์ผ ์ฃผ์†Œ๋Š” ์‚ฌ์šฉ์ค‘์ž…๋‹ˆ๋‹ค"
   format: "ํ˜•์‹์ด ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค"
@@ -1072,7 +1087,6 @@ _sfx:
   antenna: "์•ˆํ…Œ๋‚˜ ์ˆ˜์‹ "
   channel: "์ฑ„๋„ ์•Œ๋ฆผ"
 _ago:
-  unknown: "์•Œ ์ˆ˜ ์—†์Œ"
   future: "๋ฏธ๋ž˜"
   justNow: "๋ฐฉ๊ธˆ ์ „"
   secondsAgo: "{n}์ดˆ ์ „"
@@ -1116,6 +1130,7 @@ _2fa:
   registerKey: "ํ‚ค๋ฅผ ๋“ฑ๋ก"
   step1: "๋จผ์ €, {a}๋‚˜ {b}๋“ฑ์˜ ์ธ์ฆ ์•ฑ์„ ์‚ฌ์šฉ ์ค‘์ธ ๋””๋ฐ”์ด์Šค์— ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค."
   step2: "๊ทธ ํ›„, ํ‘œ์‹œ๋˜์–ด ์žˆ๋Š” QR์ฝ”๋“œ๋ฅผ ์•ฑ์œผ๋กœ ์Šค์บ”ํ•ฉ๋‹ˆ๋‹ค."
+  step2Url: "๋ฐ์Šคํฌํ†ฑ ์•ฑ์—์„œ๋Š” ๋‹ค์Œ URL์„ ์ž…๋ ฅํ•˜์„ธ์š”:"
   step3: "์•ฑ์— ํ‘œ์‹œ๋œ ํ† ํฐ์„ ์ž…๋ ฅํ•˜์‹œ๋ฉด ์™„๋ฃŒ๋ฉ๋‹ˆ๋‹ค."
   step4: "๋‹ค์Œ ๋กœ๊ทธ์ธ๋ถ€ํ„ฐ๋Š” ํ† ํฐ์„ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
   securityKeyInfo: "FIDO2๋ฅผ ์ง€์›ํ•˜๋Š” ํ•˜๋“œ์›จ์–ด ๋ณด์•ˆ ํ‚ค ํ˜น์€ ๋””๋ฐ”์ด์Šค์˜ ์ง€๋ฌธ์ธ์‹์ด๋‚˜ ํ™”๋ฉด์ž ๊ธˆ PIN์„ ์ด์šฉํ•ด์„œ ๋กœ๊ทธ์ธํ•˜๋„๋ก ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
@@ -1249,7 +1264,7 @@ _profile:
   youCanIncludeHashtags: "ํ•ด์‹œ ํƒœ๊ทธ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค."
   metadata: "์ถ”๊ฐ€ ์ •๋ณด"
   metadataEdit: "์ถ”๊ฐ€ ์ •๋ณด ํŽธ์ง‘"
-  metadataDescription: "ํ”„๋กœํ•„์— ์ตœ๋Œ€ 4๊ฐœ์˜ ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์š”"
+  metadataDescription: "ํ”„๋กœํ•„์— ์ถ”๊ฐ€ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์–ด์š”"
   metadataLabel: "๋ผ๋ฒจ"
   metadataContent: "๋‚ด์šฉ"
   changeAvatar: "์•„๋ฐ”ํƒ€ ์ด๋ฏธ์ง€ ๋ณ€๊ฒฝ"
@@ -1599,6 +1614,8 @@ _notification:
   youReceivedFollowRequest: "์ƒˆ๋กœ์šด ํŒ”๋กœ์šฐ ์š”์ฒญ์ด ์žˆ์Šต๋‹ˆ๋‹ค"
   yourFollowRequestAccepted: "ํŒ”๋กœ์šฐ ์š”์ฒญ์ด ์ˆ˜๋ฝ๋˜์—ˆ์Šต๋‹ˆ๋‹ค"
   youWereInvitedToGroup: "๊ทธ๋ฃน์— ์ดˆ๋Œ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค"
+  pollEnded: "ํˆฌํ‘œ ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœํ‘œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค"
+  emptyPushNotificationMessage: "ํ‘ธ์‹œ ์•Œ๋ฆผ์ด ๊ฐฑ์‹ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค"
   _types:
     all: "์ „๋ถ€"
     follow: "ํŒ”๋กœ์ž‰"
@@ -1608,10 +1625,15 @@ _notification:
     quote: "์ธ์šฉ"
     reaction: "๋ฆฌ์•ก์…˜"
     pollVote: "ํˆฌํ‘œ ์ฐธ์—ฌ"
+    pollEnded: "ํˆฌํ‘œ๊ฐ€ ์ข…๋ฃŒ๋จ"
     receiveFollowRequest: "ํŒ”๋กœ์šฐ ์š”์ฒญ์„ ๋ฐ›์•˜์„ ๋•Œ"
     followRequestAccepted: "ํŒ”๋กœ์šฐ ์š”์ฒญ์ด ์Šน์ธ๋˜์—ˆ์„ ๋•Œ"
     groupInvited: "๊ทธ๋ฃน์— ์ดˆ๋Œ€๋˜์—ˆ์„ ๋•Œ"
     app: "์—ฐ๋™๋œ ์•ฑ์„ ํ†ตํ•œ ์•Œ๋ฆผ"
+  _actions:
+    followBack: "ํŒ”๋กœ์šฐ"
+    reply: "๋‹ต๊ธ€"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "๋ฉ”์ธ ์นผ๋Ÿผ ํ•ญ์ƒ ํ‘œ์‹œ"
   columnAlign: "์นผ๋Ÿผ ์ •๋ ฌ"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index f4e4a62182..0ded573948 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -303,6 +303,8 @@ muteThread: "Discussies dempen "
 unmuteThread: "Dempen van discussie ongedaan maken"
 hide: "Verbergen"
 searchByGoogle: "Zoeken"
+cropImage: "Afbeelding bijsnijden"
+cropImageAsk: "Bijsnijdengevraagd"
 _email:
   _follow:
     title: "volgde jou"
@@ -371,6 +373,9 @@ _notification:
     renote: "Herdelen"
     quote: "Quote"
     reaction: "Reacties"
+  _actions:
+    reply: "Antwoord"
+    renote: "Herdelen"
 _deck:
   _columns:
     notifications: "Meldingen"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 78d86dd7e3..fa1dad2173 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -946,7 +946,6 @@ _sfx:
   chatBg: "Rozmowy (tล‚o)"
   channel: "Powiadomienia kanaล‚u"
 _ago:
-  unknown: "Nieznane"
   future: "W przyszล‚oล›ci"
   justNow: "Przed chwilฤ…"
   secondsAgo: "{n} sek. temu"
@@ -1401,6 +1400,9 @@ _notification:
     followRequestAccepted: "Przyjฤ™to proล›bฤ™ o moลผliwoล›ฤ‡ obserwacji"
     groupInvited: "Zaproszono do grup"
     app: "Powiadomienia z aplikacji"
+  _actions:
+    reply: "Odpowiedz"
+    renote: "Udostฤ™pnij"
 _deck:
   alwaysShowMainColumn: "Zawsze pokazuj gล‚รณwnฤ… kolumnฤ™"
   columnAlign: "Wyrรณwnaj kolumny"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 104e4ceb7c..0dc15a27bb 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -37,26 +37,117 @@ favorites: "Favoritar"
 unfavorite: "Remover dos favoritos"
 favorited: "Adicionado aos favoritos."
 alreadyFavorited: "Jรก adicionado aos favoritos."
+cantFavorite: "Nรฃo foi possรญvel adicionar aos favoritos."
+pin: "Afixar no perfil"
+unpin: "Desafixar do perfil"
+copyContent: "Copiar conteรบdos"
+copyLink: "Copiar hiperligaรงรฃo"
+delete: "Eliminar"
+deleteAndEdit: "Eliminar e editar"
+deleteAndEditConfirm: "Tens a certeza que pretendes eliminar esta nota e editรก-la? Irรกs perder todas as suas reaรงรตes, renotas e respostas."
+addToList: "Adicionar a lista"
+sendMessage: "Enviar uma mensagem"
+copyUsername: "Copiar nome de utilizador"
+searchUser: "Pesquisar utilizador"
+reply: "Responder"
+loadMore: "Carregar mais"
 showMore: "Ver mais"
 youGotNewFollower: "Vocรช tem um novo seguidor"
+receiveFollowRequest: "Pedido de seguimento recebido"
 followRequestAccepted: "Pedido de seguir aceito"
+mention: "Menรงรฃo"
+mentions: "Menรงรตes"
+directNotes: "Notas diretas"
+importAndExport: "Importar/Exportar"
+import: "Importar"
+export: "Exportar"
+files: "Ficheiros"
+download: "Descarregar"
+driveFileDeleteConfirm: "Tens a certeza que pretendes apagar o ficheiro \"{name}\"? As notas que tenham este ficheiro anexado serรฃo tambรฉm apagadas."
+unfollowConfirm: "Tens a certeza que queres deixar de seguir {name}?"
+exportRequested: "Pediste uma exportaรงรฃo. Este processo pode demorar algum tempo. Serรก adicionado ร  tua Drive apรณs a conclusรฃo do processo."
+importRequested: "Pediste uma importaรงรฃo. Este processo pode demorar algum tempo."
+lists: "Listas"
+noLists: "Nรฃo tens nenhuma lista"
 note: "Post"
 notes: "Posts"
+following: "Seguindo"
+followers: "Seguidores"
+followsYou: "Segue-te"
+createList: "Criar lista"
+manageLists: "Gerir listas"
+error: "Erro"
+somethingHappened: "Ocorreu um erro"
+retry: "Tentar novamente"
+pageLoadError: "Ocorreu um erro ao carregar a pรกgina."
+pageLoadErrorDescription: "Isto รฉ normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente apรณs algum tempo."
+serverIsDead: "O servidor nรฃo estรก respondendo. Por favor espere um pouco e tente novamente."
+youShouldUpgradeClient: "Para visualizar essa pรกgina, por favor recarregue-a para atualizar seu cliente."
+enterListName: "Insira um nome para a lista"
+privacy: "Privacidade"
+makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados"
+defaultNoteVisibility: "Visibilidade padrรฃo"
+follow: "Seguindo"
+followRequest: "Mandar pedido de seguimento"
+followRequests: "Pedidos de seguimento"
+unfollow: "Deixar de seguir"
+followRequestPending: "Pedido de seguimento pendente"
 enterEmoji: "Inserir emoji"
 renote: "Repostar"
 renoted: "Repostado"
 cantRenote: "Nรฃo pode repostar"
 cantReRenote: "Nรฃo pode repostar este repost"
+quote: "Citar"
 pinnedNote: "Post fixado"
+pinned: "Afixar no perfil"
+you: "Vocรช"
+clickToShow: "Clique para ver"
 sensitive: "Conteรบdo sensรญvel"
+add: "Adicionar"
+reaction: "Reaรงรตes"
+reactionSetting: "Quais reaรงรตes a mostrar no selecionador de reaรงรตes"
+rememberNoteVisibility: "Lembrar das configuraรงรตes de visibilidade de notas"
+attachCancel: "Remover anexo"
+markAsSensitive: "Marcar como sensรญvel"
+unmarkAsSensitive: "Desmarcar como sensรญvel"
+enterFileName: "Digite o nome do ficheiro"
 mute: "Silenciar"
 unmute: "Dessilenciar"
+block: "Bloquear"
+unblock: "Desbloquear"
+suspend: "Suspender"
+unsuspend: "Cancelar suspensรฃo"
+blockConfirm: "Tem certeza que gostaria de bloquear essa conta?"
+unblockConfirm: "Tem certeza que gostaria de desbloquear essa conta?"
+suspendConfirm: "Tem certeza que gostaria de suspender essa conta?"
+unsuspendConfirm: "Tem certeza que gostaria de cancelar a suspensรฃo dessa conta?"
+selectList: "Escolhe uma lista"
+selectAntenna: "Escolhe uma antena"
+selectWidget: "Escolhe um widget"
+editWidgets: "Editar widgets"
+editWidgetsExit: "Pronto"
+customEmojis: "Emoji personalizado"
+emoji: "Emoji"
+emojis: "Emojis"
+emojiName: "Nome do Emoji"
+emojiUrl: "URL do Emoji"
+addEmoji: "Adicionar um Emoji"
 settingGuide: "Guia de configuraรงรฃo"
+flagAsBot: "Marcar conta como robรด"
+flagAsCat: "Marcar conta como gato"
+flagAsCatDescription: "Ative essa opรงรฃo para marcar essa conta como gato."
+flagShowTimelineReplies: "Mostrar respostas na linha de tempo"
+general: "Geral"
+wallpaper: "Papel de parede"
+searchWith: "Buscar: {q}"
+youHaveNoLists: "Nรฃo tem nenhuma lista"
+followConfirm: "Tem certeza que quer deixar de seguir {name}?"
 instances: "Instรขncia"
 registeredAt: "Registrado em"
 perHour: "por hora"
 perDay: "por dia"
 noUsers: "Sem usuรกrios"
+remove: "Eliminar"
 messageRead: "Lida"
 lightThemes: "Tema claro"
 darkThemes: "Tema escuro"
@@ -64,6 +155,9 @@ addFile: "Adicionar arquivo"
 nsfw: "Conteรบdo sensรญvel"
 monthX: "mรชs de {month}"
 pinnedNotes: "Post fixado"
+userList: "Listas"
+none: "Nenhum"
+output: "Resultado"
 smtpUser: "Nome de usuรกrio"
 smtpPass: "Senha"
 user: "Usuรกrios"
@@ -72,9 +166,13 @@ _email:
   _follow:
     title: "Vocรช tem um novo seguidor"
 _mfm:
+  mention: "Menรงรฃo"
+  quote: "Citar"
+  emoji: "Emoji personalizado"
   search: "Pesquisar"
 _theme:
   keys:
+    mention: "Menรงรฃo"
     renote: "Repostar"
 _sfx:
   note: "Posts"
@@ -82,15 +180,238 @@ _sfx:
 _widgets:
   notifications: "Notificaรงรตes"
   timeline: "Timeline"
+_cw:
+  show: "Carregar mais"
+_visibility:
+  followers: "Seguidores"
 _profile:
   username: "Nome de usuรกrio"
 _exportOrImport:
+  followingList: "Seguindo"
   muteList: "Silenciar"
+  blockingList: "Bloquear"
+  userLists: "Listas"
+_pages:
+  blocks:
+    _button:
+      _action:
+        _pushEvent:
+          event: "Nome do evento"
+          message: "Mostrar mensagem quando ativado"
+          variable: "Variรกvel a mandar"
+          no-variable: "Nenhum"
+        callAiScript: "Invocar AiScript"
+        _callAiScript:
+          functionName: "Nome da funรงรฃo"
+    radioButton: "Escolha"
+    _radioButton:
+      values: "Lista de escolhas separadas por quebras de texto"
+  script:
+    categories:
+      logical: "Operaรงรฃo lรณgica"
+      operation: "Cรกlculos"
+      comparison: "Comparaรงรฃo"
+      list: "Listas"
+    blocks:
+      _strReplace:
+        arg2: "Texto que irรก ser substituรญdo"
+        arg3: "Substituir com"
+      strReverse: "Virar texto"
+      join: "Sequรชncia de texto"
+      _join:
+        arg1: "Listas"
+        arg2: "Separador"
+      add: "Somar"
+      _add:
+        arg1: "A"
+        arg2: "B"
+      subtract: "Subtrair"
+      _subtract:
+        arg1: "A"
+        arg2: "B"
+      multiply: "Multiplicar"
+      _multiply:
+        arg1: "A"
+        arg2: "B"
+      divide: "Dividir"
+      _divide:
+        arg1: "A"
+        arg2: "B"
+      mod: "O resto de"
+      _mod:
+        arg1: "A"
+        arg2: "B"
+      round: "Arredondar decimal"
+      _round:
+        arg1: "Numรฉrico"
+      eq: "A e B sรฃo iguais"
+      _eq:
+        arg1: "A"
+        arg2: "B"
+      notEq: "A e B sรฃo diferentes"
+      _notEq:
+        arg1: "A"
+        arg2: "B"
+      and: "A e B"
+      _and:
+        arg1: "A"
+        arg2: "B"
+      or: "A OU B"
+      _or:
+        arg1: "A"
+        arg2: "B"
+      lt: "< A รฉ menor do que B"
+      _lt:
+        arg1: "A"
+        arg2: "B"
+      gt: "> A รฉ maior do que B"
+      _gt:
+        arg1: "A"
+        arg2: "B"
+      ltEq: "<= A รฉ maior ou igual a B"
+      _ltEq:
+        arg1: "A"
+        arg2: "B"
+      gtEq: ">= A รฉ maior ou igual a B"
+      _gtEq:
+        arg1: "A"
+        arg2: "B"
+      if: "Galho"
+      _if:
+        arg1: "Se"
+        arg2: "Entรฃo"
+        arg3: "Se nรฃo"
+      not: "NรƒO"
+      _not:
+        arg1: "NรƒO"
+      random: "Aleatรณrio"
+      _random:
+        arg1: "Probabilidade"
+      rannum: "Numeral aleatรณrio"
+      _rannum:
+        arg1: "Valor mรญnimo"
+        arg2: "Valor mรกximo"
+      randomPick: "Escolher aleatoriamente de uma lista"
+      _randomPick:
+        arg1: "Listas"
+      dailyRandom: "Aleatรณrio (Muda uma vez por dia para cada usuรกrio)"
+      _dailyRandom:
+        arg1: "Probabilidade"
+      dailyRannum: "Numeral aleatรณrio (Muda uma vez por dia para cada usuรกrio)"
+      _dailyRannum:
+        arg1: "Valor mรญnimo"
+        arg2: "Valor mรกximo"
+      dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuรกrio)"
+      _dailyRandomPick:
+        arg1: "Listas"
+      seedRandom: "Aleatรณrio (com semente)"
+      _seedRandom:
+        arg1: "Semente"
+        arg2: "Probabilidade"
+      seedRannum: "Nรบmero aleatรณrio (com semente)"
+      _seedRannum:
+        arg1: "Semente"
+        arg2: "Valor mรญnimo"
+        arg3: "Valor mรกximo"
+      seedRandomPick: "Escolher aleatoriamente de uma lista (com uma semente)"
+      _seedRandomPick:
+        arg1: "Semente"
+        arg2: "Listas"
+      DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuรกrio)"
+      _DRPWPM:
+        arg1: "Lista de texto"
+      pick: "Escolhe a partir da lista"
+      _pick:
+        arg1: "Listas"
+        arg2: "Posiรงรฃo"
+      listLen: "Pegar comprimento da lista"
+      _listLen:
+        arg1: "Listas"
+      number: "Numรฉrico"
+      stringToNumber: "Texto para numรฉrico"
+      _stringToNumber:
+        arg1: "Texto"
+      numberToString: "Numรฉrico para texto"
+      _numberToString:
+        arg1: "Numรฉrico"
+      splitStrByLine: "Dividir texto por quebras"
+      _splitStrByLine:
+        arg1: "Texto"
+      ref: "Variรกvel"
+      aiScriptVar: "Variรกvel AiScript"
+      fn: "Funรงรฃo"
+      _fn:
+        slots: "Espaรงos"
+        slots-info: "Separar cada espaรงo com uma quebra de texto"
+        arg1: "Resultado"
+      for: "Repetiรงรฃo 'for'"
+      _for:
+        arg1: "Nรบmero de repetiรงรตes"
+        arg2: "Aรงรฃo"
+    typeError: "Espaรงo {slot} aceita valores de tipo \"{expect}\", mas o valor dado รฉ do tipo \"{actual}\"!"
+    thereIsEmptySlot: "O espaรงo {slot} estรก vazio!"
+    types:
+      string: "Texto"
+      number: "Numรฉrico"
+      array: "Listas"
+      stringArray: "Lista de texto"
+    emptySlot: "Espaรงo vazio"
+    enviromentVariables: "Variรกveis de ambiente"
+    pageVariables: "Variรกveis de pรกgina"
+_relayStatus:
+  requesting: "Pendente"
+  accepted: "Aprovado"
+  rejected: "Recusado"
 _notification:
+  fileUploaded: "Carregamento de arquivo efetuado com sucesso"
+  youGotMention: "{name} te mencionou"
+  youGotReply: "{name} te respondeu"
+  youGotQuote: "{name} te citou"
+  youGotPoll: "{name} votou em sua enquete"
+  youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
+  youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
   youWereFollowed: "Vocรช tem um novo seguidor"
+  youReceivedFollowRequest: "Vocรช recebeu um pedido de seguimento"
+  yourFollowRequestAccepted: "Seu pedido de seguimento foi aceito"
+  youWereInvitedToGroup: "{userName} te convidou para um grupo"
+  pollEnded: "Os resultados da enquete agora estรฃo disponรญveis"
+  emptyPushNotificationMessage: "As notificaรงรตes de alerta foram atualizadas"
   _types:
+    all: "Todos"
+    follow: "Seguindo"
+    mention: "Menรงรฃo"
+    reply: "Respostas"
+    renote: "Repostar"
+    quote: "Citar"
+    reaction: "Reaรงรตes"
+    pollVote: "Votaรงรตes em enquetes"
+    pollEnded: "Enquetes terminando"
+    receiveFollowRequest: "Recebeu pedidos de seguimento"
+    followRequestAccepted: "Aceitou pedidos de seguimento"
+    groupInvited: "Convites de grupo"
+    app: "Notificaรงรตes de aplicativos conectados"
+  _actions:
+    followBack: "te seguiu de volta"
+    reply: "Responder"
     renote: "Repostar"
 _deck:
+  alwaysShowMainColumn: "Sempre mostrar a coluna principal"
+  columnAlign: "Alinhar colunas"
+  columnMargin: "Margem entre colunas"
+  columnHeaderHeight: "Altura do cabeรงalho de coluna"
+  addColumn: "Adicionar coluna"
+  swapLeft: "Trocar de posiรงรฃo com a coluna ร  esquerda"
+  swapRight: "Trocar de posiรงรฃo com a coluna ร  direita"
+  swapUp: "Trocar de posiรงรฃo com a coluna acima"
+  swapDown: "Trocar de posiรงรฃo com a coluna abaixo"
+  popRight: "Acoplar coluna ร  direita"
+  profile: "Perfil"
   _columns:
+    main: "Principal"
+    widgets: "Widgets"
     notifications: "Notificaรงรตes"
     tl: "Timeline"
+    antenna: "Antenas"
+    list: "Listas"
+    mentions: "Menรงรตes"
+    direct: "Notas diretas"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 6b2ff19e8e..cc74756119 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -562,13 +562,87 @@ plugins: "Pluginuri"
 deck: "Deck"
 undeck: "Pฤƒrฤƒseศ™te Deck"
 useBlurEffectForModal: "Foloseศ™te efect de blur pentru modale"
+width: "Lฤƒลฃime"
+height: "รŽnฤƒlลฃime"
+large: "Mare"
+medium: "Mediu"
+small: "Mic"
+generateAccessToken: "Genereazฤƒ token de acces"
+permission: "Permisiuni"
+enableAll: "Acteveazฤƒ tot"
+disableAll: "Dezactiveazฤƒ tot"
+tokenRequested: "Acordฤƒ acces la cont"
+pluginTokenRequestedDescription: "Acest plugin va putea sฤƒ foloseascฤƒ permisiunile setate aici."
+notificationType: "Tipul notificฤƒrii"
+edit: "Editeazฤƒ"
+useStarForReactionFallback: "Foloseศ™te โ˜… ca fallback dacฤƒ emoji-ul este necunoscut"
+emailServer: "Server email"
+enableEmail: "Activeazฤƒ distribuศ›ia de emailuri"
+emailConfigInfo: "Folosit pentru a confirma emailul tฤƒu รฎn timpul logฤƒri dacฤƒ รฎศ›i uiศ›i parola"
+email: "Email"
+emailAddress: "Adresฤƒ de email"
+smtpConfig: "Configurare Server SMTP"
 smtpHost: "Gazdฤƒ"
+smtpPort: "Port"
 smtpUser: "Nume de utilizator"
 smtpPass: "Parolฤƒ"
+emptyToDisableSmtpAuth: "Lasฤƒ username-ul ศ™i parola necompletate pentru a dezactiva verificarea SMTP"
+smtpSecure: "Foloseศ™te SSL/TLS implicit pentru conecศ›iunile SMTP"
+smtpSecureInfo: "Opreศ™te opศ›iunea asta dacฤƒ STARTTLS este folosit"
+testEmail: "Testeazฤƒ livrarea emailurilor"
+wordMute: "Cuvinte pe mut"
+regexpError: "Eroare de Expresie Regulatฤƒ"
+regexpErrorDescription: "A apฤƒrut o eroare รฎn expresia regulatฤƒ pe linia {line} al cuvintelor {tab} setate pe mut:"
+instanceMute: "Instanศ›e pe mut"
+userSaysSomething: "{name} a spus ceva"
+makeActive: "Activeazฤƒ"
+display: "Aratฤƒ"
+copy: "Copiazฤƒ"
+metrics: "Metrici"
+overview: "Privire de ansamblu"
+logs: "Log-uri"
+delayed: "รŽntรขrziate"
+database: "Baza de date"
+channel: "Canale"
+create: "Creazฤƒ"
+notificationSetting: "Setฤƒri notificฤƒri"
+notificationSettingDesc: "Selecteazฤƒ tipurile de notificฤƒri care sฤƒ fie arฤƒtate"
+useGlobalSetting: "Foloseศ™te setฤƒrile globale"
+useGlobalSettingDesc: "Dacฤƒ opศ›iunea e pornitฤƒ, notificฤƒrile contului tฤƒu vor fi folosite. Dacฤƒ e opritฤƒ, configuraศ›ia va fi individualฤƒ."
+other: "Altele"
+regenerateLoginToken: "Regenereazฤƒ token de login"
+regenerateLoginTokenDescription: "Regenereazฤƒ token-ul folosit intern รฎn timpul logฤƒri. รŽn mod normal asta nu este necesar. Odatฤƒ regenerat, toate dispozitivele vor fi delogate."
+setMultipleBySeparatingWithSpace: "Separฤƒ mai multe intrฤƒri cu spaศ›ii."
+fileIdOrUrl: "Introdu ID sau URL"
+behavior: "Comportament"
+sample: "exemplu"
+abuseReports: "Rapoarte"
+reportAbuse: "Raporteazฤƒ"
+reportAbuseOf: "Raporteazฤƒ {name}"
+fillAbuseReportDescription: "Te rog scrie detaliile legate de acest raport. Dacฤƒ este despre o notฤƒ specificฤƒ, te rog introdu URL-ul ei."
+abuseReported: "Raportul tฤƒu a fost trimis. Mulศ›umim."
+reporter: "Raportorul"
+reporteeOrigin: "Originea raportatului"
+reporterOrigin: "Originea raportorului"
+forwardReport: "Redirecศ›ioneazฤƒ raportul cฤƒtre instanศ›a externฤƒ"
+forwardReportIsAnonymous: "รŽn locul contului tฤƒu, va fi afiศ™at un cont anonim, de sistem, ca raportor cฤƒtre instanศ›a externฤƒ."
+send: "Trimite"
+abuseMarkAsResolved: "Marcheazฤƒ raportul ca rezolvat"
+openInNewTab: "Deschide รฎn tab nou"
+openInSideView: "Deschide รฎn vedere lateralฤƒ"
+defaultNavigationBehaviour: "Comportament de navigare implicit"
+editTheseSettingsMayBreakAccount: "Editarea acestor setฤƒri รฎศ›i pot defecta contul."
+waitingFor: "Aศ™teptรขnd pentru {x}"
+random: "Aleator"
+system: "Sistem"
+switchUi: "Schimbฤƒ UI"
+desktop: "Desktop"
 clearCache: "Goleศ™te cache-ul"
 info: "Despre"
 user: "Utilizatori"
 administration: "Gestionare"
+middle: "Mediu"
+sent: "Trimite"
 searchByGoogle: "Cautฤƒ"
 _email:
   _follow:
@@ -641,6 +715,9 @@ _notification:
     renote: "Re-noteazฤƒ"
     quote: "Citeazฤƒ"
     reaction: "Reacศ›ie"
+  _actions:
+    reply: "Rฤƒspunde"
+    renote: "Re-noteazฤƒ"
 _deck:
   _columns:
     notifications: "Notificฤƒri"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 877e1e185d..c44589a7e5 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -141,6 +141,8 @@ flagAsBot: "ะะบะบะฐัƒะฝั‚ ะฑะพั‚ะฐ"
 flagAsBotDescription: "ะ’ะบะปัŽั‡ะธั‚ะต, ะตัะปะธ ัั‚ะพั‚ ะฐะบะบะฐัƒะฝั‚ ัƒะฟั€ะฐะฒะปัะตั‚ัั ะฟั€ะพะณั€ะฐะผะผะพะน. ะญั‚ะพ ะฟะพะทะฒะพะปะธั‚ ัะธัั‚ะตะผะต Misskey ัƒั‡ะธั‚ั‹ะฒะฐั‚ัŒ ัั‚ะพ, ะฐ ั‚ะฐะบะถะต ะฟะพะผะพะถะตั‚ ั€ะฐะทั€ะฐะฑะพั‚ั‡ะธะบะฐะผ ะดั€ัƒะณะธั… ะฑะพั‚ะพะฒ ะฟั€ะตะดะพั‚ะฒั€ะฐั‚ะธั‚ัŒ ะฑะตัะบะพะฝะตั‡ะฝั‹ะต ั†ะธะบะปั‹ ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะธั."
 flagAsCat: "ะะบะบะฐัƒะฝั‚ ะบะพั‚ะฐ"
 flagAsCatDescription: "ะ’ะบะปัŽั‡ะธั‚ะต, ะธ ัั‚ะพั‚ ะฐะบะบะฐัƒะฝั‚ ะฑัƒะดะตั‚ ะฟะพะผะตั‡ะตะฝ ะบะฐะบ ะบะพัˆะฐั‡ะธะน."
+flagShowTimelineReplies: "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะพั‚ะฒะตั‚ั‹ ะฝะฐ ะทะฐะผะตั‚ะบะธ ะฒ ะปะตะฝั‚ะต"
+flagShowTimelineRepliesDescription: "ะ•ัะปะธ ัั‚ะพั‚ ะฟะฐั€ะฐะผะตั‚ั€ ะฒะบะปัŽั‡ะตะฝ, ั‚ะพ ะฒ ะปะตะฝั‚ะต, ะฒ ะดะพะฟะพะปะฝะตะฝะธะต ะบ ะทะฐะผะตั‚ะบะฐะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั, ะพั‚ะพะฑั€ะฐะถะฐัŽั‚ัั ะพั‚ะฒะตั‚ั‹ ะฝะฐ ะดั€ัƒะณะธะต ะทะฐะผะตั‚ะบะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั."
 autoAcceptFollowed: "ะŸั€ะธะฝะธะผะฐั‚ัŒ ะฟะพะดะฟะธัั‡ะธะบะพะฒ ะฐะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ"
 addAccount: "ะ”ะพะฑะฐะฒะธั‚ัŒ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ"
 loginFailed: "ะะตัƒะดะฐั‡ะฝะฐั ะฟะพะฟั‹ั‚ะบะฐ ะฒั…ะพะดะฐ"
@@ -236,6 +238,7 @@ saved: "ะกะพั…ั€ะฐะฝะตะฝะพ"
 messaging: "ะกะพะพะฑั‰ะตะฝะธั"
 upload: "ะ—ะฐะณั€ัƒะทะธั‚ัŒ"
 keepOriginalUploading: "ะกะพั…ั€ะฐะฝะธั‚ัŒ ะธัั…ะพะดะฝะพะต ะธะทะพะฑั€ะฐะถะตะฝะธะต"
+keepOriginalUploadingDescription: "ะกะพั…ั€ะฐะฝัะตั‚ ะธัั…ะพะดะฝัƒัŽ ะฒะตั€ัะธัŽ ะฟั€ะธ ะทะฐะณั€ัƒะทะบะต ะธะทะพะฑั€ะฐะถะตะฝะธะน. ะ•ัะปะธ ะฒั‹ะบะปัŽั‡ะธั‚ัŒ, ั‚ะพ ะฟั€ะธ ะทะฐะณั€ัƒะทะบะต ะฑั€ะฐัƒะทะตั€ ะณะตะฝะตั€ะธั€ัƒะตั‚ ะธะทะพะฑั€ะฐะถะตะฝะธะต ะดะปั ะฟัƒะฑะปะธะบะฐั†ะธะธ."
 fromDrive: "ะก ยซะดะธัะบะฐยป"
 fromUrl: "ะŸะพ ััั‹ะปะบะต"
 uploadFromUrl: "ะ—ะฐะณั€ัƒะทะธั‚ัŒ ะฟะพ ััั‹ะปะบะต"
@@ -589,6 +592,7 @@ smtpSecure: "ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ SSL/TLS ะดะปั SMTP-ัะพะตะดะธะฝะตะฝะธะน"
 smtpSecureInfo: "ะ’ั‹ะบะปัŽั‡ะธั‚ะต ะฟั€ะธ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธะธ STARTTLS."
 testEmail: "ะŸั€ะพะฒะตั€ะบะฐ ะดะพัั‚ะฐะฒะบะธ ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹"
 wordMute: "ะกะบั€ั‹ั‚ะธะต ัะปะพะฒ"
+regexpError: "ะžัˆะธะฑะบะฐ ะฒ ั€ะตะณัƒะปัั€ะฝะพะผ ะฒั‹ั€ะฐะถะตะฝะธะธ"
 instanceMute: "ะ“ะปัƒัˆะตะฝะธะต ะธะฝัั‚ะฐะฝัะพะฒ"
 userSaysSomething: "{name} ั‡ั‚ะพ-ั‚ะพ ัะพะพะฑั‰ะฐะตั‚"
 makeActive: "ะะบั‚ะธะฒะธั€ะพะฒะฐั‚ัŒ"
@@ -619,6 +623,8 @@ fillAbuseReportDescription: "ะžะฟะธัˆะธั‚ะต, ะฟะพะถะฐะปัƒะนัั‚ะฐ, ะฟั€ะธั‡ะธะฝ
 abuseReported: "ะ–ะฐะปะพะฑะฐ ะพั‚ะฟั€ะฐะฒะปะตะฝะฐ. ะ‘ะพะปัŒัˆะพะต ัะฟะฐัะธะฑะพ ะทะฐ ะธะฝั„ะพั€ะผะฐั†ะธัŽ."
 reporteeOrigin: "ะž ะบะพะผ ัะพะพะฑั‰ะตะฝะพ"
 reporterOrigin: "ะšั‚ะพ ัะพะพะฑั‰ะธะป"
+forwardReport: "ะŸะตั€ะตะฝะฐะฟั€ะฐะฒะปะตะฝะธะต ะพั‚ั‡ะตั‚ะฐ ะฝะฐ ะธะฝัั‚ะฐะฝั‚."
+forwardReportIsAnonymous: "ะฃะดะฐะปะตะฝะฝั‹ะน ะธะฝัั‚ะฐะฝั‚ ะฝะต ัะผะพะถะตั‚ ัƒะฒะธะดะตั‚ัŒ ะฒะฐัˆัƒ ะธะฝั„ะพั€ะผะฐั†ะธัŽ ะธ ะฑัƒะดะตั‚ ะพั‚ะพะฑั€ะฐะถะฐั‚ัŒัั ะบะฐะบ ะฐะฝะพะฝะธะผะฝะฐั ัะธัั‚ะตะผะฝะฐั ัƒั‡ะตั‚ะฝะฐั ะทะฐะฟะธััŒ."
 send: "ะžั‚ะฟั€ะฐะฒะธั‚ัŒ"
 abuseMarkAsResolved: "ะžั‚ะผะตั‚ะธั‚ัŒ ะถะฐะปะพะฑัƒ ะบะฐะบ ั€ะตัˆั‘ะฝะฝัƒัŽ"
 openInNewTab: "ะžั‚ะบั€ั‹ั‚ัŒ ะฒ ะฝะพะฒะพะน ะฒะบะปะฐะดะบะต"
@@ -815,7 +821,16 @@ leaveGroupConfirm: "ะŸะพะบะธะฝัƒั‚ัŒ ะณั€ัƒะฟะฟัƒ ยซ{name}ยป?"
 useDrawerReactionPickerForMobile: "ะ’ั‹ะดะฒะธะถะฝะฐั ะฟะฐะปะธั‚ั€ะฐ ะฝะฐ ะผะพะฑะธะปัŒะฝะพะผ ัƒัั‚ั€ะพะนัั‚ะฒะต"
 welcomeBackWithName: "ะก ะฒะพะทะฒั€ะฐั‰ะตะฝะธะตะผ, {name}!"
 clickToFinishEmailVerification: "ะŸะพะถะฐะปัƒะนัั‚ะฐ, ะฝะฐะถะผะธั‚ะต [{ok}], ั‡ั‚ะพะฑั‹ ะทะฐะฒะตั€ัˆะธั‚ัŒ ะฟะพะดั‚ะฒะตั€ะถะดะตะฝะธะต ะฐะดั€ะตัะฐ ัะปะตะบั‚ั€ะพะฝะฝะพะน ะฟะพั‡ั‚ั‹."
+overridedDeviceKind: "ะขะธะฟ ัƒัั‚ั€ะพะนัั‚ะฒะฐ"
+smartphone: "ะกะผะฐั€ั‚ั„ะพะฝ"
+tablet: "ะŸะปะฐะฝัˆะตั‚"
+auto: "ะะฒั‚ะพะผะฐั‚ะธั‡ะตัะบะธ"
+themeColor: "ะฆะฒะตั‚ ั‚ะตะผั‹"
+size: "ะ ะฐะทะผะตั€"
+numberOfColumn: "ะšะพะปะธั‡ะตัั‚ะฒะพ ัั‚ะพะปะฑั†ะพะฒ"
 searchByGoogle: "ะŸะพะธัะบ"
+instanceDefaultLightTheme: "ะกะฒะตั‚ะปะฐั ั‚ะตะผะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ"
+instanceDefaultDarkTheme: "ะขะตะผะฝะฐั ั‚ะตะผะฐ ะฟะพ ัƒะผะพะปั‡ะฐะฝะธัŽ"
 indefinitely: "ะฒะตั‡ะฝะพ"
 _emailUnavailable:
   used: "ะฃะถะต ะธัะฟะพะปัŒะทัƒะตั‚ัั"
@@ -1059,7 +1074,6 @@ _sfx:
   antenna: "ะะฝั‚ะตะฝะฝะฐ"
   channel: "ะšะฐะฝะฐะป"
 _ago:
-  unknown: "ะšะพะณะดะฐ-ั‚ะพ"
   future: "ะ˜ะท ะฑัƒะดัƒั‰ะตะณะพ"
   justNow: "ะขะพะปัŒะบะพ ั‡ั‚ะพ"
   secondsAgo: "{n} ั ะฝะฐะทะฐะด"
@@ -1599,6 +1613,9 @@ _notification:
     followRequestAccepted: "ะ—ะฐะฟั€ะพั ะฝะฐ ะฟะพะดะฟะธัะบัƒ ะพะดะพะฑั€ะตะฝ"
     groupInvited: "ะŸั€ะธะณะปะฐัˆะตะฝะธะต ะฒ ะณั€ัƒะฟะฟั‹"
     app: "ะฃะฒะตะดะพะผะปะตะฝะธั ะธะท ะฟั€ะธะปะพะถะตะฝะธะน"
+  _actions:
+    reply: "ะžั‚ะฒะตั‚ะธั‚ัŒ"
+    renote: "ะ ะตะฟะพัั‚"
 _deck:
   alwaysShowMainColumn: "ะ’ัะตะณะดะฐ ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ ะณะปะฐะฒะฝัƒัŽ ะบะพะปะพะฝะบัƒ"
   columnAlign: "ะ’ั‹ั€ะฐะฒะฝะธะฒะฐะฝะธะต ะบะพะปะพะฝะพะบ"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index c6f2f59bdf..dc1151522e 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -841,6 +841,7 @@ oneDay: "1 deลˆ"
 oneWeek: "1 tรฝลพdeลˆ"
 reflectMayTakeTime: "Zmeny mรดลพu chvรญฤพu trvaลฅ kรฝm sa prejavia."
 failedToFetchAccountInformation: "Nepodarilo sa naฤรญtaลฅ informรกcie o รบฤte."
+rateLimitExceeded: "Prekroฤenรฝ limit rรฝchlosti"
 _emailUnavailable:
   used: "Tรกto emailovรก adresa sa uลพ pouลพรญva"
   format: "Formรกt emailovej adresy je nesprรกvny"
@@ -1086,7 +1087,6 @@ _sfx:
   antenna: "Antรฉny"
   channel: "Upozornenia kanรกla"
 _ago:
-  unknown: "Neznรกme"
   future: "Budรบcnosลฅ"
   justNow: "Teraz"
   secondsAgo: "pred {n} sekundami"
@@ -1130,6 +1130,7 @@ _2fa:
   registerKey: "Registrovaลฅ bezpeฤnostnรฝ kฤพรบฤ"
   step1: "Najprv si nainลกtalujte autentifikaฤnรบ aplikรกciu (naprรญklad {a} alebo {b}) na svoje zariadenie."
   step2: "Potom, naskenujte QR kรณd zobrazenรฝ na obrazovke."
+  step2Url: "Do aplikรกcie zadajte nasledujรบcu URL adresu:"
   step3: "Nastavenie dokonฤรญte zadanรญm tokenu z vaลกej aplikรกcie."
   step4: "Od teraz, vลกetky ฤalลกie prihlรกsenia budรบ vyลพadovaลฅ prihlasovacรญ token."
   securityKeyInfo: "Okrem odtlaฤku prsta alebo PIN autentifikรกcie si mรดลพete nastaviลฅ autentifikรกciu cez hardvรฉrovรฝ bezpeฤnostnรฝ kฤพรบฤ podporujรบci FIDO2 a tak eลกte viac zabezpeฤiลฅ svoj รบฤet."
@@ -1628,6 +1629,10 @@ _notification:
     followRequestAccepted: "Schvรกlenรฉ ลพiadosti o sledovanie"
     groupInvited: "Pozvรกnky do skupรญn"
     app: "Oznรกmenia z prepojenรฝch aplikรกciรญ"
+  _actions:
+    followBack: "Sledovaลฅ spรคลฅ\n"
+    reply: "Odpovedaลฅ"
+    renote: "Preposlaลฅ"
 _deck:
   alwaysShowMainColumn: "Vลพdy zobraziลฅ v hlavnom stฤบpci"
   columnAlign: "Zarovnaลฅ stฤบpce"
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
new file mode 100644
index 0000000000..42bfa45f25
--- /dev/null
+++ b/locales/sv-SE.yml
@@ -0,0 +1,319 @@
+---
+_lang_: "Svenska"
+headlineMisskey: "Ett nรคtverk kopplat av noter"
+introMisskey: "Vรคlkommen! Misskey รคr en รถppen och decentraliserad mikrobloggningstjรคnst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. ๐Ÿ“ก\nMed \"reaktioner\" kan du snabbt uttrycka dina kรคnslor kring andras noter.๐Ÿ‘\nLรฅt oss utforska en nya vรคrld!๐Ÿš€"
+monthAndDay: "{day}/{month}"
+search: "Sรถk"
+notifications: "Notifikationer"
+username: "Anvรคndarnamn"
+password: "Lรถsenord"
+forgotPassword: "Glรถmt lรถsenord"
+fetchingAsApObject: "Hรคmtar frรฅn Fediversum..."
+ok: "OK"
+gotIt: "Uppfattat!"
+cancel: "Avbryt"
+enterUsername: "Ange anvรคndarnamn"
+renotedBy: "Omnoterad av {user}"
+noNotes: "Inga noteringar"
+noNotifications: "Inga aviseringar"
+instance: "Instanser"
+settings: "Instรคllningar"
+basicSettings: "Basinstรคllningar"
+otherSettings: "Andra instรคllningar"
+openInWindow: "ร–ppna i ett fรถnster"
+profile: "Profil"
+timeline: "Tidslinje"
+noAccountDescription: "Anvรคndaren har inte skrivit en biografi รคn."
+login: "Logga in"
+loggingIn: "Loggar in"
+logout: "Logga ut"
+signup: "Registrera"
+uploading: "Uppladdning sker..."
+save: "Spara"
+users: "Anvรคndare"
+addUser: "Lรคgg till anvรคndare"
+favorite: "Lรคgg till i favoriter"
+favorites: "Favoriter"
+unfavorite: "Avfavorisera"
+favorited: "Tillagd i favoriter."
+alreadyFavorited: "Redan tillagd i favoriter."
+cantFavorite: "Gick inte att lรคgga till i favoriter."
+pin: "Fรคst till profil"
+unpin: "Lossa frรฅn profil"
+copyContent: "Kopiera innehรฅll"
+copyLink: "Kopiera lรคnk"
+delete: "Radera"
+deleteAndEdit: "Radera och รคndra"
+deleteAndEditConfirm: "ร„r du sรคker att du vill radera denna not och รคndra den? Du kommer fรถrlora alla reaktioner, omnoteringar och svar till den."
+addToList: "Lรคgg till i lista"
+sendMessage: "Skicka ett meddelande"
+copyUsername: "Kopiera anvรคndarnamn"
+searchUser: "Sรถk anvรคndare"
+reply: "Svara"
+loadMore: "Ladda mer"
+showMore: "Visa mer"
+youGotNewFollower: "fรถljde dig"
+receiveFollowRequest: "Fรถljarfรถrfrรฅgan mottagen"
+followRequestAccepted: "Fรถljarfรถrfrรฅgan accepterad"
+mention: "Nรคmn"
+mentions: "Omnรคmningar"
+directNotes: "Direktnoter"
+importAndExport: "Importera / Exportera"
+import: "Importera"
+export: "Exportera"
+files: "Filer"
+download: "Nedladdning"
+driveFileDeleteConfirm: "ร„r du sรคker att du vill radera filen \"{name}\"? Noter med denna fil bifogad kommer ocksรฅ raderas."
+unfollowConfirm: "ร„r du sรคker att du vill avfรถlja {name}?"
+exportRequested: "Du har begรคrt en export. Detta kan ta lite tid. Den kommer lรคggas till i din Drive nรคr den blir klar."
+importRequested: "Du har begรคrt en import. Detta kan ta lite tid."
+lists: "Listor"
+noLists: "Du har inga listor"
+note: "Not"
+notes: "Noter"
+following: "Fรถljer"
+followers: "Fรถljare"
+followsYou: "Fรถljer dig"
+createList: "Skapa lista"
+manageLists: "Hantera lista"
+error: "Fel!"
+somethingHappened: "Ett fel har uppstรฅtt"
+retry: "Fรถrsรถk igen"
+pageLoadError: "Det gick inte att ladda sidan."
+pageLoadErrorDescription: "Detta hรคnder oftast p.g.a. nรคtverksfel eller din webblรคsarcache. Fรถrsรถk tรถmma din cache och testa sedan igen efter en liten stund."
+serverIsDead: "Servern svarar inte. Vรคnta ett litet tag och fรถrsรถk igen."
+youShouldUpgradeClient: "Fรถr att kunna se denna sida, vรคnligen ladda om sidan fรถr att uppdatera din klient."
+enterListName: "Skriv ett namn till listan"
+privacy: "Integritet"
+makeFollowManuallyApprove: "Fรถljarfรถrfrรฅgningar krรคver manuellt godkรคnnande"
+defaultNoteVisibility: "Standardsynlighet"
+follow: "Fรถlj"
+followRequest: "Skicka fรถljarfรถrfrรฅgan"
+followRequests: "Fรถljarfรถrfrรฅgningar"
+unfollow: "Avfรถlj"
+followRequestPending: "Fรถljarfรถrfrรฅgning avvaktar fรถr svar"
+enterEmoji: "Skriv en emoji"
+renote: "Omnotera"
+unrenote: "Ta tillbaka omnotering"
+renoted: "Omnoterad."
+cantRenote: "Inlรคgget kunde inte bli omnoterat."
+cantReRenote: "En omnotering kan inte bli omnoterad."
+quote: "Citat"
+pinnedNote: "Fรคstad not"
+pinned: "Fรคst till profil"
+you: "Du"
+clickToShow: "Klicka fรถr att visa"
+sensitive: "Kรคnsligt innehรฅll"
+add: "Lรคgg till"
+reaction: "Reaktioner"
+reactionSetting: "Reaktioner som ska visas i reaktionsvรคljaren"
+reactionSettingDescription2: "Dra fรถr att omordna, klicka fรถr att radera, tryck \"+\" fรถr att lรคgga till."
+rememberNoteVisibility: "Komihรฅg notvisningsinstรคllningar"
+attachCancel: "Ta bort bilaga"
+markAsSensitive: "Markera som kรคnsligt innehรฅll"
+unmarkAsSensitive: "Avmarkera som kรคnsligt innehรฅll"
+enterFileName: "Ange filnamn"
+mute: "Tysta"
+unmute: "Avtysta"
+block: "Blockera"
+unblock: "Avblockera"
+suspend: "Suspendera"
+unsuspend: "Ta bort suspenderingen"
+blockConfirm: "ร„r du sรคker att du vill blockera kontot?"
+unblockConfirm: "ร„r du sรคkert att du vill avblockera kontot?"
+suspendConfirm: "ร„r du sรคker att du vill suspendera detta konto?"
+unsuspendConfirm: "ร„r du sรคker att du vill avsuspendera detta konto?"
+selectList: "Vรคlj lista"
+selectAntenna: "Vรคlj en antenn"
+selectWidget: "Vรคlj en widget"
+editWidgets: "Redigera widgets"
+editWidgetsExit: "Avsluta redigering"
+customEmojis: "Anpassa emoji"
+emoji: "Emoji"
+emojis: "Emoji"
+emojiName: "Emoji namn"
+emojiUrl: "Emoji lรคnk"
+addEmoji: "Lรคgg till emoji"
+settingGuide: "Rekommenderade instรคllningar"
+cacheRemoteFiles: "Spara externa filer till cachen"
+cacheRemoteFilesDescription: "Nรคr denna instรคllning รคr avstรคngd kommer externa filer laddas direkt frรฅn den externa instansen. Genom att stรคnga av detta kommer lagringsutrymme minska i anvรคndning men kommer รถka datatrafiken eftersom miniatyrer inte kommer genereras."
+flagAsBot: "Markera konto som bot"
+flagAsBotDescription: "Aktivera det hรคr alternativet om kontot รคr kontrollerat av ett program. Om aktiverat kommer den fungera som en flagga fรถr andra utvecklare fรถr att hindra รคndlรถsa kedjor med andra bottar. Det kommer ocksรฅ fรฅ Misskeys interna system att hantera kontot som en bot."
+flagAsCat: "Markera konto som katt"
+flagAsCatDescription: "Aktivera denna instรคllning fรถr att markera kontot som en katt."
+flagShowTimelineReplies: "Visa svar i tidslinje"
+flagShowTimelineRepliesDescription: "Visar anvรคndarsvar till andra anvรคndares noter i tidslinjen om pรฅslagen."
+autoAcceptFollowed: "Godkรคnn fรถljarfรถrfrรฅgningar frรฅn anvรคndare du fรถljer automatiskt"
+addAccount: "Lรคgg till konto"
+loginFailed: "Inloggningen misslyckades"
+showOnRemote: "Se pรฅ extern instans"
+general: "Allmรคnt"
+wallpaper: "Bakgrundsbild"
+setWallpaper: "Vรคlj bakgrund"
+removeWallpaper: "Ta bort bakgrund"
+searchWith: "Sรถk: {q}"
+youHaveNoLists: "Du har inga listor"
+followConfirm: "ร„r du sรคker att du vill fรถlja {name}?"
+proxyAccount: "Proxykonto"
+proxyAccountDescription: "Ett proxykonto รคr ett konto som agerar som en extern fรถljare fรถr anvรคndare under vissa villkor. Till exempel, nรคr en anvรคndare lรคgger till en extern anvรคndare till en lista sรฅ kommer den externa anvรคndarens aktivitet inte levireras till instansen om ingen lokal anvรคndare fรถljer det kontot, sรฅ proxykontot anvรคnds istรคllet."
+host: "Vรคrd"
+selectUser: "Vรคlj anvรคndare"
+recipient: "Mottagare"
+annotation: "Kommentarer"
+federation: "Federation"
+instances: "Instanser"
+registeredAt: "Registrerad pรฅ"
+latestRequestSentAt: "Senaste fรถrfrรฅgan skickad"
+latestRequestReceivedAt: "Senaste begรคran mottagen"
+latestStatus: "Senaste status"
+storageUsage: "Anvรคnt lagringsutrymme"
+charts: "Diagram"
+perHour: "Per timme"
+perDay: "Per dag"
+stopActivityDelivery: "Sluta skicka aktiviteter"
+blockThisInstance: "Blockera instans"
+operations: "Operationer"
+software: "Mjukvara"
+version: "Version"
+metadata: "Metadata"
+withNFiles: "{n} fil(er)"
+monitor: "ร–vervakning"
+jobQueue: "Jobbkรถ"
+cpuAndMemory: "CPU och minne"
+network: "Nรคtverk"
+disk: "Disk"
+instanceInfo: "Instansinformation"
+statistics: "Statistik"
+clearQueue: "Rensa kรถ"
+clearQueueConfirmTitle: "ร„r du sรคker att du vill rensa kรถn?"
+clearQueueConfirmText: "Om nรฅgon not รคr olevererad i kรถn kommer den inte federeras. Vanligtvis behรถvs inte denna handling."
+clearCachedFiles: "Rensa cache"
+clearCachedFilesConfirm: "ร„r du sรคker att du vill radera alla cachade externa filer?"
+blockedInstances: "Blockerade instanser"
+blockedInstancesDescription: "Lista adressnamn av instanser som du vill blockera. Listade instanser kommer inte lรคngre kommunicera med denna instans."
+muteAndBlock: "Tystningar och blockeringar"
+mutedUsers: "Tystade anvรคndare"
+blockedUsers: "Blockerade anvรคndare"
+noUsers: "Det finns inga anvรคndare"
+editProfile: "Redigera profil"
+noteDeleteConfirm: "ร„r du sรคker pรฅ att du vill ta bort denna not?"
+pinLimitExceeded: "Du kan inte fรคsta fler noter"
+intro: "Misskey har installerats! Vรคnligen skapa en adminanvรคndare."
+done: "Klar"
+processing: "Bearbetar..."
+preview: "Fรถrhandsvisning"
+default: "Standard"
+noCustomEmojis: "Det finns ingen emoji"
+noJobs: "Det finns inga jobb"
+federating: "Federerar"
+blocked: "Blockerad"
+suspended: "Suspenderad"
+all: "Allt"
+subscribing: "Prenumererar"
+publishing: "Publiceras"
+notResponding: "Svarar inte"
+instanceFollowing: "Fรถljer pรฅ instans"
+instanceFollowers: "Fรถljare av instans"
+instanceUsers: "Anvรคndare av denna instans"
+changePassword: "ร„ndra lรถsenord"
+security: "Sรคkerhet"
+retypedNotMatch: "Inmatningen matchar inte"
+currentPassword: "Nuvarande lรถsenord"
+newPassword: "Nytt lรถsenord"
+newPasswordRetype: "Bekrรคfta lรถsenord"
+attachFile: "Bifoga filer"
+more: "Mer!"
+featured: "Utvalda"
+usernameOrUserId: "Anvรคndarnamn eller anvรคndar-id"
+noSuchUser: "Kan inte hitta anvรคndaren"
+lookup: "Sรถkning"
+announcements: "Nyheter"
+imageUrl: "Bild-URL"
+remove: "Radera"
+removed: "Borttaget"
+removeAreYouSure: "ร„r du sรคker att du vill radera \"{x}\"?"
+deleteAreYouSure: "ร„r du sรคker att du vill radera \"{x}\"?"
+resetAreYouSure: "Vill du รฅterstรคlla?"
+saved: "Sparad"
+messaging: "Chatt"
+upload: "Ladda upp"
+keepOriginalUploading: "Behรฅll originalbild"
+nsfw: "Kรคnsligt innehรฅll"
+pinnedNotes: "Fรคstad not"
+userList: "Listor"
+smtpHost: "Vรคrd"
+smtpUser: "Anvรคndarnamn"
+smtpPass: "Lรถsenord"
+clearCache: "Rensa cache"
+user: "Anvรคndare"
+searchByGoogle: "Sรถk"
+_email:
+  _follow:
+    title: "fรถljde dig"
+_mfm:
+  mention: "Nรคmn"
+  quote: "Citat"
+  emoji: "Anpassa emoji"
+  search: "Sรถk"
+_theme:
+  keys:
+    mention: "Nรคmn"
+    renote: "Omnotera"
+_sfx:
+  note: "Noter"
+  notification: "Notifikationer"
+  chat: "Chatt"
+_widgets:
+  notifications: "Notifikationer"
+  timeline: "Tidslinje"
+  federation: "Federation"
+  jobQueue: "Jobbkรถ"
+_cw:
+  show: "Ladda mer"
+_visibility:
+  followers: "Fรถljare"
+_profile:
+  username: "Anvรคndarnamn"
+_exportOrImport:
+  followingList: "Fรถljer"
+  muteList: "Tysta"
+  blockingList: "Blockera"
+  userLists: "Listor"
+_charts:
+  federation: "Federation"
+_pages:
+  script:
+    categories:
+      list: "Listor"
+    blocks:
+      _join:
+        arg1: "Listor"
+      _randomPick:
+        arg1: "Listor"
+      _dailyRandomPick:
+        arg1: "Listor"
+      _seedRandomPick:
+        arg2: "Listor"
+      _pick:
+        arg1: "Listor"
+      _listLen:
+        arg1: "Listor"
+    types:
+      array: "Listor"
+_notification:
+  youWereFollowed: "fรถljde dig"
+  _types:
+    follow: "Fรถljer"
+    mention: "Nรคmn"
+    renote: "Omnotera"
+    quote: "Citat"
+    reaction: "Reaktioner"
+  _actions:
+    reply: "Svara"
+    renote: "Omnotera"
+_deck:
+  _columns:
+    notifications: "Notifikationer"
+    tl: "Tidslinje"
+    list: "Listor"
+    mentions: "Omnรคmningar"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 073b2c310e..7e7ef8685f 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -7,6 +7,7 @@ search: "ะŸะพัˆัƒะบ"
 notifications: "ะกะฟะพะฒั–ั‰ะตะฝะฝั"
 username: "ะ†ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ"
 password: "ะŸะฐั€ะพะปัŒ"
+forgotPassword: "ะฏ ะทะฐะฑัƒะฒ ะฟะฐั€ะพะปัŒ"
 fetchingAsApObject: "ะžั‚ั€ะธะผัƒั”ะผะพ ะท ั„ะตะดั–ะฒะตั€ััƒ..."
 ok: "OK"
 gotIt: "ะ—ั€ะพะทัƒะผั–ะปะพ!"
@@ -80,6 +81,8 @@ somethingHappened: "ะฉะพััŒ ะฟั–ัˆะปะพ ะฝะต ั‚ะฐะบ"
 retry: "ะกะฟั€ะพะฑัƒะฒะฐั‚ะธ ะทะฝะพะฒัƒ"
 pageLoadError: "ะŸะพะผะธะปะบะฐ ะฟั€ะธ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั– ัั‚ะพั€ั–ะฝะบะธ"
 pageLoadErrorDescription: "ะ—ะฐะทะฒะธั‡ะฐะน ั†ะต ะฟะพะฒโ€™ัะทะฐะฝะพ ะท ะฟะพะผะธะปะบะฐะผะธ ะผะตั€ะตะถั– ะฐะฑะพ ะบะตัˆะตะผ ะฑั€ะฐัƒะทะตั€ะฐ. ะžั‡ะธัั‚ั–ั‚ัŒ ะบะตัˆ ะฐะฑะพ ะฟะพั‡ะตะบะฐะนั‚ะต ั‚ั€ะพั…ะธ ะน ัะฟั€ะพะฑัƒะนั‚ะต ั‰ะต ั€ะฐะท."
+serverIsDead: "ะ’ั–ะดะฟะพะฒั–ะดั– ะฒั–ะด ัะตั€ะฒะตั€ะฐ ะฝะตะผะฐั”. ะ—ะฐั‡ะตะบะฐะนั‚ะต ะดะตัะบะธะน ั‡ะฐั ั– ะฟะพะฒั‚ะพั€ั–ั‚ัŒ ัะฟั€ะพะฑัƒ."
+youShouldUpgradeClient: "ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถั‚ะต ั‚ะฐ ะฒะธะบะพั€ะธัั‚ะพะฒัƒะนั‚ะต ะฝะพะฒัƒ ะฒะตั€ัั–ัŽ ะบะปั–ั”ะฝั‚ะฐ, ั‰ะพะฑ ะฟะตั€ะตะณะปัะฝัƒั‚ะธ ั†ัŽ ัั‚ะพั€ั–ะฝะบัƒ."
 enterListName: "ะ’ะฒะตะดั–ั‚ัŒ ะฝะฐะทะฒัƒ ัะฟะธัะบัƒ"
 privacy: "ะšะพะฝั„ั–ะดะตะฝั†ั–ะนะฝั–ัั‚ัŒ"
 makeFollowManuallyApprove: "ะŸั–ะดั‚ะฒะตั€ะดะถัƒะฒะฐั‚ะธ ะฟั–ะดะฟะธัะฝะธะบั–ะฒ ัƒั€ัƒั‡ะฝัƒ"
@@ -103,6 +106,7 @@ clickToShow: "ะะฐั‚ะธัะฝั–ั‚ัŒ ะดะปั ะฟะตั€ะตะณะปัะดัƒ"
 sensitive: "NSFW"
 add: "ะ”ะพะดะฐั‚ะธ"
 reaction: "ะ ะตะฐะบั†ั–ั—"
+reactionSetting: "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ั€ะตะฐะบั†ั–ะน"
 reactionSettingDescription2: "ะŸะตั€ะตะผั–ัั‚ะธั‚ะธ ั‰ะพะฑ ะทะผั–ะฝะธั‚ะธ ะฟะพั€ัะดะพะบ, ะšะปะฐั†ะฝัƒั‚ะธ ะผะธัˆะพัŽ ั‰ะพะฑ ะฒะธะดะฐะปะธั‚ะธ, ะะฐั‚ะธัะฝัƒั‚ะธ \"+\" ั‰ะพะฑ ะดะพะดะฐั‚ะธ."
 rememberNoteVisibility: "ะŸะฐะผโ€™ัั‚ะฐั‚ะธ ะฟะฐั€ะฐะผะตั‚ั€ะธ ะฒะธะดะธะผั–ัั‚ั–"
 attachCancel: "ะ’ะธะดะฐะปะธั‚ะธ ะฒะบะปะฐะดะตะฝะฝั"
@@ -137,7 +141,10 @@ flagAsBot: "ะะบะฐัƒะฝั‚ ะฑะพั‚ะฐ"
 flagAsBotDescription: "ะ’ะฒั–ะผะบะฝั–ั‚ัŒ ัะบั‰ะพ ั†ะตะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะฑะพั‚ะพะผ. ะฆั ะพะฟั†ั–ั ะฟะพะทะฝะฐั‡ะธั‚ัŒ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ัะบ ะฑะพั‚ะฐ. ะฆะต ะฟะพั‚ั€ั–ะฑะฝะพ ั‰ะพะฑ ะฒะธะบะปัŽั‡ะธั‚ะธ ะฑะตะทะบั–ะฝะตั‡ะฝัƒ ั–ะฝั‚ะตั€ะฐะบั†ั–ัŽ ะผั–ะถ ะฑะพั‚ะฐะผะธ ะฐ ั‚ะฐะบะพะถ ะฒั–ะดะฟะพะฒั–ะดะฝะพะณะพ ะฟั–ะดะปะฐัˆั‚ัƒะฒะฐะฝะฝั Misskey."
 flagAsCat: "ะะบะฐัƒะฝั‚ ะบะพั‚ะฐ"
 flagAsCatDescription: "ะ’ะฒั–ะผะบะฝั–ั‚ัŒ, ั‰ะพะฑ ะฟะพะทะฝะฐั‡ะธั‚ะธ, ั‰ะพ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ั” ะบะพั‚ะธะบะพะผ."
+flagShowTimelineReplies: "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฒั–ะดะฟะพะฒั–ะดั– ะฝะฐ ะฝะพั‚ะฐั‚ะบะธ ะฝะฐ ั‡ะฐัะพะฒั–ะน ัˆะบะฐะปั–"
+flagShowTimelineRepliesDescription: "ะŸะพะบะฐะทัƒั” ะฒั–ะดะฟะพะฒั–ะดั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะฝะฐ ะฝะพั‚ะฐั‚ะบะธ ั–ะฝัˆะธั… ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะฝะฐ ั‡ะฐัะพะฒั–ะน ัˆะบะฐะปั–."
 autoAcceptFollowed: "ะะฒั‚ะพะผะฐั‚ะธั‡ะฝะพ ะฟั€ะธะนะผะฐั‚ะธ ะทะฐะฟะธั‚ะธ ะฝะฐ ะฟั–ะดะฟะธัะบัƒ ะฒั–ะด ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ, ะฝะฐ ัะบะธั… ะฒะธ ะฟั–ะดะฟะธัะฐะฝั–"
+addAccount: "ะ”ะพะดะฐั‚ะธ ะฐะบะฐัƒะฝั‚"
 loginFailed: "ะะต ะฒะดะฐะปะพัั ัƒะฒั–ะนั‚ะธ"
 showOnRemote: "ะŸะตั€ะตะณะปัะฝัƒั‚ะธ ะฒ ะพั€ะธะณั–ะฝะฐะปั–"
 general: "ะ—ะฐะณะฐะปัŒะฝะต"
@@ -148,6 +155,7 @@ searchWith: "ะŸะพัˆัƒะบ: {q}"
 youHaveNoLists: "ะฃ ะฒะฐั ะฝะตะผะฐั” ัะฟะธัะบั–ะฒ"
 followConfirm: "ะŸั–ะดะฟะธัะฐั‚ะธัั ะฝะฐ {name}?"
 proxyAccount: "ะŸั€ะพะบัั–-ะฐะบะฐัƒะฝั‚"
+proxyAccountDescription: "ะžะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฟั€ะพะบัั– โ€“ ั†ะต ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั, ัะบะธะน ะดั–ั” ัะบ ะฒั–ะดะดะฐะปะตะฝะธะน ะฟั–ะดะฟะธัะฝะธะบ ะดะปั ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะทะฐ ะฟะตะฒะฝะธั… ัƒะผะพะฒ. ะะฐะฟั€ะธะบะปะฐะด, ะบะพะปะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ ะดะพะดะฐั” ะฒั–ะดะดะฐะปะตะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะดะพ ัะฟะธัะบัƒ, ะฐะบั‚ะธะฒะฝั–ัั‚ัŒ ะฒั–ะดะดะฐะปะตะฝะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฝะต ะฑัƒะดะต ะดะพัั‚ะฐะฒะปะตะฝะฐ ะฝะฐ ัะตั€ะฒะตั€, ัะบั‰ะพ ะถะพะดะตะฝ ะปะพะบะฐะปัŒะฝะธะน ะบะพั€ะธัั‚ัƒะฒะฐั‡ ะฝะต ัั‚ะตะถะธั‚ัŒ ะทะฐ ั†ะธะผ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะตะผ, ั‚ะพ ะทะฐะผั–ัั‚ัŒ ะฝัŒะพะณะพ ะฑัƒะดะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธัั ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฟั€ะพะบัั–-ัะตั€ะฒะตั€ะฐ."
 host: "ะฅะพัั‚"
 selectUser: "ะ’ะธะฑะตั€ั–ั‚ัŒ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ"
 recipient: "ะžั‚ั€ะธะผัƒะฒะฐั‡"
@@ -229,6 +237,8 @@ resetAreYouSure: "ะกะฟั€ะฐะฒะดั– ัะบะธะฝัƒั‚ะธ?"
 saved: "ะ—ะฑะตั€ะตะถะตะฝะพ"
 messaging: "ะงะฐั‚ะธ"
 upload: "ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ"
+keepOriginalUploading: "ะ—ะฑะตั€ะตะณั‚ะธ ะพั€ะธะณั–ะฝะฐะปัŒะฝะต ะทะพะฑั€ะฐะถะตะฝะฝั"
+keepOriginalUploadingDescription: "ะ—ะฑะตั€ั–ะณะฐั” ะฟะพั‡ะฐั‚ะบะพะฒะพ ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะต ะทะพะฑั€ะฐะถะตะฝะฝั ัะบ ั”. ะฏะบั‰ะพ ะฒะธะผะบะฝะตะฝะพ, ะฒะตั€ัั–ั ะดะปั ะฒั–ะดะพะฑั€ะฐะถะตะฝะฝั ะฒ ะ†ะฝั‚ะตั€ะฝะตั‚ั– ะฑัƒะดะต ัั‚ะฒะพั€ะตะฝะฐ ะฟั–ะด ั‡ะฐั ะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั."
 fromDrive: "ะ— ะดะธัะบะฐ"
 fromUrl: "ะ— ะฟะพัะธะปะฐะฝะฝั"
 uploadFromUrl: "ะ—ะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ ะท ะฟะพัะธะปะฐะฝะฝั"
@@ -275,6 +285,7 @@ emptyDrive: "ะ”ะธัะบ ะฟะพั€ะพะถะฝั–ะน"
 emptyFolder: "ะขะตะบะฐ ะฟะพั€ะพะถะฝั"
 unableToDelete: "ะ’ะธะดะฐะปะตะฝะฝั ะฝะตะผะพะถะปะธะฒะต"
 inputNewFileName: "ะ’ะฒะตะดั–ั‚ัŒ ั–ะผ'ั ะฝะพะฒะพะณะพ ั„ะฐะนะปัƒ"
+inputNewDescription: "ะ’ะฒะตะดั–ั‚ัŒ ะฝะพะฒะธะน ะทะฐะณะพะปะพะฒะพะบ"
 inputNewFolderName: "ะ’ะฒะตะดั–ั‚ัŒ ั–ะผ'ั ะฝะพะฒะพั— ั‚ะตะบะธ"
 circularReferenceFolder: "ะ’ะธ ะฝะฐะผะฐะณะฐั”ั‚ะตััŒ ะฟะตั€ะตะผั–ัั‚ะธั‚ะธ ะฟะฐะฟะบัƒ ะฒ ั—ั— ะฟั–ะดะฟะฐะฟะบัƒ."
 hasChildFilesOrFolders: "ะฆั ั‚ะตะบะฐ ะฝะต ะฟะพั€ะพะถะฝั ั– ะฝะต ะผะพะถะต ะฑัƒั‚ะธ ะฒะธะดะฐะปะตะฝะฐ"
@@ -306,6 +317,8 @@ monthX: "{month}"
 yearX: "{year}"
 pages: "ะกั‚ะพั€ั–ะฝะบะธ"
 integration: "ะ†ะฝั‚ะตะณั€ะฐั†ั–ั"
+connectService: "ะŸั–ะดโ€™ั”ะดะฝะฐั‚ะธ"
+disconnectService: "ะ’ั–ะดะบะปัŽั‡ะธั‚ะธัั"
 enableLocalTimeline: "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะปะพะบะฐะปัŒะฝัƒ ัั‚ั€ั–ั‡ะบัƒ"
 enableGlobalTimeline: "ะฃะฒั–ะผะบะฝัƒั‚ะธ ะณะปะพะฑะฐะปัŒะฝัƒ ัั‚ั€ั–ั‡ะบัƒ"
 disablingTimelinesInfo: "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะธ ั‚ะฐ ะผะพะดะตั€ะฐั‚ะพั€ะธ ะทะฐะฒะถะดะธ ะผะฐัŽั‚ัŒ ะดะพัั‚ัƒะฟ ะดะพ ะฒัั–ั… ัั‚ั€ั–ั‡ะพะบ, ะฝะฐะฒั–ั‚ัŒ ัะบั‰ะพ ะฒะพะฝะธ ะฒะธะผะบะฝัƒั‚ั–."
@@ -317,6 +330,7 @@ driveCapacityPerRemoteAccount: "ะžะฑ'ั”ะผ ะดะธัะบะฐ ะฝะฐ ะพะดะฝะพะณะพ ะฒั–ะดะด
 inMb: "ะ’ ะผะตะณะฐะฑะฐะนั‚ะฐั…"
 iconUrl: "URL ะฐะฒะฐั‚ะฐั€ะฐ"
 bannerUrl: "URL ะฑะฐะฝะตั€ะฐ"
+backgroundImageUrl: "URL-ะฐะดั€ะตัะฐ ั„ะพะฝะพะฒะพะณะพ ะทะพะฑั€ะฐะถะตะฝะฝั"
 basicInfo: "ะžัะฝะพะฒะฝะฐ ั–ะฝั„ะพั€ะผะฐั†ั–ั"
 pinnedUsers: "ะ—ะฐะบั€ั–ะฟะปะตะฝั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–"
 pinnedUsersDescription: "ะ’ะฟะธัˆั–ั‚ัŒ ะฒ ัะฟะธัะพะบ ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ, ัะบะธั… ั…ะพั‡ะตั‚ะต ะทะฐะบั€ั–ะฟะธั‚ะธ ะฝะฐ ัั‚ะพั€ั–ะฝั†ั– \"ะ—ะฝะฐะนั‚ะธ\", ั–ะผ'ั ะฒ ัั‚ะพะฒะฟั‡ะธะบ."
@@ -332,6 +346,7 @@ recaptcha: "reCAPTCHA"
 enableRecaptcha: "ะฃะฒั–ะผะบะฝัƒั‚ะธ reCAPTCHA"
 recaptchaSiteKey: "ะšะปัŽั‡ ัะฐะนั‚ัƒ"
 recaptchaSecretKey: "ะกะตะบั€ะตั‚ะฝะธะน ะบะปัŽั‡"
+avoidMultiCaptchaConfirm: "ะ’ะธะบะพั€ะธัั‚ะฐะฝะฝั ะบั–ะปัŒะบะพั… ัะธัั‚ะตะผ Captcha ะผะพะถะต ัะฟั€ะธั‡ะธะฝะธั‚ะธ ะฟะตั€ะตัˆะบะพะดะธ ะผั–ะถ ะฝะธะผะธ. ะ‘ะฐะถะฐั”ั‚ะต ะฒะธะผะบะฝัƒั‚ะธ ั–ะฝัˆั– ะฐะบั‚ะธะฒะฝั– ัะธัั‚ะตะผะธ Captcha? ะฏะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต, ั‰ะพะฑ ะฒะพะฝะธ ะทะฐะปะธัˆะฐะปะธัั ะฒะฒั–ะผะบะฝะตะฝะธะผะธ, ะฝะฐั‚ะธัะฝั–ั‚ัŒ ยซะกะบะฐััƒะฒะฐั‚ะธยป."
 antennas: "ะะฝั‚ะตะฝะธ"
 manageAntennas: "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฐะฝั‚ะตะฝ"
 name: "ะ†ะผ'ั"
@@ -428,10 +443,12 @@ signinWith: "ะฃะฒั–ะนั‚ะธ ะทะฐ ะดะพะฟะพะผะพะณะพัŽ {x}"
 signinFailed: "ะะต ะฒะดะฐะปะพัั ัƒะฒั–ะนั‚ะธ. ะ’ะฒะตะดะตะฝั– ั–ะผโ€™ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฐะฑะพ ะฟะฐั€ะพะปัŒ ะฝะตะฟั€ะฐะฒะธะปัŒะฝi."
 tapSecurityKey: "ะขะพั€ะบะฝั–ั‚ัŒัั ะบะปัŽั‡ะฐ ะฑะตะทะฟะตะบะธ"
 or: "ะฐะฑะพ"
+language: "ะœะพะฒะฐ"
 uiLanguage: "ะœะพะฒะฐ ั–ะฝั‚ะตั€ั„ะตะนััƒ"
 groupInvited: "ะ—ะฐะฟั€ะพัˆะตะฝะฝั ะดะพ ะณั€ัƒะฟะธ"
 aboutX: "ะŸั€ะพ {x}"
 useOsNativeEmojis: "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะตะผะพะดะทั– ะžะก"
+disableDrawer: "ะะต ะฒะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฒะธััƒะฒะฝั– ะผะตะฝัŽ"
 youHaveNoGroups: "ะะตะผะฐั” ะณั€ัƒะฟ"
 joinOrCreateGroup: "ะžั‚ั€ะธะผัƒะนั‚ะต ะทะฐะฟั€ะพัˆะตะฝะฝั ะดะพ ะณั€ัƒะฟ ะฐะฑะพ ัั‚ะฒะพั€ัŽะนั‚ะต ัะฒะพั— ะฒะปะฐัะฝั– ะณั€ัƒะฟะธ."
 noHistory: "ะ†ัั‚ะพั€ั–ั ะฟะพั€ะพะถะฝั"
@@ -442,6 +459,7 @@ category: "ะšะฐั‚ะตะณะพั€ั–ั"
 tags: "ะขะตะณะธ"
 docSource: "ะ”ะถะตั€ะตะปะพ ั†ัŒะพะณะพ ะดะพะบัƒะผะตะฝั‚ะฐ"
 createAccount: "ะกั‚ะฒะพั€ะธั‚ะธ ะฐะบะฐัƒะฝั‚"
+existingAccount: "ะ†ัะฝัƒัŽั‡ะธะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั"
 regenerate: "ะžะฝะพะฒะธั‚ะธ"
 fontSize: "ะ ะพะทะผั–ั€ ัˆั€ะธั„ั‚ัƒ"
 noFollowRequests: "ะะตะผะฐั” ะทะฐะฟะธั‚ั–ะฒ ะฝะฐ ะฟั–ะดะฟะธัะบัƒ"
@@ -463,6 +481,7 @@ showFeaturedNotesInTimeline: "ะŸะพะบะฐะทัƒะฒะฐั‚ะธ ะฟะพะฟัƒะปัั€ะฝั– ะฝะพั‚ะฐั‚
 objectStorage: "Object Storage"
 useObjectStorage: "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ object storage"
 objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrlDesc: "ะฆะต ะฟะพั‡ะฐั‚ะบะพะฒะฐ ั‡ะฐัั‚ะธะฝะฐ ะฐะดั€ะตัะธ, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั CDN ะฐะฑะพ ะฟั€ะพะบัั–, ะฝะฐะฟั€ะธะบะปะฐะด ะดะปั S3: https://<bucket>.s3.amazonaws.com, ะฐะฑะพ GCS: 'https://storage.googleapis.com/<bucket>'"
 objectStorageBucket: "Bucket"
 objectStorageBucketDesc: "ะ‘ัƒะดัŒ ะปะฐัะบะฐ ะฒะบะฐะถั–ั‚ัŒ ะฝะฐะทะฒัƒ ะฒั–ะดั€ะฐ ะฒ ะฝะฐะปะฐัˆั‚ะพะฒะฐะฝะพะผัƒ ัะตั€ะฒั–ัั–."
 objectStoragePrefix: "Prefix"
@@ -513,6 +532,9 @@ removeAllFollowing: "ะกะบะฐััƒะฒะฐั‚ะธ ะฒัั– ะฟั–ะดะฟะธัะบะธ"
 removeAllFollowingDescription: "ะกะบะฐััƒะฒะฐั‚ะธ ะฟั–ะดะฟะธัะบัƒ ะฝะฐ ะฒัั– ะฐะบะฐัƒะฝั‚ะธ ะท {host}. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ั€ะพะฑั–ั‚ัŒ ั†ะต, ัะบั‰ะพ ั–ะฝัั‚ะฐะฝั ะฑั–ะปัŒัˆะต ะฝะต ั–ัะฝัƒั”."
 userSuspended: "ะžะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะทะฐะฑะปะพะบะพะฒะฐะฝะธะน."
 userSilenced: "ะžะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฟั€ะธะณะปัƒัˆะตะฝะธะน."
+yourAccountSuspendedTitle: "ะฆะตะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะทะฐะฑะปะพะบะพะฒะฐะฝะพ"
+yourAccountSuspendedDescription: "ะฆะตะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฑัƒะปะพ ะทะฐะฑะปะพะบะพะฒะฐะฝะพ ั‡ะตั€ะตะท ะฟะพั€ัƒัˆะตะฝะฝั ัƒะผะพะฒ ะฝะฐะดะฐะฝะฝั ะฟะพัะปัƒะณ ัะตั€ะฒะตั€ะฐ. ะ—ะฒ'ัะถั–ั‚ัŒัั ะท ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะพะผ, ัะบั‰ะพ ะฒะธ ั…ะพั‡ะตั‚ะต ะดั–ะทะฝะฐั‚ะธัั ะดะพะบะปะฐะดะฝั–ัˆัƒ ะฟั€ะธั‡ะธะฝัƒ. ะ‘ัƒะดัŒ ะปะฐัะบะฐ, ะฝะต ัั‚ะฒะพั€ัŽะนั‚ะต ะฝะพะฒะธะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั."
+menu: "ะœะตะฝัŽ"
 divider: "ะ ะพะทะดั–ะปัŽะฒะฐั‡"
 addItem: "ะ”ะพะดะฐั‚ะธ ะตะปะตะผะตะฝั‚"
 relays: "ะ ะตั‚ั€ะฐะฝัะปัั‚ะพั€ะธ"
@@ -531,6 +553,8 @@ disablePlayer: "ะ—ะฐะบั€ะธั‚ะธ ะฒั–ะดะตะพะฟะปะตั”ั€"
 expandTweet: "ะ ะพะทะณะพั€ะฝัƒั‚ะธ ั‚ะฒั–ั‚"
 themeEditor: "ะ ะตะดะฐะบั‚ะพั€ ั‚ะตะผ"
 description: "ะžะฟะธั"
+describeFile: "ะ”ะพะดะฐั‚ะธ ะฟั–ะดะฟะธั"
+enterFileDescription: "ะ’ะฒะตะดั–ั‚ัŒ ะฟั–ะดะฟะธั"
 author: "ะะฒั‚ะพั€"
 leaveConfirm: "ะ—ะผั–ะฝะธ ะฝะต ะทะฑะตั€ะตะถะตะฝั–. ะ’ะธ ะดั–ะนัะฝะพ ั…ะพั‡ะตั‚ะต ัะบะฐััƒะฒะฐั‚ะธ ะทะผั–ะฝะธ?"
 manage: "ะฃะฟั€ะฐะฒะปั–ะฝะฝั"
@@ -553,6 +577,7 @@ pluginTokenRequestedDescription: "ะฆะตะน ะฟะปะฐะณั–ะฝ ะทะผะพะถะต ะฒะธะบะพั€ะธั
 notificationType: "ะขะธะฟ ัะฟะพะฒั–ั‰ะตะฝะฝั"
 edit: "ะ ะตะดะฐะณัƒะฒะฐั‚ะธ"
 useStarForReactionFallback: "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ โ˜… ัะบ ะทะฐะฟะฐัะฝะธะน ะฒะฐั€ั–ะฐะฝั‚, ัะบั‰ะพ ะตะผะพะดะทั– ั€ะตะฐะบั†ั–ั— ะฝะตะฒั–ะดะพะผะธะน"
+emailServer: "ะกะตั€ะฒะตั€ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ"
 enableEmail: "ะฃะฒั–ะผะบะฝัƒั‚ะธ ั„ัƒะฝะบั†ั–ัŽ ะดะพัั‚ะฐะฒะบะธ ะฟะพัˆั‚ะธ"
 emailConfigInfo: "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั ะดะปั ะฟั–ะดั‚ะฒะตั€ะดะถะตะฝะฝั ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ ะฟั–ะดั‡ะฐั ั€ะตั”ัั‚ั€ะฐั†ั–ั—, ะฐ ั‚ะฐะบะพะถ ะดะปั ะฒั–ะดะฝะพะฒะปะตะฝะฝั ะฟะฐั€ะพะปัŽ."
 email: "E-mail"
@@ -567,6 +592,9 @@ smtpSecure: "ะ’ะธะบะพั€ะธัั‚ะพะฒัƒะฒะฐั‚ะธ ะฑะตะทัƒะผะพะฒะฝะต ัˆะธั„ั€ัƒะฒะฐะฝ
 smtpSecureInfo: "ะ’ะธะผะบะฝั–ั‚ัŒ ะฟั€ะธ ะฒะธะบะพั€ะธัั‚ะฐะฝะฝั– STARTTLS  "
 testEmail: "ะขะตัั‚ะพะฒะธะน email"
 wordMute: "ะ‘ะปะพะบัƒะฒะฐะฝะฝั ัะปั–ะฒ"
+regexpError: "ะŸะพะผะธะปะบะฐ ั€ะตะณัƒะปัั€ะฝะพะณะพ ะฒะธั€ะฐะทัƒ"
+regexpErrorDescription: "ะกั‚ะฐะปะฐัั ะฟะพะผะธะปะบะฐ ะฒ ั€ะตะณัƒะปัั€ะฝะพะผัƒ ะฒะธั€ะฐะทั– ะฒ ั€ัะดะบัƒ {line} ะฒะฐัˆะพะณะพ ัะปะพะฒะฐ {tab} ัะปะพะฒะฐ ั‰ะพ ั–ะณะฝะพั€ัƒัŽั‚ัŒัั:"
+instanceMute: "ะŸั€ะธะณะปัƒัˆะตะฝะฝั ั–ะฝัั‚ะฐะฝัั–ะฒ"
 userSaysSomething: "{name} ั‰ะพััŒ ัะบะฐะทะฐะฒ(ะปะฐ)"
 makeActive: "ะะบั‚ะธะฒัƒะฒะฐั‚ะธ"
 display: "ะ’ั–ะดะพะฑั€ะฐะถะตะฝะฝั"
@@ -594,6 +622,11 @@ reportAbuse: "ะŸะพัะบะฐั€ะถะธั‚ะธััŒ"
 reportAbuseOf: "ะŸะพัะบะฐั€ะถะธั‚ะธััŒ ะฝะฐ {name}"
 fillAbuseReportDescription: "ะ‘ัƒะดัŒ ะปะฐัะบะฐ ะฒะบะฐะถั–ั‚ัŒ ะฟะพะดั€ะพะฑะธั†ั– ัะบะฐั€ะณะธ. ะฏะบั‰ะพ ัะบะฐั€ะณะฐ ัั‚ะพััƒั”ั‚ัŒัั ะทะฐะฟะธััƒ, ะฒะบะฐะถั–ั‚ัŒ ะฟะพัะธะปะฐะฝะฝั ะฝะฐ ะฝัŒะพะณะพ."
 abuseReported: "ะ”ัะบัƒั”ะผะพ, ะฒะฐัˆัƒ ัะบะฐั€ะณัƒ ะฑัƒะปะพ ะฒั–ะดะฟั€ะฐะฒะปะตะฝะพ. "
+reporter: "ะ ะตะฟะพั€ั‚ะตั€"
+reporteeOrigin: "ะŸั€ะพ ะบะพะณะพ ะฟะพะฒั–ะดะพะผะปะตะฝะพ"
+reporterOrigin: "ะฅั‚ะพ ะฟะพะฒั–ะดะพะผะธะฒ"
+forwardReport: "ะŸะตั€ะตัะปะฐั‚ะธ ะทะฒั–ั‚ ะฝะฐ ะฒั–ะดะดะฐะปะตะฝะธะน ั–ะฝัั‚ะฐะฝั"
+forwardReportIsAnonymous: "ะ—ะฐะผั–ัั‚ัŒ ะฒะฐัˆะพะณะพ ะพะฑะปั–ะบะพะฒะพะณะพ ะทะฐะฟะธััƒ ะฐะฝะพะฝั–ะผะฝะธะน ัะธัั‚ะตะผะฝะธะน ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฑัƒะดะต ะฒั–ะดะพะฑั€ะฐะถะฐั‚ะธัั ัะบ ะดะพะฟะพะฒั–ะดะฐั‡ ะฝะฐ ะฒั–ะดะดะฐะปะตะฝะพะผัƒ ั–ะฝัั‚ะฐะฝัั–"
 send: "ะ’ั–ะดะฟั€ะฐะฒะธั‚ะธ"
 abuseMarkAsResolved: "ะŸะพะทะฝะฐั‡ะธั‚ะธ ัะบะฐั€ะณัƒ ัะบ ะฒะธั€ั–ัˆะตะฝัƒ"
 openInNewTab: "ะ’ั–ะดะบั€ะธั‚ะธ ะฒ ะฝะพะฒั–ะน ะฒะบะปะฐะดั†ั–"
@@ -655,6 +688,7 @@ center: "ะฆะตะฝั‚ั€"
 wide: "ะจะธั€ะพะบะธะน"
 narrow: "ะ’ัƒะทัŒะบะธะน"
 reloadToApplySetting: "ะะฐะปะฐัˆั‚ัƒะฒะฐะฝะฝั ะฒะฒั–ะนะดะต ะฒ ะดั–ัŽ ะฟั€ะธ ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั–. ะŸะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะธั‚ะธ?"
+needReloadToApply: "ะ—ะผั–ะฝะธ ะฝะฐะฑัƒะดัƒั‚ัŒ ั‡ะธะฝะฝะพัั‚ั– ะฟั–ัะปั ะฟะตั€ะตะทะฐะฒะฐะฝั‚ะฐะถะตะฝะฝั ัั‚ะพั€ั–ะฝะบะธ."
 showTitlebar: "ะŸะพะบะฐะทะฐั‚ะธ ั‚ะธั‚ัƒะปัŒะฝะธะน ั€ัะดะพะบ"
 clearCache: "ะžั‡ะธัั‚ะธั‚ะธ ะบะตัˆ"
 onlineUsersCount: "{n} ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะพะฝะปะฐะนะฝ"
@@ -669,12 +703,28 @@ textColor: "ะขะตะบัั‚"
 saveAs: "ะ—ะฑะตั€ะตะณั‚ะธ ัะบโ€ฆ"
 advanced: "ะ ะพะทัˆะธั€ะตะฝั–"
 value: "ะ—ะฝะฐั‡ะตะฝะฝั"
+createdAt: "ะกั‚ะฒะพั€ะตะฝะพ"
 updatedAt: "ะžัั‚ะฐะฝะฝั” ะพะฝะพะฒะปะตะฝะฝั"
 saveConfirm: "ะ—ะฑะตั€ะตะณั‚ะธ ะทะผั–ะฝะธ?"
 deleteConfirm: "ะ’ะธ ะดั–ะนัะฝะพ ะฑะฐะถะฐั”ั‚ะต ั†ะต ะฒะธะดะฐะปะธั‚ะธ?"
 invalidValue: "ะะตะบะพั€ะตะบั‚ะฝะต ะทะฝะฐั‡ะตะฝะฝั."
 registry: "ะ ะตั”ัั‚ั€"
 closeAccount: "ะ—ะฐะบั€ะธั‚ะธ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั"
+currentVersion: "ะ’ะตั€ัั–ั, ั‰ะพ ะฒะธะบะพั€ะธัั‚ะพะฒัƒั”ั‚ัŒัั"
+latestVersion: "ะกะฐะผะฐ ัะฒั–ะถะฐ ะฒะตั€ัั–ั"
+youAreRunningUpToDateClient: "ะฃ ะฒะฐั ะฝะฐะนัะฒั–ะถั–ัˆะฐ ะฒะตั€ัั–ั ะบะปั–ั”ะฝั‚ะฐ."
+newVersionOfClientAvailable: "ะ”ะพัั‚ัƒะฟะฝั–ัˆะฐ ัะฒั–ะถะฐ ะฒะตั€ัั–ั ะบะปั–ั”ะฝั‚ะฐ."
+usageAmount: "ะ’ะธะบะพั€ะธัั‚ะฐะฝะต"
+capacity: "ะ„ะผะฝั–ัั‚ัŒ"
+inUse: "ะ—ะฐะนะฝัั‚ะพ"
+editCode: "ะ ะตะดะฐะณัƒะฒะฐั‚ะธ ะฒะธั…ั–ะดะฝะธะน ั‚ะตะบัั‚"
+apply: "ะ—ะฐัั‚ะพััƒะฒะฐั‚ะธ"
+receiveAnnouncementFromInstance: "ะžั‚ั€ะธะผัƒะฒะฐั‚ะธ ะพะฟะพะฒั–ั‰ะตะฝะฝั ะท ั–ะฝัั‚ะฐะฝััƒ"
+emailNotification: "ะกะฟะพะฒั–ั‰ะตะฝะฝั ะตะปะตะบั‚ั€ะพะฝะฝะพัŽ ะฟะพัˆั‚ะพัŽ"
+publish: "ะžะฟัƒะฑะปั–ะบัƒะฒะฐั‚ะธ"
+inChannelSearch: "ะŸะพัˆัƒะบ ะทะฐ ะบะฐะฝะฐะปะพะผ"
+useReactionPickerForContextMenu: "ะ’ั–ะดะบั€ะธะฒะฐั‚ะธ ะฟะฐะปั–ั‚ั€ัƒ ั€ะตะฐะบั†ั–ะน ะฟั€ะฐะฒะพัŽ ะบะฝะพะฟะบะพัŽ"
+typingUsers: "ะกั‚ัƒะบ ะบะปะฐะฒั–ัˆ. ะฆะต {users}โ€ฆ"
 goBack: "ะะฐะทะฐะด"
 info: "ะ†ะฝั„ะพั€ะผะฐั†ั–ั"
 user: "ะšะพั€ะธัั‚ัƒะฒะฐั‡ั–"
@@ -687,6 +737,8 @@ hashtags: "ะฅะตัˆั‚ะตา‘"
 hide: "ะกั…ะพะฒะฐั‚ะธ"
 searchByGoogle: "ะŸะพัˆัƒะบ"
 indefinitely: "ะั–ะบะพะปะธ"
+_ffVisibility:
+  public: "ะžะฟัƒะฑะปั–ะบัƒะฒะฐั‚ะธ"
 _ad:
   back: "ะะฐะทะฐะด"
 _gallery:
@@ -867,7 +919,6 @@ _sfx:
   antenna: "ะŸั€ะธะนะพะผ ะฐะฝั‚ะตะฝะธ"
   channel: "ะŸะพะฒั–ะดะพะผะปะตะฝะฝั ะบะฐะฝะฐะปัƒ"
 _ago:
-  unknown: "ะะตะฒั–ะดะพะผะพ"
   future: "ะœะฐะนะฑัƒั‚ะฝั”"
   justNow: "ะฉะพะนะฝะพ"
   secondsAgo: "{n}ั ั‚ะพะผัƒ"
@@ -1377,6 +1428,9 @@ _notification:
     followRequestAccepted: "ะŸั€ะธะนะฝัั‚ั– ะฟั–ะดะฟะธัะบะธ"
     groupInvited: "ะ—ะฐะฟั€ะพัˆะตะฝะฝั ะดะพ ะณั€ัƒะฟ"
     app: "ะกะฟะพะฒั–ั‰ะตะฝะฝั ะฒั–ะด ะดะพะดะฐั‚ะบั–ะฒ"
+  _actions:
+    reply: "ะ’ั–ะดะฟะพะฒั–ัั‚ะธ"
+    renote: "ะŸะพัˆะธั€ะธั‚ะธ"
 _deck:
   alwaysShowMainColumn: "ะ—ะฐะฒะถะดะธ ะฟะพะบะฐะทัƒะฒะฐั‚ะธ ะณะพะปะพะฒะฝัƒ ะบะพะปะพะฝะบัƒ"
   columnAlign: "ะ’ะธั€ั–ะฒะฝัั‚ะธ ัั‚ะพะฒะฟั†ั–"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 42f86b3359..9919e0a0a4 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1,35 +1,1093 @@
 ---
 _lang_: "Tiแบฟng Viแป‡t"
 headlineMisskey: "Mแบกng xรฃ hแป™i liรชn hแปฃp"
+introMisskey: "Xin chร o! Misskey lร  mแป™t nแปn tแบฃng tiแปƒu blog phi tแบญp trung mรฃ nguแป“n mแปŸ.\nViแบฟt \"tรบt\" ฤ‘แปƒ chia sแบป nhแปฏng suy nghฤฉ cแปงa bแบกn ๐Ÿ“ก\nBแบฑng \"biแปƒu cแบฃm\", bแบกn cรณ thแปƒ bร y tแป nhanh chรณng cแบฃm xรบc cแปงa bแบกn vแป›i cรกc tรบt ๐Ÿ‘\nHรฃy khรกm phรก mแป™t thแบฟ giแป›i mแป›i! ๐Ÿš€"
 monthAndDay: "{day} thรกng {month}"
 search: "Tรฌm kiแบฟm"
 notifications: "Thรดng bรกo"
 username: "Tรชn ngฦฐแปi dรนng"
 password: "Mแบญt khแบฉu"
 forgotPassword: "Quรชn mแบญt khแบฉu"
+fetchingAsApObject: "ฤang nแบกp dแปฏ liแป‡u tแปซ Fediverse..."
 ok: "ฤแป“ng รฝ"
+gotIt: "ฤรฃ hiแปƒu!"
+cancel: "Hแปงy"
+enterUsername: "Nhแบญp tรชn ngฦฐแปi dรนng"
 renotedBy: "Chia sแบป bแปŸi {user}"
+noNotes: "Chฦฐa cรณ tรบt nร o."
+noNotifications: "Khรดng cรณ thรดng bรกo"
+instance: "Mรกy chแปง"
+settings: "Cร i ฤ‘แบทt"
+basicSettings: "Thiแบฟt lแบญp chung"
+otherSettings: "Thiแบฟt lแบญp khรกc"
+openInWindow: "MแปŸ trong cแปญa sแป• mแป›i"
+profile: "Trang cรก nhรขn"
+timeline: "Bแบฃng tin"
+noAccountDescription: "Ngฦฐแปi nร y chฦฐa viแบฟt mรด tแบฃ."
+login: "ฤฤƒng nhแบญp"
+loggingIn: "ฤang ฤ‘ฤƒng nhแบญp..."
+logout: "ฤฤƒng xuแบฅt"
+signup: "ฤฤƒng kรฝ"
+uploading: "ฤang tแบฃi lรชnโ€ฆ"
+save: "Lฦฐu"
+users: "Ngฦฐแปi dรนng"
+addUser: "Thรชm ngฦฐแปi dรนng"
+favorite: "Thรชm vร o yรชu thรญch"
+favorites: "Lฦฐแปฃt thรญch"
+unfavorite: "Bแป thรญch"
+favorited: "ฤรฃ thรชm vร o yรชu thรญch."
+alreadyFavorited: "ฤรฃ thรชm vร o yรชu thรญch rแป“i."
+cantFavorite: "Khรดng thแปƒ thรชm vร o yรชu thรญch."
+pin: "Ghim"
+unpin: "Bแป ghim"
+copyContent: "Chรฉp nแป™i dung"
+copyLink: "Chรฉp liรชn kแบฟt"
+delete: "Xรณa"
+deleteAndEdit: "Sแปญa"
+deleteAndEditConfirm: "Bแบกn cรณ chแบฏc muแป‘n sแปญa tรบt nร y? Nhแปฏng biแปƒu cแบฃm, lฦฐแปฃt trแบฃ lแปi vร  ฤ‘ฤƒng lแบกi sแบฝ bแป‹ mแบฅt."
+addToList: "Thรชm vร o danh sรกch"
+sendMessage: "Gแปญi tin nhแบฏn"
+copyUsername: "Chรฉp tรชn ngฦฐแปi dรนng"
+searchUser: "Tรฌm kiแบฟm ngฦฐแปi dรนng"
+reply: "Trแบฃ lแปi"
+loadMore: "Tแบฃi thรชm"
+showMore: "Xem thรชm"
+youGotNewFollower: "ฤ‘รฃ theo dรตi bแบกn"
+receiveFollowRequest: "ฤรฃ yรชu cแบงu theo dรตi"
+followRequestAccepted: "ฤรฃ chแบฅp nhแบญn yรชu cแบงu theo dรตi"
+mention: "Nhแบฏc ฤ‘แบฟn"
+mentions: "Lฦฐแปฃt nhแบฏc"
+directNotes: "Nhแบฏn riรชng"
+importAndExport: "Nhแบญp vร  xuแบฅt dแปฏ liแป‡u"
+import: "Nhแบญp dแปฏ liแป‡u"
+export: "Xuแบฅt dแปฏ liแป‡u"
+files: "Tแบญp tin"
+download: "Tแบฃi xuแป‘ng"
+driveFileDeleteConfirm: "Bแบกn cรณ chแบฏc muแป‘n xรณa tแบญp tin \"{name}\"? Tรบt liรชn quan cลฉng sแบฝ bแป‹ xรณa theo."
+unfollowConfirm: "Bแบกn cรณ chแบฏc muแป‘n ngฦฐng theo dรตi {name}?"
+exportRequested: "ฤang chuแบฉn bแป‹ xuแบฅt tแบญp tin. Quรก trรฌnh nร y cรณ thแปƒ mแบฅt รญt phรบt. Nรณ sแบฝ ฤ‘ฦฐแปฃc tแปฑ ฤ‘แป™ng thรชm vร o Drive sau khi hoร n thร nh."
+importRequested: "Bแบกn vแปซa yรชu cแบงu nhแบญp dแปฏ liแป‡u. Quรก trรฌnh nร y cรณ thแปƒ mแบฅt รญt phรบt."
+lists: "Danh sรกch"
+noLists: "Bแบกn chฦฐa cรณ danh sรกch nร o"
+note: "Tรบt"
+notes: "Tรบt"
+following: "ฤang theo dรตi"
+followers: "Ngฦฐแปi theo dรตi"
+followsYou: "Theo dรตi bแบกn"
+createList: "Tแบกo danh sรกch"
+manageLists: "Quแบฃn lรฝ danh sรกch"
+error: "Lแป—i"
+somethingHappened: "Xแบฃy ra lแป—i"
+retry: "Thแปญ lแบกi"
+pageLoadError: "Xแบฃy ra lแป—i khi tแบฃi trang."
+pageLoadErrorDescription: "Cรณ thแปƒ lร  do bแป™ nhแป› ฤ‘แป‡m cแปงa trรฌnh duyแป‡t. Hรฃy thแปญ xรณa bแป™ nhแป› ฤ‘แป‡m vร  thแปญ lแบกi sau รญt phรบt."
+serverIsDead: "Mรกy chแปง khรดng phแบฃn hแป“i. Vui lรฒng thแปญ lแบกi sau giรขy lรกt."
+youShouldUpgradeClient: "ฤแปƒ xem trang nร y, hรฃy lร m tฦฐฦกi ฤ‘แปƒ cแบญp nhแบญt แปฉng dแปฅng."
+enterListName: "ฤแบทt tรชn cho danh sรกch"
+privacy: "Bแบฃo mแบญt"
+makeFollowManuallyApprove: "Yรชu cแบงu theo dรตi cแบงn ฤ‘ฦฐแปฃc duyแป‡t"
+defaultNoteVisibility: "Kiแปƒu tรบt mแบทc ฤ‘แป‹nh"
+follow: "ฤang theo dรตi"
+followRequest: "Gแปญi yรชu cแบงu theo dรตi"
+followRequests: "Yรชu cแบงu theo dรตi"
+unfollow: "Ngฦฐng theo dรตi"
+followRequestPending: "Yรชu cแบงu theo dรตi ฤ‘ang chแป"
+enterEmoji: "Chรจn emoji"
+renote: "ฤฤƒng lแบกi"
+unrenote: "Hแปงy ฤ‘ฤƒng lแบกi"
+renoted: "ฤรฃ ฤ‘ฤƒng lแบกi."
+cantRenote: "Khรดng thแปƒ ฤ‘ฤƒng lแบกi tรบt nร y."
+cantReRenote: "Khรดng thแปƒ ฤ‘ฤƒng lแบกi mแป™t tรบt ฤ‘ฤƒng lแบกi."
+quote: "Trรญch dแบซn"
+pinnedNote: "Tรบt ghim"
+pinned: "Ghim"
+you: "Bแบกn"
+clickToShow: "Nhแบฅn ฤ‘แปƒ xem"
+sensitive: "Nhแบกy cแบฃm"
+add: "Thรชm"
+reaction: "Biแปƒu cแบฃm"
+reactionSetting: "Chแปn nhแปฏng biแปƒu cแบฃm hiแปƒn thแป‹"
+reactionSettingDescription2: "Kรฉo ฤ‘แปƒ sแบฏp xแบฟp, nhแบฅn ฤ‘แปƒ xรณa, nhแบฅn \"+\" ฤ‘แปƒ thรชm."
+rememberNoteVisibility: "Lฦฐu kiแปƒu tรบt mแบทc ฤ‘แป‹nh"
+attachCancel: "Gแปก tแบญp tin ฤ‘รญnh kรจm"
+markAsSensitive: "ฤรกnh dแบฅu lร  nhแบกy cแบฃm"
+unmarkAsSensitive: "Bแป ฤ‘รกnh dแบฅu nhแบกy cแบฃm"
+enterFileName: "Nhแบญp tรชn tแบญp tin"
+mute: "แบจn"
+unmute: "Bแป แบฉn"
+block: "Chแบทn"
+unblock: "Bแป chแบทn"
+suspend: "Vรด hiแป‡u hรณa"
+unsuspend: "Bแป vรด hiแป‡u hรณa"
+blockConfirm: "Bแบกn cรณ chแบฏc muแป‘n chแบทn ngฦฐแปi nร y?"
+unblockConfirm: "Bแบกn cรณ chแบฏc muแป‘n bแป chแบทn ngฦฐแปi nร y?"
+suspendConfirm: "Bแบกn cรณ chแบฏc muแป‘n vรด hiแป‡u hรณa ngฦฐแปi nร y?"
+unsuspendConfirm: "Bแบกn cรณ chแบฏc muแป‘n bแป vรด hiแป‡u hรณa ngฦฐแปi nร y?"
+selectList: "Chแปn danh sรกch"
+selectAntenna: "Chแปn mแป™t antenna"
+selectWidget: "Chแปn tiแป‡n รญch"
+editWidgets: "Sแปญa tiแป‡n รญch"
+editWidgetsExit: "Xong"
+customEmojis: "Tรนy chแป‰nh emoji"
+emoji: "Emoji"
+emojis: "Emoji"
+emojiName: "Tรชn emoji"
+emojiUrl: "URL Emoji"
+addEmoji: "Thรชm emoji"
+settingGuide: "Cร i ฤ‘แบทt ฤ‘แป xuแบฅt"
+cacheRemoteFiles: "Tแบญp tin cache tแปซ xa"
+cacheRemoteFilesDescription: "Khi tรนy chแปn nร y bแป‹ tแบฏt, cรกc tแบญp tin tแปซ xa sแบฝ ฤ‘ฦฐแปฃc tแบฃi trแปฑc tiแบฟp tแปซ mรกy chแปง khรกc. ฤiแปu nร y sแบฝ giรบp giแบฃm dung lฦฐแปฃng lฦฐu trแปฏ nhฦฐng lแบกi tฤƒng lฦฐu lฦฐแปฃng truy cแบญp, vรฌ hรฌnh thu nhแป sแบฝ khรดng ฤ‘ฦฐแปฃc tแบกo."
 flagAsBot: "ฤรกnh dแบฅu ฤ‘รขy lร  tร i khoแบฃn bot"
+flagAsBotDescription: "Bแบญt tรนy chแปn nร y nแบฟu tร i khoแบฃn nร y ฤ‘ฦฐแปฃc kiแปƒm soรกt bแปŸi mแป™t chฦฐฦกng trรฌnh. Nแบฟu ฤ‘ฦฐแปฃc bแบญt, nรณ sแบฝ ฤ‘ฦฐแปฃc ฤ‘รกnh dแบฅu ฤ‘แปƒ cรกc nhร  phรกt triแปƒn khรกc ngฤƒn chแบทn chuแป—i tฦฐฦกng tรกc vรด tแบญn vแป›i cรกc bot khรกc vร  ฤ‘iแปu chแป‰nh hแป‡ thแป‘ng nแป™i bแป™ cแปงa Misskey ฤ‘แปƒ coi tร i khoแบฃn nร y nhฦฐ mแป™t bot."
+flagAsCat: "Tร i khoแบฃn nร y lร  mรจo"
+flagAsCatDescription: "Bแบญt tรนy chแปn nร y ฤ‘แปƒ ฤ‘รกnh dแบฅu tร i khoแบฃn lร  mแป™t con mรจo."
+flagShowTimelineReplies: "Hiแป‡n lฦฐแปฃt trแบฃ lแปi trong bแบฃng tin"
+flagShowTimelineRepliesDescription: "Hiแป‡n lฦฐแปฃt trแบฃ lแปi cแปงa ngฦฐแปi bแบกn theo dรตi trรชn tรบt cแปงa nhแปฏng ngฦฐแปi khรกc."
+autoAcceptFollowed: "Tแปฑ ฤ‘แป™ng phรช duyแป‡t theo dรตi tแปซ nhแปฏng ngฦฐแปi mร  bแบกn ฤ‘ang theo dรตi"
+addAccount: "Thรชm tร i khoแบฃn"
+loginFailed: "ฤฤƒng nhแบญp khรดng thร nh cรดng"
+showOnRemote: "Truy cแบญp trang cแปงa ngฦฐแปi nร y"
+general: "Tแป•ng quan"
+wallpaper: "แบขnh bรฌa"
+setWallpaper: "ฤแบทt แบฃnh bรฌa"
+removeWallpaper: "Xรณa แบฃnh bรฌa"
 searchWith: "Tรฌm kiแบฟm: {q}"
+youHaveNoLists: "Bแบกn chฦฐa cรณ danh sรกch nร o"
 followConfirm: "Bแบกn cรณ chแบฏc muแป‘n theo dรตi {name}๏ผŸ"
+proxyAccount: "Tร i khoแบฃn proxy"
+proxyAccountDescription: "Tร i khoแบฃn proxy lร  tร i khoแบฃn hoแบกt ฤ‘แป™ng nhฦฐ mแป™t ngฦฐแปi theo dรตi tแปซ xa cho ngฦฐแปi dรนng trong nhแปฏng ฤ‘iแปu kiแป‡n nhแบฅt ฤ‘แป‹nh. Vรญ dแปฅ: khi ngฦฐแปi dรนng thรชm ngฦฐแปi dรนng tแปซ xa vร o danh sรกch, hoแบกt ฤ‘แป™ng cแปงa ngฦฐแปi dรนng tแปซ xa sแบฝ khรดng ฤ‘ฦฐแปฃc chuyแปƒn ฤ‘แบฟn phiรชn bแบฃn nแบฟu khรดng cรณ ngฦฐแปi dรนng cแปฅc bแป™ nร o theo dรตi ngฦฐแปi dรนng ฤ‘รณ, vรฌ vแบญy tร i khoแบฃn proxy sแบฝ theo dรตi."
+host: "Host"
+selectUser: "Chแปn ngฦฐแปi dรนng"
+recipient: "Ngฦฐแปi nhแบญn"
+annotation: "Bรฌnh luแบญn"
+federation: "Liรชn hแปฃp"
+instances: "Mรกy chแปง"
+registeredAt: "ฤฤƒng kรฝ vร o"
+latestRequestSentAt: "Yรชu cแบงu cuแป‘i gแปญi lรบc"
+latestRequestReceivedAt: "Yรชu cแบงu cuแป‘i nhแบญn lรบc"
+latestStatus: "Trแบกng thรกi cuแป‘i cรนng"
+storageUsage: "Dung lฦฐแปฃng lฦฐu trแปฏ"
+charts: "ฤแป“ thแป‹"
+perHour: "Mแป—i Giแป"
+perDay: "Mแป—i Ngร y"
+stopActivityDelivery: "Ngฦฐng gแปญi hoแบกt ฤ‘แป™ng"
+blockThisInstance: "Chแบทn mรกy chแปง nร y"
+operations: "Vแบญn hร nh"
+software: "Phแบงn mแปm"
+version: "Phiรชn bแบฃn"
+metadata: "Metadata"
+withNFiles: "{n} tแบญp tin"
+monitor: "Giรกm sรกt"
+jobQueue: "Cรดng viแป‡c chแป xแปญ lรฝ"
 cpuAndMemory: "CPU vร  Dung lฦฐแปฃng"
+network: "Mแบกng"
+disk: "แป” ฤ‘ฤฉa"
+instanceInfo: "Thรดng tin mรกy chแปง"
+statistics: "Thแป‘ng kรช"
+clearQueue: "Xรณa hร ng ฤ‘แปฃi"
+clearQueueConfirmTitle: "Bแบกn cรณ chแบฏc muแป‘n xรณa hร ng ฤ‘แปฃi?"
+clearQueueConfirmText: "Mแปi tรบt chฦฐa ฤ‘ฦฐแปฃc gแปญi cรฒn lแบกi trong hร ng ฤ‘แปฃi sแบฝ khรดng ฤ‘ฦฐแปฃc liรชn hแปฃp. Thรดng thฦฐแปng thao tรกc nร y khรดng cแบงn thiแบฟt."
+clearCachedFiles: "Xรณa bแป™ nhแป› ฤ‘แป‡m"
+clearCachedFilesConfirm: "Bแบกn cรณ chแบฏc muแป‘n xรณa sแบกch bแป™ nhแป› ฤ‘แป‡m?"
+blockedInstances: "Mรกy chแปง ฤ‘รฃ chแบทn"
+blockedInstancesDescription: "Danh sรกch nhแปฏng mรกy chแปง bแบกn muแป‘n chแบทn. Chรบng sแบฝ khรดng thแปƒ giao tiแบฟp vแป›i mรกy chแปงy nร y nแปฏa."
+muteAndBlock: "แบจn vร  Chแบทn"
+mutedUsers: "Ngฦฐแปi ฤ‘รฃ แบฉn"
+blockedUsers: "Ngฦฐแปi ฤ‘รฃ chแบทn"
+noUsers: "Chฦฐa cรณ ai"
+editProfile: "Sแปญa hแป“ sฦก"
+noteDeleteConfirm: "Bแบกn cรณ chแบฏc muแป‘n xรณa tรบt nร y?"
+pinLimitExceeded: "Bแบกn ฤ‘รฃ ฤ‘แบกt giแป›i hแบกn sแป‘ lฦฐแปฃng tรบt cรณ thแปƒ ghim"
+intro: "ฤรฃ cร i ฤ‘แบทt Misskey! Xin hรฃy tแบกo tร i khoแบฃn admin."
+done: "Xong"
+processing: "ฤang xแปญ lรฝ"
+preview: "Xem trฦฐแป›c"
+default: "Mแบทc ฤ‘แป‹nh"
+noCustomEmojis: "Khรดng cรณ emoji"
+noJobs: "Khรดng cรณ cรดng viแป‡c"
+federating: "ฤang liรชn hแปฃp"
+blocked: "ฤรฃ chแบทn"
+suspended: "ฤรฃ vรด hiแป‡u hรณa"
+all: "Tแบฅt cแบฃ"
+subscribing: "ฤang ฤ‘ฤƒng kรฝ"
+publishing: "ฤang ฤ‘ฤƒng"
+notResponding: "Khรดng cรณ phแบฃn hแป“i"
+instanceFollowing: "ฤang theo dรตi mรกy chแปง"
+instanceFollowers: "Ngฦฐแปi theo dรตi cแปงa mรกy chแปง"
+instanceUsers: "Ngฦฐแปi dรนng trรชn mรกy chแปง nร y"
+changePassword: "ฤแป•i mแบญt khแบฉu"
+security: "Bแบฃo mแบญt"
+retypedNotMatch: "Mแบญt khแบฉu khรดng trรนng khแป›p."
+currentPassword: "Mแบญt khแบฉu hiแป‡n tแบกi"
+newPassword: "Mแบญt khแบฉu mแป›i"
+newPasswordRetype: "Nhแบญp lแบกi mแบญt khแบฉu mแป›i"
+attachFile: "ฤรญnh kรจm tแบญp tin"
+more: "Thรชm nแปฏa!"
+featured: "Nแป•i bแบญt"
+usernameOrUserId: "Tรชn ngฦฐแปi dรนng hoแบทc ID"
+noSuchUser: "Khรดng tรฌm thแบฅy ngฦฐแปi dรนng"
+lookup: "Tรฌm kiแบฟm"
+announcements: "Thรดng bรกo"
+imageUrl: "URL แบฃnh"
+remove: "Xรณa"
+removed: "ฤรฃ xรณa"
+removeAreYouSure: "Bแบกn cรณ chแบฏc muแป‘n gแปก \"{x}\"?"
+deleteAreYouSure: "Bแบกn cรณ chแบฏc muแป‘n xรณa \"{x}\"?"
+resetAreYouSure: "Bแบกn cรณ chแบฏc muแป‘n ฤ‘แบทt lแบกi?"
+saved: "ฤรฃ lฦฐu"
+messaging: "Trรฒ chuyแป‡n"
+upload: "Tแบฃi lรชn"
+keepOriginalUploading: "Giแปฏ hรฌnh แบฃnh gแป‘c"
+keepOriginalUploadingDescription: "Giแปฏ nguyรชn nhฦฐ hรฌnh แบฃnh ฤ‘ฦฐแปฃc tแบฃi lรชn ban ฤ‘แบงu. Nแบฟu tแบฏt, mแป™t phiรชn bแบฃn ฤ‘แปƒ hiแปƒn thแป‹ trรชn web sแบฝ ฤ‘ฦฐแปฃc tแบกo khi tแบฃi lรชn."
+fromDrive: "Tแปซ แป• ฤ‘ฤฉa"
+fromUrl: "Tแปซ URL"
+uploadFromUrl: "Tแบฃi lรชn bแบฑng mแป™t URL"
+uploadFromUrlDescription: "URL cแปงa tแบญp tin bแบกn muแป‘n tแบฃi lรชn"
+uploadFromUrlRequested: "ฤรฃ yรชu cแบงu tแบฃi lรชn"
+uploadFromUrlMayTakeTime: "Sแบฝ mแบฅt mแป™t khoแบฃng thแปi gian ฤ‘แปƒ tแบฃi lรชn xong."
+explore: "Khรกm phรก"
+messageRead: "ฤรฃ ฤ‘แปc"
+noMoreHistory: "Khรดng cรฒn gรฌ ฤ‘แปƒ ฤ‘แปc"
+startMessaging: "Bแบฏt ฤ‘แบงu trรฒ chuyแป‡n"
+nUsersRead: "ฤ‘แปc bแปŸi {n}"
+agreeTo: "Tรดi ฤ‘แป“ng รฝ {0}"
+tos: "ฤiแปu khoแบฃn dแป‹ch vแปฅ"
+start: "Bแบฏt ฤ‘แบงu"
+home: "Trang chรญnh"
+remoteUserCaution: "Vรฌ ngฦฐแปi dรนng nร y แปŸ mรกy chแปง khรกc, thรดng tin hiแปƒn thแป‹ cรณ thแปƒ khรดng ฤ‘แบงy ฤ‘แปง."
+activity: "Hoแบกt ฤ‘แป™ng"
+images: "Hรฌnh แบฃnh"
+birthday: "Sinh nhรขฬฃt"
+yearsOld: "{age} tuแป•i"
+registeredDate: "Tham gia"
+location: "ฤแบฟn tแปซ"
+theme: "Chแปง ฤ‘แป"
+themeForLightMode: "Chแปง ฤ‘แป dรนng trong trong chแบฟ ฤ‘แป™ Sรกng"
+themeForDarkMode: "Chแปง ฤ‘แป dรนng trong chแบฟ ฤ‘แป™ Tแป‘i"
+light: "Sรกng"
+dark: "Tแป‘i"
+lightThemes: "Nhแปฏng chแปง ฤ‘แป sรกng"
+darkThemes: "Nhแปฏng chแปง ฤ‘แป tแป‘i"
+syncDeviceDarkMode: "ฤแป“ng bแป™ vแป›i thiแบฟt bแป‹"
+drive: "แป” ฤ‘ฤฉa"
+fileName: "Tรชn tแบญp tin"
+selectFile: "Chแปn tแบญp tin"
+selectFiles: "Chแปn nhiแปu tแบญp tin"
+selectFolder: "Chแปn thฦฐ mแปฅc"
+selectFolders: "Chแปn nhiแปu thฦฐ mแปฅc"
+renameFile: "ฤแป•i tรชn tแบญp tin"
+folderName: "Tรชn thฦฐ mแปฅc"
+createFolder: "Tแบกo thฦฐ mแปฅc"
+renameFolder: "ฤแป•i tรชn thฦฐ mแปฅc"
+deleteFolder: "Xรณa thฦฐ mแปฅc"
+addFile: "Thรชm tแบญp tin"
+emptyDrive: "แป” ฤ‘ฤฉa cแปงa bแบกn trแป‘ng trฦกn"
+emptyFolder: "Thฦฐ mแปฅc trแป‘ng"
+unableToDelete: "Khรดng thแปƒ xรณa"
+inputNewFileName: "Nhแบญp tรชn mแป›i cho tแบญp tin"
+inputNewDescription: "Nhแบญp mรด tแบฃ mแป›i"
+inputNewFolderName: "Nhแบญp tรชn mแป›i cho thฦฐ mแปฅc"
+circularReferenceFolder: "Thฦฐ mแปฅc ฤ‘รญch lร  mแป™t thฦฐ mแปฅc con cแปงa thฦฐ mแปฅc bแบกn muแป‘n di chuyแปƒn."
+hasChildFilesOrFolders: "Khรดng thแปƒ xรณa cho ฤ‘แบฟn khi khรดng cรฒn gรฌ trong thฦฐ mแปฅc."
+copyUrl: "Sao chรฉp URL"
+rename: "ฤแป•i tรชn"
+avatar: "แบขnh ฤ‘แบกi diแป‡n"
+banner: "แบขnh bรฌa"
+nsfw: "Nhแบกy cแบฃm"
+whenServerDisconnected: "Khi mแบฅt kแบฟt nแป‘i tแป›i mรกy chแปง"
+disconnectedFromServer: "Mแบฅt kแบฟt nแป‘i tแป›i mรกy chแปง"
+reload: "Tแบฃi lแบกi"
+doNothing: "Bแป qua"
+reloadConfirm: "Bแบกn cรณ muแป‘n thแปญ tแบฃi lแบกi bแบฃng tin?"
+watch: "Xem"
+unwatch: "Ngแปซng xem"
+accept: "ฤแป“ng รฝ"
+reject: "Tแปซ chแป‘i"
+normal: "Bรฌnh thฦฐแปng"
+instanceName: "Tรชn mรกy chแปง"
+instanceDescription: "Mรด tแบฃ mรกy chแปง"
+maintainerName: "ฤแป™i ngลฉ vแบญn hร nh"
+maintainerEmail: "Email ฤ‘แป™i ngลฉ"
+tosUrl: "URL ฤiแปu khoแบฃn dแป‹ch vแปฅ"
+thisYear: "Nฤƒm"
+thisMonth: "Thรกng"
+today: "Hรดm nay"
 dayX: "{day}"
+monthX: "{month}"
 yearX: "{year}"
+pages: "Trang"
+integration: "Tฦฐฦกng tรกc"
+connectService: "Kแบฟt nแป‘i"
+disconnectService: "Ngแบฏt kแบฟt nแป‘i"
+enableLocalTimeline: "Bแบญt bแบฃng tin mรกy chแปง"
+enableGlobalTimeline: "Bแบญt bแบฃng tin liรชn hแปฃp"
+disablingTimelinesInfo: "Quแบฃn trแป‹ viรชn vร  Kiแปƒm duyแป‡t viรชn luรดn cรณ quyแปn truy cแบญp mแปi bแบฃng tin, kแปƒ cแบฃ khi chรบng khรดng ฤ‘ฦฐแปฃc bแบญt."
+registration: "ฤฤƒng kรฝ"
+enableRegistration: "Cho phรฉp ฤ‘ฤƒng kรฝ mแป›i"
+invite: "Mแปi"
+driveCapacityPerLocalAccount: "Dung lฦฐแปฃng แป• ฤ‘ฤฉa tแป‘i ฤ‘a cho mแป—i ngฦฐแปi dรนng"
+driveCapacityPerRemoteAccount: "Dung lฦฐแปฃng แป• ฤ‘ฤฉa tแป‘i ฤ‘a cho mแป—i ngฦฐแปi dรนng tแปซ xa"
+inMb: "Tรญnh bแบฑng MB"
+iconUrl: "URL Icon"
+bannerUrl: "URL แบขnh bรฌa"
+backgroundImageUrl: "URL แบขnh nแปn"
+basicInfo: "Thรดng tin cฦก bแบฃn"
+pinnedUsers: "Nhแปฏng ngฦฐแปi thรบ vแป‹"
+pinnedUsersDescription: "Liแป‡t kรช mแป—i hร ng mแป™t tรชn ngฦฐแปi dรนng xuแป‘ng dรฒng ฤ‘แปƒ ghim trรชn tab \"Khรกm phรก\"."
+pinnedPages: "Trang ฤ‘รฃ ghim"
+pinnedPagesDescription: "Liแป‡t kรช cรกc trang thรบ vแป‹ ฤ‘แปƒ ghim trรชn mรกy chแปง."
+pinnedClipId: "ID cแปงa clip muแป‘n ghim"
+pinnedNotes: "Tรบt ghim"
+hcaptcha: "hCaptcha"
+enableHcaptcha: "Bแบญt hCaptcha"
+hcaptchaSiteKey: "Khรณa cแปงa trang"
+hcaptchaSecretKey: "Khรณa bรญ mแบญt"
+recaptcha: "reCAPTCHA"
+enableRecaptcha: "Bแบญt reCAPTCHA"
+recaptchaSiteKey: "Khรณa cแปงa trang"
+recaptchaSecretKey: "Khรณa bรญ mแบญt"
+avoidMultiCaptchaConfirm: "Dรนng nhiแปu hแป‡ thแป‘ng Captcha cรณ thแปƒ gรขy nhiแป…u giแปฏa chรบng. Bแบกn cรณ muแป‘n tแบฏt cรกc hแป‡ thแป‘ng Captcha khรกc hiแป‡n ฤ‘ang hoแบกt ฤ‘แป™ng khรดng? Nแบฟu bแบกn muแป‘n chรบng tiแบฟp tแปฅc ฤ‘ฦฐแปฃc bแบญt, hรฃy nhแบฅn hแปงy."
+antennas: "Trแบกm phรกt sรณng"
+manageAntennas: "Quแบฃn lรฝ trแบกm phรกt sรณng"
+name: "Tรชn"
+antennaSource: "Nguแป“n trแบกm phรกt sรณng"
+antennaKeywords: "Tแปซ khรณa ฤ‘แปƒ nghe"
+antennaExcludeKeywords: "Tแปซ khรณa ฤ‘แปƒ lแปc ra"
+antennaKeywordsDescription: "Phรขn cรกch bแบฑng dแบฅu cรกch cho ฤ‘iแปu kiแป‡n AND hoแบทc bแบฑng xuแป‘ng dรฒng cho ฤ‘iแปu kiแป‡n OR."
+notifyAntenna: "Thรดng bรกo cรณ tรบt mแป›i"
+withFileAntenna: "Chแป‰ nhแปฏng tรบt cรณ media"
+enableServiceworker: "Bแบญt ServiceWorker"
+antennaUsersDescription: "Liแป‡t kรช mแป—i hร ng mแป™t tรชn ngฦฐแปi dรนng"
+caseSensitive: "Trฦฐแปng hแปฃp nhแบกy cแบฃm"
+withReplies: "Bao gแป“m lฦฐแปฃt trแบฃ lแปi"
+connectedTo: "Nhแปฏng tร i khoแบฃn sau ฤ‘รฃ kแบฟt nแป‘i"
+notesAndReplies: "Tรบt kรจm trแบฃ lแปi"
+withFiles: "Media"
+silence: "แบจn"
+silenceConfirm: "Bแบกn cรณ chแบฏc muแป‘n แบฉn ngฦฐแปi nร y?"
+unsilence: "Bแป แบฉn"
+unsilenceConfirm: "Bแบกn cรณ chแบฏc muแป‘n bแป แบฉn ngฦฐแปi nร y?"
+popularUsers: "Nhแปฏng ngฦฐแปi nแป•i tiแบฟng"
+recentlyUpdatedUsers: "Hoแบกt ฤ‘แป™ng gแบงn ฤ‘รขy"
+recentlyRegisteredUsers: "Mแป›i tham gia"
+recentlyDiscoveredUsers: "Mแป›i khรกm phรก"
+exploreUsersCount: "Cรณ {count} ngฦฐแปi"
+exploreFediverse: "Khรกm phรก Fediverse"
+popularTags: "Hashtag thรดng dแปฅng"
+userList: "Danh sรกch"
+about: "Giแป›i thiแป‡u"
 aboutMisskey: "Vแป Misskey"
+administrator: "Quแบฃn trแป‹ viรชn"
+token: "Token"
+twoStepAuthentication: "Xรกc minh 2 bฦฐแป›c"
+moderator: "Kiแปƒm duyแป‡t viรชn"
+nUsersMentioned: "Dรนng bแปŸi {n} ngฦฐแปi"
+securityKey: "Khรณa bแบฃo mแบญt"
+securityKeyName: "Tรชn khoรก"
+registerSecurityKey: "ฤฤƒng kรฝ khรณa bแบฃo mแบญt"
+lastUsed: "Dรนng lแบงn cuแป‘i"
+unregister: "Hแปงy ฤ‘ฤƒng kรฝ"
+passwordLessLogin: "ฤฤƒng nhแบญp khรดng mแบญt khแบฉu"
+resetPassword: "ฤแบทt lแบกi mแบญt khแบฉu"
+newPasswordIs: "Mแบญt khแบฉu mแป›i lร  \"{password}\""
+reduceUiAnimation: "Giแบฃm chuyแปƒn ฤ‘แป™ng UI"
+share: "Chia sแบป"
+notFound: "Khรดng tรฌm thแบฅy"
+notFoundDescription: "Khรดng tรฌm thแบฅy trang nร o tฦฐฦกng แปฉng vแป›i URL nร y."
+uploadFolder: "Thฦฐ mแปฅc tแบฃi lรชn mแบทc ฤ‘แป‹nh"
+cacheClear: "Xรณa bแป™ nhแป› ฤ‘แป‡m"
+markAsReadAllNotifications: "ฤรกnh dแบฅu tแบฅt cแบฃ cรกc thรดng bรกo lร  ฤ‘รฃ ฤ‘แปc"
+markAsReadAllUnreadNotes: "ฤรกnh dแบฅu tแบฅt cแบฃ cรกc tรบt lร  ฤ‘รฃ ฤ‘แปc"
+markAsReadAllTalkMessages: "ฤรกnh dแบฅu tแบฅt cแบฃ cรกc tin nhแบฏn lร  ฤ‘รฃ ฤ‘แปc"
+help: "Trแปฃ giรบp"
+inputMessageHere: "Nhแบญp nแป™i dung tin nhแบฏn"
+close: "ฤรณng"
+group: "Nhรณm"
+groups: "Cรกc nhรณm"
+createGroup: "Tแบกo nhรณm"
+ownedGroups: "Nhรณm tรดi quแบฃn lรฝ"
+joinedGroups: "Nhรณm tรดi tham gia"
+invites: "Mแปi"
+groupName: "Tรชn nhรณm"
+members: "Thร nh viรชn"
+transfer: "Chuyแปƒn giao"
+messagingWithUser: "Nhแบฏn riรชng"
+messagingWithGroup: "Chat nhรณm"
+title: "Tแปฑa ฤ‘แป"
+text: "Nแป™i dung"
+enable: "Bแบญt"
+next: "Kแบฟ tiแบฟp"
+retype: "Nhแบญp lแบกi"
+noteOf: "Tรบt cแปงa {user}"
+inviteToGroup: "Mแปi vร o nhรณm"
+quoteAttached: "Trรญch dแบซn"
+quoteQuestion: "Trรญch dแบซn lแบกi?"
+noMessagesYet: "Chฦฐa cรณ tin nhแบฏn"
+newMessageExists: "Bแบกn cรณ tin nhแบฏn mแป›i"
+onlyOneFileCanBeAttached: "Bแบกn chแป‰ cรณ thแปƒ ฤ‘รญnh kรจm mแป™t tแบญp tin"
+signinRequired: "Vui lรฒng ฤ‘ฤƒng nhแบญp"
+invitations: "Mแปi"
+invitationCode: "Mรฃ mแปi"
+checking: "ฤang kiแปƒm tra..."
+available: "Khแบฃ dแปฅng"
+unavailable: "Khรดng khแบฃ dแปฅng"
+usernameInvalidFormat: "Bแบกn cรณ thแปƒ dรนng viแบฟt hoa/viแบฟt thฦฐแปng, chแปฏ sแป‘, vร  dแบฅu gแบกch dฦฐแป›i."
+tooShort: "Quรก ngแบฏn"
+tooLong: "Quรก dร i"
+weakPassword: "Mแบญt khแบฉu yแบฟu"
+normalPassword: "Mแบญt khแบฉu tแบกm ฤ‘ฦฐแปฃc"
+strongPassword: "Mแบญt khแบฉu mแบกnh"
+passwordMatched: "Trรนng khแป›p"
+passwordNotMatched: "Khรดng trรนng khแป›p"
+signinWith: "ฤฤƒng nhแบญp bแบฑng {x}"
+signinFailed: "Khรดng thแปƒ ฤ‘ฤƒng nhแบญp. Vui lรฒng kiแปƒm tra tรชn ngฦฐแปi dรนng vร  mแบญt khแบฉu cแปงa bแบกn."
+tapSecurityKey: "Nhแบฅn mรฃ bแบฃo mแบญt cแปงa bแบกn"
+or: "Hoแบทc"
+language: "Ngรดn ngแปฏ"
+uiLanguage: "Ngรดn ngแปฏ giao diแป‡n"
+groupInvited: "Bแบกn ฤ‘รฃ ฤ‘ฦฐแปฃc mแปi tham gia nhรณm"
+aboutX: "Giแป›i thiแป‡u {x}"
+useOsNativeEmojis: "Dรนng emoji hแป‡ thแป‘ng"
+disableDrawer: "Khรดng dรนng menu thanh bรชn"
+youHaveNoGroups: "Khรดng cรณ nhรณm nร o"
+joinOrCreateGroup: "Tham gia hoแบทc tแบกo mแป™t nhรณm mแป›i."
+noHistory: "Khรดng cรณ dแปฏ liแป‡u"
+signinHistory: "Lแป‹ch sแปญ ฤ‘ฤƒng nhแบญp"
+disableAnimatedMfm: "Tแบฏt MFM vแป›i chuyแปƒn ฤ‘แป™ng"
+doing: "ฤang xแปญ lรฝ..."
+category: "Phรขn loแบกi"
+tags: "Thแบป"
+docSource: "Nguแป“n tร i liแป‡u"
+createAccount: "Tแบกo tร i khoแบฃn"
+existingAccount: "Tร i khoแบฃn hiแป‡n cรณ"
+regenerate: "Tแบกo lแบกi"
+fontSize: "Cแปก chแปฏ"
+noFollowRequests: "Bแบกn khรดng cรณ yรชu cแบงu theo dรตi nร o"
+openImageInNewTab: "MแปŸ แบฃnh trong tab mแป›i"
+dashboard: "Trang chรญnh"
+local: "Mรกy chแปง nร y"
+remote: "Mรกy chแปง khรกc"
+total: "Tแป•ng cแป™ng"
+weekOverWeekChanges: "Thay ฤ‘แป•i tuแบงn rแป“i"
+dayOverDayChanges: "Thay ฤ‘แป•i hรดm qua"
+appearance: "Giao diแป‡n"
+clientSettings: "Cร i ฤ‘แบทt Client"
+accountSettings: "Cร i ฤ‘แบทt tร i khoแบฃn"
+promotion: "Quแบฃng cรกo"
+promote: "Quแบฃng cรกo"
+numberOfDays: "Sแป‘ ngร y"
+hideThisNote: "แบจn tรบt nร y"
+showFeaturedNotesInTimeline: "Hiแป‡n tรบt nแป•i bแบญt trong bแบฃng tin"
+objectStorage: "ฤแป‘i tฦฐแปฃng lฦฐu trแปฏ"
+useObjectStorage: "Dรนng ฤ‘แป‘i tฦฐแปฃng lฦฐu trแปฏ"
+objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrlDesc: "URL ฤ‘ฦฐแปฃc sแปญ dแปฅng lร m tham khแบฃo. Chแป‰ ฤ‘แป‹nh URL cแปงa CDN hoแบทc Proxy cแปงa bแบกn nแบฟu bแบกn ฤ‘ang sแปญ dแปฅng. Vแป›i S3 dรนng 'https://<bucket>.s3.amazonaws.com', cรฒn GCS hoแบทc dแป‹ch vแปฅ tฦฐฦกng tแปฑ dรนng 'https://storage.googleapis.com/<bucket>', etc."
+objectStorageBucket: "Bucket"
+objectStorageBucketDesc: "Nhแบญp tรชn bucket dรนng แปŸ nhร  cung cแบฅp cแปงa bแบกn."
+objectStoragePrefix: "Tiแปn tแป‘"
+objectStoragePrefixDesc: "Cรกc tแบญp tin sแบฝ ฤ‘ฦฐแปฃc lฦฐu trแปฏ trong cรกc thฦฐ mแปฅc cรณ tiแปn tแป‘ nร y."
+objectStorageEndpoint: "ฤแบงu cuแป‘i"
+objectStorageEndpointDesc: "ฤแปƒ trแป‘ng nแบฟu bแบกn ฤ‘ang dรนng AWS S3, nแบฟu khรดng thรฌ chแป‰ ฤ‘แป‹nh ฤ‘แบงu cuแป‘i lร  '<host>' hoแบทc '<host>:<port>', tรนy thuแป™c vร o nhร  cung cแบฅp dแป‹ch vแปฅ."
+objectStorageRegion: "Khu vแปฑc"
+objectStorageRegionDesc: "Nhแบญp mแป™t khu vแปฑc cแปฅ thแปƒ nhฦฐ 'xx-east-1'. Nแบฟu nhร  cung cแบฅp dแป‹ch vแปฅ cแปงa bแบกn khรดng phรขn biแป‡t giแปฏa cรกc khu vแปฑc, hรฃy ฤ‘แปƒ trแป‘ng hoแบทc nhแบญp 'us-east-1'."
+objectStorageUseSSL: "Dรนng SSL"
+objectStorageUseSSLDesc: "Tแบฏt nแบฟu bแบกn khรดng dรนng HTTPS ฤ‘แปƒ kแบฟt nแป‘i API"
+objectStorageUseProxy: "Kแบฟt nแป‘i thรดng qua Proxy"
+objectStorageUseProxyDesc: "Tแบฏt nแบฟu bแบกn khรดng dรนng Proxy ฤ‘แปƒ kแบฟt nแป‘i API"
+objectStorageSetPublicRead: "ฤแบทt \"public-read\" khi tแบฃi lรชn"
+serverLogs: "Nhแบญt kรฝ mรกy chแปง"
+deleteAll: "Xรณa tแบฅt cแบฃ"
+showFixedPostForm: "Hiแป‡n khung soแบกn tรบt แปŸ phรญa trรชn bแบฃng tin"
+newNoteRecived: "ฤรฃ nhแบญn tรบt mแป›i"
+sounds: "ร‚m thanh"
+listen: "Nghe"
+none: "Khรดng"
+showInPage: "Hiแป‡n trong trang"
+popout: "Pop-out"
+volume: "ร‚m lฦฐแปฃng"
+masterVolume: "ร‚m thanh chung"
+details: "Chi tiแบฟt"
+chooseEmoji: "Chแปn emoji"
+unableToProcess: "Khรดng thแปƒ hoร n tแบฅt hร nh ฤ‘แป™ng"
+recentUsed: "Sแปญ dแปฅng gแบงn ฤ‘รขy"
+install: "Cร i ฤ‘แบทt"
+uninstall: "Gแปก bแป"
+installedApps: "แปจng dแปฅng ฤ‘รฃ cร i ฤ‘แบทt"
+nothing: "Khรดng cรณ gรฌ แปŸ ฤ‘รขy"
+installedDate: "Cho phรฉp vร o"
+lastUsedDate: "Dรนng gแบงn nhแบฅt"
+state: "Trแบกng thรกi"
+sort: "Sแบฏp xแบฟp"
+ascendingOrder: "Tฤƒng dแบงn"
+descendingOrder: "Giแบฃm dแบงn"
+scratchpad: "Scratchpad"
+scratchpadDescription: "Scratchpad cung cแบฅp mรดi trฦฐแปng cho cรกc thแปญ nghiแป‡m AiScript. Bแบกn cรณ thแปƒ viแบฟt, thแปฑc thi vร  kiแปƒm tra kแบฟt quแบฃ tฦฐฦกng tรกc vแป›i Misskey trong ฤ‘รณ."
+output: "Nguแป“n ra"
+script: "Kแป‹ch bแบฃn"
+disablePagesScript: "Tแบฏt AiScript trรชn Trang"
+updateRemoteUser: "Cแบญp nhแบญt thรดng tin ngฦฐแปi dรนng แปŸ mรกy chแปง khรกc"
+deleteAllFiles: "Xรณa toร n bแป™ tแบญp tin"
+deleteAllFilesConfirm: "Bแบกn cรณ chแบฏc xรณa toร n bแป™ tแบญp tin?"
+removeAllFollowing: "Ngฦฐng theo dรตi tแบฅt cแบฃ mแปi ngฦฐแปi"
+removeAllFollowingDescription: "Thแปฑc hiแป‡n ฤ‘iแปu nร y sแบฝ ngฦฐng theo dรตi tแบฅt cแบฃ cรกc tร i khoแบฃn khแปi {host}. Chแป‰ thแปฑc hiแป‡n ฤ‘iแปu nร y nแบฟu mรกy chแปง khรดng cรฒn tแป“n tแบกi."
+userSuspended: "Ngฦฐแปi nร y ฤ‘รฃ bแป‹ vรด hiแป‡u hรณa."
+userSilenced: "Ngฦฐแปi nร y ฤ‘รฃ bแป‹ แบฉn"
+yourAccountSuspendedTitle: "Tร i khoแบฃn bแป‹ vรด hiแป‡u hรณa"
+yourAccountSuspendedDescription: "Tร i khoแบฃn nร y ฤ‘รฃ bแป‹ vรด hiแป‡u hรณa do vi phแบกm quy tแบฏc mรกy chแปง hoแบทc ฤ‘iแปu tฦฐฦกng tแปฑ. Liรชn hแป‡ vแป›i quแบฃn trแป‹ viรชn nแบฟu bแบกn muแป‘n biแบฟt lรฝ do chi tiแบฟt hฦกn. Vui lรฒng khรดng tแบกo tร i khoแบฃn mแป›i."
+menu: "Menu"
+divider: "Phรขn chia"
+addItem: "Thรชm mแปฅc"
+relays: "Chuyแปƒn tiแบฟp"
+addRelay: "Thรชm chuyแปƒn tiแบฟp"
+inboxUrl: "URL Hแป™p thฦฐ ฤ‘แบฟn"
+addedRelays: "ฤรฃ thรชm cรกc chuyแปƒn tiแบฟp"
+serviceworkerInfo: "Phแบฃi ฤ‘ฦฐแปฃc bแบญt cho thรดng bรกo ฤ‘แบฉy."
+deletedNote: "Tรบt ฤ‘รฃ bแป‹ xรณa"
+invisibleNote: "Tรบt รขฬ‰n"
+enableInfiniteScroll: "Tแปฑ ฤ‘แป™ng tแบฃi tรบt mแป›i"
+visibility: "Hiแปƒn thแป‹"
+poll: "Bรฌnh chแปn"
+useCw: "แบจn nแป™i dung"
+enablePlayer: "MแปŸ trรฌnh phรกt video"
+disablePlayer: "ฤรณng trรฌnh phรกt video"
+expandTweet: "MแปŸ rแป™ng tweet"
+themeEditor: "Cรดng cแปฅ thiแบฟt kแบฟ theme"
+description: "Mรด tแบฃ"
+describeFile: "Thรชm mรด tแบฃ"
+enterFileDescription: "Nhแบญp mรด tแบฃ"
+author: "Tรกc giแบฃ"
+leaveConfirm: "Cรณ nhแปฏng thay ฤ‘แป•i chฦฐa ฤ‘ฦฐแปฃc lฦฐu. Bแบกn cรณ muแป‘n bแป chรบng khรดng?"
+manage: "Quแบฃn lรฝ"
+plugins: "Plugin"
+deck: "Deck"
+undeck: "Bแป Deck"
+useBlurEffectForModal: "Sแปญ dแปฅng hiแป‡u แปฉng mแป cho cรกc hแป™p thoแบกi"
+useFullReactionPicker: "Dรนng bแป™ chแปn biแปƒu cแบฃm cแปก lแป›n"
+width: "Chiแปu rแป™ng"
+height: "Chiแปu cao"
+large: "Lแป›n"
+medium: "Vแปซa"
+small: "Nhแป"
+generateAccessToken: "Tแบกo mรฃ truy cแบญp"
+permission: "Cho phรฉp "
+enableAll: "Bแบญt toร n bแป™"
+disableAll: "Tแบฏt toร n bแป™"
+tokenRequested: "Cแบฅp quyแปn truy cแบญp vร o tร i khoแบฃn"
+pluginTokenRequestedDescription: "Plugin nร y sแบฝ cรณ thแปƒ sแปญ dแปฅng cรกc quyแปn ฤ‘ฦฐแปฃc ฤ‘แบทt แปŸ ฤ‘รขy."
+notificationType: "Loแบกi thรดng bรกo"
+edit: "Sแปญa"
+useStarForReactionFallback: "Dรนng โ˜… nแบฟu emoji biแปƒu cแบฃm khรดng cรณ"
+emailServer: "Email mรกy chแปง"
+enableEmail: "Bแบญt phรขn phแป‘i email"
+emailConfigInfo: "ฤฦฐแปฃc dรนng ฤ‘แปƒ xรกc minh email cแปงa bแบกn lรบc ฤ‘ฤƒng kรฝ hoแบทc nแบฟu bแบกn quรชn mแบญt khแบฉu cแปงa mรฌnh"
+email: "Email"
+emailAddress: "ฤแป‹a chแป‰ email"
+smtpConfig: "Cแบฅu hรฌnh mรกy chแปง SMTP"
+smtpHost: "Host"
+smtpPort: "Cแป•ng"
 smtpUser: "Tรชn ngฦฐแปi dรนng"
 smtpPass: "Mแบญt khแบฉu"
+emptyToDisableSmtpAuth: "ฤแปƒ trแป‘ng tรชn ngฦฐแปi dรนng vร  mแบญt khแบฉu ฤ‘แปƒ tแบฏt xรกc thแปฑc SMTP"
+smtpSecure: "Dรนng SSL/TLS ngแบงm ฤ‘แป‹nh cho cรกc kแบฟt nแป‘i SMTP"
+smtpSecureInfo: "Tแบฏt cรกi nร y nแบฟu dรนng STARTTLS"
+testEmail: "Kiแปƒm tra vแบญn chuyแปƒn email"
+wordMute: "แบจn chแปฏ"
+regexpError: "Lแป—i biแปƒu thแปฉc"
+regexpErrorDescription: "Xแบฃy ra lแป—i biแปƒu thแปฉc แปŸ dรฒng {line} cแปงa {tab} chแปฏ แบฉn:"
+instanceMute: "Nhแปฏng mรกy chแปง แบฉn"
+userSaysSomething: "{name} nรณi gรฌ ฤ‘รณ"
+makeActive: "Kรญch hoแบกt"
+display: "Hiแปƒn thแป‹"
+copy: "Sao chรฉp"
+metrics: "Sแป‘ liแป‡u"
+overview: "Tแป•ng quan"
+logs: "Nhแบญt kรฝ"
+delayed: "ฤแป™ trแป…"
+database: "Cฦก sแปŸ dแปฏ liแป‡u"
+channel: "Kรชnh"
+create: "Tแบกo"
+notificationSetting: "Cร i ฤ‘แบทt thรดng bรกo"
+notificationSettingDesc: "Chแปn loแบกi thรดng bรกo bแบกn muแป‘n hiแปƒn thแป‹."
+useGlobalSetting: "Dรนng thiแบฟt lแบญp chung"
+useGlobalSettingDesc: "Nแบฟu ฤ‘ฦฐแปฃc bแบญt, cร i ฤ‘แบทt thรดng bรกo cแปงa bแบกn sแบฝ ฤ‘ฦฐแปฃc รกp dแปฅng. Nแบฟu bแป‹ tแบฏt, cรณ thแปƒ thแปฑc hiแป‡n cรกc thiแบฟt lแบญp riรชng lแบป."
+other: "Khรกc"
+regenerateLoginToken: "Tแบกo lแบกi mรฃ ฤ‘ฤƒng nhแบญp"
+regenerateLoginTokenDescription: "Tแบกo lแบกi mรฃ nแป™i bแป™ cรณ thแปƒ dรนng ฤ‘แปƒ ฤ‘ฤƒng nhแบญp. Thรดng thฦฐแปng hร nh ฤ‘แป™ng nร y lร  khรดng cแบงn thiแบฟt. Nแบฟu ฤ‘ฦฐแปฃc tแบกo lแบกi, tแบฅt cแบฃ cรกc thiแบฟt bแป‹ sแบฝ bแป‹ ฤ‘ฤƒng xuแบฅt."
+setMultipleBySeparatingWithSpace: "Tรกch nhiแปu mแปฅc nhแบญp bแบฑng dแบฅu cรกch."
+fileIdOrUrl: "ID tแบญp tin hoแบทc URL"
+behavior: "Thao tรกc"
+sample: "Vรญ dแปฅ"
+abuseReports: "Lฦฐแปฃt bรกo cรกo"
+reportAbuse: "Bรกo cรกo"
 reportAbuseOf: "Bรกo cรกo {name}"
+fillAbuseReportDescription: "Vui lรฒng ฤ‘iแปn thรดng tin chi tiแบฟt vแป bรกo cรกo nร y. Nแบฟu ฤ‘รณ lร  vแป mแป™t tรบt cแปฅ thแปƒ, hรฃy kรจm theo URL cแปงa tรบt."
+abuseReported: "Bรกo cรกo ฤ‘รฃ ฤ‘ฦฐแปฃc gแปญi. Cแบฃm ฦกn bแบกn nhiแปu."
+reporter: "Ngฦฐแปi bรกo cรกo"
+reporteeOrigin: "Bแป‹ bรกo cรกo"
+reporterOrigin: "Mรกy chแปง ngฦฐแปi bรกo cรกo"
+forwardReport: "Chuyแปƒn tiแบฟp bรกo cรกo cho mรกy chแปง tแปซ xa"
+forwardReportIsAnonymous: "Thay vรฌ tร i khoแบฃn cแปงa bแบกn, mแป™t tร i khoแบฃn hแป‡ thแป‘ng แบฉn danh sแบฝ ฤ‘ฦฐแปฃc hiแปƒn thแป‹ dฦฐแป›i dแบกng ngฦฐแปi bรกo cรกo แปŸ mรกy chแปง tแปซ xa."
+send: "Gแปญi"
+abuseMarkAsResolved: "ฤรกnh dแบฅu ฤ‘รฃ xแปญ lรฝ"
+openInNewTab: "MแปŸ trong tab mแป›i"
+openInSideView: "MแปŸ trong thanh bรชn"
+defaultNavigationBehaviour: "Thao tรกc ฤ‘iแปu hฦฐแป›ng mแบทc ฤ‘แป‹nh"
+editTheseSettingsMayBreakAccount: "Viแป‡c chแป‰nh sแปญa cรกc cร i ฤ‘แบทt nร y cรณ thแปƒ lร m hแปng tร i khoแบฃn cแปงa bแบกn."
+instanceTicker: "Thรดng tin mรกy chแปง cแปงa tรบt"
+waitingFor: "ฤang ฤ‘แปฃi {x}"
+random: "Ngแบซu nhiรชn"
+system: "Hแป‡ thแป‘ng"
+switchUi: "Chuyแปƒn ฤ‘แป•i giao diแป‡n ngฦฐแปi dรนng"
+desktop: "Desktop"
+clip: "Ghim"
+createNew: "Tแบกo mแป›i"
+optional: "Khรดng bแบฏt buแป™c"
+createNewClip: "Tแบกo mแป™t ghim mแป›i"
+public: "Cรดng khai"
+i18nInfo: "Misskey ฤ‘ang ฤ‘ฦฐแปฃc cรกc tรฌnh nguyแป‡n viรชn dแป‹ch sang nhiแปu thแปฉ tiแบฟng khรกc nhau. Bแบกn cรณ thแปƒ hแป— trแปฃ tแบกi {link}."
+manageAccessTokens: "Tแบกo mรฃ truy cแบญp"
+accountInfo: "Thรดng tin tร i khoแบฃn"
+notesCount: "Sแป‘ lฦฐแปฃng tรบt"
+repliesCount: "Sแป‘ lฦฐแปฃt trแบฃ lแปi ฤ‘รฃ gแปญi"
+renotesCount: "Sแป‘ lฦฐแปฃt ฤ‘ฤƒng lแบกi ฤ‘รฃ gแปญi"
+repliedCount: "Sแป‘ lฦฐแปฃt trแบฃ lแปi ฤ‘รฃ nhแบญn"
 renotedCount: "Lฦฐแปฃt chia sแบป"
+followingCount: "Sแป‘ lฦฐแปฃng ngฦฐแปi tรดi theo dรตi"
+followersCount: "Sแป‘ lฦฐแปฃng ngฦฐแปi theo dรตi tรดi"
+sentReactionsCount: "Sแป‘ lฦฐแปฃng biแปƒu cแบฃm ฤ‘รฃ gแปญi"
+receivedReactionsCount: "Sแป‘ lฦฐแปฃng biแปƒu cแบฃm ฤ‘รฃ nhแบญn"
+pollVotesCount: "Sแป‘ lฦฐแปฃng bรฌnh chแปn ฤ‘รฃ gแปญi"
+pollVotedCount: "Sแป‘ lฦฐแปฃng bรฌnh chแปn ฤ‘รฃ nhแบญn"
+yes: "ฤแป“ng รฝ"
+no: "Tแปซ chแป‘i"
+driveFilesCount: "Sแป‘ tแบญp tin trong แป” ฤ‘ฤฉa"
+driveUsage: "Dung lฦฐแปฃng แป• ฤ‘ฤฉa"
+noCrawle: "Tแปซ chแป‘i lแบญp chแป‰ mแปฅc"
+noCrawleDescription: "Khรดng cho cรดng cแปฅ tรฌm kiแบฟm lแบญp chแป‰ mแปฅc trang hแป“ sฦก, tรบt, Trang, etc."
+lockedAccountInfo: "Ghi chรบ cแปงa bแบกn sแบฝ hiแปƒn thแป‹ vแป›i bแบฅt kแปณ ai, trแปซ khi bแบกn ฤ‘แบทt chแบฟ ฤ‘แป™ hiแปƒn thแป‹ tรบt cแปงa mรฌnh thร nh \"Chแป‰ ngฦฐแปi theo dรตi\"."
+alwaysMarkSensitive: "Luรดn ฤ‘รกnh dแบฅu NSFW"
+loadRawImages: "Tแบฃi แบฃnh gแป‘c thay vรฌ แบฃnh thu nhแป"
+disableShowingAnimatedImages: "Khรดng phรกt แบฃnh ฤ‘แป™ng"
+verificationEmailSent: "Mแป™t email xรกc minh ฤ‘รฃ ฤ‘ฦฐแปฃc gแปญi. Vui lรฒng nhแบฅn vร o liรชn kแบฟt ฤ‘รญnh kรจm ฤ‘แปƒ hoร n tแบฅt xรกc minh."
+notSet: "Chฦฐa ฤ‘แบทt"
+emailVerified: "Email ฤ‘รฃ ฤ‘ฦฐแปฃc xรกc minh"
+noteFavoritesCount: "Sแป‘ lฦฐแปฃng tรบt yรชu thรญch"
+pageLikesCount: "Sแป‘ lฦฐแปฃng trang ฤ‘รฃ thรญch"
+pageLikedCount: "Sแป‘ lฦฐแปฃng thรญch trang ฤ‘รฃ nhแบญn"
+contact: "Liรชn hแป‡"
+useSystemFont: "Dรนng phรดng chแปฏ mแบทc ฤ‘แป‹nh cแปงa hแป‡ thแป‘ng"
+clips: "Ghim"
+experimentalFeatures: "Tรญnh nฤƒng thแปญ nghiแป‡m"
+developer: "Nhร  phรกt triแปƒn"
+makeExplorable: "Khรดng hiแป‡n tรดi trong \"Khรกm phรก\""
+makeExplorableDescription: "Nแบฟu bแบกn tแบฏt, tร i khoแบฃn cแปงa bแบกn sแบฝ khรดng hiแป‡n trong mแปฅc \"Khรกm phรก\"."
+showGapBetweenNotesInTimeline: "Hiแป‡n dแบฃi phรขn cรกch giแปฏa cรกc tรบt trรชn bแบฃng tin"
+duplicate: "Tแบกo bแบฃn sao"
+left: "Bรชn traฬi"
+center: "Giแปฏa"
+wide: "Rแป™ng"
+narrow: "Thu hแบนp"
+reloadToApplySetting: "Cร i ฤ‘แบทt nร y sแบฝ chแป‰ รกp dแปฅng sau khi tแบฃi lแบกi trang. Tแบฃi lแบกi ngay bรขy giแป?"
+needReloadToApply: "Cแบงn tแบฃi lแบกi ฤ‘แปƒ ฤ‘iแปu nร y ฤ‘ฦฐแปฃc รกp dแปฅng."
+showTitlebar: "Hiแป‡n thanh tแปฑa ฤ‘แป"
+clearCache: "Xรณa bแป™ nhแป› ฤ‘แป‡m"
+onlineUsersCount: "{n} ngฦฐแปi ฤ‘ang online"
+nUsers: "{n} Ngฦฐแปi"
+nNotes: "{n} Tรบt"
+sendErrorReports: "Bรกo lแป—i"
+sendErrorReportsDescription: "Khi ฤ‘ฦฐแปฃc bแบญt, thรดng tin chi tiแบฟt vแป lแป—i sแบฝ ฤ‘ฦฐแปฃc chia sแบป vแป›i Misskey khi xแบฃy ra sแปฑ cแป‘, giรบp nรขng cao chแบฅt lฦฐแปฃng cแปงa Misskey.\nBao gแป“m thรดng tin nhฦฐ phiรชn bแบฃn hแป‡ ฤ‘iแปu hร nh cแปงa bแบกn, trรฌnh duyแป‡t bแบกn ฤ‘ang sแปญ dแปฅng, hoแบกt ฤ‘แป™ng cแปงa bแบกn trong Misskey, v.v."
+myTheme: "Theme cแปงa tรดi"
+backgroundColor: "Mร u nแปn"
+accentColor: "Mร u phแปฅ"
+textColor: "Mร u chแปฏ"
+saveAs: "Lฦฐu thร nh"
+advanced: "Nรขng cao"
+value: "Giรก trแป‹"
+createdAt: "Ngร y tแบกo"
+updatedAt: "Cแบญp nhแบญt lรบc"
+saveConfirm: "Lฦฐu thay ฤ‘แป•i?"
+deleteConfirm: "Bแบกn cรณ muแป‘n xรณa khรดng?"
+invalidValue: "Giรก trแป‹ khรดng hแปฃp lแป‡."
+registry: "Registry"
+closeAccount: "ฤรณng tร i khoแบฃn"
+currentVersion: "Phiรชn bแบฃn hiแป‡n tแบกi"
+latestVersion: "Phiรชn bแบฃn mแป›i nhแบฅt"
+youAreRunningUpToDateClient: "Bแบกn ฤ‘ang sแปญ dแปฅng phiรชn bแบฃn mแป›i nhแบฅt."
+newVersionOfClientAvailable: "Cรณ phiรชn bแบฃn mแป›i cho bแบกn cแบญp nhแบญt."
+usageAmount: "Sแปญ dแปฅng"
+capacity: "Sแปฉc chแปฉa"
+inUse: "ฤรฃ dรนng"
+editCode: "Chแป‰nh sแปญa mรฃ"
+apply: "รp dแปฅng"
+receiveAnnouncementFromInstance: "Nhแบญn thรดng bรกo tแปซ mรกy chแปง nร y"
+emailNotification: "Thรดng bรกo email"
+publish: "ฤฤƒng"
+inChannelSearch: "Tรฌm trong kรชnh"
+useReactionPickerForContextMenu: "Nhแบฅn chuแป™t phแบฃi ฤ‘แปƒ mแปŸ bแป™ chแปn biแปƒu cแบฃm"
+typingUsers: "{users} ฤ‘ang nhแบญpโ€ฆ"
+jumpToSpecifiedDate: "ฤแบฟn mแป™t ngร y cแปฅ thแปƒ"
+showingPastTimeline: "Hiแป‡n ฤ‘ang hiแปƒn thแป‹ dรฒng thแปi gian cลฉ"
+clear: "Hoร n lแบกi"
+markAllAsRead: "ฤรกnh dแบฅu tแบฅt cแบฃ ฤ‘รฃ ฤ‘แปc"
+goBack: "Quay lแบกi"
+unlikeConfirm: "Bแบกn cรณ chแบฏc muแป‘n bแป thรญch ?"
+fullView: "Kรญch thฦฐแป›c ฤ‘แบงy ฤ‘แปง"
+quitFullView: "Thoรกt toร n mร n hรฌnh"
+addDescription: "Thรชm mรด tแบฃ"
+userPagePinTip: "Bแบกn cรณ thแปƒ hiแปƒn thแป‹ cรกc tรบt แปŸ ฤ‘รขy bแบฑng cรกch chแปn \"Ghim vร o hแป“ sฦก\" tแปซ menu cแปงa mแป—i tรบt."
+notSpecifiedMentionWarning: "Tรบt nร y cรณ ฤ‘แป cแบญp ฤ‘แบฟn nhแปฏng ngฦฐแปi khรดng mong muแป‘n"
+info: "Giแป›i thiแป‡u"
+userInfo: "Thรดng tin ngฦฐแปi dรนng"
+unknown: "Chฦฐa biแบฟt"
+onlineStatus: "Trแบกng thรกi"
+hideOnlineStatus: "แบจn trแบกng thรกi online"
+hideOnlineStatusDescription: "แบจn trแบกng thรกi online cแปงa bแบกn lร m giแบฃm sแปฑ tiแป‡n lแปฃi cแปงa mแป™t sแป‘ tรญnh nฤƒng nhฦฐ tรฌm kiแบฟm."
+online: "Online"
+active: "Hoแบกt ฤ‘แป™ng"
+offline: "Offline"
+notRecommended: "Khรดng ฤ‘แป xuแบฅt"
+botProtection: "Bแบฃo vแป‡ Bot"
+instanceBlocking: "Mรกy chแปง ฤ‘รฃ chแบทn"
+selectAccount: "Chแปn mแป™t tร i khoแบฃn"
+switchAccount: "Chuyแปƒn tร i khoแบฃn"
+enabled: "ฤรฃ bแบญt"
+disabled: "ฤรฃ tแบฏt"
+quickAction: "Thao tรกc nhanh"
+user: "Ngฦฐแปi dรนng"
+administration: "Quแบฃn lรฝ"
+accounts: "Tร i khoแบฃn cแปงa bแบกn"
+switch: "Chuyแปƒn ฤ‘แป•i"
+noMaintainerInformationWarning: "Chฦฐa thiแบฟt lแบญp thรดng tin vแบญn hร nh."
+noBotProtectionWarning: "Bแบฃo vแป‡ Bot chฦฐa thiแบฟt lแบญp."
+configure: "Thiแบฟt lแบญp"
+postToGallery: "Tแบกo tรบt cรณ แบฃnh"
+gallery: "Thฦฐ viแป‡n แบฃnh"
+recentPosts: "Tรบt gแบงn ฤ‘รขy"
+popularPosts: "Tรบt ฤ‘ฦฐแปฃc xem nhiแปu nhแบฅt"
+shareWithNote: "Chia sแบป kรจm vแป›i tรบt"
+ads: "Quแบฃng cรกo"
+expiration: "Thแปi hแบกn"
+memo: "Lฦฐu รฝ"
+priority: "ฦฏu tiรชn"
+high: "Cao"
+middle: "Vแปซa"
+low: "Thแบฅp"
+emailNotConfiguredWarning: "Chฦฐa ฤ‘แบทt ฤ‘แป‹a chแป‰ email."
+ratio: "Tแปท lแป‡"
+previewNoteText: "Hiแป‡n xem trฦฐแป›c"
+customCss: "Tรนy chแป‰nh CSS"
+customCssWarn: "Chแป‰ sแปญ dแปฅng nhแปฏng cร i ฤ‘แบทt nร y nแบฟu bแบกn biแบฟt rรต vแป nรณ. Viแป‡c nhแบญp cรกc giรก trแป‹ khรดng ฤ‘รบng cรณ thแปƒ khiแบฟn mรกy chแปง hoแบกt ฤ‘แป™ng khรดng bรฌnh thฦฐแปng."
+global: "Toร n cแบงu"
+squareAvatars: "แบขnh ฤ‘แบกi diแป‡n vuรดng"
+sent: "Gแปญi"
+received: "ฤรฃ nhแบญn"
+searchResult: "Kแบฟt quแบฃ tรฌm kiแบฟm"
+hashtags: "Hashtag"
+troubleshooting: "Khแบฏc phแปฅc sแปฑ cแป‘"
+useBlurEffect: "Dรนng hiแป‡u แปฉng lร m mแป trong giao diแป‡n"
+learnMore: "Tรฌm hiแปƒu thรชm"
+misskeyUpdated: "Misskey vแปซa ฤ‘ฦฐแปฃc cแบญp nhแบญt!"
+whatIsNew: "Hiแป‡n nhแปฏng thay ฤ‘แป•i"
+translate: "Dแป‹ch"
 translatedFrom: "Dแป‹ch tแปซ {x}"
+accountDeletionInProgress: "ฤang xแปญ lรฝ viแป‡c xรณa tร i khoแบฃn"
+usernameInfo: "Bแบกn cรณ thแปƒ sแปญ dแปฅng chแปฏ cรกi (a ~ z, A ~ Z), chแปฏ sแป‘ (0 ~ 9) hoแบทc dแบฅu gแบกch dฦฐแป›i (_). Tรชn ngฦฐแปi dรนng khรดng thแปƒ thay ฤ‘แป•i sau nร y."
+aiChanMode: "Chแบฟ ฤ‘แป™ Ai"
+keepCw: "Giแปฏ cแบฃnh bรกo nแป™i dung"
+pubSub: "Tร i khoแบฃn Chรญnh/Phแปฅ"
+lastCommunication: "Lแบงn giao tiแบฟp cuแป‘i"
+resolved: "ฤรฃ xแปญ lรฝ"
+unresolved: "Chแป xแปญ lรฝ"
+breakFollow: "Xรณa ngฦฐแปi theo dรตi"
+itsOn: "ฤรฃ bแบญt"
+itsOff: "ฤรฃ tแบฏt"
+emailRequiredForSignup: "Yรชu cแบงu ฤ‘แป‹a chแป‰ email khi ฤ‘ฤƒng kรฝ"
+unread: "Chฦฐa ฤ‘แปc"
+filter: "Bแป™ lแปc"
+controlPanel: "Bแบฃng ฤ‘iแปu khiแปƒn"
+manageAccounts: "Quแบฃn lรฝ tร i khoแบฃn"
+makeReactionsPublic: "ฤแบทt lแป‹ch sแปญ biแปƒu cแบฃm cรดng khai"
+makeReactionsPublicDescription: "ฤiแปu nร y sแบฝ hiแปƒn thแป‹ cรดng khai danh sรกch tแบฅt cแบฃ cรกc biแปƒu cแบฃm trฦฐแป›c ฤ‘รขy cแปงa bแบกn."
+classic: "Cแป• ฤ‘iแปƒn"
+muteThread: "Khรดng quan tรขm nแปฏa"
+unmuteThread: "Quan tรขm tรบt nร y"
+ffVisibility: "Hiแปƒn thแป‹ Theo dรตi/Ngฦฐแปi theo dรตi"
+ffVisibilityDescription: "Quyแบฟt ฤ‘แป‹nh ai cรณ thแปƒ xem nhแปฏng ngฦฐแปi bแบกn theo dรตi vร  nhแปฏng ngฦฐแปi theo dรตi bแบกn."
+continueThread: "Tiแบฟp tแปฅc xem chuแป—i tรบt"
+deleteAccountConfirm: "ฤiแปu nร y sแบฝ khiแบฟn tร i khoแบฃn bแป‹ xรณa vฤฉnh viแป…n. Vแบซn tiแบฟp tแปฅc?"
+incorrectPassword: "Sai mแบญt khแบฉu."
+voteConfirm: "Xรกc nhแบญn bรฌnh chแปn \"{choice}\"?"
+hide: "แบจn"
+leaveGroup: "Rแปi khแปi nhรณm"
+leaveGroupConfirm: "Bแบกn cรณ chแบฏc muแป‘n rแปi khแปi nhรณm \"{name}\"?"
+useDrawerReactionPickerForMobile: "Hiแป‡n bแป™ chแปn biแปƒu cแบฃm dแบกng xแป• ra trรชn ฤ‘iแป‡n thoแบกi"
+welcomeBackWithName: "Chร o mแปซng trแปŸ lแบกi, {name}"
+clickToFinishEmailVerification: "Vui lรฒng nhแบฅn [{ok}] ฤ‘แปƒ hoร n tแบฅt viแป‡c ฤ‘ฤƒng kรฝ."
+overridedDeviceKind: "Loแบกi thiแบฟt bแป‹"
+smartphone: "ฤiแป‡n thoแบกi"
+tablet: "Mรกy tรญnh bแบฃng"
+auto: "Tแปฑ ฤ‘แป™ng"
+themeColor: "Mร u theme"
+size: "Kรญch thฦฐแป›c"
+numberOfColumn: "Sแป‘ lฦฐแปฃng cแป™t"
 searchByGoogle: "Google"
+instanceDefaultLightTheme: "Theme mรกy chแปง Sรกng-Rแป™ng"
+instanceDefaultDarkTheme: "Theme mรกy chแปง Tแป‘i-Rแป™ng"
+instanceDefaultThemeDescription: "Nhแบญp mรฃ theme trong ฤ‘แป‹nh dแบกng ฤ‘แป‘i tฦฐแปฃng."
+mutePeriod: "Thแปi hแบกn แบฉn"
+indefinitely: "Vฤฉnh viแป…n"
+tenMinutes: "10 phรบt"
+oneHour: "1 giแป"
+oneDay: "1 ngร y"
+oneWeek: "1 tuแบงn"
+reflectMayTakeTime: "Cรณ thแปƒ mแบฅt mแป™t thแปi gian ฤ‘แปƒ ฤ‘iแปu nร y ฤ‘ฦฐแปฃc รกp dแปฅng."
+failedToFetchAccountInformation: "Khรดng thแปƒ lแบฅy thรดng tin tร i khoแบฃn"
+rateLimitExceeded: "Giแป›i hแบกn quรก mแปฉc"
+_emailUnavailable:
+  used: "ฤแป‹a chแป‰ email ฤ‘รฃ ฤ‘ฦฐแปฃc sแปญ dแปฅng"
+  format: "ฤแป‹a chแป‰ email khรดng hแปฃp lแป‡"
+  disposable: "Cแบฅm sแปญ dแปฅng ฤ‘แป‹a chแป‰ email dรนng mแป™t lแบงn"
+  mx: "Mรกy chแปง email khรดng hแปฃp lแป‡"
+  smtp: "Mรกy chแปง email khรดng phแบฃn hแป“i"
+_ffVisibility:
+  public: "ฤฤƒng"
+  followers: "Chแป‰ ngฦฐแปi theo dรตi mแป›i xem ฤ‘ฦฐแปฃc"
+  private: "Riรชng tฦฐ"
+_signup:
+  almostThere: "Gแบงn xong rแป“i"
+  emailAddressInfo: "Hรฃy ฤ‘iแปn ฤ‘แป‹a chแป‰ email cแปงa bแบกn. Nรณ sแบฝ khรดng ฤ‘ฦฐแปฃc cรดng khai."
+  emailSent: "Mแป™t email xรกc minh ฤ‘รฃ ฤ‘ฦฐแปฃc gแปญi ฤ‘แบฟn ฤ‘แป‹a chแป‰ email ({email}) cแปงa bแบกn. Vui lรฒng nhแบฅn vร o liรชn kแบฟt trong ฤ‘รณ ฤ‘แปƒ hoร n tแบฅt viแป‡c tแบกo tร i khoแบฃn."
+_accountDelete:
+  accountDelete: "Xรณa tร i khoแบฃn"
+  mayTakeTime: "Vรฌ xรณa tร i khoแบฃn lร  mแป™t quรก trรฌnh tแป‘n nhiแปu tร i nguyรชn nรชn cรณ thแปƒ mแบฅt mแป™t khoแบฃng thแปi gian ฤ‘แปƒ hoร n thร nh, tรนy thuแป™c vร o lฦฐแปฃng nแป™i dung bแบกn ฤ‘รฃ tแบกo vร  sแป‘ lฦฐแปฃng tแบญp tin bแบกn ฤ‘รฃ tแบฃi lรชn."
+  sendEmail: "Sau khi hoร n tแบฅt viแป‡c xรณa tร i khoแบฃn, mแป™t email sแบฝ ฤ‘ฦฐแปฃc gแปญi ฤ‘แบฟn ฤ‘แป‹a chแป‰ email ฤ‘รฃ ฤ‘ฤƒng kรฝ tร i khoแบฃn nร y."
+  requestAccountDelete: "Yรชu cแบงu xรณa tร i khoแบฃn"
+  started: "ฤang bแบฏt ฤ‘แบงu xรณa tร i khoแบฃn."
+  inProgress: "ฤang xรณa dแบงn tร i khoแบฃn."
+_ad:
+  back: "Quay lแบกi"
+  reduceFrequencyOfThisAd: "Hiแป‡n รญt lแบกi"
+_forgotPassword:
+  enterEmail: "Nhแบญp ฤ‘แป‹a chแป‰ email bแบกn ฤ‘รฃ sแปญ dแปฅng ฤ‘แปƒ ฤ‘ฤƒng kรฝ. Mแป™t liรชn kแบฟt mร  bแบกn cรณ thแปƒ ฤ‘แบทt lแบกi mแบญt khแบฉu cแปงa mรฌnh sau ฤ‘รณ sแบฝ ฤ‘ฦฐแปฃc gแปญi ฤ‘แบฟn nรณ."
+  ifNoEmail: "Nแบฟu bแบกn khรดng sแปญ dแปฅng email lรบc ฤ‘ฤƒng kรฝ, vui lรฒng liรชn hแป‡ vแป›i quแบฃn trแป‹ viรชn."
+  contactAdmin: "Mรกy chแปง nร y khรดng hแป— trแปฃ sแปญ dแปฅng ฤ‘แป‹a chแป‰ email, vui lรฒng liรชn hแป‡ vแป›i quแบฃn trแป‹ viรชn ฤ‘แปƒ ฤ‘แบทt lแบกi mแบญt khแบฉu cแปงa bแบกn."
+_gallery:
+  my: "Kho แบขnh"
+  liked: "Tรบt ฤรฃ Thรญch"
+  like: "Thรญch"
+  unlike: "Bแป thรญch"
+_email:
+  _follow:
+    title: "ฤ‘รฃ theo dรตi bแบกn"
+  _receiveFollowRequest:
+    title: "Chแบฅp nhแบญn yรชu cแบงu theo dรตi"
+_plugin:
+  install: "Cร i ฤ‘แบทt tiแป‡n รญch"
+  installWarn: "Vui lรฒng khรดng cร i ฤ‘แบทt nhแปฏng tiแป‡n รญch ฤ‘รกng ngแป."
+  manage: "Quแบฃn lรฝ plugin"
+_registry:
+  scope: "Phแบกm vi"
+  key: "Mรฃ"
+  keys: "Cรกc mรฃ"
+  domain: "Tรชn miแปn"
+  createKey: "Tแบกo mรฃ"
+_aboutMisskey:
+  about: "Misskey lร  phแบงn mแปm mรฃ nguแป“n mแปŸ ฤ‘ฦฐแปฃc phรกt triแปƒn bแปŸi syuilo tแปซ nฤƒm 2014."
+  contributors: "Nhแปฏng ngฦฐแปi ฤ‘รณng gรณp nแป•i bแบญt"
+  allContributors: "Toร n bแป™ ngฦฐแปi ฤ‘รณng gรณp"
+  source: "Mรฃ nguแป“n"
+  translation: "Dแป‹ch Misskey"
+  donate: "แปฆng hแป™ Misskey"
+  morePatrons: "Chรบng tรดi cลฉng trรขn trแปng sแปฑ hแป— trแปฃ cแปงa nhiแปu ngฦฐแปi ฤ‘รณng gรณp khรกc khรดng ฤ‘ฦฐแปฃc liแป‡t kรช แปŸ ฤ‘รขy. Cแบฃm ฦกn! ๐Ÿฅฐ"
+  patrons: "Ngฦฐแปi แปงng hแป™"
+_nsfw:
+  respect: "แบจn nแป™i dung NSFW"
+  ignore: "Hiแป‡n nแป™i dung NSFW"
+  force: "แบจn mแปi media"
 _mfm:
+  cheatSheet: "MFM Cheatsheet"
+  intro: "MFM lร  ngรดn ngแปฏ phรกt triแปƒn ฤ‘แป™c quyแปn cแปงa Misskey cรณ thแปƒ ฤ‘ฦฐแปฃc sแปญ dแปฅng แปŸ nhiแปu nฦกi. Tแบกi ฤ‘รขy bแบกn cรณ thแปƒ xem danh sรกch tแบฅt cแบฃ cรกc cรบ phรกp MFM cรณ sแบตn."
+  dummy: "Misskey mแปŸ rแป™ng thแบฟ giแป›i Fediverse"
+  mention: "Nhแบฏc ฤ‘แบฟn"
+  mentionDescription: "Bแบกn cรณ thแปƒ nhแบฏc ฤ‘แบฟn ai ฤ‘รณ bแบฑng cรกch sแปญ dแปฅng @tรชn ngฦฐแปi dรนng."
+  hashtag: "Hashtag"
+  hashtagDescription: "Bแบกn cรณ thแปƒ tแบกo mแป™t hashtag bแบฑng #chแปฏ hoแบทc #sแป‘."
+  url: "URL"
+  urlDescription: "Nhแปฏng URL cรณ thแปƒ hiแปƒn thแป‹."
+  link: "ฤฦฐแปng dแบซn"
+  linkDescription: "Cรกc phแบงn cแปฅ thแปƒ cแปงa vฤƒn bแบฃn cรณ thแปƒ ฤ‘ฦฐแปฃc hiแปƒn thแป‹ dฦฐแป›i dแบกng URL."
+  bold: "In ฤ‘แบญm"
+  boldDescription: "Nแป•i bแบญt cรกc chแปฏ cรกi bแบฑng cรกch lร m chรบng dร y hฦกn."
+  small: "Nhแป"
+  smallDescription: "Hiแปƒn thแป‹ nแป™i dung nhแป vร  mแปng."
+  center: "Giแปฏa"
+  centerDescription: "Hiแปƒn thแป‹ nแป™i dung cฤƒn giแปฏa."
+  inlineCode: "Mรฃ (Trong dรฒng)"
+  inlineCodeDescription: "Hiแปƒn thแป‹ tรด sรกng cรบ phรกp trong dรฒng cho mรฃ (chฦฐฦกng trรฌnh)."
+  blockCode: "Mรฃ (Khแป‘i)"
+  blockCodeDescription: "Hiแปƒn thแป‹ tรด sรกng cรบ phรกp cho mรฃ nhiแปu dรฒng (chฦฐฦกng trรฌnh) trong mแป™t khแป‘i."
+  inlineMath: "Toรกn hแปc (Trong dรฒng)"
+  inlineMathDescription: "Hiแปƒn thแป‹ cรดng thแปฉc toรกn (KaTeX) trong dรฒng"
+  blockMath: "Toรกn hแปc (Khแป‘i)"
+  blockMathDescription: "Hiแปƒn thแป‹ cรดng thแปฉc toรกn hแปc nhiแปu dรฒng (KaTeX) trong mแป™t khแป‘i"
+  quote: "Trรญch dแบซn"
+  quoteDescription: "Hiแปƒn thแป‹ nแป™i dung dแบกng lแปi trรญch dแบกng."
+  emoji: "Tรนy chแป‰nh emoji"
+  emojiDescription: "Hiแปƒn thแป‹ emoji vแป›i cรบ phรกp :tรชn emoji:"
   search: "Tรฌm kiแบฟm"
+  searchDescription: "Hiแปƒn thแป‹ hแป™p tรฌm kiแบฟm vแป›i vฤƒn bแบฃn ฤ‘ฦฐแปฃc nhแบญp trฦฐแป›c."
+  flip: "Lแบญt"
+  flipDescription: "Lแบญt nแป™i dung theo chiแปu ngang hoแบทc chiแปu dแปc."
+  jelly: "Chuyแปƒn ฤ‘แป™ng (Thแบกch rau cรขu)"
+  jellyDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng giแป‘ng nhฦฐ thแบกch rau cรขu."
+  tada: "Chuyแปƒn ฤ‘แป™ng (Tada)"
+  tadaDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng kiแปƒu \"Tada!\"."
+  jump: "Chuyแปƒn ฤ‘แป™ng (Nhแบฃy mรบa)"
+  jumpDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng nhแบฃy nhรณt."
+  bounce: "Chuyแปƒn ฤ‘แป™ng (Cร  tฦฐng)"
+  bounceDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng cร  tฦฐng."
+  shake: "Chuyแปƒn ฤ‘แป™ng (Rung)"
+  shakeDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng rung lแบฏc."
+  twitch: "Chuyแปƒn ฤ‘แป™ng (Co rรบt)"
+  twitchDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng co rรบt."
+  spin: "Chuyแปƒn ฤ‘แป™ng (Xoay tรญt)"
+  spinDescription: "Cho phรฉp nแป™i dung chuyแปƒn ฤ‘แป™ng xoay tรญt."
+  x2: "Lฦกฬn"
+  x2Description: "Hiแปƒn thแป‹ nแป™i dung cแปก lแป›n hฦกn."
+  x3: "Rแบฅt lแป›n"
+  x3Description: "Hiแปƒn thแป‹ nแป™i dung cแปก lแป›n hฦกn nแปฏa."
+  x4: "Khแป•ng lแป“"
+  x4Description: "Hiแปƒn thแป‹ nแป™i dung cแปก khแป•ng lแป“."
+  blur: "Lร m mแป"
+  blurDescription: "Lร m mแป nแป™i dung. Nรณ sแบฝ ฤ‘ฦฐแปฃc hiแปƒn thแป‹ rรต rร ng khi di chuแป™t qua."
+  font: "Phรดng chแปฏ"
+  fontDescription: "Chแปn phรดng chแปฏ ฤ‘แปƒ hiแปƒn thแป‹ nแป™i dung."
+  rainbow: "Cแบงu vแป“ng"
+  rainbowDescription: "Lร m cho nแป™i dung hiแปƒn thแป‹ vแป›i mร u sแบฏc cแบงu vแป“ng."
+  sparkle: "Lแบฅp lรกnh"
+  sparkleDescription: "Lร m cho nแป™i dung hiแป‡u แปฉng hแบกt lแบฅp lรกnh."
+  rotate: "Xoay"
+  rotateDescription: "Xoay nแป™i dung theo mแป™t gรณc cแปฅ thแปƒ."
+_instanceTicker:
+  none: "Khรดng hiแปƒn thแป‹"
+  remote: "Hiแป‡n cho ngฦฐแปi dรนng tแปซ mรกy chแปง khรกc"
+  always: "Luรดn hiแป‡n"
+_serverDisconnectedBehavior:
+  reload: "Tแปฑ ฤ‘แป™ng tแบฃi lแบกi"
+  dialog: "Hiแป‡n hแป™p thoแบกi cแบฃnh bรกo"
+  quiet: "Hiแปƒn thแป‹ cแบฃnh bรกo khรดng phรด trฦฐฦกng"
+_channel:
+  create: "Tแบกo kรชnh"
+  edit: "Chแป‰nh sแปญa kรชnh"
+  setBanner: "ฤแบทt แบฃnh bรฌa"
+  removeBanner: "Xรณa แบฃnh bรฌa"
+  featured: "Xu hฦฐฦกฬng"
+  owned: "Do tรดi quแบฃn lรฝ"
+  following: "ฤang theo dรตi"
+  usersCount: "{n} Thร nh viรชn"
+  notesCount: "{n} Tรบt"
+_menuDisplay:
+  sideFull: "Thanh bรชn"
+  sideIcon: "Thanh bรชn (Biแปƒu tฦฐแปฃng)"
+  top: "Trรชn cรนng"
+  hide: "แบจn"
+_wordMute:
+  muteWords: "แบจn tแปซ ngแปฏ"
+  muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
+  muteWordsDescription2: "Bao quanh cรกc tแปซ khรณa bแบฑng dแบฅu gแบกch chรฉo ฤ‘แปƒ sแปญ dแปฅng cแปฅm tแปซ thรดng dแปฅng."
+  softDescription: "แบจn cรกc tรบt phรน hแปฃp ฤ‘iแปu kiแป‡n ฤ‘รฃ ฤ‘แบทt khแปi bแบฃng tin."
+  hardDescription: "Ngฤƒn cรกc tรบt ฤ‘รกp แปฉng cรกc ฤ‘iแปu kiแป‡n ฤ‘รฃ ฤ‘แบทt xuแบฅt hiแป‡n trรชn bแบฃng tin. Lฦฐu รฝ, nhแปฏng tรบt nร y sแบฝ khรดng ฤ‘ฦฐแปฃc thรชm vร o bแบฃng tin ngay cแบฃ khi cรกc ฤ‘iแปu kiแป‡n ฤ‘ฦฐแปฃc thay ฤ‘แป•i."
+  soft: "Yแบฟu"
+  hard: "Mแบกnh"
+  mutedNotes: "Nhแปฏng tรบt ฤ‘รฃ แบฉn"
+_instanceMute:
+  instanceMuteDescription: "Thao tรกc nร y sแบฝ แบฉn mแปi tรบt/lฦฐแปฃt ฤ‘ฤƒng lแบกi tแปซ cรกc mรกy chแปง ฤ‘ฦฐแปฃc liแป‡t kรช, bao gแป“m cแบฃ nhแปฏng tรบt  dแบกng trแบฃ lแปi tแปซ mรกy chแปง bแป‹ แบฉn."
+  instanceMuteDescription2: "Tรกch bแบฑng cรกch xuแป‘ng dรฒng"
+  title: "แบจn tรบt tแปซ nhแปฏng mรกy chแปง ฤ‘รฃ liแป‡t kรช."
+  heading: "Danh sรกch nhแปฏng mรกy chแปง bแป‹ แบฉn"
 _theme:
+  explore: "Khรกm phรก theme"
+  install: "Cร i ฤ‘แบทt theme"
+  manage: "Quแบฃn lรฝ theme"
+  code: "Mรฃ theme"
+  description: "Mรด tแบฃ"
   installed: "{name} ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt"
+  installedThemes: "Theme ฤ‘รฃ cร i ฤ‘แบทt"
+  builtinThemes: "Theme tรญch hแปฃp sแบตn"
+  alreadyInstalled: "Theme nร y ฤ‘รฃ ฤ‘ฦฐแปฃc cร i ฤ‘แบทt"
+  invalid: "ฤแป‹nh dแบกng cแปงa theme nร y khรดng hแปฃp lแป‡"
+  make: "Tแบกo theme"
+  base: "Dแปฑa trรชn cรณ sแบตn"
+  addConstant: "Thรชm hแบฑng sแป‘"
+  constant: "Hแบฑng sแป‘"
+  defaultValue: "Giรก trแป‹ mแบทc ฤ‘แป‹nh"
+  color: "Mร u sแบฏc"
+  refProp: "Tham chiแบฟu mแป™t thuแป™c tรญnh"
+  refConst: "Tham chiแบฟu mแป™t hแบฑng sแป‘"
+  key: "Khรณa"
+  func: "Hร m"
+  funcKind: "Loแบกi hร m"
+  argument: "Tham sแป‘"
+  basedProp: "Thuแป™c tรญnh tham chiแบฟu"
+  alpha: "ฤแป™ trong suแป‘t"
+  darken: "ฤแป™ tแป‘i"
+  lighten: "ฤรดฬฃ saฬng"
+  inputConstantName: "Nhแบญp tรชn cho hแบฑng sแป‘ nร y"
+  importInfo: "Nแบฟu bแบกn nhแบญp mรฃ theme แปŸ ฤ‘รขy, bแบกn cรณ thแปƒ nhแบญp mรฃ ฤ‘รณ vร o trรฌnh chแป‰nh sแปญa theme"
+  deleteConstantConfirm: "Bแบกn cรณ chแบฏc muแป‘n xรณa hแบฑng sแป‘ {const} khรดng?"
+  keys:
+    accent: "Mร u phแปฅ"
+    bg: "Mร u nแปn"
+    fg: "Mร u chแปฏ"
+    focus: "Trแปng tรขm"
+    indicator: "Chแป‰ bรกo"
+    panel: "Thanh bรชn"
+    shadow: "Bรณng mแป"
+    header: "แบขnh bรฌa"
+    navBg: "Nแปn thanh bรชn"
+    navFg: "Chแปฏ thanh bรชn"
+    navHoverFg: "Chแปฏ thanh bรชn (Khi chแบกm)"
+    navActive: "Chแปฏ thanh bรชn (Khi chแปn)"
+    navIndicator: "Chแป‰ bรกo thanh bรชn"
+    link: "ฤฦฐแปng dแบซn"
+    hashtag: "Hashtag"
+    mention: "Nhแบฏc ฤ‘แบฟn"
+    mentionMe: "Lฦฐแปฃt nhแบฏc (Tรดi)"
+    renote: "ฤฤƒng lแบกi"
+    modalBg: "Nแปn phฦฐฦกng thแปฉc"
+    divider: "Phรขn chia"
+    scrollbarHandle: "Thanh cuแป™n khi giแปฏ"
+    scrollbarHandleHover: "Thanh cuแป™n khi chแบกm"
+    dateLabelFg: "Mร u ngร y thรกng nฤƒm"
+    infoBg: "Nแปn thรดng tin"
+    infoFg: "Chแปฏ thรดng tin"
+    infoWarnBg: "Nแปn cแบฃnh bรกo"
+    infoWarnFg: "Chแปฏ cแบฃnh bรกo"
+    cwBg: "Nแปn nรบt nแป™i dung แบฉn"
+    cwFg: "Chแปฏ nรบt nแป™i dung แบฉn"
+    cwHoverBg: "Nแปn nรบt nแป™i dung แบฉn (Chแบกm)"
+    toastBg: "Nแปn thรดng bรกo"
+    toastFg: "Chแปฏ thรดng bรกo"
+    buttonBg: "Nแปn nรบt"
+    buttonHoverBg: "Nแปn nรบt (Chแบกm)"
+    inputBorder: "ฤฦฐแปng viแปn khung soแบกn thแบฃo"
+    listItemHoverBg: "Nแปn mแปฅc liแป‡t kรช (Chแบกm)"
+    driveFolderBg: "Nแปn thฦฐ mแปฅc แป” ฤ‘ฤฉa"
+    wallpaperOverlay: "Lแป›p phแปง hรฌnh nแปn"
+    badge: "Huy hiแป‡u"
+    messageBg: "Nแปn chat"
+    accentDarken: "Mร u phแปฅ (Tแป‘i)"
+    accentLighten: "Mร u phแปฅ (Sรกng)"
+    fgHighlighted: "Chแปฏ nแป•i bแบญt"
 _sfx:
+  note: "Tรบt"
+  noteMy: "Tรบt cแปงa tรดi"
   notification: "Thรดng bรกo"
+  chat: "Trรฒ chuyแป‡n"
+  chatBg: "Chat (Nแปn)"
+  antenna: "Trแบกm phรกt sรณng"
+  channel: "Kรชnh"
 _ago:
-  unknown: "Khรดng rรต"
   future: "Tฦฐฦกng lai"
   justNow: "Vแปซa xong"
   secondsAgo: "{n}s trฦฐแป›c"
@@ -39,10 +1097,563 @@ _ago:
   weeksAgo: "{n} tuแบงn trฦฐแป›c"
   monthsAgo: "{n} thรกng trฦฐแป›c"
   yearsAgo: "{n} nฤƒm trฦฐแป›c"
+_time:
+  second: "s"
+  minute: "phรบt"
+  hour: "giแป"
+  day: "ngร y"
+_tutorial:
+  title: "Cรกch dรนng Misskey"
+  step1_1: "Xin chร o!"
+  step1_2: "Trang nร y gแปi lร  \"bแบฃng tin\". Nรณ hiแป‡n \"tรบt\" tแปซ nhแปฏng ngฦฐแปi mร  bแบกn \"theo dรตi\" theo thแปฉ tแปฑ thแปi gian."
+  step1_3: "Bแบฃng tin cแปงa bแบกn ฤ‘ang trแป‘ng, bแปŸi vรฌ bแบกn chฦฐa ฤ‘ฤƒng tรบt nร o hoแบทc chฦฐa theo dรตi ai."
+  step2_1: "Hรฃy hoร n thร nh viแป‡c thiแบฟt lแบญp hแป“ sฦก cแปงa bแบกn trฦฐแป›c khi viแบฟt tรบt hoแบทc theo dรตi bแบฅt kแปณ ai."
+  step2_2: "Cung cแบฅp mแป™t sแป‘ thรดng tin giแป›i thiแป‡u bแบกn lร  ai sแบฝ giรบp ngฦฐแปi khรกc dแป… dร ng biแบฟt ฤ‘ฦฐแปฃc hแป muแป‘n ฤ‘แปc tรบt hay theo dรตi bแบกn."
+  step3_1: "Hoร n thร nh thiแบฟt lแบญp hแป“ sฦก cแปงa bแบกn?"
+  step3_2: "Sau ฤ‘รณ, hรฃy thแปญ ฤ‘ฤƒng mแป™t tรบt tiแบฟp theo. Bแบกn cรณ thแปƒ lร m nhฦฐ vแบญy bแบฑng cรกch nhแบฅn vร o nรบt cรณ biแปƒu tฦฐแปฃng bรบt chรฌ trรชn mร n hรฌnh."
+  step3_3: "Nhแบญp nแป™i dung vร o khung soแบกn thแบฃo vร  nhแบฅn nรบt ฤ‘ฤƒng แปŸ gรณc trรชn."
+  step3_4: "Chฦฐa biแบฟt nรณi gรฌ? Thแปญ \"Tรดi mแป›i tham gia Misskey\"!"
+  step4_1: "ฤฤƒng xong tรบt ฤ‘แบงu tiรชn cแปงa bแบกn?"
+  step4_2: "De! Tรบt ฤ‘แบงu tiรชn cแปงa bแบกn ฤ‘รฃ hiแป‡n trรชn bแบฃng tin."
+  step5_1: "Bรขy giแป, hรฃy thแปญ lร m cho bแบฃng tin cแปงa bแบกn sinh ฤ‘แป™ng hฦกn bแบฑng cรกch theo dรตi nhแปฏng ngฦฐแปi khรกc."
+  step5_2: "{feature} sแบฝ hiแปƒn thแป‹ cho bแบกn cรกc tรบt nแป•i bแบญt trรชn mรกy chแปง nร y. {explore} sแบฝ cho phรฉp bแบกn tรฌm thแบฅy nhแปฏng ngฦฐแปi dรนng thรบ vแป‹. Hรฃy thแปญ tรฌm nhแปฏng ngฦฐแปi bแบกn muแป‘n theo dรตi แปŸ ฤ‘รณ!"
+  step5_3: "ฤแปƒ theo dรตi nhแปฏng ngฦฐแปi dรนng khรกc, hรฃy nhแบฅn vร o แบฃnh ฤ‘แบกi diแป‡n cแปงa hแป vร  nhแบฅn nรบt \"Theo dรตi\" trรชn hแป“ sฦก cแปงa hแป."
+  step5_4: "Nแบฟu ngฦฐแปi dรนng khรกc cรณ biแปƒu tฦฐแปฃng แป• khรณa bรชn cแบกnh tรชn cแปงa hแป, cรณ thแปƒ mแบฅt mแป™t khoแบฃng thแปi gian ฤ‘แปƒ ngฦฐแปi dรนng ฤ‘รณ phรช duyแป‡t yรชu cแบงu theo dรตi cแปงa bแบกn theo cรกch thแปง cรดng."
+  step6_1: "Bแบกn sแบฝ cรณ thแปƒ xem tรบt cแปงa nhแปฏng ngฦฐแปi dรนng khรกc trรชn bแบฃng tin cแปงa mรฌnh ngay bรขy giแป."
+  step6_2: "Bแบกn cลฉng cรณ thแปƒ ฤ‘แบทt \"biแปƒu cแบฃm\" trรชn tรบt cแปงa ngฦฐแปi khรกc ฤ‘แปƒ phแบฃn hแป“i nhanh chรบng."
+  step6_3: "ฤแปƒ ฤ‘รญnh kรจm \"biแปƒu cแบฃm\", hรฃy nhแบฅn vร o dแบฅu \"+\" trรชn tรบt cแปงa ngฦฐแปi dรนng khรกc rแป“i chแปn biแปƒu tฦฐแปฃng cแบฃm xรบc mร  bแบกn muแป‘n dรนng."
+  step7_1: "Xin chรบc mแปซng! Bรขy giแป bแบกn ฤ‘รฃ hoร n thร nh phแบงn hฦฐแป›ng dแบซn cฦก bแบฃn cแปงa Misskey."
+  step7_2: "Nแบฟu bแบกn muแป‘n tรฌm hiแปƒu thรชm vแป Misskey, hรฃy thแปญ phแบงn {help}."
+  step7_3: "Bรขy giแป, chรบc may mแบฏn vร  vui vแบป vแป›i Misskey! ๐Ÿš€"
+_2fa:
+  alreadyRegistered: "Bแบกn ฤ‘รฃ ฤ‘ฤƒng kรฝ thiแบฟt bแป‹ xรกc minh 2 bฦฐแป›c."
+  registerDevice: "ฤฤƒng kรฝ mแป™t thiแบฟt bแป‹"
+  registerKey: "ฤฤƒng kรฝ mแป™t mรฃ bแบฃo vแป‡"
+  step1: "Trฦฐแป›c tiรชn, hรฃy cร i ฤ‘แบทt mแป™t แปฉng dแปฅng xรกc minh (chแบณng hแบกn nhฦฐ {a} hoแบทc {b}) trรชn thiแบฟt bแป‹ cแปงa bแบกn."
+  step2: "Sau ฤ‘รณ, quรฉt mรฃ QR hiแปƒn thแป‹ trรชn mร n hรฌnh nร y."
+  step2Url: "Bแบกn cลฉng cรณ thแปƒ nhแบญp URL nร y nแบฟu sแปญ dแปฅng mแป™t chฦฐฦกng trรฌnh mรกy tรญnh:"
+  step3: "Nhแบญp mรฃ token do แปฉng dแปฅng cแปงa bแบกn cung cแบฅp ฤ‘แปƒ hoร n tแบฅt thiแบฟt lแบญp."
+  step4: "Kแปƒ tแปซ bรขy giแป, nhแปฏng lแบงn ฤ‘ฤƒng nhแบญp trong tฦฐฦกng lai sแบฝ yรชu cแบงu mรฃ token ฤ‘ฤƒng nhแบญp ฤ‘รณ."
+  securityKeyInfo: "Bรชn cแบกnh xรกc minh bแบฑng vรขn tay hoแบทc mรฃ PIN, bแบกn cลฉng cรณ thแปƒ thiแบฟt lแบญp xรกc minh thรดng qua khรณa bแบฃo mแบญt phแบงn cแปฉng hแป— trแปฃ FIDO2 ฤ‘แปƒ bแบฃo mแบญt hฦกn nแปฏa cho tร i khoแบฃn cแปงa mรฌnh."
+_permissions:
+  "read:account": "Xem thรดng tin tร i khoแบฃn cแปงa bแบกn"
+  "write:account": "Sแปญa thรดng tin tร i khoแบฃn cแปงa bแบกn"
+  "read:blocks": "Xem danh sรกch ngฦฐแปi bแบกn chแบทn"
+  "write:blocks": "Sแปญa danh sรกch ngฦฐแปi bแบกn chแบทn"
+  "read:drive": "Truy cแบญp tแบญp tin, thฦฐ mแปฅc trong แป” ฤ‘ฤฉa"
+  "write:drive": "Sแปญa vร  xรณa tแบญp tin, thฦฐ mแปฅc trong แป” ฤ‘ฤฉa"
+  "read:favorites": "Xem lฦฐแปฃt thรญch cแปงa tรดi"
+  "write:favorites": "Sแปญa lฦฐแปฃt thรญch cแปงa tรดi"
+  "read:following": "Xem nhแปฏng ngฦฐแปi bแบกn theo dรตi"
+  "write:following": "Theo dรตi hoแบทc ngฦฐng theo dรตi ai ฤ‘รณ"
+  "read:messaging": "Xem lแป‹ch sแปญ chat"
+  "write:messaging": "Soแบกn hoแบทc xรณa tin nhแบฏn"
+  "read:mutes": "Xem nhแปฏng ngฦฐแปi bแบกn แบฉn"
+  "write:mutes": "Sแปญa nhแปฏng ngฦฐแปi bแบกn แบฉn"
+  "write:notes": "Soแบกn hoแบทc xรณa tรบt"
+  "read:notifications": "Xem thรดng bรกo cแปงa tรดi"
+  "write:notifications": "Quแบฃn lรฝ thรดng bรกo cแปงa tรดi"
+  "read:reactions": "Xem lฦฐแปฃt biแปƒu cแบฃm cแปงa tรดi"
+  "write:reactions": "Sแปญa lฦฐแปฃt biแปƒu cแบฃm cแปงa tรดi"
+  "write:votes": "Bรฌnh chแปn"
+  "read:pages": "Xem trang cแปงa tรดi"
+  "write:pages": "Sแปญa hoแบทc xรณa trang cแปงa tรดi"
+  "read:page-likes": "Xem lฦฐแปฃt thรญch trรชn trang cแปงa tรดi"
+  "write:page-likes": "Sแปญa lฦฐแปฃt thรญch cแปงa tรดi trรชn trang"
+  "read:user-groups": "Xem nhรณm cแปงa tรดi"
+  "write:user-groups": "Sแปญa hoแบทc xรณa nhรณm cแปงa tรดi"
+  "read:channels": "Xem kรชnh cแปงa tรดi"
+  "write:channels": "Sแปญa kรชnh cแปงa tรดi"
+  "read:gallery": "Xem kho แบฃnh cแปงa tรดi"
+  "write:gallery": "Sแปญa kho แบฃnh cแปงa tรดi"
+  "read:gallery-likes": "Xem danh sรกch cรกc tรบt ฤ‘รฃ thรญch trong thฦฐ viแป‡n cแปงa tรดi"
+  "write:gallery-likes": "Sแปญa danh sรกch cรกc tรบt ฤ‘รฃ thรญch trong thฦฐ viแป‡n cแปงa tรดi"
+_auth:
+  shareAccess: "Bแบกn cรณ muแป‘n cho phรฉp \"{name}\" truy cแบญp vร o tร i khoแบฃn nร y khรดng?"
+  shareAccessAsk: "Bแบกn cรณ chแบฏc muแป‘n cho phรฉp แปฉng dแปฅng nร y truy cแบญp vร o tร i khoแบฃn cแปงa mรฌnh khรดng?"
+  permissionAsk: "แปจng dแปฅng nร y yรชu cแบงu cรกc quyแปn sau"
+  pleaseGoBack: "Vui lรฒng quay lแบกi แปฉng dแปฅng"
+  callback: "Quay lแบกi แปฉng dแปฅng"
+  denied: "Truy cแบญp bแป‹ tแปซ chแป‘i"
+_antennaSources:
+  all: "Toร n bแป™ tรบt"
+  homeTimeline: "Tรบt tแปซ nhแปฏng ngฦฐแปi ฤ‘รฃ theo dรตi"
+  users: "Tรบt tแปซ nhแปฏng ngฦฐแปi cแปฅ thแปƒ"
+  userList: "Tรบt tแปซ danh sรกch ngฦฐแปi dรนng cแปฅ thแปƒ"
+  userGroup: "Tรบt tแปซ ngฦฐแปi dรนng trong mแป™t nhรณm cแปฅ thแปƒ"
+_weekday:
+  sunday: "Chแปง Nhแบญt"
+  monday: "Thแปฉ Hai"
+  tuesday: "Thแปฉ Ba"
+  wednesday: "Thแปฉ Tฦฐ"
+  thursday: "Thแปฉ Nฤƒm"
+  friday: "Thแปฉ Sรกu"
+  saturday: "Thแปฉ Bแบฃy"
 _widgets:
+  memo: "Tรบt ฤ‘รฃ ghim"
   notifications: "Thรดng bรกo"
+  timeline: "Bแบฃng tin"
+  calendar: "Lแป‹ch"
+  trends: "Xu hฦฐฦกฬng"
+  clock: "ฤแป“ng hแป“"
+  rss: "Trรฌnh ฤ‘แปc RSS"
+  activity: "Hoแบกt ฤ‘แป™ng"
+  photos: "Kho แบฃnh"
+  digitalClock: "ฤแป“ng hแป“ sแป‘"
+  federation: "Liรชn hแปฃp"
+  postForm: "Mแบซu ฤ‘ฤƒng"
+  slideshow: "Trรฌnh chiแบฟu"
+  button: "Nรบt"
+  onlineUsers: "Ai ฤ‘ang online"
+  jobQueue: "Cรดng viแป‡c chแป xแปญ lรฝ"
+  serverMetric: "Thแป‘ng kรช mรกy chแปง"
+  aiscript: "AiScript console"
+  aichan: "Ai"
+_cw:
+  hide: "แบจn"
+  show: "Tแบฃi thรชm"
+  chars: "{count} kรฝ tแปฑ"
+  files: "{count} tแบญp tin"
+_poll:
+  noOnlyOneChoice: "Cแบงn รญt nhแบฅt hai lแปฑa chแปn."
+  choiceN: "Lแปฑa chแปn {n}"
+  noMore: "Bแบกn khรดng thแปƒ thรชm lแปฑa chแปn"
+  canMultipleVote: "Cho phรฉp chแปn nhiแปu lแปฑa chแปn"
+  expiration: "Thแปi hแบกn"
+  infinite: "Vฤฉnh viแป…n"
+  at: "Kแบฟt thรบc vร o..."
+  after: "Kแบฟt thรบc sau..."
+  deadlineDate: "Ngร y kแบฟt thรบc"
+  deadlineTime: "giแป"
+  duration: "Thแปi hแบกn"
+  votesCount: "{n} bรฌnh chแปn"
+  totalVotes: "{n} tแป•ng bรฌnh chแปn"
+  vote: "Bรฌnh chแปn"
+  showResult: "Xem kแบฟt quแบฃ"
+  voted: "ฤรฃ bรฌnh chแปn"
+  closed: "ฤรฃ kแบฟt thรบc"
+  remainingDays: "{d} ngร y {h} giแป cรฒn lแบกi"
+  remainingHours: "{h} giแป {m} phรบt cรฒn lแบกi"
+  remainingMinutes: "{m} phรบt {s}s cรฒn lแบกi"
+  remainingSeconds: "{s}s cรฒn lแบกi"
+_visibility:
+  public: "Cรดng khai"
+  publicDescription: "Mแปi ngฦฐแปi ฤ‘แปu cรณ thแปƒ ฤ‘แปc tรบt cแปงa bแบกn"
+  home: "Trang chรญnh"
+  homeDescription: "Chแป‰ ฤ‘ฤƒng lรชn bแบฃng tin nhร "
+  followers: "Ngฦฐแปi theo dรตi"
+  followersDescription: "Dร nh riรชng cho ngฦฐแปi theo dรตi"
+  specified: "Nhแบฏn riรชng"
+  specifiedDescription: "Chแป‰ ngฦฐแปi ฤ‘ฦฐแปฃc nhแบฏc ฤ‘แบฟn mแป›i thแบฅy"
+  localOnly: "Chแป‰ trรชn mรกy chแปง"
+  localOnlyDescription: "Khรดng hiแปƒn thแป‹ vแป›i ngฦฐแปi แปŸ mรกy chแปง khรกc"
+_postForm:
+  replyPlaceholder: "Trแบฃ lแปi tรบt nร y"
+  quotePlaceholder: "Trรญch dแบซn tรบt nร y"
+  channelPlaceholder: "ฤฤƒng lรชn mแป™t kรชnh"
+  _placeholders:
+    a: "Bแบกn ฤ‘ang ฤ‘แป‹nh lร m gรฌ?"
+    b: "Hรดm nay bแบกn cรณ gรฌ vui?"
+    c: "Bแบกn ฤ‘ang nghฤฉ gรฌ?"
+    d: "Bแบกn muแป‘n nรณi gรฌ?"
+    e: "Bแบฏt ฤ‘แบงu viแบฟt..."
+    f: "ฤang chแป bแบกn viแบฟt..."
 _profile:
+  name: "Tรชn"
   username: "Tรชn ngฦฐแปi dรนng"
+  description: "Tiแปƒu sแปญ"
+  youCanIncludeHashtags: "Bแบกn cรณ thแปƒ dรนng hashtag trong tiแปƒu sแปญ."
+  metadata: "Thรดng tin bแป• sung"
+  metadataEdit: "Sแปญa thรดng tin bแป• sung"
+  metadataDescription: "Sแปญ dแปฅng phแบงn nร y, bแบกn cรณ thแปƒ hiแปƒn thแป‹ cรกc mแปฅc thรดng tin bแป• sung trong hแป“ sฦก cแปงa mรฌnh."
+  metadataLabel: "Nhรฃn"
+  metadataContent: "Nแป™i dung"
+  changeAvatar: "ฤแป•i แบฃnh ฤ‘แบกi diแป‡n"
+  changeBanner: "ฤแป•i แบฃnh bรฌa"
+_exportOrImport:
+  allNotes: "Toร n bแป™ tรบt"
+  followingList: "ฤang theo dรตi"
+  muteList: "แบจn"
+  blockingList: "Chแบทn"
+  userLists: "Danh sรกch"
+  excludeMutingUsers: "Loแบกi trแปซ nhแปฏng ngฦฐแปi dรนng bแป‹ แบฉn"
+  excludeInactiveUsers: "Loแบกi trแปซ nhแปฏng ngฦฐแปi dรนng khรดng hoแบกt ฤ‘แป™ng"
+_charts:
+  federation: "Liรชn hแปฃp"
+  apRequest: "Yรชu cแบงu"
+  usersIncDec: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng ngฦฐแปi dรนng"
+  usersTotal: "Tแป•ng sแป‘ ngฦฐแปi dรนng"
+  activeUsers: "Sแป‘ ngฦฐแปi ฤ‘ang hoแบกt ฤ‘แป™ng"
+  notesIncDec: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng tรบt"
+  localNotesIncDec: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng tรบt mรกy chแปง nร y"
+  remoteNotesIncDec: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng tรบt tแปซ mรกy chแปง khรกc"
+  notesTotal: "Tแป•ng sแป‘ sรบt"
+  filesIncDec: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng tแบญp tin"
+  filesTotal: "Tแป•ng sแป‘ tแบญp tin"
+  storageUsageIncDec: "Sแปฑ khรกc biแป‡t vแป dung lฦฐแปฃng lฦฐu trแปฏ"
+  storageUsageTotal: "Tแป•ng dung lฦฐแปฃng lฦฐu trแปฏ"
+_instanceCharts:
+  requests: "Lฦฐแปฃt yรชu cแบงu"
+  users: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng ngฦฐแปi dรนng"
+  usersTotal: "Sแป‘ lฦฐแปฃng ngฦฐแปi dรนng tรญch lลฉy"
+  notes: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng tรบt"
+  notesTotal: "Sแป‘ lฦฐแปฃng tรบt tรญch lลฉy"
+  ff: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng ngฦฐแปi dรนng ฤ‘ฦฐแปฃc theo dรตi/ngฦฐแปi theo dรตi"
+  ffTotal: "Sแป‘ lฦฐแปฃng ngฦฐแปi dรนng ฤ‘ฦฐแปฃc theo dรตi/ngฦฐแปi theo dรตi tรญch lลฉy"
+  cacheSize: "Sแปฑ khรกc biแป‡t vแป dung lฦฐแปฃng bแป™ nhแป› ฤ‘แป‡m"
+  cacheSizeTotal: "Dung lฦฐแปฃng bแป™ nhแป› ฤ‘แป‡m tรญch lลฉy"
+  files: "Sแปฑ khรกc biแป‡t vแป sแป‘ lฦฐแปฃng tแบญp tin"
+  filesTotal: "Sแป‘ lฦฐแปฃng tแบญp tin tรญch lลฉy"
+_timelines:
+  home: "Trang chรญnh"
+  local: "Mรกy chแปง nร y"
+  social: "Xรฃ hแป™i"
+  global: "Liรชn hแปฃp"
+_pages:
+  newPage: "Tแบกo Trang mแป›i"
+  editPage: "Sแปญa Trang nร y"
+  readPage: "Xem mรฃ nguแป“n Trang nร y"
+  created: "Trang ฤ‘รฃ ฤ‘ฦฐแปฃc tแบกo thร nh cรดng"
+  updated: "Trang ฤ‘รฃ ฤ‘ฦฐแปฃc cแบญp nhแบญt thร nh cรดng"
+  deleted: "Trang ฤ‘รฃ ฤ‘ฦฐแปฃc xรณa thร nh cรดng"
+  pageSetting: "Cร i ฤ‘แบทt trang"
+  nameAlreadyExists: "URL Trang ฤ‘รฃ tแป“n tแบกi"
+  invalidNameTitle: "URL Trang khรดng hแปฃp lแป‡"
+  invalidNameText: "Khรดng ฤ‘ฦฐแปฃc ฤ‘แปƒ trแป‘ng tแปฑa ฤ‘แป Trang"
+  editThisPage: "Sแปญa Trang nร y"
+  viewSource: "Xem maฬƒ nguรดฬ€n"
+  viewPage: "Xem trang cแปงa tรดi"
+  like: "Thรญch"
+  unlike: "Bแป thรญch"
+  my: "Trang cแปงa tรดi"
+  liked: "Trang ฤ‘รฃ thรญch"
+  featured: "Nแป•i tiแบฟng"
+  inspector: "Thanh tra"
+  contents: "Nแป™i dung"
+  content: "Chแบทn Trang"
+  variables: "Biแบฟn thแปƒ"
+  title: "Tแปฑa ฤ‘แป"
+  url: "URL Trang"
+  summary: "Mรด tแบฃ Trang"
+  alignCenter: "Cฤƒn giแปฏa"
+  hideTitleWhenPinned: "แบจn tแปฑa ฤ‘แป Trang khi ghim lรชn hแป“ sฦก"
+  font: "Phรดng chแปฏ"
+  fontSerif: "Serif"
+  fontSansSerif: "Sans Serif"
+  eyeCatchingImageSet: "ฤแบทt แบฃnh thu nhแป"
+  eyeCatchingImageRemove: "Xรณa แบฃnh thu nhแป"
+  chooseBlock: "Thรชm khแป‘i"
+  selectType: "Chแปn kiแปƒu"
+  enterVariableName: "Nhแบญp tรชn mแป™t biแบฟn thแปƒ"
+  variableNameIsAlreadyUsed: "Tรชn biแบฟn thแปƒ nร y ฤ‘รฃ ฤ‘ฦฐแปฃc sแปญ dแปฅng"
+  contentBlocks: "Nแป™i dung"
+  inputBlocks: "Nhแบญp"
+  specialBlocks: "ฤแบทc biแป‡t"
+  blocks:
+    text: "Vฤƒn bแบฃn"
+    textarea: "Khu vแปฑc vฤƒn bแบฃn"
+    section: "Mแปฅc "
+    image: "Hรฌnh แบฃnh"
+    button: "Nรบt"
+    if: "Nแบฟu"
+    _if:
+      variable: "Biแบฟn thแปƒ"
+    post: "Mแบซu ฤ‘ฤƒng"
+    _post:
+      text: "Nแป™i dung"
+      attachCanvasImage: "ฤรญnh kรจm hรฌnh canva"
+      canvasId: "ID Canva"
+    textInput: "Vฤƒn bแบฃn ฤ‘แบงu vร o"
+    _textInput:
+      name: "Tรชn biแบฟn thแปƒ"
+      text: "Tแปฑa ฤ‘แป"
+      default: "Giรก trแป‹ mแบทc ฤ‘แป‹nh"
+    textareaInput: "Vฤƒn bแบฃn nhiแปu dรฒng ฤ‘แบงu vร o"
+    _textareaInput:
+      name: "Tรชn biแบฟn thแปƒ"
+      text: "Tแปฑa ฤ‘แป"
+      default: "Giรก trแป‹ mแบทc ฤ‘แป‹nh"
+    numberInput: "ฤแบงu vร o sแป‘"
+    _numberInput:
+      name: "Tรชn biแบฟn thแปƒ"
+      text: "Tแปฑa ฤ‘แป"
+      default: "Giรก trแป‹ mแบทc ฤ‘แป‹nh"
+    canvas: "Canva"
+    _canvas:
+      id: "ID Canva"
+      width: "Chiแปu rแป™ng"
+      height: "Chiแปu cao"
+    note: "Tรบt ฤ‘รฃ nhรบng"
+    _note:
+      id: "ID tรบt"
+      idDescription: "Ngoร i ra, bแบกn cรณ thแปƒ dรกn URL tรบt vร o ฤ‘รขy."
+      detailed: "Xem chi tiแบฟt"
+    switch: "Chuyแปƒn ฤ‘แป•i"
+    _switch:
+      name: "Tรชn biแบฟn thแปƒ"
+      text: "Tแปฑa ฤ‘แป"
+      default: "Giรก trแป‹ mแบทc ฤ‘แป‹nh"
+    counter: "Bแป™ ฤ‘แบฟm"
+    _counter:
+      name: "Tรชn biแบฟn thแปƒ"
+      text: "Tแปฑa ฤ‘แป"
+      inc: "Bฦฐแป›c"
+    _button:
+      text: "Tแปฑa ฤ‘แป"
+      colored: "Vแป›i mร u"
+      action: "Thao tรกc khi nhแบฅn nรบt"
+      _action:
+        dialog: "Hiแป‡n hแป™p thoแบกi"
+        _dialog:
+          content: "Nแป™i dung"
+        resetRandom: "ฤแบทt lแบกi seed ngแบซu nhiรชn"
+        pushEvent: "Gแปญi mแป™t sแปฑ kiแป‡n"
+        _pushEvent:
+          event: "Tรชn sแปฑ kiแป‡n"
+          message: "Tin nhแบฏn hiแปƒn thแป‹ khi kรญch hoแบกt"
+          variable: "Biแปƒn thแปƒ ฤ‘แปƒ gแปญi"
+          no-variable: "Khรดng"
+        callAiScript: "Gแปi AiScript"
+        _callAiScript:
+          functionName: "Tรชn tรญnh nฤƒng"
+    radioButton: "Lแปฑa chแปn"
+    _radioButton:
+      name: "Tรชn biแบฟn thแปƒ"
+      title: "Tแปฑa ฤ‘แป"
+      values: "Phรขn tรกch cรกc mแปฅc bแบฑng cรกch xuแป‘ng dรฒng"
+      default: "Giรก trแป‹ mแบทc ฤ‘แป‹nh"
+  script:
+    categories:
+      flow: "ฤiแปu khiแปƒn"
+      logical: "Hoแบกt ฤ‘แป™ng logic"
+      operation: "Tรญnh toรกn"
+      comparison: "So sรกnh"
+      random: "Ngแบซu nhiรชn"
+      value: "Giรก trแป‹"
+      fn: "Tรญnh nฤƒng"
+      text: "Tรกc vแปฅ vฤƒn bแบฃn"
+      convert: "Chuyแปƒn ฤ‘แป•i"
+      list: "Danh sรกch"
+    blocks:
+      text: "Vฤƒn bแบฃn"
+      multiLineText: "Vฤƒn bแบฃn (nhiแปu dรฒng)"
+      textList: "Vฤƒn bแบฃn liแป‡t kรช"
+      _textList:
+        info: "Phรขn tรกch mแปฅc bแบฑng cรกch xuแป‘ng dรฒng"
+      strLen: "ฤแป™ dร i vฤƒn bแบฃn"
+      _strLen:
+        arg1: "Vฤƒn bแบฃn"
+      strPick: "Trรญch xuแบฅt chuแป—i"
+      _strPick:
+        arg1: "Vฤƒn bแบฃn"
+        arg2: "Vแป‹ trรญ chuแป—i"
+      strReplace: "Thay thแบฟ chuแป—i"
+      _strReplace:
+        arg1: "Nแป™i dung"
+        arg2: "Vฤƒn bแบฃn thay thแบฟ"
+        arg3: "Thay thแบฟ bแบฑng"
+      strReverse: "Lแบญt vฤƒn bแบฃn"
+      _strReverse:
+        arg1: "Vฤƒn bแบฃn"
+      join: "Nแป‘i vฤƒn bแบฃn"
+      _join:
+        arg1: "Danh sรกch"
+        arg2: "Phรขn cรกch"
+      add: "Cแป™ng"
+      _add:
+        arg1: "A"
+        arg2: "B"
+      subtract: "Trแปซ"
+      _subtract:
+        arg1: "A"
+        arg2: "B"
+      multiply: "Nhรขn"
+      _multiply:
+        arg1: "A"
+        arg2: "B"
+      divide: "Chia"
+      _divide:
+        arg1: "A"
+        arg2: "B"
+      mod: "Phแบงn cรฒn lแบกi"
+      _mod:
+        arg1: "A"
+        arg2: "B"
+      round: "Lร m trรฒn thแบญp phรขn"
+      _round:
+        arg1: "Sแป‘"
+      eq: "A vร  B bแบฑng nhau"
+      _eq:
+        arg1: "A"
+        arg2: "B"
+      notEq: "A vร  B khรกc nhau"
+      _notEq:
+        arg1: "A"
+        arg2: "B"
+      and: "A Vร€ B"
+      _and:
+        arg1: "A"
+        arg2: "B"
+      or: "A HOแบถC B"
+      _or:
+        arg1: "A"
+        arg2: "B"
+      lt: "< A nhแป hฦกn B"
+      _lt:
+        arg1: "A"
+        arg2: "B"
+      gt: "> A lแป›n hฦกn B"
+      _gt:
+        arg1: "A"
+        arg2: "B"
+      ltEq: "<= A nhแป hฦกn hoแบทc bแบฑng B"
+      _ltEq:
+        arg1: "A"
+        arg2: "B"
+      gtEq: ">= A lแป›n hฦกn hoแบทc bแบฑng B"
+      _gtEq:
+        arg1: "A"
+        arg2: "B"
+      if: "Nhรกnh"
+      _if:
+        arg1: "Nแบฟu"
+        arg2: "Sau ฤ‘รณ"
+        arg3: "Khรกc"
+      not: "KHร”NG"
+      _not:
+        arg1: "KHร”NG"
+      random: "Ngแบซu nhiรชn"
+      _random:
+        arg1: "Xรกc suแบฅt"
+      rannum: "Sแป‘ ngแบซu nhiรชn"
+      _rannum:
+        arg1: "Giรก trแป‹ tแป‘i thiแปƒu"
+        arg2: "Giรก trแป‹ tแป‘i ฤ‘a"
+      randomPick: "Chแปn ngแบซu nhiรชn tแปซ danh sรกch"
+      _randomPick:
+        arg1: "Danh sรกch"
+      dailyRandom: "Ngแบซu nhiรชn (ฤแป•i mแป—i ngฦฐแปi mแป™t lแบงn mแป—i ngร y)"
+      _dailyRandom:
+        arg1: "Xรกc suแบฅt"
+      dailyRannum: "Sแป‘ ngแบซu nhiรชn (ฤแป•i mแป—i ngฦฐแปi mแป™t lแบงn mแป—i ngร y)"
+      _dailyRannum:
+        arg1: "Giรก trแป‹ tแป‘i thiแปƒu"
+        arg2: "Giรก trแป‹ tแป‘i ฤ‘a"
+      dailyRandomPick: "Chแปn ngแบซu nhiรชn tแปซ mแป™t danh sรกch (ฤแป•i mแป—i ngฦฐแปi mแป™t lแบงn mแป—i ngร y)"
+      _dailyRandomPick:
+        arg1: "Danh sรกch"
+      seedRandom: "Ngแบซu nhiรชn (vแป›i seed)"
+      _seedRandom:
+        arg1: "Seed"
+        arg2: "Xรกc suแบฅt"
+      seedRannum: "Sแป‘ ngแบซu nhiรชn (vแป›i seed)"
+      _seedRannum:
+        arg1: "Seed"
+        arg2: "Giรก trแป‹ tแป‘i thiแปƒu"
+        arg3: "Giรก trแป‹ tแป‘i ฤ‘a"
+      seedRandomPick: "Chแปn ngแบซu nhiรชn tแปซ danh sรกch (vแป›i seed)"
+      _seedRandomPick:
+        arg1: "Seed"
+        arg2: "Danh sรกch"
+      DRPWPM: "Chแปn ngแบซu nhiรชn tแปซ danh sรกch nแบทng (ฤแป•i mแป—i ngฦฐแปi mแป™t lแบงn mแป—i ngร y)"
+      _DRPWPM:
+        arg1: "Vฤƒn bแบฃn liแป‡t kรช"
+      pick: "Chแปn tแปซ danh sรกch"
+      _pick:
+        arg1: "Danh sรกch"
+        arg2: "Vแป‹ trรญ"
+      listLen: "Lแบฅy ฤ‘แป™ dร i danh sรกch"
+      _listLen:
+        arg1: "Danh sรกch"
+      number: "Sแป‘"
+      stringToNumber: "Chแปฏ thร nh sแป‘"
+      _stringToNumber:
+        arg1: "Vฤƒn bแบฃn"
+      numberToString: "Sแป‘ thร nh chแปฏ"
+      _numberToString:
+        arg1: "Sแป‘"
+      splitStrByLine: "Phรขn cรกch vฤƒn bแบฃn bแบฑng cรกch xuแป‘ng dรฒng"
+      _splitStrByLine:
+        arg1: "Vฤƒn bแบฃn"
+      ref: "Biแบฟn thแปƒ"
+      aiScriptVar: "Biแปƒn thแปƒ AiScript"
+      fn: "Tรญnh nฤƒng"
+      _fn:
+        slots: "Chแป—"
+        slots-info: "Phรขn cรกch chแป— bแบฑng cรกch xuแป‘ng dรฒng"
+        arg1: "ฤแบงu ra"
+      for: "ฤ‘แปƒ-Lแบทp lแบกi"
+      _for:
+        arg1: "Sแป‘ lแบงn lแบทp lแบกi"
+        arg2: "Hร nh ฤ‘แป™ng"
+    typeError: "Chแป— {slot} chแบฅp nhแบญn cรกc giรก trแป‹ thuแป™c loแบกi \"{expect}\", nhฦฐng giรก trแป‹ ฤ‘ฦฐแปฃc cung cแบฅp thuแป™c loแบกi \"{actual}\"!"
+    thereIsEmptySlot: "Chแป— {slot} ฤ‘ang trแป‘ng!"
+    types:
+      string: "Vฤƒn bแบฃn"
+      number: "Sแป‘"
+      boolean: "Cแป"
+      array: "Danh sรกch"
+      stringArray: "Vฤƒn bแบฃn liแป‡t kรช"
+    emptySlot: "Chแป— trแป‘ng"
+    enviromentVariables: "Biแบฟn mรดi trฦฐแปng"
+    pageVariables: "Biแบฟn trang"
+    argVariables: "ฤแบงu vร o chแป—"
+_relayStatus:
+  requesting: "ฤang chแป"
+  accepted: "ฤรฃ duyแป‡t"
+  rejected: "ฤรฃ tแปซ chแป‘i"
+_notification:
+  fileUploaded: "ฤรฃ tแบฃi lรชn tแบญp tin"
+  youGotMention: "{name} nhแบฏc ฤ‘แบฟn bแบกn"
+  youGotReply: "{name} trแบฃ lแปi bแบกn"
+  youGotQuote: "{name} trรญch dแบซn tรบt cแปงa bแบกn"
+  youRenoted: "{name} ฤ‘ฤƒng lแบกi tรบt cแปงa bแบกn"
+  youGotPoll: "{name} bรฌnh chแปn tรบt cแปงa bแบกn"
+  youGotMessagingMessageFromUser: "{name} nhแบฏn tin cho bแบกn"
+  youGotMessagingMessageFromGroup: "Mแป™t tin nhแบฏn trong nhรณm {name}"
+  youWereFollowed: "ฤ‘รฃ theo dรตi bแบกn"
+  youReceivedFollowRequest: "Bแบกn vแปซa cรณ mแป™t yรชu cแบงu theo dรตi"
+  yourFollowRequestAccepted: "Yรชu cแบงu theo dรตi cแปงa bแบกn ฤ‘รฃ ฤ‘ฦฐแปฃc chแบฅp nhแบญn"
+  youWereInvitedToGroup: "Bแบกn ฤ‘รฃ ฤ‘ฦฐแปฃc mแปi tham gia nhรณm"
+  pollEnded: "Cuแป™c bรฌnh chแปn ฤ‘รฃ kแบฟt thรบc"
+  emptyPushNotificationMessage: "ฤรฃ cแบญp nhแบญt thรดng bรกo ฤ‘แบฉy"
+  _types:
+    all: "Toร n bแป™"
+    follow: "ฤang theo dรตi"
+    mention: "Nhแบฏc ฤ‘แบฟn"
+    reply: "Lฦฐแปฃt trแบฃ lแปi"
+    renote: "ฤฤƒng lแบกi"
+    quote: "Trรญch dแบซn"
+    reaction: "Biแปƒu cแบฃm"
+    pollVote: "Lฦฐแปฃt bรฌnh chแปn"
+    pollEnded: "Bรฌnh chแปn kแบฟt thรบc"
+    receiveFollowRequest: "Yรชu cแบงu theo dรตi"
+    followRequestAccepted: "Yรชu cแบงu theo dรตi ฤ‘ฦฐแปฃc chแบฅp nhแบญn"
+    groupInvited: "Mแปi vร o nhรณm"
+    app: "Tแปซ app liรชn kแบฟt"
+  _actions:
+    followBack: "ฤ‘รฃ theo dรตi lแบกi bแบกn"
+    reply: "Trแบฃ lแปi"
+    renote: "ฤฤƒng lแบกi"
 _deck:
+  alwaysShowMainColumn: "Luรดn hiแป‡n cแป™t chรญnh"
+  columnAlign: "Cฤƒn cแป™t"
+  columnMargin: "Cฤƒn lแป giแปฏa cรกc cแป™t"
+  columnHeaderHeight: "Chiแปu rแป™ng cแป™t แบฃnh bรฌa"
+  addColumn: "Thรชm cแป™t"
+  swapLeft: "Hoรกn ฤ‘แป•i vแป›i cแป™t bรชn trรกi"
+  swapRight: "Hoรกn ฤ‘แป•i vแป›i cแป™t bรชn phแบฃi"
+  swapUp: "Hoรกn ฤ‘แป•i vแป›i cแป™t trรชn"
+  swapDown: "Hoรกn ฤ‘แป•i vแป›i cแป™t dฦฐแป›i"
+  stackLeft: "Xแบฟp chแป“ng vแป›i cแป™t bรชn trรกi"
+  popRight: "Xแบฟp chแป“ng vแป›i cแป™t bรชn trรกi"
+  profile: "Hแป“ sฦก"
   _columns:
+    main: "Chรญnh"
+    widgets: "Tiแป‡n รญch"
     notifications: "Thรดng bรกo"
+    tl: "Bแบฃng tin"
+    antenna: "Trแบกm phรกt sรณng"
+    list: "Danh sรกch"
+    mentions: "Lฦฐแปฃt nhแบฏc"
+    direct: "Nhแบฏn riรชng"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index f644585835..4953f55280 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -8,7 +8,7 @@ notifications: "้€š็Ÿฅ"
 username: "็”จๆˆทๅ"
 password: "ๅฏ†็ "
 forgotPassword: "ๅฟ˜่ฎฐๅฏ†็ "
-fetchingAsApObject: "ๅœจ่”้‚ฆๅฎ‡ๅฎ™ๆŸฅ่ฏขไธญ..."
+fetchingAsApObject: "ๆญฃๅœจ่”้‚ฆๅฎ‡ๅฎ™ๆŸฅ่ฏขไธญ..."
 ok: "OK"
 gotIt: "ๆˆ‘ๆ˜Ž็™ฝไบ†"
 cancel: "ๅ–ๆถˆ"
@@ -69,7 +69,7 @@ exportRequested: "ๅฏผๅ‡บ่ฏทๆฑ‚ๅทฒๆไบค๏ผŒ่ฟ™ๅฏ่ƒฝ้œ€่ฆ่Šฑไธ€ไบ›ๆ—ถ้—ด๏ผŒๅฏผ
 importRequested: "ๅฏผๅ…ฅ่ฏทๆฑ‚ๅทฒๆไบค๏ผŒ่ฟ™ๅฏ่ƒฝ้œ€่ฆ่Šฑไธ€็‚นๆ—ถ้—ดใ€‚"
 lists: "ๅˆ—่กจ"
 noLists: "ๅˆ—่กจไธบ็ฉบ"
-note: "ๅ‘ๅธ–"
+note: "ๅธ–ๅญ"
 notes: "ๅธ–ๅญ"
 following: "ๅ…ณๆณจไธญ"
 followers: "ๅ…ณๆณจ่€…"
@@ -96,7 +96,7 @@ enterEmoji: "่พ“ๅ…ฅ่กจๆƒ…็ฌฆๅท"
 renote: "่ฝฌๅ‘"
 unrenote: "ๅ–ๆถˆ่ฝฌๅ‘"
 renoted: "ๅทฒ่ฝฌๅ‘ใ€‚"
-cantRenote: "่ฏฅๅธ–ๅญๆ— ๆณ•่ฝฌๅ‘ใ€‚"
+cantRenote: "่ฏฅๅธ–ๆ— ๆณ•่ฝฌๅ‘ใ€‚"
 cantReRenote: "่ฝฌๅ‘ๆ— ๆณ•่ขซๅ†ๆฌก่ฝฌๅ‘ใ€‚"
 quote: "ๅผ•็”จ"
 pinnedNote: "ๅทฒ็ฝฎ้กถ็š„ๅธ–ๅญ"
@@ -155,7 +155,7 @@ searchWith: "ๆœ็ดข:{q}"
 youHaveNoLists: "ๅˆ—่กจไธบ็ฉบ"
 followConfirm: "ไฝ ็กฎๅฎš่ฆๅ…ณๆณจ{name}ๅ—๏ผŸ"
 proxyAccount: "ไปฃ็†่ดฆๆˆท"
-proxyAccountDescription: "ไปฃ็†ๅธๆˆทๆ˜ฏๅœจๆŸไบ›ๆƒ…ๅ†ตไธ‹ๅ……ๅฝ“็”จๆˆท็š„่ฟœ็จ‹ๅ…ณๆณจ่€…็š„ๅธๆˆทใ€‚ ไพ‹ๅฆ‚๏ผŒๅฝ“ไธ€ไธช็”จๆˆทๅˆ—ๅ‡บไธ€ไธช่ฟœ็จ‹็”จๆˆทๆ—ถ๏ผŒๅฆ‚ๆžœๆฒกๆœ‰ไบบ่ทŸ้š่ฏฅๅˆ—ๅ‡บ็š„็”จๆˆท๏ผŒๅˆ™่ฏฅๆดปๅŠจๅฐ†ไธไผšไผ ้€’ๅˆฐ่ฏฅๅฎžไพ‹๏ผŒๅ› ๆญคๅฐ†ไปฃไน‹ไปฅไปฃ็†ๅธๆˆทใ€‚"
+proxyAccountDescription: "ไปฃ็†่ดฆๆˆทๆ˜ฏๅœจๆŸไบ›ๆƒ…ๅ†ตไธ‹ๅ……ๅฝ“็”จๆˆท็š„่ฟœ็จ‹ๅ…ณๆณจ่€…็š„่ดฆๆˆทใ€‚ ไพ‹ๅฆ‚๏ผŒๅฝ“ไธ€ไธช็”จๆˆทๅˆ—ๅ‡บไธ€ไธช่ฟœ็จ‹็”จๆˆทๆ—ถ๏ผŒๅฆ‚ๆžœๆฒกๆœ‰ไบบ่ทŸ้š่ฏฅๅˆ—ๅ‡บ็š„็”จๆˆท๏ผŒๅˆ™่ฏฅๆดปๅŠจๅฐ†ไธไผšไผ ้€’ๅˆฐ่ฏฅๅฎžไพ‹๏ผŒๅ› ๆญคๅฐ†ไปฃไน‹ไปฅไปฃ็†่ดฆๆˆทใ€‚"
 host: "ไธปๆœบๅ"
 selectUser: "้€‰ๆ‹ฉ็”จๆˆท"
 recipient: "ๆ”ถไปถไบบ"
@@ -171,7 +171,7 @@ charts: "ๅ›พ่กจ"
 perHour: "ๆฏๅฐๆ—ถ"
 perDay: "ๆฏๅคฉ"
 stopActivityDelivery: "ๅœๆญขๅ‘้€ๆดปๅŠจ"
-blockThisInstance: "้˜ปๆญขๆญคๅฎžไพ‹"
+blockThisInstance: "้˜ปๆญขๆญคๅฎžไพ‹ๅ‘ๆœฌๅฎžไพ‹ๆŽจๆต"
 operations: "ๆ“ไฝœ"
 software: "่ฝฏไปถ"
 version: "็‰ˆๆœฌ"
@@ -250,7 +250,7 @@ messageRead: "ๅทฒ่ฏป"
 noMoreHistory: "ๆฒกๆœ‰ๆ›ดๅคš็š„ๅŽ†ๅฒ่ฎฐๅฝ•"
 startMessaging: "ๆทปๅŠ ่Šๅคฉ"
 nUsersRead: "{n}ไบบๅทฒ่ฏป"
-agreeTo: "{0}ไบบๅŒๆ„"
+agreeTo: "{0}ๅ‹พ้€‰ๅˆ™่กจ็คบๅทฒ้˜…่ฏปๅนถๅŒๆ„"
 tos: "ๆœๅŠกๆกๆฌพ"
 start: "ๅผ€ๅง‹"
 home: "้ฆ–้กต"
@@ -321,7 +321,7 @@ connectService: "่ฟžๆŽฅ"
 disconnectService: "ๆ–ญๅผ€่ฟžๆŽฅ"
 enableLocalTimeline: "ๅฏ็”จๆœฌๅœฐๆ—ถ้—ด็บฟๅŠŸ่ƒฝ"
 enableGlobalTimeline: "ๅฏ็”จๅ…จๅฑ€ๆ—ถ้—ด็บฟ"
-disablingTimelinesInfo: "ๅณไฝฟๆ—ถ้—ด็บฟๅŠŸ่ƒฝ่ขซ็ฆ็”จ๏ผŒๅ‡บไบŽไพฟๅˆฉๆ€ง็š„ๅŽŸๅ› ๏ผŒ็ฎก็†ๅ‘˜ๅ’Œๆ•ฐๆฎๅ›พ่กจไนŸๅฏไปฅ็ปง็ปญไฝฟ็”จใ€‚"
+disablingTimelinesInfo: "ๅณไฝฟๆ—ถ้—ด็บฟๅŠŸ่ƒฝ่ขซ็ฆ็”จ๏ผŒๅ‡บไบŽๆ–นไพฟ๏ผŒ็ฎก็†ๅ‘˜ๅ’Œๆ•ฐๆฎๅ›พ่กจไนŸๅฏไปฅ็ปง็ปญไฝฟ็”จใ€‚"
 registration: "ๆณจๅ†Œ"
 enableRegistration: "ๅ…่ฎธๆ–ฐ็”จๆˆทๆณจๅ†Œ"
 invite: "้‚€่ฏท"
@@ -440,7 +440,7 @@ strongPassword: "ๅฏ†็ ๅผบๅบฆ๏ผšๅผบ"
 passwordMatched: "ๅฏ†็ ไธ€่‡ด"
 passwordNotMatched: "ๅฏ†็ ไธไธ€่‡ด"
 signinWith: "ไปฅ{x}็™ปๅฝ•"
-signinFailed: "ๆ— ๆณ•็™ปๅฝ•๏ผŒ่ฏทๆฃ€ๆŸฅๆ‚จ็š„็”จๆˆทๅๅ’Œๅฏ†็ ใ€‚"
+signinFailed: "ๆ— ๆณ•็™ปๅฝ•๏ผŒ่ฏทๆฃ€ๆŸฅๆ‚จ็š„็”จๆˆทๅๅ’Œๅฏ†็ ๆ˜ฏๅฆๆญฃ็กฎใ€‚"
 tapSecurityKey: "่ฝป่งฆ็กฌไปถๅฎ‰ๅ…จๅฏ†้’ฅ"
 or: "ๆˆ–่€…"
 language: "่ฏญ่จ€"
@@ -459,7 +459,7 @@ category: "็ฑปๅˆซ"
 tags: "ๆ ‡็ญพ"
 docSource: "ๆ–‡ไปถๆฅๆบ"
 createAccount: "ๆณจๅ†Œ่ดฆๆˆท"
-existingAccount: "็Žฐๆœ‰็š„ๅธๆˆท"
+existingAccount: "็Žฐๆœ‰็š„่ดฆๆˆท"
 regenerate: "้‡ๆ–ฐ็”Ÿๆˆ"
 fontSize: "ๅญ—ไฝ“ๅคงๅฐ"
 noFollowRequests: "ๆฒกๆœ‰ๅ…ณๆณจ็”ณ่ฏท"
@@ -533,7 +533,7 @@ removeAllFollowingDescription: "ๅ–ๆถˆ{host}็š„ๆ‰€ๆœ‰ๅ…ณๆณจ่€…ใ€‚ๅฝ“ๅฎžไพ‹ไธๅญ˜
 userSuspended: "่ฏฅ็”จๆˆทๅทฒ่ขซๅ†ป็ป“ใ€‚"
 userSilenced: "่ฏฅ็”จๆˆทๅทฒ่ขซ็ฆ่จ€ใ€‚"
 yourAccountSuspendedTitle: "่ดฆๆˆทๅทฒ่ขซๅ†ป็ป“"
-yourAccountSuspendedDescription: "็”ฑไบŽ่ฟๅไบ†ๆœๅŠกๅ™จ็š„ๆœๅŠกๆกๆฌพๆˆ–ๅ…ถไป–ๅŽŸๅ› ๏ผŒ่ฏฅ่ดฆๆˆทๅทฒ่ขซๅ†ป็ป“ใ€‚ ๆ‚จๅฏไปฅไธŽ็ฎก็†ๅ‘˜่”็ณปไปฅไบ†่งฃๆ›ดๅคšไฟกๆฏใ€‚ ่ฏทไธ่ฆๅˆ›ๅปบไธ€ไธชๆ–ฐ็š„ๅธๆˆทใ€‚"
+yourAccountSuspendedDescription: "็”ฑไบŽ่ฟๅไบ†ๆœๅŠกๅ™จ็š„ๆœๅŠกๆกๆฌพๆˆ–ๅ…ถไป–ๅŽŸๅ› ๏ผŒ่ฏฅ่ดฆๆˆทๅทฒ่ขซๅ†ป็ป“ใ€‚ ๆ‚จๅฏไปฅไธŽ็ฎก็†ๅ‘˜่”็ณปไปฅไบ†่งฃๆ›ดๅคšไฟกๆฏใ€‚ ่ฏทไธ่ฆๅˆ›ๅปบไธ€ไธชๆ–ฐ็š„่ดฆๆˆทใ€‚"
 menu: "่œๅ•"
 divider: "ๅˆ†ๅ‰ฒ็บฟ"
 addItem: "ๆทปๅŠ ้กน็›ฎ"
@@ -609,7 +609,7 @@ create: "ๅˆ›ๅปบ"
 notificationSetting: "้€š็Ÿฅ่ฎพ็ฝฎ"
 notificationSettingDesc: "้€‰ๆ‹ฉ่ฆๆ˜พ็คบ็š„้€š็Ÿฅ็ฑปๅž‹ใ€‚"
 useGlobalSetting: "ไฝฟ็”จๅ…จๅฑ€่ฎพ็ฝฎ"
-useGlobalSettingDesc: "ๅฏ็”จๆ—ถ๏ผŒๅฐ†ไฝฟ็”จๅธๆˆท้€š็Ÿฅ่ฎพ็ฝฎใ€‚ๅ…ณ้—ญๆ—ถ๏ผŒๅˆ™ๅฏไปฅๅ•็‹ฌ่ฎพ็ฝฎใ€‚"
+useGlobalSettingDesc: "ๅฏ็”จๆ—ถ๏ผŒๅฐ†ไฝฟ็”จ่ดฆๆˆท้€š็Ÿฅ่ฎพ็ฝฎใ€‚ๅ…ณ้—ญๆ—ถ๏ผŒๅˆ™ๅฏไปฅๅ•็‹ฌ่ฎพ็ฝฎใ€‚"
 other: "ๅ…ถไป–"
 regenerateLoginToken: "้‡ๆ–ฐ็”Ÿๆˆ็™ปๅฝ•ไปค็‰Œ"
 regenerateLoginTokenDescription: "้‡ๆ–ฐ็”Ÿๆˆ็”จไบŽ็™ปๅฝ•็š„ๅ†…้ƒจไปค็‰Œใ€‚้€šๅธธๆ‚จไธ้œ€่ฆ่ฟ™ๆ ทๅšใ€‚้‡ๆ–ฐ็”ŸๆˆๅŽ๏ผŒๆ‚จๅฐ†ๅœจๆ‰€ๆœ‰่ฎพๅค‡ไธŠ็™ปๅ‡บใ€‚"
@@ -621,12 +621,12 @@ abuseReports: "ไธพๆŠฅ"
 reportAbuse: "ไธพๆŠฅ"
 reportAbuseOf: "ไธพๆŠฅ{name}"
 fillAbuseReportDescription: "่ฏทๅกซๅ†™ไธพๆŠฅ็š„่ฏฆ็ป†ๅŽŸๅ› ใ€‚ๅฆ‚ๆžœๆœ‰ๅฏนๆ–นๅ‘็š„ๅธ–ๅญ๏ผŒ่ฏทๅŒๆ—ถๅกซๅ†™URLๅœฐๅ€ใ€‚"
-abuseReported: "ๅ†…ๅฎนๅทฒๅ‘้€ใ€‚ๆ„Ÿ่ฐขๆ‚จ็š„ๆŠฅๅ‘Šใ€‚"
-reporter: "ๆŠฅๅ‘Š่€…"
+abuseReported: "ๅ†…ๅฎนๅทฒๅ‘้€ใ€‚ๆ„Ÿ่ฐขๆ‚จๆไบคไฟกๆฏใ€‚"
+reporter: "ไธพๆŠฅ่€…"
 reporteeOrigin: "ไธพๆŠฅๆฅๆบ"
 reporterOrigin: "ไธพๆŠฅ่€…ๆฅๆบ"
-forwardReport: "ๅฐ†ๆŠฅๅ‘Š่ฝฌๅ‘็ป™่ฟœ็จ‹ๅฎžไพ‹"
-forwardReportIsAnonymous: "ๅœจ่ฟœ็จ‹ๅฎžไพ‹ไธŠๆ˜พ็คบ็š„ๆŠฅๅ‘Š่€…ๆ˜ฏๅŒฟๅ็š„็ณป็ปŸ่ดฆๅท๏ผŒ่€Œไธๆ˜ฏๆ‚จ็š„่ดฆๅทใ€‚"
+forwardReport: "ๅฐ†่ฏฅไธพๆŠฅไฟกๆฏ่ฝฌๅ‘็ป™่ฟœ็จ‹ๅฎžไพ‹"
+forwardReportIsAnonymous: "ๅ‹พ้€‰ๅˆ™ๅœจ่ฟœ็จ‹ๅฎžไพ‹ไธŠๆ˜พ็คบ็š„ไธพๆŠฅ่€…ๆ˜ฏๅŒฟๅ็š„็ณป็ปŸ่ดฆๅท๏ผŒ่€Œไธๆ˜ฏๆ‚จ็š„่ดฆๅทใ€‚"
 send: "ๅ‘้€"
 abuseMarkAsResolved: "ๅค„็†ๅฎŒๆฏ•"
 openInNewTab: "ๅœจๆ–ฐๆ ‡็ญพ้กตไธญๆ‰“ๅผ€"
@@ -644,9 +644,9 @@ createNew: "ๆ–ฐๅปบ"
 optional: "ๅฏ้€‰"
 createNewClip: "ๆ–ฐๅปบไนฆ็ญพ"
 public: "ๅ…ฌๅผ€"
-i18nInfo: "Misskeyๅทฒ็ป่ขซๅฟ—ๆ„ฟ่€…ไปฌ็ฟป่ฏ‘ๅˆฐไบ†ๅ„็ง่ฏญ่จ€ใ€‚ๅฆ‚ๆžœไฝ ไนŸๆœ‰ๅ…ด่ถฃ๏ผŒๅฏไปฅ้€š่ฟ‡{link}ๅธฎๅŠฉ็ฟป่ฏ‘ใ€‚"
+i18nInfo: "Misskeyๅทฒ็ป่ขซๅฟ—ๆ„ฟ่€…ไปฌ็ฟป่ฏ‘ๆˆไบ†ๅ„็ง่ฏญ่จ€ใ€‚ๅฆ‚ๆžœไฝ ไนŸๆœ‰ๅ…ด่ถฃ๏ผŒๅฏไปฅ้€š่ฟ‡{link}ๅธฎๅŠฉ็ฟป่ฏ‘ใ€‚"
 manageAccessTokens: "็ฎก็† Access Tokens"
-accountInfo: "ๅธๆˆทไฟกๆฏ"
+accountInfo: "่ดฆๆˆทไฟกๆฏ"
 notesCount: "ๅธ–ๅญๆ•ฐ้‡"
 repliesCount: "ๅ›žๅคๆ•ฐ้‡"
 renotesCount: "่ฝฌๅธ–ๆ•ฐ้‡"
@@ -662,7 +662,7 @@ yes: "ๆ˜ฏ"
 no: "ๅฆ"
 driveFilesCount: "็ฝ‘็›˜็š„ๆ–‡ไปถๆ•ฐ"
 driveUsage: "็ฝ‘็›˜็š„็ฉบ้—ด็”จ้‡"
-noCrawle: "ๆ‹’็ปๆœ็ดขๅผ•ๆ“Ž็š„็ดขๅผ•"
+noCrawle: "่ฆๆฑ‚ๆœ็ดขๅผ•ๆ“Žไธ็ดขๅผ•่ฏฅ็ซ™็‚น"
 noCrawleDescription: "่ฆๆฑ‚ๆœ็ดขๅผ•ๆ“Žไธ่ฆๆ”ถๅฝ•๏ผˆ็ดขๅผ•๏ผ‰ๆ‚จ็š„็”จๆˆท้กต้ข๏ผŒๅธ–ๅญ๏ผŒ้กต้ข็ญ‰ใ€‚"
 lockedAccountInfo: "ๅณไฝฟ้€š่ฟ‡ไบ†ๅ…ณๆณจ่ฏทๆฑ‚๏ผŒๅช่ฆๆ‚จไธๅฐ†ๅธ–ๅญๅฏ่ง่Œƒๅ›ด่ฎพ็ฝฎๆˆโ€œๅ…ณๆณจ่€…โ€๏ผŒไปปไฝ•ไบบ้ƒฝๅฏไปฅ็œ‹ๅˆฐๆ‚จ็š„ๅธ–ๅญใ€‚"
 alwaysMarkSensitive: "้ป˜่ฎคๅฐ†ๅช’ไฝ“ๆ–‡ไปถๆ ‡่ฎฐไธบๆ•ๆ„Ÿๅ†…ๅฎน"
@@ -1087,7 +1087,6 @@ _sfx:
   antenna: "ๅคฉ็บฟๆŽฅๆ”ถ"
   channel: "้ข‘้“้€š็Ÿฅ"
 _ago:
-  unknown: "ๆœช็Ÿฅ"
   future: "ๆœชๆฅ"
   justNow: "ๆœ€่ฟ‘"
   secondsAgo: "{n}็ง’ๅ‰"
@@ -1131,6 +1130,7 @@ _2fa:
   registerKey: "ๆณจๅ†Œๅฏ†้’ฅ"
   step1: "้ฆ–ๅ…ˆ๏ผŒๅœจๆ‚จ็š„่ฎพๅค‡ไธŠๅฎ‰่ฃ…้ชŒ่ฏๅบ”็”จ๏ผŒไพ‹ๅฆ‚{a}ๆˆ–{b}ใ€‚"
   step2: "็„ถๅŽ๏ผŒๆ‰ซๆๅฑๅน•ไธŠๆ˜พ็คบ็š„ไบŒ็ปด็ ใ€‚"
+  step2Url: "ๅœจๆกŒ้ขๅบ”็”จ็จ‹ๅบไธญ่พ“ๅ…ฅไปฅไธ‹URL๏ผš"
   step3: "่พ“ๅ…ฅๆ‚จ็š„ๅบ”็”จๆไพ›็š„ๅŠจๆ€ๅฃไปคไปฅๅฎŒๆˆ่ฎพ็ฝฎใ€‚"
   step4: "ไปŽ็Žฐๅœจๅผ€ๅง‹๏ผŒไปปไฝ•็™ปๅฝ•ๆ“ไฝœ้ƒฝๅฐ†่ฆๆฑ‚ๆ‚จๆไพ›ๅŠจๆ€ๅฃไปคใ€‚"
   securityKeyInfo: "ๆ‚จๅฏไปฅ่ฎพ็ฝฎไฝฟ็”จๆ”ฏๆŒFIDO2็š„็กฌไปถๅฎ‰ๅ…จๅฏ†้’ฅใ€่ฎพๅค‡ไธŠ็š„ๆŒ‡็บนๆˆ–PINๆฅไฟๆŠคๆ‚จ็š„็™ปๅฝ•่ฟ‡็จ‹ใ€‚"
@@ -1615,6 +1615,7 @@ _notification:
   yourFollowRequestAccepted: "ๆ‚จ็š„ๅ…ณๆณจ่ฏทๆฑ‚ๅทฒ้€š่ฟ‡"
   youWereInvitedToGroup: "ๆ‚จๆœ‰ๆ–ฐ็š„็พค็ป„้‚€่ฏท"
   pollEnded: "้—ฎๅท่ฐƒๆŸฅ็ป“ๆžœๅทฒ็”Ÿๆˆใ€‚"
+  emptyPushNotificationMessage: "ๆŽจ้€้€š็Ÿฅๅทฒๆ›ดๆ–ฐ"
   _types:
     all: "ๅ…จ้ƒจ"
     follow: "ๅ…ณๆณจไธญ"
@@ -1629,6 +1630,10 @@ _notification:
     followRequestAccepted: "ๅ…ณๆณจ่ฏทๆฑ‚ๅทฒ้€š่ฟ‡"
     groupInvited: "ๅŠ ๅ…ฅ็พค็ป„้‚€่ฏท"
     app: "ๅ…ณ่”ๅบ”็”จ็š„้€š็Ÿฅ"
+  _actions:
+    followBack: "ๅ›žๅ…ณ"
+    reply: "ๅ›žๅค"
+    renote: "่ฝฌๅ‘"
 _deck:
   alwaysShowMainColumn: "ๆ€ปๆ˜ฏๆ˜พ็คบไธปๅˆ—"
   columnAlign: "ๅˆ—ๅฏน้ฝ"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 18c6f17154..f088fdc0e9 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -135,13 +135,14 @@ emojiName: "่กจๆƒ…็ฌฆ่™Ÿๅ็จฑ"
 emojiUrl: "่กจๆƒ…็ฌฆ่™ŸURL"
 addEmoji: "ๅŠ ๅ…ฅ่กจๆƒ…็ฌฆ่™Ÿ"
 settingGuide: "ๆŽจ่–ฆ่จญๅฎš"
-cacheRemoteFiles: "็ทฉๅญ˜้ž้ ็จ‹ๆช”ๆกˆ"
+cacheRemoteFiles: "ๅฟซๅ–้ ็ซฏๆช”ๆกˆ"
 cacheRemoteFilesDescription: "็ฆ็”จๆญค่จญๅฎšๆœƒๅœๆญข้ ็ซฏๆช”ๆกˆ็š„็ทฉๅญ˜๏ผŒๅพž่€Œ็ฏ€็œๅ„ฒๅญ˜็ฉบ้–“๏ผŒไฝ†่ณ‡ๆ–™ๆœƒๅ› ็›ดๆŽฅ้€ฃ็ทšๅพž่€Œ็”ข็”Ÿ้กๅค–้€ฃๆŽฅๆ•ธๆ“šใ€‚"
 flagAsBot: "ๆญคไฝฟ็”จ่€…ๆ˜ฏๆฉŸๅ™จไบบ"
 flagAsBotDescription: "ๅฆ‚ๆžœๆœฌๅธณๆˆถๆ˜ฏ็”ฑ็จ‹ๅผๆŽงๅˆถ๏ผŒ่ซ‹ๅ•Ÿ็”จๆญค้ธ้ …ใ€‚ๅ•Ÿ็”จๅพŒ๏ผŒๆœƒไฝœ็‚บๆจ™็คบๅนซๅŠฉๅ…ถไป–้–‹็™ผ่€…้˜ฒๆญขๆฉŸๅ™จไบบไน‹้–“็”ข็”Ÿ็„ก้™ไบ’ๅ‹•็š„่กŒ็‚บ๏ผŒไธฆๆœƒ่ชฟๆ•ดMisskeyๅ…ง้ƒจ็ณป็ตฑๅฐ‡ๆœฌๅธณๆˆถ่ญ˜ๅˆฅ็‚บๆฉŸๅ™จไบบ"
 flagAsCat: "ๆญคไฝฟ็”จ่€…ๆ˜ฏ่ฒ“"
 flagAsCatDescription: "ๅฆ‚ๆžœๆƒณๅฐ‡ๆœฌๅธณๆˆถๆจ™็คบ็‚บไธ€้šป่ฒ“๏ผŒ่ซ‹้–‹ๅ•Ÿๆญคๆจ™็คบ"
 flagShowTimelineReplies: "ๅœจๆ™‚้–“่ปธไธŠ้กฏ็คบ่ฒผๆ–‡็š„ๅ›ž่ฆ†"
+flagShowTimelineRepliesDescription: "ๅ•Ÿ็”จๆ™‚๏ผŒๆ™‚้–“็ทš้™คไบ†้กฏ็คบ็”จๆˆถ็š„่ฒผๆ–‡ไปฅๅค–๏ผŒ้‚„ๆœƒ้กฏ็คบ็”จๆˆถๅฐๅ…ถไป–่ฒผๆ–‡็š„ๅ›ž่ฆ†ใ€‚"
 autoAcceptFollowed: "่‡ชๅ‹•่ฟฝ้šจไธญไฝฟ็”จ่€…็š„่ฟฝ้šจ่ซ‹ๆฑ‚"
 addAccount: "ๆทปๅŠ ๅธณๆˆถ"
 loginFailed: "็™ปๅ…ฅๅคฑๆ•—"
@@ -153,8 +154,8 @@ removeWallpaper: "็งป้™คๆกŒๅธƒ"
 searchWith: "ๆœๅฐ‹: {q}"
 youHaveNoLists: "ไฝ ๆฒ’ๆœ‰ไปปไฝ•ๆธ…ๅ–ฎ"
 followConfirm: "ไฝ ็œŸ็š„่ฆ่ฟฝ้šจ{name}ๅ—Ž๏ผŸ"
-proxyAccount: "ไปฃ็†ๅธณ่™Ÿ"
-proxyAccountDescription: "ไปฃ็†ๅธณ่™Ÿๆ˜ฏๅœจๆŸไบ›ๆƒ…ๆณไธ‹ๅ……็•ถๅ…ถไป–ไผบๆœๅ™จ็”จๆˆถ็š„ๅธณ่™Ÿใ€‚ไพ‹ๅฆ‚๏ผŒ็•ถไฝฟ็”จ่€…ๅฐ‡ไธ€ๅ€‹ไพ†่‡ชๅ…ถไป–ไผบๆœๅ™จ็š„ๅธณ่™Ÿๆ”พๅœจๅˆ—่กจไธญๆ™‚๏ผŒ็”ฑๆ–ผๆฒ’ๆœ‰ๅ…ถไป–ไฝฟ็”จ่€…้—œๆณจ่ฉฒๅธณ่™Ÿ๏ผŒ่ฉฒๆŒ‡ไปคไธๆœƒๅ‚ณ้€ๅˆฐ่ฉฒไผบๆœๅ™จไธŠ๏ผŒๅ› ๆญคๆœƒ็”ฑไปฃ็†ๅธณๆˆถ้—œๆณจใ€‚"
+proxyAccount: "ไปฃ็†ๅธณๆˆถ"
+proxyAccountDescription: "ไปฃ็†ๅธณๆˆถๆ˜ฏๅœจๆŸไบ›ๆƒ…ๆณไธ‹ๅ……็•ถๅ…ถไป–ไผบๆœๅ™จ็”จๆˆถ็š„ๅธณๆˆถใ€‚ไพ‹ๅฆ‚๏ผŒ็•ถไฝฟ็”จ่€…ๅฐ‡ไธ€ๅ€‹ไพ†่‡ชๅ…ถไป–ไผบๆœๅ™จ็š„ๅธณๆˆถๆ”พๅœจๅˆ—่กจไธญๆ™‚๏ผŒ็”ฑๆ–ผๆฒ’ๆœ‰ๅ…ถไป–ไฝฟ็”จ่€…้—œๆณจ่ฉฒๅธณๆˆถ๏ผŒ่ฉฒๆŒ‡ไปคไธๆœƒๅ‚ณ้€ๅˆฐ่ฉฒไผบๆœๅ™จไธŠ๏ผŒๅ› ๆญคๆœƒ็”ฑไปฃ็†ๅธณๆˆถ้—œๆณจใ€‚"
 host: "ไธปๆฉŸ"
 selectUser: "้ธๅ–ไฝฟ็”จ่€…"
 recipient: "ๆ”ถไปถไบบ"
@@ -197,7 +198,7 @@ noUsers: "ๆฒ’ๆœ‰ไปปไฝ•ไฝฟ็”จ่€…"
 editProfile: "็ทจ่ผฏๅ€‹ไบบๆช”ๆกˆ"
 noteDeleteConfirm: "็ขบๅฎšๅˆช้™คๆญค่ฒผๆ–‡ๅ—Ž๏ผŸ"
 pinLimitExceeded: "ไธ่ƒฝ็ฝฎ้ ‚ๆ›ดๅคš่ฒผๆ–‡ไบ†"
-intro: "Misskey ้ƒจ็ฝฒๅฎŒๆˆ๏ผ่ซ‹ๅปบ็ซ‹็ฎก็†ๅ“กๅธณ่™Ÿ๏ผ"
+intro: "Misskey ้ƒจ็ฝฒๅฎŒๆˆ๏ผ่ซ‹ๅปบ็ซ‹็ฎก็†ๅ“กๅธณๆˆถใ€‚"
 done: "ๅฎŒๆˆ"
 processing: "่™•็†ไธญ"
 preview: "้ ่ฆฝ"
@@ -236,6 +237,8 @@ resetAreYouSure: "็ขบๅฎš่ฆ้‡่จญๅ—Ž๏ผŸ"
 saved: "ๅทฒๅ„ฒๅญ˜"
 messaging: "ๅ‚ณ้€่จŠๆฏ"
 upload: "ไธŠๅ‚ณ"
+keepOriginalUploading: "ไฟ็•™ๅŽŸๅœ–"
+keepOriginalUploadingDescription: "ไธŠๅ‚ณๅœ–็‰‡ๆ™‚ไฟ็•™ๅŽŸๅง‹ๅœ–็‰‡ใ€‚้—œ้–‰ๆ™‚๏ผŒ็€่ฆฝๅ™จๆœƒๅœจไธŠๅ‚ณๆ™‚็”Ÿๆˆไธ€ๅผต็”จๆ–ผweb็™ผๅธƒ็š„ๅœ–็‰‡ใ€‚"
 fromDrive: "ๅพž้›ฒ็ซฏ็ฉบ้–“"
 fromUrl: "ๅพžURL"
 uploadFromUrl: "ๅพž็ถฒๅ€ไธŠๅ‚ณ"
@@ -357,7 +360,7 @@ enableServiceworker: "้–‹ๅ•Ÿ ServiceWorker"
 antennaUsersDescription: "ๆŒ‡ๅฎš็”จๆ›่กŒ็ฌฆๅˆ†้š”็š„็”จๆˆถๅ"
 caseSensitive: "ๅ€ๅˆ†ๅคงๅฐๅฏซ"
 withReplies: "ๅŒ…ๅซๅ›ž่ฆ†"
-connectedTo: "ๆ‚จ็š„ๅธณ่™Ÿๅทฒ้€ฃๆŽฅๅˆฐไปฅไธ‹็คพไบคๅธณ่™Ÿ"
+connectedTo: "ๆ‚จ็š„ๅธณๆˆถๅทฒ้€ฃๆŽฅๅˆฐไปฅไธ‹็คพไบคๅธณๆˆถ"
 notesAndReplies: "่ฒผๆ–‡่ˆ‡ๅ›ž่ฆ†"
 withFiles: "้™„ไปถ"
 silence: "็ฆ่จ€"
@@ -445,6 +448,7 @@ uiLanguage: "ไป‹้ข่ชž่จ€"
 groupInvited: "ๆ‚จๆœ‰ๆ–ฐ็š„็พค็ต„้‚€่ซ‹"
 aboutX: "้—œๆ–ผ{x}"
 useOsNativeEmojis: "ไฝฟ็”จOSๅŽŸ็”Ÿ่กจๆƒ…็ฌฆ่™Ÿ"
+disableDrawer: "ไธ้กฏ็คบไธ‹ๆ‹‰ๅผ้ธๅ–ฎ"
 youHaveNoGroups: "ๆ‰พไธๅˆฐ็พค็ต„"
 joinOrCreateGroup: "่ซ‹ๅŠ ๅ…ฅ็พๆœ‰็พค็ต„๏ผŒๆˆ–ๅ‰ตๅปบๆ–ฐ็พค็ต„ใ€‚"
 noHistory: "ๆฒ’ๆœ‰ๆญทๅฒ็ด€้Œ„"
@@ -468,7 +472,7 @@ weekOverWeekChanges: "่ˆ‡ไธŠ้€ฑ็›ธๆฏ”"
 dayOverDayChanges: "่ˆ‡ๅ‰ไธ€ๆ—ฅ็›ธๆฏ”"
 appearance: "ๅค–่ง€"
 clientSettings: "็”จๆˆถ็ซฏ่จญๅฎš"
-accountSettings: "ๅธณ่™Ÿ่จญๅฎš"
+accountSettings: "ๅธณๆˆถ่จญๅฎš"
 promotion: "ๆŽจๅปฃ"
 promote: "ๆŽจๅปฃ"
 numberOfDays: "ๆœ‰ๆ•ˆๅคฉๆ•ธ"
@@ -477,6 +481,7 @@ showFeaturedNotesInTimeline: "ๅœจๆ™‚้–“่ปธไธŠ้กฏ็คบ็†ฑ้–€ๆŽจ่–ฆ"
 objectStorage: "Object Storage (็‰ฉไปถๅ„ฒๅญ˜)"
 useObjectStorage: "ไฝฟ็”จObject Storage"
 objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrlDesc: "ๅผ•็”จๆ™‚็š„URLใ€‚ๅฆ‚ๆžœๆ‚จไฝฟ็”จ็š„ๆ˜ฏCDNๆˆ–ๅๅ‘ไปฃ็†๏ผŒ่ฏทๆŒ‡ๅฎšๅ…ถURL๏ผŒไพ‹ๅฆ‚S3๏ผšโ€œhttps://<bucket>.s3.amazonaws.comโ€๏ผŒGCS๏ผšโ€œhttps://storage.googleapis.com/<bucket>โ€"
 objectStorageBucket: "ๅ„ฒๅญ˜็ฉบ้–“๏ผˆBucket๏ผ‰"
 objectStorageBucketDesc: "่ซ‹ๆŒ‡ๅฎšๆ‚จๆญฃๅœจไฝฟ็”จ็š„ๆœๅ‹™็š„ๅญ˜ๅ„ฒๆกถๅ็จฑใ€‚ "
 objectStoragePrefix: "ๅ‰็ถด"
@@ -484,8 +489,11 @@ objectStoragePrefixDesc: "ๅฎƒๅญ˜ๅ„ฒๅœจๆญคๅ‰็ถด็›ฎ้Œ„ไธ‹ใ€‚"
 objectStorageEndpoint: "็ซฏ้ปž๏ผˆEndpoint๏ผ‰"
 objectStorageEndpointDesc: "ๅฆ‚่ฆไฝฟ็”จAWS S3๏ผŒ่ซ‹็•™็ฉบใ€‚ๅฆๅ‰‡่ซ‹ไพ็…งไฝ ไฝฟ็”จ็š„ๆœๅ‹™ๅ•†็š„่ชชๆ˜Žๆ›ธ้€ฒ่กŒ่จญๅฎš๏ผŒไปฅ'<host>'ๆˆ– '<host>:<port>'็š„ๅฝขๅผ่จญๅฎš็ซฏ้ปž๏ผˆEndpoint๏ผ‰ใ€‚"
 objectStorageRegion: "ๅœฐๅŸŸ๏ผˆRegion๏ผ‰"
+objectStorageRegionDesc: "ๆŒ‡ๅฎšไธ€ๅ€‹ๅˆ†ๅ€๏ผŒไพ‹ๅฆ‚โ€œxx-east-1โ€ใ€‚ ๅฆ‚ๆžœๆ‚จไฝฟ็”จ็š„ๆœๅ‹™ๆฒ’ๆœ‰ๅˆ†ๅ€็š„ๆฆ‚ๅฟต๏ผŒ่ซ‹็•™็ฉบๆˆ–ๅกซๅฏซโ€œus-east-1โ€ใ€‚"
 objectStorageUseSSL: "ไฝฟ็”จSSL"
+objectStorageUseSSLDesc: "ๅฆ‚ๆžœไธไฝฟ็”จhttps้€ฒ่กŒAPI้€ฃๆŽฅ๏ผŒ่ซ‹้—œ้–‰"
 objectStorageUseProxy: "ไฝฟ็”จ็ถฒ่ทฏไปฃ็†"
+objectStorageUseProxyDesc: "ๅฆ‚ๆžœไธไฝฟ็”จไปฃ็†้€ฒ่กŒAPI้€ฃๆŽฅ๏ผŒ่ซ‹้—œ้–‰"
 objectStorageSetPublicRead: "ไธŠๅ‚ณๆ™‚่จญๅฎš็‚บ\"public-read\""
 serverLogs: "ไผบๆœๅ™จๆ—ฅ่ชŒ"
 deleteAll: "ๅˆช้™คๆ‰€ๆœ‰่จ˜้Œ„"
@@ -513,6 +521,7 @@ sort: "ๆŽ’ๅบ"
 ascendingOrder: "ๆ˜‡ๅ†ช"
 descendingOrder: "้™ๅ†ช"
 scratchpad: "ๆšซๅญ˜่จ˜ๆ†ถ้ซ”"
+scratchpadDescription: "AiScriptๆŽงๅˆถๅฐ็‚บAiScriptๆไพ›ไบ†ๅฏฆ้ฉ—็’ฐๅขƒใ€‚ๆ‚จๅฏไปฅๅœจๆญค็ทจๅฏซใ€ๅŸท่กŒๅ’Œ็ขบ่ชไปฃ็ขผ่ˆ‡Misskeyไบ’ๅ‹•็š„็ป“ๆžœใ€‚"
 output: "่ผธๅ‡บ"
 script: "่…ณๆœฌ"
 disablePagesScript: "ๅœ็”จ้ ้ข็š„AiScript่…ณๆœฌ"
@@ -523,6 +532,9 @@ removeAllFollowing: "่งฃ้™คๆ‰€ๆœ‰่ฟฝ่นค"
 removeAllFollowingDescription: "่งฃ้™ค{host}ๆ‰€ๆœ‰็š„่ฟฝ่นคใ€‚ๅœจๅฏฆไพ‹ไธๅ†ๅญ˜ๅœจๆ™‚ๅŸท่กŒใ€‚"
 userSuspended: "่ฉฒไฝฟ็”จ่€…ๅทฒ่ขซๅœ็”จ"
 userSilenced: "่ฉฒ็”จๆˆถๅทฒ่ขซ็ฆ่จ€ใ€‚"
+yourAccountSuspendedTitle: "ๅธณๆˆถๅทฒ่ขซๅ‡็ต"
+yourAccountSuspendedDescription: "็”ฑๆ–ผ้•ๅไบ†ไผบๆœๅ™จ็š„ๆœๅ‹™ๆขๆฌพๆˆ–ๅ…ถไป–ๅŽŸๅ› ๏ผŒ่ฉฒๅธณๆˆถๅทฒ่ขซๅ‡็ตใ€‚ ๆ‚จๅฏไปฅ่ˆ‡็ฎก็†ๅ“ก้€ฃ็นซไปฅไบ†่งฃๆ›ดๅคš่จŠๆฏใ€‚ ่ซ‹ไธ่ฆๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ็š„ๅธณๆˆถใ€‚"
+menu: "้ธๅ–ฎ"
 divider: "ๅˆ†ๅ‰ฒ็ทš"
 addItem: "ๆ–ฐๅขž้ …็›ฎ"
 relays: "ไธญ็นผ"
@@ -546,7 +558,7 @@ enterFileDescription: "่ผธๅ…ฅๆจ™้กŒ "
 author: "ไฝœ่€…"
 leaveConfirm: "ๆœ‰ๆœชไฟๅญ˜็š„ๆ›ดๆ”นใ€‚่ฆๆ”พๆฃ„ๅ—Ž๏ผŸ"
 manage: "็ฎก็†"
-plugins: "ๆ’ไปถ"
+plugins: "ๅค–ๆŽ›"
 deck: "ๅคšๆฌ„ๆจกๅผ"
 undeck: "ๅ–ๆถˆๅคšๆฌ„ๆจกๅผ"
 useBlurEffectForModal: "ๅœจๆจกๆ…‹ๆก†ไฝฟ็”จๆจก็ณŠๆ•ˆๆžœ"
@@ -556,10 +568,12 @@ height: "้ซ˜ๅบฆ"
 large: "ๅคง"
 medium: "ไธญ"
 small: "ๅฐ"
+generateAccessToken: "็™ผ่กŒๅญ˜ๅ–ๆฌŠๆ–"
 permission: "ๆฌŠ้™"
 enableAll: "ๅ•Ÿ็”จๅ…จ้ƒจ"
 disableAll: "ๅœ็”จๅ…จ้ƒจ"
-tokenRequested: "ๅ…่จฑๅญ˜ๅ–ๅธณ่™Ÿ"
+tokenRequested: "ๅ…่จฑๅญ˜ๅ–ๅธณๆˆถ"
+pluginTokenRequestedDescription: "ๆญคๅค–ๆŽ›ๅฐ‡ๆ“ๆœ‰ๅœจๆญค่จญๅฎš็š„ๆฌŠ้™ใ€‚"
 notificationType: "้€š็Ÿฅๅฝขๅผ"
 edit: "็ทจ่ผฏ"
 useStarForReactionFallback: "ไปฅโ˜…ไปฃๆ›ฟๆœช็Ÿฅ็š„่กจๆƒ…็ฌฆ่™Ÿ"
@@ -574,8 +588,13 @@ smtpPort: "ๅŸ "
 smtpUser: "ไฝฟ็”จ่€…ๅ็จฑ"
 smtpPass: "ๅฏ†็ขผ"
 emptyToDisableSmtpAuth: "็•™็ฉบไฝฟ็”จ่€…ๅ็จฑๅ’Œๅฏ†็ขผไปฅ้—œ้–‰SMTP้ฉ—่ญ‰ใ€‚"
+smtpSecure: "ๅœจ SMTP ้€ฃๆŽฅไธญไฝฟ็”จ้šฑๅผ SSL/TLS"
+smtpSecureInfo: "ไฝฟ็”จSTARTTLSๆ™‚้—œ้–‰ใ€‚"
 testEmail: "ๆธฌ่ฉฆ้ƒตไปถ็™ผ้€"
-wordMute: "้œ้Ÿณๆ–‡ๅญ—"
+wordMute: "่ขซ้œ้Ÿณ็š„ๆ–‡ๅญ—"
+regexpError: "ๆญฃ่ฆ่กจ้”ๅผ้Œฏ่ชค"
+regexpErrorDescription: "{tab} ้œ้Ÿณๆ–‡ๅญ—็š„็ฌฌ {line} ่กŒ็š„ๆญฃ่ฆ่กจ้”ๅผๆœ‰้Œฏ่ชค๏ผš"
+instanceMute: "ๅฏฆไพ‹็š„้œ้Ÿณ"
 userSaysSomething: "{name}่ชชไบ†ไป€้บผ"
 makeActive: "ๅ•Ÿ็”จ"
 display: "ๆชข่ฆ–"
@@ -606,6 +625,8 @@ abuseReported: "ๅ›žๅ ฑๅทฒ้€ๅ‡บใ€‚ๆ„Ÿ่ฌๆ‚จ็š„ๅ ฑๅ‘Šใ€‚"
 reporter: "ๆชข่ˆ‰่€…"
 reporteeOrigin: "ๆชข่ˆ‰ไพ†ๆบ"
 reporterOrigin: "ๆชข่ˆ‰่€…ไพ†ๆบ"
+forwardReport: "ๅฐ‡ๅ ฑๅ‘Š่ฝ‰้€็ตฆ้ ็ซฏๅฏฆไพ‹"
+forwardReportIsAnonymous: "ๅœจ้ ็ซฏๅฏฆไพ‹ไธŠ็œ‹ไธๅˆฐๆ‚จ็š„่ณ‡่จŠ๏ผŒ้กฏ็คบ็š„ๅ ฑๅ‘Š่€…ๆ˜ฏๅŒฟๅ็š„็ณป็ปŸๅธณๆˆถใ€‚"
 send: "็™ผ้€"
 abuseMarkAsResolved: "่™•็†ๅฎŒ็•ข"
 openInNewTab: "ๅœจๆ–ฐๅˆ†้ ไธญ้–‹ๅ•Ÿ"
@@ -667,6 +688,7 @@ center: "็ฝฎไธญ"
 wide: "ๅฏฌ"
 narrow: "็ช„"
 reloadToApplySetting: "่จญๅฎšๅฐ‡ๆœƒๅœจ้ ้ข้‡ๆ–ฐ่ผ‰ๅ…ฅไน‹ๅพŒ็”Ÿๆ•ˆใ€‚่ฆ็พๅœจๅฐฑ้‡่ผ‰้ ้ขๅ—Ž๏ผŸ"
+needReloadToApply: "ๅฟ…้ ˆ้‡ๆ–ฐ่ผ‰ๅ…ฅๆ‰ๆœƒ็”Ÿๆ•ˆใ€‚"
 showTitlebar: "้กฏ็คบๆจ™้กŒๅˆ—"
 clearCache: "ๆธ…้™คๅฟซๅ–่ณ‡ๆ–™"
 onlineUsersCount: "{n}ไบบๆญฃๅœจ็ทšไธŠ"
@@ -727,6 +749,7 @@ notRecommended: "ไธๆŽจ่–ฆ"
 botProtection: "Bot้˜ฒ่ญท"
 instanceBlocking: "ๅทฒๅฐ้Ž–็š„ๅฏฆไพ‹"
 selectAccount: "้ธๆ“‡ๅธณๆˆถ"
+switchAccount: "ๅˆ‡ๆ›ๅธณๆˆถ"
 enabled: "ๅทฒๅ•Ÿ็”จ"
 disabled: "ๅทฒๅœ็”จ"
 quickAction: "ๅฟซๆทๆ“ไฝœ"
@@ -753,32 +776,92 @@ emailNotConfiguredWarning: "ๆฒ’ๆœ‰่จญๅฎš้›ปๅญ้ƒตไปถๅœฐๅ€"
 ratio: "%"
 previewNoteText: "้ ่ฆฝๆ–‡ๆœฌ"
 customCss: "่‡ชๅฎš็พฉ CSS"
+customCssWarn: "้€™ๅ€‹่จญๅฎšๅฟ…้ ˆ็”ฑๅ…ทๅ‚™็›ธ้—œ็Ÿฅ่ญ˜็š„ไบบๅ“กๆ“ไฝœ๏ผŒไธ็•ถ็š„่จญๅฎšๅฏ่ƒฝๅฏผ่‡ดๅฎขๆˆถ็ซฏ็„กๆณ•ๆญฃๅธธไฝฟ็”จใ€‚"
 global: "ๅ…ฌ้–‹"
+squareAvatars: "้ ญๅƒไปฅๆ–นๅฝข้กฏ็คบ"
 sent: "็™ผ้€"
 received: "ๆ”ถๅ–"
 searchResult: "ๆœๅฐ‹็ตๆžœ"
 hashtags: "#tag"
 troubleshooting: "ๆ•…้šœๆŽ’้™ค"
 useBlurEffect: "ๅœจ UI ไธŠไฝฟ็”จๆจก็ณŠๆ•ˆๆžœ"
+learnMore: "ๆ›ดๅคš่ณ‡่จŠ"
 misskeyUpdated: "Misskey ๆ›ดๆ–ฐๅฎŒๆˆ๏ผ"
+whatIsNew: "้กฏ็คบๆ›ดๆ–ฐ่ณ‡่จŠ"
 translate: "็ฟป่ญฏ"
 translatedFrom: "ๅพž {x} ็ฟป่ญฏ"
 accountDeletionInProgress: "ๆญฃๅœจๅˆช้™คๅธณๆˆถ"
+usernameInfo: "ๅœจไผบๆœๅ™จไธŠๆ‚จ็š„ๅธณๆˆถๆ˜ฏๅ”ฏไธ€็š„่ญ˜ๅˆฅๅ็จฑใ€‚ๆ‚จๅฏไปฅไฝฟ็”จๅญ—ๆฏ (a ~ z, A ~ Z)ใ€ๆ•ธๅญ— (0 ~ 9) ๅ’Œไธ‹ๅบ•็ทš (_)ใ€‚ไน‹ๅพŒๅธณๆˆถๅๆ˜ฏไธ่ƒฝๆ›ดๆ”น็š„ใ€‚"
+aiChanMode: "ๅฐ่—ๆจกๅผ"
+keepCw: "ไฟๆŒCW"
 pubSub: "Pub/Sub ๅธณๆˆถ"
+lastCommunication: "ๆœ€่ฟ‘็š„้€šไฟก"
 resolved: "ๅทฒ่งฃๆฑบ"
 unresolved: "ๆœช่งฃๆฑบ"
 breakFollow: "็งป้™ค่ฟฝ่นค่€…"
+itsOn: "ๅทฒ้–‹ๅ•Ÿ"
+itsOff: "ๅทฒ้—œ้–‰"
+emailRequiredForSignup: "่จปๅ†Šๅธณๆˆถ้œ€่ฆ้›ปๅญ้ƒตไปถๅœฐๅ€"
+unread: "ๆœช่ฎ€"
+filter: "็ฏฉ้ธ"
+controlPanel: "ๆŽงๅˆถๅฐ"
+manageAccounts: "็ฎก็†ๅธณๆˆถ"
+makeReactionsPublic: "ๅฐ‡ๅ›žๆ‡‰่จญ็‚บๅ…ฌ้–‹"
+makeReactionsPublicDescription: "ๅฐ‡ๆ‚จๅš้Ž็š„ๅ›žๆ‡‰่จญ็‚บๅ…ฌ้–‹ๅฏ่ฆ‹ใ€‚"
+classic: "็ถ“ๅ…ธ"
+muteThread: "ๅฐ‡่ฒผๆ–‡ไธฒ่จญ็‚บ้œ้Ÿณ"
+unmuteThread: "ๅฐ‡่ฒผๆ–‡ไธฒ็š„้œ้Ÿณ่งฃ้™ค"
+ffVisibility: "้€ฃๆŽฅ็š„ๅ…ฌ้–‹็ฏ„ๅœ"
+ffVisibilityDescription: "ๆ‚จๅฏไปฅ่จญๅฎšๆ‚จ็š„้—œๆณจ/้—œๆณจ่€…่ณ‡่จŠ็š„ๅ…ฌ้–‹็ฏ„ๅœ"
+continueThread: "ๆŸฅ็œ‹ๆ›ดๅคš่ฒผๆ–‡"
+deleteAccountConfirm: "ๅฐ‡่ฆๅˆช้™คๅธณๆˆถใ€‚ๆ˜ฏๅฆ็ขบๅฎš๏ผŸ"
+incorrectPassword: "ๅฏ†็ขผ้Œฏ่ชคใ€‚"
+voteConfirm: "็ขบๅฎšๆŠ•็ตฆใ€Œ{choice}ใ€๏ผŸ"
 hide: "้šฑ่—"
+leaveGroup: "้›ข้–‹็พค็ต„"
 leaveGroupConfirm: "็ขบๅฎš้›ข้–‹ใ€Œ{name}ใ€๏ผŸ"
+useDrawerReactionPickerForMobile: "ๅœจ็งปๅ‹•่จญๅ‚™ไธŠไฝฟ็”จๆŠฝๅฑœ้กฏ็คบ"
+welcomeBackWithName: "ๆญก่ฟŽๅ›žไพ†๏ผŒ{name}"
+clickToFinishEmailVerification: "้ปžๆ“Š [{ok}] ๅฎŒๆˆ้›ปๅญ้ƒตไปถๅœฐๅ€่ช่ญ‰ใ€‚"
+overridedDeviceKind: "่ฃ็ฝฎ้กžๅž‹"
+smartphone: "ๆ™บๆ…งๅž‹ๆ‰‹ๆฉŸ"
+tablet: "ๅนณๆฟ"
 auto: "่‡ชๅ‹•"
+themeColor: "ไธป้กŒ้ก่‰ฒ"
+size: "ๅคงๅฐ"
+numberOfColumn: "ๅˆ—ๆ•ธ"
 searchByGoogle: "ๆœๅฐ‹"
+instanceDefaultLightTheme: "ๅฏฆไพ‹้ ่จญ็š„ๆทบ่‰ฒไธป้กŒ"
+instanceDefaultDarkTheme: "ๅฏฆไพ‹้ ่จญ็š„ๆทฑ่‰ฒไธป้กŒ"
+instanceDefaultThemeDescription: "่ผธๅ…ฅ็‰ฉไปถๅฝขๅผ็š„ไธป้ข˜ไปฃ็ขผ"
+mutePeriod: "้œ้Ÿณ็š„ๆœŸ้™"
 indefinitely: "็„กๆœŸ้™"
+tenMinutes: "10ๅˆ†้˜"
+oneHour: "1ๅฐๆ™‚"
+oneDay: "1ๅคฉ"
+oneWeek: "1้€ฑ"
+reflectMayTakeTime: "ๅฏ่ƒฝ้œ€่ฆไธ€ไบ›ๆ™‚้–“ๆ‰ๆœƒๅ‡บ็พๆ•ˆๆžœใ€‚"
+failedToFetchAccountInformation: "ๅ–ๅพ—ๅธณๆˆถ่ณ‡่จŠๅคฑๆ•—"
+_emailUnavailable:
+  used: "ๅทฒ็ถ“ๅœจไฝฟ็”จไธญ"
+  format: "ๆ ผๅผ็„กๆ•ˆ"
+  disposable: "ไธๆ˜ฏๆฐธไน…ๅฏ็”จ็š„ๅœฐๅ€"
+  mx: "้ƒตไปถไผบๆœๅ™จไธๆญฃ็ขบ"
+  smtp: "้ƒตไปถไผบๆœๅ™จๆฒ’ๆœ‰ๆ‡‰็ญ”"
 _ffVisibility:
   public: "็™ผไฝˆ"
+  followers: "ๅชๆœ‰้—œๆณจไฝ ็š„็”จๆˆถ่ƒฝ็œ‹ๅˆฐ"
   private: "็งๅฏ†"
 _signup:
   almostThere: "ๅณๅฐ‡ๅฎŒๆˆ"
+  emailAddressInfo: "่ซ‹่ผธๅ…ฅๆ‚จๆ‰€ไฝฟ็”จ็š„้›ปๅญ้ƒตไปถๅœฐๅ€ใ€‚้›ปๅญ้ƒตไปถๅœฐๅ€ไธๆœƒ่ขซๅ…ฌ้–‹ใ€‚"
+  emailSent: "ๅทฒๅฐ‡็ขบ่ช้ƒตไปถ็™ผ้€่‡ณๆ‚จ่ผธๅ…ฅ็š„้›ปๅญ้ƒตไปถๅœฐๅ€ ({email})ใ€‚่ซ‹้–‹ๅ•Ÿ้›ปๅญ้ƒตไปถไธญ็š„้€ฃ็ตไปฅๅฎŒๆˆๅธณๆˆถๅ‰ตๅปบใ€‚"
 _accountDelete:
+  accountDelete: "ๅˆช้™คๅธณๆˆถ"
+  mayTakeTime: "ๅˆช้™คๅธณๆˆถ็š„่™•็†่ฒ ่ท่ผƒๅคง๏ผŒๅฆ‚ๆžœๅธณๆˆถ็”ข็”Ÿ็š„ๅ…งๅฎนๆ•ธ้‡ไธŠ่ˆน็š„ๆช”ๆกˆๆ•ธ้‡่ผƒๅคš็š„่ฉฑ๏ผŒๅฐฑ้œ€่ฆ่Šฑ่ดนไธ€ๆฎตๆ™‚้–“ๆ‰่ƒฝๅฎŒๆˆใ€‚"
+  sendEmail: "ๅธณๆˆถๅˆ ้™คๅฎŒๆˆๅพŒ๏ผŒๅฐ‡ๅ‘่จปๅ†Šๅœฐ้›ปๅญ้ƒตไปถๅœฐๅ€็™ผ้€้€š็Ÿฅใ€‚"
+  requestAccountDelete: "ๅˆช้™คๅธณๆˆถ่ซ‹ๆฑ‚"
+  started: "ๅทฒ้–‹ๅง‹ๅˆช้™คไฝœๆฅญใ€‚"
   inProgress: "ๆญฃๅœจๅˆช้™ค"
 _ad:
   back: "่ฟ”ๅ›ž"
@@ -800,7 +883,7 @@ _email:
 _plugin:
   install: "ๅฎ‰่ฃๅค–ๆŽ›็ต„ไปถ"
   installWarn: "่ซ‹ไธ่ฆๅฎ‰่ฃไพ†ๆบไธๆ˜Ž็š„ๅค–ๆŽ›็ต„ไปถใ€‚"
-  manage: "็ฎก็†ๆ’ไปถ"
+  manage: "็ฎก็†ๅค–ๆŽ›"
 _registry:
   scope: "็ฏ„ๅœ"
   key: "ๆฉŸ็ขผ"
@@ -833,14 +916,21 @@ _mfm:
   link: "้ˆๆŽฅ"
   linkDescription: "ๆ‚จๅฏไปฅๅฐ‡็‰นๅฎš็ฏ„ๅœ็š„ๆ–‡็ซ ่ˆ‡ URL ็›ธ้—œ่ฏใ€‚ "
   bold: "็ฒ—้ซ”"
+  boldDescription: "ๅฏไปฅๅฐ‡ๆ–‡ๅญ—้กฏ็คบไธบ็ฒ—้ซ”ๆฅๅผท่ชฟใ€‚"
   small: "็ธฎๅฐ"
+  smallDescription: "ๅฏไปฅไฝฟๅ…งๅฎนๆ–‡ๅญ—่ฎŠๅฐใ€่ฎŠๆทกใ€‚"
   center: "็ฝฎไธญ"
+  centerDescription: "ๅฏไปฅๅฐ‡ๅ…งๅฎน็ฝฎไธญ้กฏ็คบใ€‚"
   inlineCode: "็จ‹ๅผ็ขผ(ๅ†…ๅตŒ)"
+  inlineCodeDescription: "ๅœจ่กŒๅ…ง็”จ้ซ˜ไบฎๅบฆ้กฏ็คบ๏ผŒไพ‹ๅฆ‚็จ‹ๅผ็ขผ่ชžๆณ•ใ€‚"
   blockCode: "็จ‹ๅผ็ขผ(ๅ€ๅกŠ)"
+  blockCodeDescription: "ๅœจๅ€ๅกŠไธญ็”จ้ซ˜ไบฎๅบฆ้กฏ็คบ๏ผŒไพ‹ๅฆ‚่ค‡ๆ•ธ่กŒ็š„็จ‹ๅผ็ขผ่ชžๆณ•ใ€‚"
   inlineMath: "ๆ•ธๅญธๅ…ฌๅผ(ๅ…งๅตŒ)"
   inlineMathDescription: "้กฏ็คบๅ…งๅตŒ็š„KaTexๆ•ธๅญธๅ…ฌๅผใ€‚"
   blockMath: "ๆ•ธๅญธๅ…ฌๅผ(ๆ–นๅกŠ)"
+  blockMathDescription: "ไปฅๅ€ๅกŠ้กฏ็คบ่ค‡ๆ•ธ่กŒ็š„KaTexๆ•ธๅญธๅผใ€‚"
   quote: "ๅผ•็”จ"
+  quoteDescription: "ๅฏไปฅ็”จไพ†่กจ็คบๅผ•็”จ็š„ๅ†…ๅฎนใ€‚"
   emoji: "่‡ช่จ‚่กจๆƒ…็ฌฆ่™Ÿ"
   emojiDescription: "ๆ‚จๅฏไปฅ้€š้Žๅฐ‡่‡ชๅฎš็พฉ่กจๆƒ…็ฌฆ่™Ÿๅ็จฑๆ‹ฌๅœจๅ†’่™Ÿไธญไพ†้กฏ็คบ่‡ชๅฎš็พฉ่กจๆƒ…็ฌฆ่™Ÿใ€‚ "
   search: "ๆœๅฐ‹"
@@ -849,22 +939,34 @@ _mfm:
   flipDescription: "ๅฐ‡ๅ…งๅฎนไธŠไธ‹ๆˆ–ๅทฆๅณ็ฟป่ฝ‰ใ€‚"
   jelly: "ๅ‹•็•ซ(ๆžœๅ‡)"
   jellyDescription: "้กฏ็คบๆžœๅ‡ไธ€ๆจฃ็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
+  tada: "ๅ‹•็•ซ๏ผˆ้˜๏ฝž๏ผ‰"
+  tadaDescription: "้กฏ็คบใ€Œ้˜๏ฝž๏ผใ€้€™็จฎๆ„Ÿ่ฆบ็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
   jump: "ๅ‹•็•ซ(่ทณๅ‹•)"
+  jumpDescription: "้กฏ็คบ่ทณๅ‹•็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
   bounce: "ๅ‹•็•ซ(ๅๅฝˆ)"
+  bounceDescription: "้กฏ็คบๆœ‰ๅฝˆๆ€ง็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
   shake: "ๅ‹•็•ซ(ๆ–ๆ™ƒ)"
+  shakeDescription: "้กฏ็คบ้กซๆŠ–็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
   twitch: "ๅ‹•็•ซ(้กซๆŠ–)"
   twitchDescription: "้กฏ็คบๅผท็ƒˆ้กซๆŠ–็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
   spin: "ๅ‹•็•ซ(ๆ—‹่ฝ‰)"
   spinDescription: "้กฏ็คบๆ—‹่ฝ‰็š„ๅ‹•็•ซๆ•ˆๆžœใ€‚"
   x2: "ๅคง"
+  x2Description: "ๆ”พๅคง้กฏ็คบๅ…งๅฎนใ€‚"
   x3: "่ผƒๅคง"
   x3Description: "ๆ”พๅคง้กฏ็คบๅ…งๅฎนใ€‚"
   x4: "ๆœ€ๅคง"
   x4Description: "ๅฐ‡้กฏ็คบๅ…งๅฎนๆ”พ่‡ณๆœ€ๅคงใ€‚"
   blur: "ๆจก็ณŠ"
+  blurDescription: "็”ข็”Ÿๆจก็ณŠๆ•ˆๆžœใ€‚ๅฐ†ๆธธๆจ™ๆ”พๅœจไธŠ้ขๅณๅฏๅฐ‡ๅ†…ๅฎน้กฏ็คบๅ‡บไพ†ใ€‚"
   font: "ๅญ—ๅž‹"
   fontDescription: "ๆ‚จๅฏไปฅ่จญๅฎš้กฏ็คบๅ…งๅฎน็š„ๅญ—ๅž‹"
+  rainbow: "ๅฝฉ่™น"
+  rainbowDescription: "็”จๅฝฉ่™น่‰ฒไพ†้กฏ็คบๅ…งๅฎนใ€‚"
+  sparkle: "้–ƒ้–ƒ็™ผๅ…‰"
+  sparkleDescription: "ๆทปๅŠ ้–ƒ้–ƒ็™ผๅ…‰็š„็ฒ’ๅญๆ•ˆๆžœใ€‚"
   rotate: "ๆ—‹่ฝ‰"
+  rotateDescription: "ไปฅๆŒ‡ๅฎš็š„่ง’ๅบฆๆ—‹่ฝ‰ใ€‚"
 _instanceTicker:
   none: "้šฑ่—"
   remote: "ๅ‘้ ็ซฏไฝฟ็”จ่€…้กฏ็คบ"
@@ -884,11 +986,24 @@ _channel:
   usersCount: "ๆœ‰{n}ไบบๅƒ่ˆ‡"
   notesCount: "ๆœ‰{n}ๅ€‹่ฒผๆ–‡"
 _menuDisplay:
+  sideFull: "ๅดๅ‘"
+  sideIcon: "ๅดๅ‘๏ผˆๅœ–็คบ๏ผ‰"
+  top: "้ ‚้ƒจ"
   hide: "้šฑ่—"
 _wordMute:
   muteWords: "ๅŠ ๅ…ฅ้œ้Ÿณๆ–‡ๅญ—"
+  muteWordsDescription: "็”จ็ฉบๆ ผๅˆ†้š”ๆŒ‡ๅฎšAND๏ผŒ็”จๆ›่กŒๅˆ†้š”ๆŒ‡ๅฎšORใ€‚"
+  muteWordsDescription2: "ๅฐ‡้—œ้ตๅญ—็”จๆ–œ็ทšๆ‹ฌ่ตทไพ†่กจ็คบๆญฃ่ฆ่กจ้”ๅผใ€‚"
   softDescription: "้šฑ่—ๆ™‚้–“่ปธไธญๆŒ‡ๅฎšๆขไปถ็š„่ฒผๆ–‡ใ€‚"
+  hardDescription: "ๅ…ทๆœ‰ๆŒ‡ๅฎšๆขไปถ็š„่ฒผๆ–‡ๅฐ‡ไธๆทปๅŠ ๅˆฐๆ™‚้–“่ปธใ€‚ ๅณไฝฟๆ‚จๆ›ดๆ”นๆขไปถ๏ผŒๆœช่ขซๆทปๅŠ ็š„่ฒผๆ–‡ไนŸๆœƒ่ขซๆŽ’้™คๅœจๅค–ใ€‚"
+  soft: "่ปŸๆ€ง้œ้Ÿณ"
+  hard: "็กฌๆ€ง้œ้Ÿณ"
   mutedNotes: "ๅทฒ้œ้Ÿณ็š„่ฒผๆ–‡"
+_instanceMute:
+  instanceMuteDescription: "ๅŒ…ๆ‹ฌๅฐ่ขซ้œ้Ÿณๅฏฆไพ‹ไธŠ็š„็”จๆˆถ็š„ๅ›ž่ฆ†๏ผŒ่ขซ่จญๅฎš็š„ๅฏฆไพ‹ไธŠๆ‰€ๆœ‰่ฒผๆ–‡ๅŠ่ฝ‰็™ผ้ƒฝๆœƒ่ขซ้œ้Ÿณใ€‚"
+  instanceMuteDescription2: "่จญๅฎšๆ™‚ไปฅๆ›่กŒ้€ฒ่กŒๅˆ†้š”"
+  title: "่ขซ่จญๅฎš็š„ๅฏฆไพ‹๏ผŒ่ฒผๆ–‡ๅฐ‡่ขซ้šฑ่—ใ€‚"
+  heading: "ๅฐ‡ๅฏฆไพ‹้œ้Ÿณ"
 _theme:
   explore: "ๅ–ๅพ—ไฝˆๆ™ฏไธป้กŒ"
   install: "ๅฎ‰่ฃไฝˆๆ™ฏไธป้กŒ"
@@ -902,10 +1017,12 @@ _theme:
   invalid: "ไธป้กŒๆ ผๅผ้Œฏ่ชค"
   make: "่ฃฝไฝœไธป้กŒ"
   base: "ๅŸบๆ–ผ"
+  addConstant: "ๆทปๅŠ ๅธธๆ•ธ"
   constant: "ๅธธๆ•ธ"
   defaultValue: "้ ่จญๅ€ผ"
   color: "้ก่‰ฒ"
   refProp: "ๆŸฅ็œ‹ๅฑฌๆ€ง "
+  refConst: "ๆŸฅ็œ‹ๅธธๆ•ธ"
   key: "ๆŒ‰้ต"
   func: "ๅ‡ฝๆ•ฐ"
   funcKind: "ๅŠŸ่ƒฝ้กžๅž‹"
@@ -914,6 +1031,9 @@ _theme:
   alpha: "้€ๆ˜Žๅบฆ"
   darken: "ๆš—ๅบฆ"
   lighten: "ไบฎๅบฆ"
+  inputConstantName: "่ซ‹่ผธๅ…ฅๅธธๆ•ธ็š„ๅ็จฑ"
+  importInfo: "ๆ‚จๅฏไปฅๅœจๆญค่ฒผไธŠไธป้กŒไปฃ็ขผ๏ผŒๅฐ‡ๅ…ถๅŒฏๅ…ฅ็ทจ่ผฏๅ™จไธญ"
+  deleteConstantConfirm: "็ขบๅฎš่ฆๅˆ ้™คๅธธๆ•ธ{const}ๅ—Ž๏ผŸ"
   keys:
     accent: "้‡้ปž่‰ฒๅฝฉ"
     bg: "่ƒŒๆ™ฏ"
@@ -933,6 +1053,7 @@ _theme:
     mention: "ๆๅˆฐ"
     mentionMe: "ๆๅˆฐไบ†ๆˆ‘"
     renote: "่ฝ‰็™ผ่ฒผๆ–‡"
+    modalBg: "ๅฐ่ฉฑๆก†่ƒŒๆ™ฏ"
     divider: "ๅˆ†ๅ‰ฒ็ทš"
     scrollbarHandle: "ๆฒๅ‹•ๆข"
     scrollbarHandleHover: "ๆฒๅ‹•ๆข (ๆผ‚ๆตฎ)"
@@ -966,7 +1087,6 @@ _sfx:
   antenna: "ๅคฉ็ทšๆŽฅๆ”ถ"
   channel: "้ ป้“้€š็Ÿฅ"
 _ago:
-  unknown: "ๆœช็Ÿฅ"
   future: "ๆœชไพ†"
   justNow: "ๅ‰›ๅ‰›"
   secondsAgo: "{n}็ง’ๅ‰"
@@ -1010,9 +1130,13 @@ _2fa:
   registerKey: "่จปๅ†Š้ต"
   step1: "้ฆ–ๅ…ˆ๏ผŒๅœจๆ‚จ็š„่จญๅ‚™ไธŠๅฎ‰่ฃไบŒๆญฅ้ฉ—่ญ‰็จ‹ๅผ๏ผŒไพ‹ๅฆ‚{a}ๆˆ–{b}ใ€‚"
   step2: "็„ถๅพŒ๏ผŒๆŽƒๆ่žขๅน•ไธŠ็š„QR codeใ€‚"
+  step2Url: "ๅœจๆกŒ้ข็‰ˆๆ‡‰็”จไธญ๏ผŒ่ซ‹่ผธๅ…ฅไปฅไธ‹็š„URL๏ผš"
+  step3: "่ผธๅ…ฅๆ‚จ็š„Appๆไพ›็š„ๆฌŠๆ–ไปฅๅฎŒๆˆ่จญๅฎšใ€‚"
+  step4: "ๅพž็พๅœจ้–‹ๅง‹๏ผŒไปปไฝ•็™ปๅ…ฅๆ“ไฝœ้ƒฝๅฐ‡่ฆๆฑ‚ๆ‚จๆไพ›ๆฌŠๆ–ใ€‚"
+  securityKeyInfo: "ๆ‚จๅฏไปฅ่จญๅฎšไฝฟ็”จๆ”ฏๆดFIDO2็š„็กฌ้ซ”ๅฎ‰ๅ…จ้Ž–ใ€็ต‚็ซฏ่จญๅ‚™็š„ๆŒ‡็บน่ช่ญ‰ๆˆ–่€…PIN็ขผไพ†็™ปๅ…ฅใ€‚"
 _permissions:
-  "read:account": "ๆŸฅ็œ‹ๅธณๆˆถไฟกๆฏ"
-  "write:account": "ๆ›ดๆ”นๅธณๆˆถไฟกๆฏ"
+  "read:account": "ๆŸฅ็œ‹ๆˆ‘็š„ๅธณๆˆถ่ณ‡่จŠ"
+  "write:account": "ๆ›ดๆ”นๆˆ‘็š„ๅธณๆˆถ่ณ‡่จŠ"
   "read:blocks": "ๅทฒๅฐ้Ž–็”จๆˆถๅๅ–ฎ"
   "write:blocks": "็ทจ่ผฏๅทฒๅฐ้Ž–็”จๆˆถๅๅ–ฎ"
   "read:drive": "ๅญ˜ๅ–้›ฒ็ซฏ็กฌ็ขŸ"
@@ -1039,6 +1163,10 @@ _permissions:
   "write:user-groups": "็ทจ่ผฏไฝฟ็”จ่€…็พค็ต„"
   "read:channels": "ๅทฒๆŸฅ็œ‹็š„้ ป้“"
   "write:channels": "็ทจ่ผฏ้ ป้“"
+  "read:gallery": "็€่ฆฝๅœ–ๅบซ"
+  "write:gallery": "ๆ“ไฝœๅœ–ๅบซ"
+  "read:gallery-likes": "่ฎ€ๅ–ๅ–œๆญก็š„ๅœ–็‰‡"
+  "write:gallery-likes": "ๆ“ไฝœๅ–œๆญก็š„ๅœ–็‰‡"
 _auth:
   shareAccess: "่ฆๆŽˆๆฌŠใ€Œโ€œ{name}โ€ใ€ๅญ˜ๅ–ๆ‚จ็š„ๅธณๆˆถๅ—Ž๏ผŸ"
   shareAccessAsk: "ๆ‚จ็ขบๅฎš่ฆๆŽˆๆฌŠ้€™ๅ€‹ๆ‡‰็”จ็จ‹ๅผไฝฟ็”จๆ‚จ็š„ๅธณๆˆถๅ—Ž๏ผŸ"
@@ -1078,6 +1206,8 @@ _widgets:
   onlineUsers: "็ทšไธŠ็š„็”จๆˆถ"
   jobQueue: "ไฝ‡ๅˆ—"
   serverMetric: "ๆœๅ‹™ๅ™จๆŒ‡ๆจ™ "
+  aiscript: "AiScriptๆŽงๅˆถๅฐ"
+  aichan: "ๅฐ่—"
 _cw:
   hide: "้šฑ่—"
   show: "็€่ฆฝๆ›ดๅคš"
@@ -1103,12 +1233,15 @@ _poll:
   closed: "ๅทฒ็ตๆŸ"
   remainingDays: "{d}ๅคฉ{h}ๅฐๆ™‚ๅพŒ็ตๆŸ"
   remainingHours: "{h}ๅฐๆ™‚{m}ๅˆ†ๅพŒ็ตๆŸ"
+  remainingMinutes: "{m}ๅˆ†{s}็ง’ๅพŒ็ตๆŸ"
   remainingSeconds: "{s}็ง’ๅพŒๆˆชๆญข"
 _visibility:
   public: "ๅ…ฌ้–‹"
   publicDescription: "็™ผๅธƒ็ตฆๆ‰€ๆœ‰็”จๆˆถ "
   home: "้ฆ–้ "
+  homeDescription: "ๅƒ…็™ผ้€่‡ณ้ฆ–้ ็š„ๆ™‚้–“่ปธ"
   followers: "่ฟฝ้šจ่€…"
+  followersDescription: "ๅƒ…็™ผ้€่‡ณ้—œๆณจ่€…"
   specified: "ๆŒ‡ๅฎšไฝฟ็”จ่€…"
   specifiedDescription: "ๅƒ…็™ผ้€่‡ณๆŒ‡ๅฎšไฝฟ็”จ่€…"
   localOnly: "ๅƒ…้™ๆœฌๅœฐ"
@@ -1131,6 +1264,7 @@ _profile:
   youCanIncludeHashtags: "ไฝ ไนŸๅฏไปฅๅœจใ€Œ้—œๆ–ผๆˆ‘ใ€ไธญๅŠ ไธŠ #tag"
   metadata: "้€ฒ้šŽ่ณ‡่จŠ"
   metadataEdit: "็ทจ่ผฏ้€ฒ้šŽ่ณ‡่จŠ"
+  metadataDescription: "ๅฏไปฅๅœจๅ€‹ไบบ่ณ‡ๆ–™ไธญไปฅ่กจๆ ผๅฝขๅผ้กฏ็คบๅ…ถไป–่ณ‡่จŠใ€‚"
   metadataLabel: "ๆจ™็ฑค"
   metadataContent: "ๅ†…ๅฎน"
   changeAvatar: "ๆ›ดๆ›ๅคง้ ญ่ฒผ"
@@ -1141,6 +1275,8 @@ _exportOrImport:
   muteList: "้œ้Ÿณ"
   blockingList: "ๅฐ้Ž–"
   userLists: "ๆธ…ๅ–ฎ"
+  excludeMutingUsers: "ๆŽ’้™ค่ขซ้œ้Ÿณ็š„็”จๆˆถ"
+  excludeInactiveUsers: "ๆŽ’้™คไธๆดป่บๅธณๆˆถ"
 _charts:
   federation: "็ซ™ๅฐ่ฏ้‚ฆ"
   apRequest: "่ซ‹ๆฑ‚"
@@ -1418,6 +1554,7 @@ _pages:
       _seedRandomPick:
         arg1: "็จฎๅญ"
         arg2: "ๆธ…ๅ–ฎ"
+      DRPWPM: "ไปŽๆฉŸ็Ž‡ๅˆ—่กจไธญ้šจๆฉŸ้ธๆ“‡๏ผˆๆฏๅ€‹็”จๆˆทๆฏๅคฉ๏ผ‰"
       _DRPWPM:
         arg1: "ๅญ—ไธฒไธฒๅˆ—"
       pick: "ๅพžๆธ…ๅ–ฎไธญ้ธๅ–"
@@ -1448,6 +1585,8 @@ _pages:
       _for:
         arg1: "้‡่ค‡ๆฌกๆ•ธ"
         arg2: "่™•็†"
+    typeError: "ๆงฝๅƒๆ•ธ{slot}้œ€่ฆๅ‚ณๅ…ฅโ€œ{expect}โ€๏ผŒไฝ†ๆ˜ฏๅฏฆ้š›ๅ‚ณๅ…ฅ็‚บโ€œ{actual}โ€๏ผ"
+    thereIsEmptySlot: "ๅƒๆ•ธ{slot}ๆ˜ฏ็ฉบ็š„๏ผ"
     types:
       string: "ๅญ—ไธฒ"
       number: "ๆ•ฐๅ€ผ"
@@ -1470,10 +1609,13 @@ _notification:
   youRenoted: "{name} ่ฝ‰็™ผไบ†ไฝ ็š„่ฒผๆ–‡"
   youGotPoll: "{name}ๅทฒๆŠ•็ฅจ"
   youGotMessagingMessageFromUser: "{name}็™ผ้€็ตฆๆ‚จ็š„่จŠๆฏ"
+  youGotMessagingMessageFromGroup: "{name}็™ผ้€็ตฆๆ‚จ็š„่จŠๆฏ"
   youWereFollowed: "ๆ‚จๆœ‰ๆ–ฐ็š„่ฟฝ้šจ่€…"
   youReceivedFollowRequest: "ๆ‚จๆœ‰ๆ–ฐ็š„่ฟฝ้šจ่ซ‹ๆฑ‚"
   yourFollowRequestAccepted: "ๆ‚จ็š„่ฟฝ้šจ่ซ‹ๆฑ‚ๅทฒ้€š้Ž"
   youWereInvitedToGroup: "ๆ‚จๆœ‰ๆ–ฐ็š„็พค็ต„้‚€่ซ‹"
+  pollEnded: "ๅ•ๅท่ชฟๆŸฅๅทฒ็”ข็”Ÿ็ตๆžœ"
+  emptyPushNotificationMessage: "ๆŽจ้€้€š็Ÿฅๅทฒๆ›ดๆ–ฐ"
   _types:
     all: "ๅ…จ้ƒจ "
     follow: "่ฟฝ้šจไธญ"
@@ -1483,10 +1625,15 @@ _notification:
     quote: "ๅผ•็”จ"
     reaction: "ๅๆ‡‰"
     pollVote: "็ตฑ่จˆๅทฒๆŠ•็ฅจๆ•ธ"
+    pollEnded: "ๅ•ๅท่ชฟๆŸฅ็ตๆŸ"
     receiveFollowRequest: "ๅทฒๆ”ถๅˆฐ่ฟฝ้šจ่ซ‹ๆฑ‚"
     followRequestAccepted: "่ฟฝ้šจ่ซ‹ๆฑ‚ๅทฒๆŽฅๅ—"
     groupInvited: "ๅŠ ๅ…ฅ็คพ็พค้‚€่ซ‹"
     app: "ๆ‡‰็”จ็จ‹ๅผ้€š็Ÿฅ"
+  _actions:
+    followBack: "ๅ›ž้—œ"
+    reply: "ๅ›ž่ฆ†"
+    renote: "่ฝ‰็™ผ"
 _deck:
   alwaysShowMainColumn: "็ธฝๆ˜ฏ้กฏ็คบไธปๆฌ„"
   columnAlign: "ๅฐ้ฝŠๆฌ„ไฝ"
diff --git a/package.json b/package.json
index 606e2b7332..e3340005ad 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "12.110.1",
+	"version": "12.111.0",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",
@@ -19,10 +19,10 @@
 		"watch": "npm run dev",
 		"dev": "node ./scripts/dev.js",
 		"lint": "node ./scripts/lint.js",
-		"cy:open": "cypress open",
+		"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
 		"cy:run": "cypress run",
 		"e2e": "start-server-and-test start:test http://localhost:61812 cy:run",
-		"mocha": "cd packages/backend && cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",
+		"mocha": "cd packages/backend && cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" npx mocha",
 		"test": "npm run mocha",
 		"format": "gulp format",
 		"clean": "node ./scripts/clean.js",
@@ -30,8 +30,6 @@
 		"cleanall": "npm run clean-all"
 	},
 	"dependencies": {
-		"@types/gulp": "4.0.9",
-		"@types/gulp-rename": "2.0.1",
 		"execa": "5.1.1",
 		"gulp": "4.0.2",
 		"gulp-cssnano": "2.1.3",
@@ -41,10 +39,12 @@
 		"js-yaml": "4.1.0"
 	},
 	"devDependencies": {
-		"@typescript-eslint/parser": "5.18.0",
+		"@types/gulp": "4.0.9",
+		"@types/gulp-rename": "2.0.1",
+		"@typescript-eslint/parser": "5.27.1",
 		"cross-env": "7.0.3",
-		"cypress": "9.5.3",
+		"cypress": "10.0.3",
 		"start-server-and-test": "1.14.0",
-		"typescript": "4.6.3"
+		"typescript": "4.7.3"
 	}
 }
diff --git a/packages/backend/.eslintrc.cjs b/packages/backend/.eslintrc.cjs
index e2e31e9e33..5a06889dcd 100644
--- a/packages/backend/.eslintrc.cjs
+++ b/packages/backend/.eslintrc.cjs
@@ -6,4 +6,27 @@ module.exports = {
 	extends: [
 		'../shared/.eslintrc.js',
 	],
+	rules: {
+		'import/order': ['warn', {
+			'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
+			'pathGroups': [
+				{
+					'pattern': '@/**',
+					'group': 'external',
+					'position': 'after'
+				}
+			],
+		}],
+		'no-restricted-globals': [
+			'error',
+			{
+				'name': '__dirname',
+				'message': 'Not in ESModule. Use `import.meta.url` instead.'
+			},
+			{
+				'name': '__filename',
+				'message': 'Not in ESModule. Use `import.meta.url` instead.'
+			}
+	]
+	},
 };
diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json
index 26628066eb..87c571cfd6 100644
--- a/packages/backend/.mocharc.json
+++ b/packages/backend/.mocharc.json
@@ -5,6 +5,6 @@
 		"loader=./test/loader.js"
 	],
 	"slow": 1000,
-	"timeout": 35000,
+	"timeout": 10000,
 	"exit": true
 }
diff --git a/packages/backend/.vscode/settings.json b/packages/backend/.vscode/settings.json
index df3bf05071..9fb3b29d4a 100644
--- a/packages/backend/.vscode/settings.json
+++ b/packages/backend/.vscode/settings.json
@@ -2,5 +2,9 @@
 	"typescript.tsdk": "node_modules\\typescript\\lib",
 	"path-intellisense.mappings": {
 		"@": "${workspaceRoot}/packages/backend/src/"
+	},
+	"editor.formatOnSave": true,
+	"editor.codeActionsOnSave": {
+		"source.fixAll": true
 	}
 }
diff --git a/packages/backend/migration/1651224615271-foreign-key.js b/packages/backend/migration/1651224615271-foreign-key.js
new file mode 100644
index 0000000000..44ba7fb6c4
--- /dev/null
+++ b/packages/backend/migration/1651224615271-foreign-key.js
@@ -0,0 +1,89 @@
+export class foreignKeyReports1651224615271 {
+    name = 'foreignKeyReports1651224615271'
+
+    async up(queryRunner) {
+        await Promise.all([
+            queryRunner.query(`ALTER INDEX "public"."IDX_seoignmeoprigmkpodgrjmkpormg" RENAME TO "IDX_c8cc87bd0f2f4487d17c651fbf"`),
+            queryRunner.query(`DROP INDEX "public"."IDX_note_on_channelId_and_id_desc"`),
+
+            // remove unnecessary default null, see also down
+            queryRunner.query(`ALTER TABLE "user" ALTER COLUMN "followersUri" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "session" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "appId" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "name" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "description" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "access_token" ALTER COLUMN "iconUrl" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareName" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "softwareVersion" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "name" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "description" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerName" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "maintainerEmail" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "iconUrl" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "faviconUrl" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "themeColor" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "clip" ALTER COLUMN "description" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "channelId" DROP DEFAULT`),
+            queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" DROP DEFAULT`),
+
+            queryRunner.query(`CREATE INDEX "IDX_315c779174fe8247ab324f036e" ON "drive_file" ("isLink")`),
+            queryRunner.query(`CREATE INDEX "IDX_f22169eb10657bded6d875ac8f" ON "note" ("channelId")`),
+            queryRunner.query(`CREATE INDEX "IDX_a9021cc2e1feb5f72d3db6e9f5" ON "abuse_user_report" ("targetUserId")`),
+
+            queryRunner.query(`DELETE FROM "abuse_user_report" WHERE "targetUserId" NOT IN (SELECT "id" FROM "user")`).then(() => {
+                queryRunner.query(`ALTER TABLE "abuse_user_report" ADD CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+            }),
+
+            queryRunner.query(`ALTER TABLE "poll" ADD CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b" UNIQUE ("noteId")`),
+            queryRunner.query(`ALTER TABLE "user_keypair" ADD CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6" UNIQUE ("userId")`),
+            queryRunner.query(`ALTER TABLE "user_profile" ADD CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9" UNIQUE ("userId")`),
+            queryRunner.query(`ALTER TABLE "user_publickey" ADD CONSTRAINT "UQ_10c146e4b39b443ede016f6736d" UNIQUE ("userId")`),
+            queryRunner.query(`ALTER TABLE "promo_note" ADD CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c" UNIQUE ("noteId")`),
+
+            queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_3126dd7c502c9e4d7597ef7ef10" TO "FK_a9ca79ad939bf06066b81c9d3aa"`),
+
+            queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" ADD VALUE 'pollEnded' AFTER 'pollVote'`),
+        ]);
+    }
+
+    async down(queryRunner) {
+        await Promise.all([
+            // There is no ALTER TYPE REMOVE VALUE query, so the reverse operation is a bit more complex
+            queryRunner.query(`UPDATE "user_profile" SET "mutingNotificationTypes" = array_remove("mutingNotificationTypes", 'pollEnded')`)
+            .then(() =>
+                queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app')`)
+            ).then(() =>
+                queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`)
+            ).then(() =>
+                queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`)
+            ).then(() =>
+                queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`)
+            ).then(() =>
+                queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`)
+            ).then(() =>
+                queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`)
+            ),
+
+            queryRunner.query(`ALTER TABLE "page" RENAME CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" TO "FK_3126dd7c502c9e4d7597ef7ef10"`),
+
+            queryRunner.query(`ALTER TABLE "promo_note" DROP CONSTRAINT "UQ_e263909ca4fe5d57f8d4230dd5c"`),
+            queryRunner.query(`ALTER TABLE "user_publickey" DROP CONSTRAINT "UQ_10c146e4b39b443ede016f6736d"`),
+            queryRunner.query(`ALTER TABLE "user_profile" DROP CONSTRAINT "UQ_51cb79b5555effaf7d69ba1cff9"`),
+            queryRunner.query(`ALTER TABLE "user_keypair" DROP CONSTRAINT "UQ_f4853eb41ab722fe05f81cedeb6"`),
+            queryRunner.query(`ALTER TABLE "poll" DROP CONSTRAINT "UQ_da851e06d0dfe2ef397d8b1bf1b"`),
+
+            queryRunner.query(`ALTER TABLE "abuse_user_report" ALTER COLUMN "comment" SET DEFAULT '{}'`),
+            queryRunner.query(`ALTER TABLE "abuse_user_report" DROP CONSTRAINT "FK_a9021cc2e1feb5f72d3db6e9f5f"`),
+
+            queryRunner.query(`DROP INDEX "public"."IDX_a9021cc2e1feb5f72d3db6e9f5"`),
+            queryRunner.query(`DROP INDEX "public"."IDX_f22169eb10657bded6d875ac8f"`),
+            queryRunner.query(`DROP INDEX "public"."IDX_315c779174fe8247ab324f036e"`),
+
+            /* DEFAULT's are not set again because if the column can be NULL, then DEFAULT NULL is not necessary.
+            see also https://github.com/typeorm/typeorm/issues/7579#issuecomment-835423615 */
+
+            queryRunner.query(`CREATE INDEX "IDX_note_on_channelId_and_id_desc" ON "note" ("id", "channelId") `),
+            queryRunner.query(`ALTER INDEX "public"."IDX_c8cc87bd0f2f4487d17c651fbf" RENAME TO "IDX_seoignmeoprigmkpodgrjmkpormg"`),
+        ]);
+    }
+}
diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js
new file mode 100644
index 0000000000..8da1fd7fbb
--- /dev/null
+++ b/packages/backend/migration/1652859567549-uniform-themecolor.js
@@ -0,0 +1,36 @@
+import tinycolor from 'tinycolor2';
+
+export class uniformThemecolor1652859567549 {
+	name = 'uniformThemecolor1652859567549'
+
+	async up(queryRunner) {
+		const formatColor = (color) => {
+			let tc = new tinycolor(color);
+			if (tc.isValid()) {
+				return tc.toHexString();
+			} else {
+				return null;
+			}
+		};
+
+		await queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL')
+		.then(instances => Promise.all(instances.map(instance => {
+			// update theme color to uniform format, e.g. #00ff00
+			// invalid theme colors get set to null
+			return queryRunner.query('UPDATE "instance" SET "themeColor" = $1 WHERE "id" = $2', [formatColor(instance.themeColor), instance.id]);
+		})));
+
+		// also fix own theme color
+		await queryRunner.query('SELECT "themeColor" FROM "meta" WHERE "themeColor" IS NOT NULL LIMIT 1')
+		.then(metas => {
+			if (metas.length > 0) {
+				return queryRunner.query('UPDATE "meta" SET "themeColor" = $1', [formatColor(metas[0].themeColor)]);
+			}
+		});
+	}
+
+	async down(queryRunner) {
+		// The original representation is not stored, so migrating back is not possible.
+		// The new format also works in older versions so this is not a problem.
+	}
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 314818f80b..2186dcc6a9 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -6,7 +6,7 @@
 		"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
 		"watch": "node watch.mjs",
 		"lint": "eslint --quiet \"src/**/*.ts\"",
-		"mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
+		"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
 		"test": "npm run mocha"
 	},
 	"resolutions": {
@@ -14,71 +14,25 @@
 		"lodash": "^4.17.21"
 	},
 	"dependencies": {
-		"@discordapp/twemoji": "13.1.1",
+		"@bull-board/koa": "3.11.1",
+		"@discordapp/twemoji": "14.0.2",
 		"@elastic/elasticsearch": "7.11.0",
 		"@koa/cors": "3.1.0",
 		"@koa/multer": "3.0.0",
 		"@koa/router": "9.0.1",
-		"@sinonjs/fake-timers": "9.1.1",
+		"@peertube/http-signature": "1.6.0",
+		"@sinonjs/fake-timers": "9.1.2",
 		"@syuilo/aiscript": "0.11.1",
-		"@types/bcryptjs": "2.4.2",
-		"@types/bull": "3.15.8",
-		"@types/cbor": "6.0.0",
-		"@types/escape-regexp": "0.0.1",
-		"@types/is-url": "1.2.30",
-		"@types/js-yaml": "4.0.5",
-		"@types/jsdom": "16.2.14",
-		"@types/jsonld": "1.5.6",
-		"@types/koa": "2.13.4",
-		"@types/koa-bodyparser": "4.3.7",
-		"@types/koa-cors": "0.0.2",
-		"@types/koa-favicon": "2.0.21",
-		"@types/koa-logger": "3.1.2",
-		"@types/koa-mount": "4.0.1",
-		"@types/koa-send": "4.1.3",
-		"@types/koa-views": "7.0.0",
-		"@types/koa__cors": "3.1.1",
-		"@types/koa__multer": "2.0.4",
-		"@types/koa__router": "8.0.11",
-		"@types/mocha": "9.1.0",
-		"@types/node": "17.0.23",
-		"@types/node-fetch": "3.0.3",
-		"@types/nodemailer": "6.4.4",
-		"@types/oauth": "0.9.1",
-		"@types/parse5": "6.0.3",
-		"@types/portscanner": "2.1.1",
-		"@types/pug": "2.0.6",
-		"@types/punycode": "2.1.0",
-		"@types/qrcode": "1.4.2",
-		"@types/random-seed": "0.3.3",
-		"@types/ratelimiter": "3.4.3",
-		"@types/redis": "4.0.11",
-		"@types/rename": "1.0.4",
-		"@types/sanitize-html": "2.6.2",
-		"@types/sharp": "0.30.1",
-		"@types/sinonjs__fake-timers": "8.1.2",
-		"@types/speakeasy": "2.0.7",
-		"@types/tinycolor2": "1.4.3",
-		"@types/tmp": "0.2.3",
-		"@types/uuid": "8.3.4",
-		"@types/web-push": "3.3.2",
-		"@types/websocket": "1.0.5",
-		"@types/ws": "8.5.3",
-		"@typescript-eslint/eslint-plugin": "5.18.0",
-		"@typescript-eslint/parser": "5.18.0",
-		"@bull-board/koa": "3.10.3",
 		"abort-controller": "3.0.0",
 		"ajv": "8.11.0",
-		"archiver": "5.3.0",
+		"archiver": "5.3.1",
 		"autobind-decorator": "2.4.0",
 		"autwh": "0.1.0",
-		"aws-sdk": "2.1111.0",
+		"aws-sdk": "2.1152.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.10.0",
-		"bull": "4.8.1",
+		"bull": "4.8.3",
 		"cacheable-lookup": "6.0.4",
-		"cafy": "15.2.1",
 		"cbor": "8.1.0",
 		"chalk": "5.0.1",
 		"chalk-template": "0.4.0",
@@ -88,22 +42,19 @@
 		"date-fns": "2.28.0",
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.13.0",
-		"eslint-plugin-import": "2.26.0",
 		"feed": "4.2.2",
-		"file-type": "17.1.1",
+		"file-type": "17.1.2",
 		"fluent-ffmpeg": "2.1.2",
-		"got": "12.0.3",
+		"got": "12.1.0",
 		"hpagent": "0.1.2",
-		"http-signature": "1.3.6",
-		"ip-cidr": "3.0.4",
+		"ip-cidr": "3.0.10",
 		"is-svg": "4.3.2",
 		"js-yaml": "4.1.0",
 		"jsdom": "19.0.0",
 		"json5": "2.2.1",
 		"json5-loader": "4.0.1",
-		"jsonld": "5.2.0",
-		"jsrsasign": "8.0.20",
+		"jsonld": "6.0.0",
+		"jsrsasign": "10.5.24",
 		"koa": "2.13.4",
 		"koa-bodyparser": "4.3.0",
 		"koa-favicon": "2.1.0",
@@ -113,19 +64,18 @@
 		"koa-send": "5.0.1",
 		"koa-slow": "2.1.0",
 		"koa-views": "7.0.2",
-		"mfm-js": "0.21.0",
+		"mfm-js": "0.22.1",
 		"mime-types": "2.1.35",
 		"misskey-js": "0.0.14",
-		"mocha": "9.2.2",
+		"mocha": "10.0.0",
 		"ms": "3.0.0-canary.1",
 		"multer": "1.4.4",
 		"nested-property": "4.0.0",
-		"node-fetch": "3.2.3",
-		"nodemailer": "6.7.3",
+		"node-fetch": "3.2.6",
+		"nodemailer": "6.7.5",
 		"os-utils": "0.0.14",
 		"parse5": "6.0.1",
 		"pg": "8.7.3",
-		"portscanner": "2.2.0",
 		"private-ip": "2.3.3",
 		"probe-image-size": "7.2.3",
 		"promise-limit": "2.7.0",
@@ -144,35 +94,83 @@
 		"rndstr": "1.0.0",
 		"s-age": "1.1.2",
 		"sanitize-html": "2.7.0",
-		"semver": "7.3.6",
-		"sharp": "0.30.3",
+		"semver": "7.3.7",
+		"sharp": "0.29.3",
 		"speakeasy": "2.0.0",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"style-loader": "3.3.1",
-		"summaly": "2.5.0",
+		"summaly": "2.5.1",
 		"syslog-pro": "1.0.0",
-		"systeminformation": "5.11.9",
+		"systeminformation": "5.11.16",
 		"tinycolor2": "1.4.2",
 		"tmp": "0.2.1",
-		"ts-loader": "9.2.8",
-		"ts-node": "10.7.0",
-		"tsc-alias": "1.4.1",
-		"tsconfig-paths": "3.14.1",
+		"ts-loader": "9.3.0",
+		"ts-node": "10.8.1",
+		"tsc-alias": "1.6.9",
+		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
-		"typeorm": "0.3.5",
-		"typescript": "4.6.3",
+		"typeorm": "0.3.6",
 		"ulid": "2.3.0",
 		"unzipper": "0.10.11",
 		"uuid": "8.3.2",
-		"web-push": "3.4.5",
+		"web-push": "3.5.0",
 		"websocket": "1.0.34",
-		"ws": "8.5.0",
-		"xev": "2.0.1"
+		"ws": "8.8.0",
+		"xev": "3.0.2"
 	},
 	"devDependencies": {
-		"@redocly/openapi-core": "1.0.0-beta.93",
+		"@redocly/openapi-core": "1.0.0-beta.97",
+		"@types/semver": "7.3.9",
+		"@types/bcryptjs": "2.4.2",
+		"@types/bull": "3.15.8",
+		"@types/cbor": "6.0.0",
+		"@types/escape-regexp": "0.0.1",
 		"@types/fluent-ffmpeg": "2.1.20",
+		"@types/is-url": "1.2.30",
+		"@types/js-yaml": "4.0.5",
+		"@types/jsdom": "16.2.14",
+		"@types/jsonld": "1.5.6",
+		"@types/jsrsasign": "10.5.1",
+		"@types/koa": "2.13.4",
+		"@types/koa-bodyparser": "4.3.7",
+		"@types/koa-cors": "0.0.2",
+		"@types/koa-favicon": "2.0.21",
+		"@types/koa-logger": "3.1.2",
+		"@types/koa-mount": "4.0.1",
+		"@types/koa-send": "4.1.3",
+		"@types/koa-views": "7.0.0",
+		"@types/koa__cors": "3.1.1",
+		"@types/koa__multer": "2.0.4",
+		"@types/koa__router": "8.0.11",
+		"@types/mocha": "9.1.1",
+		"@types/node": "17.0.41",
+		"@types/node-fetch": "3.0.3",
+		"@types/nodemailer": "6.4.4",
+		"@types/oauth": "0.9.1",
+		"@types/parse5": "6.0.3",
+		"@types/pug": "2.0.6",
+		"@types/punycode": "2.1.0",
+		"@types/qrcode": "1.4.2",
+		"@types/random-seed": "0.3.3",
+		"@types/ratelimiter": "3.4.3",
+		"@types/redis": "4.0.11",
+		"@types/rename": "1.0.4",
+		"@types/sanitize-html": "2.6.2",
+		"@types/sharp": "0.30.2",
+		"@types/sinonjs__fake-timers": "8.1.2",
+		"@types/speakeasy": "2.0.7",
+		"@types/tinycolor2": "1.4.3",
+		"@types/tmp": "0.2.3",
+		"@types/uuid": "8.3.4",
+		"@types/web-push": "3.3.2",
+		"@types/websocket": "1.0.5",
+		"@types/ws": "8.5.3",
+		"@typescript-eslint/eslint-plugin": "5.27.1",
+		"@typescript-eslint/parser": "5.27.1",
+		"typescript": "4.7.3",
+		"eslint": "8.17.0",
+		"eslint-plugin-import": "2.26.0",
 		"cross-env": "7.0.3",
 		"execa": "6.1.0"
 	}
diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts
index 8d484312dc..d1f9cd9552 100644
--- a/packages/backend/src/@types/http-signature.d.ts
+++ b/packages/backend/src/@types/http-signature.d.ts
@@ -1,5 +1,5 @@
-declare module 'http-signature' {
-	import { IncomingMessage, ClientRequest } from 'http';
+declare module '@peertube/http-signature' {
+	import { IncomingMessage, ClientRequest } from 'node:http';
 
 	interface ISignature {
 		keyId: string;
diff --git a/packages/backend/src/@types/jsrsasign.d.ts b/packages/backend/src/@types/jsrsasign.d.ts
deleted file mode 100644
index bb52f8f64e..0000000000
--- a/packages/backend/src/@types/jsrsasign.d.ts
+++ /dev/null
@@ -1,800 +0,0 @@
-// Attention: Partial Type Definition
-
-declare module 'jsrsasign' {
-	//// HELPER TYPES
-
-	/**
-	 * Attention: The value might be changed by the function.
-	 */
-	type Mutable<T> = T;
-
-	/**
-	 * Deprecated: The function might be deleted in future release.
-	 */
-	type Deprecated<T> = T;
-
-	//// COMMON TYPES
-
-	/**
-	 * byte number
-	 */
-	type ByteNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255;
-
-	/**
-	 * hexadecimal string /[0-9A-F]/
-	 */
-	type HexString = string;
-
-	/**
-	 * binary string /[01]/
-	 */
-	type BinString = string;
-
-	/**
-	 * base64 string /[A-Za-z0-9+/]=+/
-	 */
-	type Base64String = string;
-
-	/**
-	 * base64 URL encoded string /[A-Za-z0-9_-]/
-	 */
-	type Base64URLString = string;
-
-	/**
-	 * time value (ex. "151231235959Z")
-	 */
-	type TimeValue = string;
-
-	/**
-	 * OID string (ex. '1.2.3.4.567')
-	 */
-	type OID = string;
-
-	/**
-	 * OID name
-	 */
-	type OIDName = string;
-
-	/**
-	 * PEM formatted string
-	 */
-	type PEM = string;
-
-	//// ASN1 TYPES
-
-	class ASN1Object {
-		public isModified: boolean;
-
-		public hTLV: ASN1TLV;
-
-		public hT: ASN1T;
-
-		public hL: ASN1L;
-
-		public hV: ASN1V;
-
-		public getLengthHexFromValue(): HexString;
-
-		public getEncodedHex(): ASN1TLV;
-
-		public getValueHex(): ASN1V;
-
-		public getFreshValueHex(): ASN1V;
-	}
-
-	class DERAbstractStructured extends ASN1Object {
-		constructor(params?: Partial<Record<'array', ASN1Object[]>>);
-
-		public setByASN1ObjectArray(asn1ObjectArray: ASN1Object[]): void;
-
-		public appendASN1Object(asn1Object: ASN1Object): void;
-	}
-
-	class DERSequence extends DERAbstractStructured {
-		constructor(params?: Partial<Record<'array', ASN1Object[]>>);
-
-		public getFreshValueHex(): ASN1V;
-	}
-
-	//// ASN1HEX TYPES
-
-	/**
-	 * ASN.1 DER encoded data (hexadecimal string)
-	 */
-	type ASN1S = HexString;
-
-	/**
-	 * index of something
-	 */
-	type Idx<T extends { [idx: string]: unknown } | { [idx: number]: unknown }> = ASN1S extends { [idx: string]: unknown } ? string : ASN1S extends { [idx: number]: unknown } ? number : never;
-
-	/**
-	 * byte length of something
-	 */
-	type ByteLength<T extends { length: unknown }> = T['length'];
-
-	/**
-	 * ASN.1 L(length) (hexadecimal string)
-	 */
-	type ASN1L = HexString;
-
-	/**
-	 * ASN.1 T(tag) (hexadecimal string)
-	 */
-	type ASN1T = HexString;
-
-	/**
-	 * ASN.1 V(value) (hexadecimal string)
-	 */
-	type ASN1V = HexString;
-
-	/**
-	 * ASN.1 TLV (hexadecimal string)
-	 */
-	type ASN1TLV = HexString;
-
-	/**
-	 * ASN.1 object string
-	 */
-	type ASN1ObjectString = string;
-
-	/**
-	 * nth
-	 */
-	type Nth = number;
-
-	/**
-	 * ASN.1 DER encoded OID value (hexadecimal string)
-	 */
-	type ASN1OIDV = HexString;
-
-	class ASN1HEX {
-		public static getLblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1L>;
-
-		public static getL(s: ASN1S, idx: Idx<ASN1S>): ASN1L;
-
-		public static getVblen(s: ASN1S, idx: Idx<ASN1S>): ByteLength<ASN1V>;
-
-		public static getVidx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1V>;
-
-		public static getV(s: ASN1S, idx: Idx<ASN1S>): ASN1V;
-
-		public static getTLV(s: ASN1S, idx: Idx<ASN1S>): ASN1TLV;
-
-		public static getNextSiblingIdx(s: ASN1S, idx: Idx<ASN1S>): Idx<ASN1ObjectString>;
-
-		public static getChildIdx(h: ASN1S, pos: Idx<ASN1S>): Idx<ASN1ObjectString>[];
-
-		public static getNthChildIdx(h: ASN1S, idx: Idx<ASN1S>, nth: Nth): Idx<ASN1ObjectString>;
-
-		public static getIdxbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): Idx<Mutable<Nth[]>>;
-
-		public static getTLVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string): ASN1TLV;
-
-		// eslint:disable-next-line:bool-param-default
-		public static getVbyList(h: ASN1S, currentIndex: Idx<ASN1ObjectString>, nthList: Mutable<Nth[]>, checkingTag?: string, removeUnusedbits?: boolean): ASN1V;
-
-		public static hextooidstr(hex: ASN1OIDV): OID;
-
-		public static dump(hexOrObj: ASN1S | ASN1Object, flags?: Record<string, unknown>, idx?: Idx<ASN1S>, indent?: string): string;
-
-		public static isASN1HEX(hex: string): hex is HexString;
-
-		public static oidname(oidDotOrHex: OID | ASN1OIDV): OIDName;
-	}
-
-	//// BIG INTEGER TYPES (PARTIAL)
-
-	class BigInteger {
-		constructor(a: null);
-
-		constructor(a: number, b: SecureRandom);
-
-		constructor(a: number, b: number, c: SecureRandom);
-
-		constructor(a: unknown);
-
-		constructor(a: string, b: number);
-
-		public am(i: number, x: number, w: number, j: number, c: number, n: number): number;
-
-		public DB: number;
-
-		public DM: number;
-
-		public DV: number;
-
-		public FV: number;
-
-		public F1: number;
-
-		public F2: number;
-
-		protected copyTo(r: Mutable<BigInteger>): void;
-
-		protected fromInt(x: number): void;
-
-		protected fromString(s: string, b: number): void;
-
-		protected clamp(): void;
-
-		public toString(b: number): string;
-
-		public negate(): BigInteger;
-
-		public abs(): BigInteger;
-
-		public compareTo(a: BigInteger): number;
-
-		public bitLength(): number;
-
-		protected dlShiftTo(n: number, r: Mutable<BigInteger>): void;
-
-		protected drShiftTo(n: number, r: Mutable<BigInteger>): void;
-
-		protected lShiftTo(n: number, r: Mutable<BigInteger>): void;
-
-		protected rShiftTo(n: number, r: Mutable<BigInteger>): void;
-
-		protected subTo(a: BigInteger, r: Mutable<BigInteger>): void;
-
-		protected multiplyTo(a: BigInteger, r: Mutable<BigInteger>): void;
-
-		protected squareTo(r: Mutable<BigInteger>): void;
-
-		protected divRemTo(m: BigInteger, q: Mutable<BigInteger>, r: Mutable<BigInteger>): void;
-
-		public mod(a: BigInteger): BigInteger;
-
-		protected invDigit(): number;
-
-		protected isEven(): boolean;
-
-		protected exp(e: number, z: Classic | Montgomery): BigInteger;
-
-		public modPowInt(e: number, m: BigInteger): BigInteger;
-
-		public static ZERO: BigInteger;
-
-		public static ONE: BigInteger;
-	}
-
-	class Classic {
-		constructor(m: BigInteger);
-
-		public convert(x: BigInteger): BigInteger;
-
-		public revert(x: BigInteger): BigInteger;
-
-		public reduce(x: Mutable<BigInteger>): void;
-
-		public mulTo(x: BigInteger, r: Mutable<BigInteger>): void;
-
-		public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void;
-	}
-
-	class Montgomery {
-		constructor(m: BigInteger);
-
-		public convert(x: BigInteger): BigInteger;
-
-		public revert(x: BigInteger): BigInteger;
-
-		public reduce(x: Mutable<BigInteger>): void;
-
-		public mulTo(x: BigInteger, r: Mutable<BigInteger>): void;
-
-		public sqrTo(x: BigInteger, y: BigInteger, r: Mutable<BigInteger>): void;
-	}
-
-	//// KEYUTIL TYPES
-
-	type DecryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
-
-	type Decrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
-
-	type DecryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
-
-	type EncryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
-
-	type Encrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
-
-	type EncryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString;
-
-	type AlgList = {
-		'AES-256-CBC':  { 'proc': DecryptAES;  'eproc': EncryptAES;  keylen: 32; ivlen: 16; };
-		'AES-192-CBC':  { 'proc': DecryptAES;  'eproc': EncryptAES;  keylen: 24; ivlen: 16; };
-		'AES-128-CBC':  { 'proc': DecryptAES;  'eproc': EncryptAES;  keylen: 16; ivlen: 16; };
-		'DES-EDE3-CBC': { 'proc': Decrypt3DES; 'eproc': Encrypt3DES; keylen: 24; ivlen: 8;  };
-		'DES-CBC':      { 'proc': DecryptDES;  'eproc': EncryptDES;  keylen: 8;  ivlen: 8;  };
-	};
-
-	type AlgName = keyof AlgList;
-
-	type PEMHeadAlgName = 'RSA' | 'EC' | 'DSA';
-
-	type GetKeyRSAParam = RSAKey | {
-		n: BigInteger;
-		e: number;
-	} | Record<'n' | 'e', HexString> | Record<'n' | 'e', HexString> & Record<'d' | 'p' | 'q' | 'dp' | 'dq' | 'co', HexString | null> | {
-		n: BigInteger;
-		e: number;
-		d: BigInteger;
-	} | {
-		kty: 'RSA';
-	} & Record<'n' | 'e', Base64URLString> | {
-		kty: 'RSA';
-	} & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | {
-		kty: 'RSA';
-	} & Record<'n' | 'e' | 'd', Base64URLString>;
-
-	type GetKeyECDSAParam = KJUR.crypto.ECDSA | {
-		curve: KJUR.crypto.CurveName;
-		xy: HexString;
-	} | {
-		curve: KJUR.crypto.CurveName;
-		d: HexString;
-	} | {
-		kty: 'EC';
-		crv: KJUR.crypto.CurveName;
-		x: Base64URLString;
-		y: Base64URLString;
-	} | {
-		kty: 'EC';
-		crv: KJUR.crypto.CurveName;
-		x: Base64URLString;
-		y: Base64URLString;
-		d: Base64URLString;
-	};
-
-	type GetKeyDSAParam = KJUR.crypto.DSA | Record<'p' | 'q' | 'g', BigInteger> & Record<'y', BigInteger | null> | Record<'p' | 'q' | 'g' | 'x', BigInteger> & Record<'y', BigInteger | null>;
-
-	type GetKeyParam = GetKeyRSAParam | GetKeyECDSAParam | GetKeyDSAParam | string;
-
-	class KEYUTIL {
-		public version: '1.0.0';
-
-		public parsePKCS5PEM(sPKCS5PEM: PEM): Partial<Record<'type' | 's', string>> & (Record<'cipher' | 'ivsalt', string> | Record<'cipher' | 'ivsalt', undefined>);
-
-		public getKeyAndUnusedIvByPasscodeAndIvsalt(algName: AlgName, passcode: string, ivsaltHex: HexString): Record<'keyhex' | 'ivhex', HexString>;
-
-		public decryptKeyB64(privateKeyB64: Base64String, sharedKeyAlgName: AlgName, sharedKeyHex: HexString, ivsaltHex: HexString): Base64String;
-
-		public getDecryptedKeyHex(sEncryptedPEM: PEM, passcode: string): HexString;
-
-		public getEncryptedPKCS5PEMFromPrvKeyHex(pemHeadAlg: PEMHeadAlgName, hPrvKey: string, passcode: string, sharedKeyAlgName?: AlgName | null, ivsaltHex?: HexString | null): PEM;
-
-		public parseHexOfEncryptedPKCS8(sHEX: HexString): {
-			ciphertext: ASN1V;
-			encryptionSchemeAlg: 'TripleDES';
-			encryptionSchemeIV: ASN1V;
-			pbkdf2Salt: ASN1V;
-			pbkdf2Iter: number;
-		};
-
-		public getPBKDF2KeyHexFromParam(info: ReturnType<this['parseHexOfEncryptedPKCS8']>, passcode: string): HexString;
-
-		private _getPlainPKCS8HexFromEncryptedPKCS8PEM(pkcs8PEM: PEM, passcode: string): HexString;
-
-		public getKeyFromEncryptedPKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>;
-
-		public parsePlainPrivatePKCS8Hex(pkcs8PrvHex: HexString): {
-			algparam: ASN1V | null;
-			algoid: ASN1V;
-			keyidx: Idx<ASN1V>;
-		};
-
-		public getKeyFromPlainPrivatePKCS8PEM(prvKeyHex: HexString): ReturnType<this['getKeyFromPlainPrivatePKCS8Hex']>;
-
-		public getKeyFromPlainPrivatePKCS8Hex(prvKeyHex: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA;
-
-		private _getKeyFromPublicPKCS8Hex(h: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA;
-
-		public parsePublicRawRSAKeyHex(pubRawRSAHex: HexString): Record<'n' | 'e', ASN1V>;
-
-		public parsePublicPKCS8Hex(pkcs8PubHex: HexString): {
-			algparam: ASN1V | Record<'p' | 'q' | 'g', ASN1V> | null;
-			algoid: ASN1V;
-			key: ASN1V;
-		};
-
-		public static getKey(param: GetKeyRSAParam): RSAKey;
-
-		public static getKey(param: GetKeyECDSAParam): KJUR.crypto.ECDSA;
-
-		public static getKey(param: GetKeyDSAParam): KJUR.crypto.DSA;
-
-		public static getKey(param: string, passcode?: string, hextype?: string): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
-
-		public static generateKeypair(alg: 'RSA', keylen: number): Record<'prvKeyObj' | 'pubKeyObj', RSAKey>;
-
-		public static generateKeypair(alg: 'EC', curve: KJUR.crypto.CurveName): Record<'prvKeyObj' | 'pubKeyObj', KJUR.crypto.ECDSA>;
-
-		public static getPEM(keyObjOrHex: RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA, formatType?: 'PKCS1PRV' | 'PKCS5PRV' | 'PKCS8PRV', passwd?: string, encAlg?: 'DES-CBC' | 'DES-EDE3-CBC' | 'AES-128-CBC' | 'AES-192-CBC' | 'AES-256-CBC', hexType?: string, ivsaltHex?: HexString): object; // To Do
-
-		public static getKeyFromCSRPEM(csrPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
-
-		public static getKeyFromCSRHex(csrHex: HexString): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
-
-		public static parseCSRHex(csrHex: HexString): Record<'p8pubkeyhex', ASN1TLV>;
-
-		public static getJWKFromKey(keyObj: RSAKey): {
-			kty: 'RSA';
-		} & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | {
-			kty: 'RSA';
-		} & Record<'n' | 'e', Base64URLString>;
-
-		public static getJWKFromKey(keyObj: KJUR.crypto.ECDSA): {
-			kty: 'EC';
-			crv: KJUR.crypto.CurveName;
-			x: Base64URLString;
-			y: Base64URLString;
-			d: Base64URLString;
-		} | {
-			kty: 'EC';
-			crv: KJUR.crypto.CurveName;
-			x: Base64URLString;
-			y: Base64URLString;
-		};
-	}
-
-	//// KJUR NAMESPACE (PARTIAL)
-
-	namespace KJUR {
-		namespace crypto {
-			type CurveName = 'secp128r1' | 'secp160k1' | 'secp160r1' | 'secp192k1' | 'secp192r1' | 'secp224r1' | 'secp256k1' | 'secp256r1' | 'secp384r1' | 'secp521r1';
-
-			class DSA {
-				public p: BigInteger | null;
-
-				public q: BigInteger | null;
-
-				public g: BigInteger | null;
-
-				public y: BigInteger | null;
-
-				public x: BigInteger | null;
-
-				public type: 'DSA';
-
-				public isPrivate: boolean;
-
-				public isPublic: boolean;
-
-				public setPrivate(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger | null, x: BigInteger): void;
-
-				public setPrivateHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString | null, hX: HexString): void;
-
-				public setPublic(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger): void;
-
-				public setPublicHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString): void;
-
-				public signWithMessageHash(sHashHex: HexString): HexString;
-
-				public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean;
-
-				public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger];
-
-				public readPKCS5PrvKeyHex(h: HexString): void;
-
-				public readPKCS8PrvKeyHex(h: HexString): void;
-
-				public readPKCS8PubKeyHex(h: HexString): void;
-
-				public readCertPubKeyHex(h: HexString, nthPKI: number): void;
-			}
-
-			class ECDSA {
-				constructor(params?: {
-					curve?: CurveName;
-					prv?: HexString;
-					pub?: HexString;
-				});
-
-				public p: BigInteger | null;
-
-				public q: BigInteger | null;
-
-				public g: BigInteger | null;
-
-				public y: BigInteger | null;
-
-				public x: BigInteger | null;
-
-				public type: 'EC';
-
-				public isPrivate: boolean;
-
-				public isPublic: boolean;
-
-				public getBigRandom(limit: BigInteger): BigInteger;
-
-				public setNamedCurve(curveName: CurveName): void;
-
-				public setPrivateKeyHex(prvKeyHex: HexString): void;
-
-				public setPublicKeyHex(pubKeyHex: HexString): void;
-
-				public getPublicKeyXYHex(): Record<'x' | 'y', HexString>;
-
-				public getShortNISTPCurveName(): 'P-256' | 'P-384' | null;
-
-				public generateKeyPairHex(): Record<'ecprvhex' | 'ecpubhex', HexString>;
-
-				public signWithMessageHash(hashHex: HexString): HexString;
-
-				public signHex(hashHex: HexString, privHex: HexString): HexString;
-
-				public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean;
-
-				public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger];
-
-				public readPKCS5PrvKeyHex(h: HexString): void;
-
-				public readPKCS8PrvKeyHex(h: HexString): void;
-
-				public readPKCS8PubKeyHex(h: HexString): void;
-
-				public readCertPubKeyHex(h: HexString, nthPKI: number): void;
-
-				public static parseSigHex(sigHex: HexString): Record<'r' | 's', BigInteger>;
-
-				public static parseSigHexInHexRS(sigHex: HexString): Record<'r' | 's', ASN1V>;
-
-				public static asn1SigToConcatSig(asn1Sig: HexString): HexString;
-
-				public static concatSigToASN1Sig(concatSig: HexString): ASN1TLV;
-
-				public static hexRSSigToASN1Sig(hR: HexString, hS: HexString): ASN1TLV;
-
-				public static biRSSigToASN1Sig(biR: BigInteger, biS: BigInteger): ASN1TLV;
-
-				public static getName(s: CurveName | HexString): 'secp256r1' | 'secp256k1' | 'secp384r1' | null;
-			}
-
-			class Signature {
-				constructor(params?: ({
-					alg: string;
-					prov?: string;
-				} | {}) & ({
-					psssaltlen: number;
-				} | {}) & ({
-					prvkeypem: PEM;
-					prvkeypas?: never;
-				} | {}));
-
-				private _setAlgNames(): void;
-
-				private _zeroPaddingOfSignature(hex: HexString, bitLength: number): HexString;
-
-				public setAlgAndProvider(alg: string, prov: string): void;
-
-				public init(key: GetKeyParam, pass?: string): void;
-
-				public updateString(str: string): void;
-
-				public updateHex(hex: HexString): void;
-
-				public sign(): HexString;
-
-				public signString(str: string): HexString;
-
-				public signHex(hex: HexString): HexString;
-
-				public verify(hSigVal: string): boolean | 0;
-			}
-		}
-	}
-
-	//// RSAKEY TYPES
-
-	class RSAKey {
-		public n: BigInteger | null;
-
-		public e: number;
-
-		public d: BigInteger | null;
-
-		public p: BigInteger | null;
-
-		public q: BigInteger | null;
-
-		public dmp1: BigInteger | null;
-
-		public dmq1: BigInteger | null;
-
-		public coeff: BigInteger | null;
-
-		public type: 'RSA';
-
-		public isPrivate?: boolean;
-
-		public isPublic?: boolean;
-
-		//// RSA PUBLIC
-
-		protected doPublic(x: BigInteger): BigInteger;
-
-		public setPublic(N: BigInteger, E: number): void;
-
-		public setPublic(N: HexString, E: HexString): void;
-
-		public encrypt(text: string): HexString | null;
-
-		public encryptOAEP(text: string, hash?: string | ((s: string) => string), hashLen?: number): HexString | null;
-
-		//// RSA PRIVATE
-
-		protected doPrivate(x: BigInteger): BigInteger;
-
-		public setPrivate(N: BigInteger, E: number, D: BigInteger): void;
-
-		public setPrivate(N: HexString, E: HexString, D: HexString): void;
-
-		public setPrivateEx(N: HexString, E: HexString, D?: HexString | null, P?: HexString | null, Q?: HexString | null, DP?: HexString | null, DQ?: HexString | null, C?: HexString | null): void;
-
-		public generate(B: number, E: HexString): void;
-
-		public decrypt(ctext: HexString): string;
-
-		public decryptOAEP(ctext: HexString, hash?: string | ((s: string) => string), hashLen?: number): string | null;
-
-		//// RSA PEM
-
-		public getPosArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[];
-
-		public getHexValueArrayOfChildrenFromHex(hPrivateKey: PEM): Idx<ASN1ObjectString>[];
-
-		public readPrivateKeyFromPEMString(keyPEM: PEM): void;
-
-		public readPKCS5PrvKeyHex(h: HexString): void;
-
-		public readPKCS8PrvKeyHex(h: HexString): void;
-
-		public readPKCS5PubKeyHex(h: HexString): void;
-
-		public readPKCS8PubKeyHex(h: HexString): void;
-
-		public readCertPubKeyHex(h: HexString, nthPKI: Nth): void;
-
-		//// RSA SIGN
-
-		public sign(s: string, hashAlg: string): HexString;
-
-		public signWithMessageHash(sHashHex: HexString, hashAlg: string): HexString;
-
-		public signPSS(s: string, hashAlg: string, sLen: number): HexString;
-
-		public signWithMessageHashPSS(hHash: HexString, hashAlg: string, sLen: number): HexString;
-
-		public verify(sMsg: string, hSig: HexString): boolean | 0;
-
-		public verifyWithMessageHash(sHashHex: HexString, hSig: HexString): boolean | 0;
-
-		public verifyPSS(sMsg: string, hSig: HexString, hashAlg: string, sLen: number): boolean;
-
-		public verifyWithMessageHashPSS(hHash: HexString, hSig: HexString, hashAlg: string, sLen: number): boolean;
-
-		public static SALT_LEN_HLEN: -1;
-
-		public static SALT_LEN_MAX: -2;
-
-		public static SALT_LEN_RECOVER: -2;
-	}
-
-	/// RNG TYPES
-	class SecureRandom {
-		public nextBytes(ba: Mutable<ByteNumber[]>): void;
-	}
-
-	//// X509 TYPES
-
-	type ExtInfo = {
-		critical: boolean;
-		oid: OID;
-		vidx: Idx<ASN1V>;
-	};
-
-	type ExtAIAInfo = Record<'ocsp' | 'caissuer', string>;
-
-	type ExtCertificatePolicy = {
-		id: OIDName;
-	} & Partial<{
-		cps: string;
-	} | {
-		unotice: string;
-	}>;
-
-	class X509 {
-		public hex: HexString | null;
-
-		public version: number;
-
-		public foffset: number;
-
-		public aExtInfo: null;
-
-		public getVersion(): number;
-
-		public getSerialNumberHex(): ASN1V;
-
-		public getSignatureAlgorithmField(): OIDName;
-
-		public getIssuerHex(): ASN1TLV;
-
-		public getIssuerString(): HexString;
-
-		public getSubjectHex(): ASN1TLV;
-
-		public getSubjectString(): HexString;
-
-		public getNotBefore(): TimeValue;
-
-		public getNotAfter(): TimeValue;
-
-		public getPublicKeyHex(): ASN1TLV;
-
-		public getPublicKeyIdx(): Idx<Mutable<Nth[]>>;
-
-		public getPublicKeyContentIdx(): Idx<Mutable<Nth[]>>;
-
-		public getPublicKey(): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
-
-		public getSignatureAlgorithmName(): OIDName;
-
-		public getSignatureValueHex(): ASN1V;
-
-		public verifySignature(pubKey: GetKeyParam): boolean | 0;
-
-		public parseExt(): void;
-
-		public getExtInfo(oidOrName: OID | string): ExtInfo | undefined;
-
-		public getExtBasicConstraints(): ExtInfo | {} | {
-			cA: true;
-			pathLen?: number;
-		};
-
-		public getExtKeyUsageBin(): BinString;
-
-		public getExtKeyUsageString(): string;
-
-		public getExtSubjectKeyIdentifier(): ASN1V | undefined;
-
-		public getExtAuthorityKeyIdentifier(): {
-			kid: ASN1V;
-		} | undefined;
-
-		public getExtExtKeyUsageName(): OIDName[] | undefined;
-
-		public getExtSubjectAltName(): Deprecated<string[]>;
-
-		public getExtSubjectAltName2(): ['MAIL' | 'DNS' | 'DN' | 'URI' | 'IP', string][] | undefined;
-
-		public getExtCRLDistributionPointsURI(): string[] | undefined;
-
-		public getExtAIAInfo(): ExtAIAInfo | undefined;
-
-		public getExtCertificatePolicies(): ExtCertificatePolicy[] | undefined;
-
-		public readCertPEM(sCertPEM: PEM): void;
-
-		public readCertHex(sCertHex: HexString): void;
-
-		public getInfo(): string;
-
-		public static hex2dn(hex: HexString, idx?: Idx<HexString>): string;
-
-		public static hex2rdn(hex: HexString, idx?: Idx<HexString>): string;
-
-		public static hex2attrTypeValue(hex: HexString, idx?: Idx<HexString>): string;
-
-		public static getPublicKeyFromCertPEM(sCertPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA;
-
-		public static getPublicKeyInfoPropOfCertPEM(sCertPEM: PEM): {
-			algparam: ASN1V | null;
-			leyhex: ASN1V;
-			algoid: ASN1V;
-		};
-	}
-}
diff --git a/packages/backend/src/boot/index.ts b/packages/backend/src/boot/index.ts
index 5bb20a729f..c3d0592256 100644
--- a/packages/backend/src/boot/index.ts
+++ b/packages/backend/src/boot/index.ts
@@ -1,6 +1,6 @@
 import cluster from 'node:cluster';
 import chalk from 'chalk';
-import { default as Xev } from 'xev';
+import Xev from 'xev';
 
 import Logger from '@/services/logger.js';
 import { envOption } from '../env.js';
@@ -12,7 +12,7 @@ import { workerMain } from './worker.js';
 
 const logger = new Logger('core', 'cyan');
 const clusterLogger = logger.createSubLogger('cluster', 'orange', false);
-const ev = new Xev.default();
+const ev = new Xev();
 
 /**
  * Init process
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index 09d20f9361..bf51960482 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -5,7 +5,6 @@ import * as os from 'node:os';
 import cluster from 'node:cluster';
 import chalk from 'chalk';
 import chalkTemplate from 'chalk-template';
-import * as portscanner from 'portscanner';
 import semver from 'semver';
 
 import Logger from '@/services/logger.js';
@@ -48,11 +47,6 @@ function greet() {
 	bootLogger.info(`Misskey v${meta.version}`, null, true);
 }
 
-function isRoot() {
-	// maybe process.getuid will be undefined under not POSIX environment (e.g. Windows)
-	return process.getuid != null && process.getuid() === 0;
-}
-
 /**
  * Init master process
  */
@@ -67,7 +61,6 @@ export async function masterMain() {
 		showNodejsVersion();
 		config = loadConfigBoot();
 		await connectDb();
-		await validatePort(config);
 	} catch (e) {
 		bootLogger.error('Fatal error occurred during initialization', null, true);
 		process.exit(1);
@@ -97,8 +90,6 @@ function showEnvironment(): void {
 		logger.warn('The environment is not in production mode.');
 		logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true);
 	}
-
-	logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`);
 }
 
 function showNodejsVersion(): void {
@@ -152,29 +143,6 @@ async function connectDb(): Promise<void> {
 	}
 }
 
-async function validatePort(config: Config): Promise<void> {
-	const isWellKnownPort = (port: number) => port < 1024;
-
-	async function isPortAvailable(port: number): Promise<boolean> {
-		return await portscanner.checkPortStatus(port, '127.0.0.1') === 'closed';
-	}
-
-	if (config.port == null || Number.isNaN(config.port)) {
-		bootLogger.error('The port is not configured. Please configure port.', null, true);
-		process.exit(1);
-	}
-
-	if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) {
-		bootLogger.error('You need root privileges to listen on well-known port on Linux', null, true);
-		process.exit(1);
-	}
-
-	if (!await isPortAvailable(config.port)) {
-		bootLogger.error(`Port ${config.port} is already in use`, null, true);
-		process.exit(1);
-	}
-}
-
 async function spawnWorkers(limit: number = 1) {
 	const workers = Math.min(limit, os.cpus().length);
 	bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`);
@@ -186,6 +154,10 @@ function spawnWorker(): Promise<void> {
 	return new Promise(res => {
 		const worker = cluster.fork();
 		worker.on('message', message => {
+			if (message === 'listenFailed') {
+				bootLogger.error(`The server Listen failed due to the previous error.`);
+				process.exit(1);
+			}
 			if (message !== 'ready') return;
 			res();
 		});
diff --git a/packages/backend/src/config/load.ts b/packages/backend/src/config/load.ts
index 7f765463e4..9654a4f3b0 100644
--- a/packages/backend/src/config/load.ts
+++ b/packages/backend/src/config/load.ts
@@ -25,6 +25,7 @@ const path = process.env.NODE_ENV === 'test'
 
 export default function load() {
 	const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, 'utf-8'));
+	const clientManifest = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/_client_dist_/manifest.json`, 'utf-8'));
 	const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
 
 	const mixin = {} as Mixin;
@@ -45,6 +46,7 @@ export default function load() {
 	mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
 	mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
 	mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
+	mixin.clientEntry = clientManifest['src/init.ts'];
 
 	if (!config.redis.prefix) config.redis.prefix = mixin.host;
 
diff --git a/packages/backend/src/config/types.ts b/packages/backend/src/config/types.ts
index 58a27803cb..948545db7a 100644
--- a/packages/backend/src/config/types.ts
+++ b/packages/backend/src/config/types.ts
@@ -80,6 +80,7 @@ export type Mixin = {
 	authUrl: string;
 	driveUrl: string;
 	userAgent: string;
+	clientEntry: string;
 };
 
 export type Config = Source & Mixin;
diff --git a/packages/backend/src/daemons/queue-stats.ts b/packages/backend/src/daemons/queue-stats.ts
index bfef110545..1535abc6af 100644
--- a/packages/backend/src/daemons/queue-stats.ts
+++ b/packages/backend/src/daemons/queue-stats.ts
@@ -1,7 +1,7 @@
-import { default as Xev } from 'xev';
+import Xev from 'xev';
 import { deliverQueue, inboxQueue } from '../queue/queues.js';
 
-const ev = new Xev.default();
+const ev = new Xev();
 
 const interval = 10000;
 
diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index 327305ccc5..faf4e6e4a4 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -1,8 +1,8 @@
 import si from 'systeminformation';
-import { default as Xev } from 'xev';
+import Xev from 'xev';
 import * as osUtils from 'os-utils';
 
-const ev = new Xev.default();
+const ev = new Xev();
 
 const interval = 2000;
 
diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts
index eb5fc2e186..298f6713ea 100644
--- a/packages/backend/src/db/postgre.ts
+++ b/packages/backend/src/db/postgre.ts
@@ -5,9 +5,6 @@ pg.types.setTypeParser(20, Number);
 import { Logger, DataSource } from 'typeorm';
 import * as highlight from 'cli-highlight';
 import config from '@/config/index.js';
-import { envOption } from '../env.js';
-
-import { dbLogger } from './logger.js';
 
 import { User } from '@/models/entities/user.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
@@ -74,6 +71,9 @@ import { UserPending } from '@/models/entities/user-pending.js';
 
 import { entities as charts } from '@/services/chart/entities.js';
 import { Webhook } from '@/models/entities/webhook.js';
+import { envOption } from '../env.js';
+import { dbLogger } from './logger.js';
+import { redisClient } from './redis.js';
 
 const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
 
@@ -208,16 +208,25 @@ export const db = new DataSource({
 	migrations: ['../../migration/*.js'],
 });
 
-export async function initDb() {
+export async function initDb(force = false) {
+	if (force) {
+		if (db.isInitialized) {
+			await db.destroy();
+		}
+		await db.initialize();
+		return;
+	}
+
 	if (db.isInitialized) {
 		// nop
 	} else {
-		await db.connect();
+		await db.initialize();
 	}
 }
 
 export async function resetDb() {
 	const reset = async () => {
+		await redisClient.FLUSHDB();
 		const tables = await db.query(`SELECT relname AS "table"
 		FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
 		WHERE nspname NOT IN ('pg_catalog', 'information_schema')
diff --git a/packages/backend/src/mfm/from-html.ts b/packages/backend/src/mfm/from-html.ts
index 623cb0e71c..15110b6b70 100644
--- a/packages/backend/src/mfm/from-html.ts
+++ b/packages/backend/src/mfm/from-html.ts
@@ -6,6 +6,9 @@ const urlRegex     = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
 const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
 
 export function fromHtml(html: string, hashtagNames?: string[]): string {
+	// some AP servers like Pixelfed use br tags as well as newlines
+	html = html.replace(/<br\s?\/?>\r?\n/gi, '\n');
+
 	const dom = parse5.parseFragment(html);
 
 	let text = '';
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index 01bbe98a85..e5b911ed32 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -48,6 +48,7 @@ export class Cache<T> {
 
 		// Cache MISS
 		const value = await fetcher();
+		this.set(key, value);
 		return value;
 	}
 
diff --git a/packages/backend/src/misc/cafy-id.ts b/packages/backend/src/misc/cafy-id.ts
deleted file mode 100644
index dd81c5c4cf..0000000000
--- a/packages/backend/src/misc/cafy-id.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Context } from 'cafy';
-
-// eslint-disable-next-line @typescript-eslint/ban-types
-export class ID<Maybe = string> extends Context<string | (Maybe extends {} ? string : Maybe)> {
-	public readonly name = 'ID';
-
-	constructor(optional = false, nullable = false) {
-		super(optional, nullable);
-
-		this.push((v: any) => {
-			if (typeof v !== 'string') {
-				return new Error('must-be-an-id');
-			}
-			return true;
-		});
-	}
-
-	public getType() {
-		return super.getType('String');
-	}
-
-	public makeOptional(): ID<undefined> {
-		return new ID(true, false);
-	}
-
-	public makeNullable(): ID<null> {
-		return new ID(false, true);
-	}
-
-	public makeOptionalNullable(): ID<undefined | null> {
-		return new ID(true, true);
-	}
-}
diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts
index 04604cf7d0..f07be634fb 100644
--- a/packages/backend/src/misc/create-temp.ts
+++ b/packages/backend/src/misc/create-temp.ts
@@ -1,10 +1,19 @@
 import * as tmp from 'tmp';
 
-export function createTemp(): Promise<[string, any]> {
-	return new Promise<[string, any]>((res, rej) => {
+export function createTemp(): Promise<[string, () => void]> {
+	return new Promise<[string, () => void]>((res, rej) => {
 		tmp.file((e, path, fd, cleanup) => {
 			if (e) return rej(e);
 			res([path, cleanup]);
 		});
 	});
 }
+
+export function createTempDir(): Promise<[string, () => void]> {
+	return new Promise<[string, () => void]>((res, rej) => {
+		tmp.dir((e, path, cleanup) => {
+			if (e) return rej(e);
+			res([path, cleanup]);
+		});
+	});
+}
diff --git a/packages/backend/src/misc/fetch-meta.ts b/packages/backend/src/misc/fetch-meta.ts
index 5417c10962..e855ac28ee 100644
--- a/packages/backend/src/misc/fetch-meta.ts
+++ b/packages/backend/src/misc/fetch-meta.ts
@@ -20,9 +20,16 @@ export async function fetchMeta(noCache = false): Promise<Meta> {
 			cache = meta;
 			return meta;
 		} else {
-			const saved = await transactionalEntityManager.save(Meta, {
-				id: 'x',
-			}) as Meta;
+			// metaใŒ็ฉบใฎใจใfetchMetaใŒๅŒๆ™‚ใซๅ‘ผใฐใ‚Œใ‚‹ใจใ“ใ“ใŒๅŒๆ™‚ใซๅ‘ผใฐใ‚Œใฆใ—ใพใ†ใ“ใจใŒใ‚ใ‚‹ใฎใงใƒ•ใ‚งใ‚คใƒซใ‚ปใƒผใƒ•ใชupsertใ‚’ไฝฟใ†
+			const saved = await transactionalEntityManager
+				.upsert(
+					Meta,
+					{
+						id: 'x',
+					},
+					['id'],
+				)
+				.then((x) => transactionalEntityManager.findOneByOrFail(Meta, x.identifiers[0]));
 
 			cache = saved;
 			return saved;
diff --git a/packages/backend/src/misc/fetch.ts b/packages/backend/src/misc/fetch.ts
index 4b1013c9f5..af6bf2fca7 100644
--- a/packages/backend/src/misc/fetch.ts
+++ b/packages/backend/src/misc/fetch.ts
@@ -1,10 +1,10 @@
-import * as http from 'http';
-import * as https from 'https';
+import * as http from 'node:http';
+import * as https from 'node:https';
+import { URL } from 'node:url';
 import CacheableLookup from 'cacheable-lookup';
 import fetch from 'node-fetch';
 import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
 import config from '@/config/index.js';
-import { URL } from 'node:url';
 
 export async function getJson(url: string, accept = 'application/json, */*', timeout = 10000, headers?: Record<string, string>) {
 	const res = await getResponse({
@@ -35,7 +35,7 @@ export async function getHtml(url: string, accept = 'text/html, */*', timeout =
 }
 
 export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) {
-	const timeout = args?.timeout || 10 * 1000;
+	const timeout = args.timeout || 10 * 1000;
 
 	const controller = new AbortController();
 	setTimeout(() => {
@@ -47,7 +47,7 @@ export async function getResponse(args: { url: string, method: string, body?: st
 		headers: args.headers,
 		body: args.body,
 		timeout,
-		size: args?.size || 10 * 1024 * 1024,
+		size: args.size || 10 * 1024 * 1024,
 		agent: getAgentByUrl,
 		signal: controller.signal,
 	});
diff --git a/packages/backend/src/misc/get-ip-hash.ts b/packages/backend/src/misc/get-ip-hash.ts
new file mode 100644
index 0000000000..379325bb13
--- /dev/null
+++ b/packages/backend/src/misc/get-ip-hash.ts
@@ -0,0 +1,9 @@
+import IPCIDR from 'ip-cidr';
+
+export function getIpHash(ip: string) {
+	// because a single person may control many IPv6 addresses,
+	// only a /64 subnet prefix of any IP will be taken into account.
+	// (this means for IPv4 the entire address is used)
+	const prefix = IPCIDR.createAddress(ip).mask(64);
+	return 'ip-' + BigInt('0b' + prefix).toString(36);
+}
diff --git a/packages/backend/src/misc/populate-emojis.ts b/packages/backend/src/misc/populate-emojis.ts
index 86f1356c31..6a185d09f6 100644
--- a/packages/backend/src/misc/populate-emojis.ts
+++ b/packages/backend/src/misc/populate-emojis.ts
@@ -63,7 +63,7 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
 
 	const isLocal = emoji.host == null;
 	const emojiUrl = emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl ใ—ใฆใ‚‹ใฎใฏๅพŒๆ–นไบ’ๆ›ๆ€งใฎใŸใ‚
-	const url = isLocal ? emojiUrl : `${config.url}/proxy/image.png?${query({ url: emojiUrl })}`;
+	const url = isLocal ? emojiUrl : `${config.url}/proxy/${encodeURIComponent((new URL(emojiUrl)).pathname)}?${query({ url: emojiUrl })}`;
 
 	return {
 		name: emojiName,
diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 5b69812090..fdecc278d4 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -89,7 +89,7 @@ export interface Schema extends OfSchema {
 	readonly optional?: boolean;
 	readonly items?: Schema;
 	readonly properties?: Obj;
-	readonly required?: ReadonlyArray<keyof NonNullable<this['properties']>>;
+	readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>;
 	readonly description?: string;
 	readonly example?: any;
 	readonly format?: string;
@@ -98,6 +98,9 @@ export interface Schema extends OfSchema {
 	readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
 	readonly maxLength?: number;
 	readonly minLength?: number;
+	readonly maximum?: number;
+	readonly minimum?: number;
+	readonly pattern?: string;
 }
 
 type RequiredPropertyNames<s extends Obj> = {
@@ -105,24 +108,26 @@ type RequiredPropertyNames<s extends Obj> = {
 		// K is not optional
 		s[K]['optional'] extends false ? K :
 		// K has default value
-		s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K : never
+		s[K]['default'] extends null | string | number | boolean | Record<string, unknown> ? K :
+		never
 }[keyof s];
 
-export interface Obj { [key: string]: Schema; }
+export type Obj = Record<string, Schema>;
 
+// https://github.com/misskey-dev/misskey/issues/8535
+// To avoid excessive stack depth error,
+// deceive TypeScript with UnionToIntersection (or more precisely, `infer` expression within it).
 export type ObjType<s extends Obj, RequiredProps extends keyof s> =
-	{ -readonly [P in keyof s]?: SchemaType<s[P]> } &
-	{ -readonly [P in RequiredProps]: SchemaType<s[P]> } &
-	{ -readonly [P in RequiredPropertyNames<s>]: SchemaType<s[P]> };
+	UnionToIntersection<
+		{ -readonly [R in RequiredPropertyNames<s>]-?: SchemaType<s[R]> } &
+		{ -readonly [R in RequiredProps]-?: SchemaType<s[R]> } &
+		{ -readonly [P in keyof s]?: SchemaType<s[P]> }
+	>;
 
 type NullOrUndefined<p extends Schema, T> =
-	p['nullable'] extends true
-		?	p['optional'] extends true
-			? (T | null | undefined)
-			: (T | null)
-		: p['optional'] extends true
-			? (T | undefined)
-			: T;
+	| (p['nullable'] extends true ? null : never)
+	| (p['optional'] extends true ? undefined : never)
+	| T;
 
 // https://stackoverflow.com/questions/54938141/typescript-convert-union-to-intersection
 // Get intersection from union 
@@ -139,9 +144,9 @@ export type SchemaTypeDef<p extends Schema> =
 	p['type'] extends 'number' ? number :
 	p['type'] extends 'string' ? (
 		p['enum'] extends readonly string[] ?
-			p['enum'][number] :
-			p['format'] extends 'date-time' ? string : // Dateใซใ™ใ‚‹๏ผŸ๏ผŸ
-			string
+		p['enum'][number] :
+		p['format'] extends 'date-time' ? string : // Dateใซใ™ใ‚‹๏ผŸ๏ผŸ
+		string
 	) :
 	p['type'] extends 'boolean' ? boolean :
 	p['type'] extends 'object' ? (
diff --git a/packages/backend/src/models/entities/access-token.ts b/packages/backend/src/models/entities/access-token.ts
index 69cdc49cec..c6e2141a46 100644
--- a/packages/backend/src/models/entities/access-token.ts
+++ b/packages/backend/src/models/entities/access-token.ts
@@ -15,7 +15,6 @@ export class AccessToken {
 
 	@Column('timestamp with time zone', {
 		nullable: true,
-		default: null,
 	})
 	public lastUsedAt: Date | null;
 
@@ -29,7 +28,6 @@ export class AccessToken {
 	@Column('varchar', {
 		length: 128,
 		nullable: true,
-		default: null,
 	})
 	public session: string | null;
 
@@ -52,7 +50,6 @@ export class AccessToken {
 	@Column({
 		...id(),
 		nullable: true,
-		default: null,
 	})
 	public appId: App['id'] | null;
 
@@ -65,21 +62,18 @@ export class AccessToken {
 	@Column('varchar', {
 		length: 128,
 		nullable: true,
-		default: null,
 	})
 	public name: string | null;
 
 	@Column('varchar', {
 		length: 512,
 		nullable: true,
-		default: null,
 	})
 	public description: string | null;
 
 	@Column('varchar', {
 		length: 512,
 		nullable: true,
-		default: null,
 	})
 	public iconUrl: string | null;
 
diff --git a/packages/backend/src/models/entities/auth-session.ts b/packages/backend/src/models/entities/auth-session.ts
index b825856201..295d1b486c 100644
--- a/packages/backend/src/models/entities/auth-session.ts
+++ b/packages/backend/src/models/entities/auth-session.ts
@@ -23,7 +23,7 @@ export class AuthSession {
 		...id(),
 		nullable: true,
 	})
-	public userId: User['id'];
+	public userId: User['id'] | null;
 
 	@ManyToOne(type => User, {
 		onDelete: 'CASCADE',
diff --git a/packages/backend/src/models/entities/clip.ts b/packages/backend/src/models/entities/clip.ts
index da6b3c7a7f..1386684c32 100644
--- a/packages/backend/src/models/entities/clip.ts
+++ b/packages/backend/src/models/entities/clip.ts
@@ -37,7 +37,7 @@ export class Clip {
 	public isPublic: boolean;
 
 	@Column('varchar', {
-		length: 2048, nullable: true, default: null,
+		length: 2048, nullable: true,
 		comment: 'The description of the Clip.',
 	})
 	public description: string | null;
diff --git a/packages/backend/src/models/entities/drive-file.ts b/packages/backend/src/models/entities/drive-file.ts
index 3d375f0e35..a636d1d519 100644
--- a/packages/backend/src/models/entities/drive-file.ts
+++ b/packages/backend/src/models/entities/drive-file.ts
@@ -79,7 +79,6 @@ export class DriveFile {
 	})
 	public properties: { width?: number; height?: number; orientation?: number; avgColor?: string };
 
-	@Index()
 	@Column('boolean')
 	public storedInternal: boolean;
 
diff --git a/packages/backend/src/models/entities/emoji.ts b/packages/backend/src/models/entities/emoji.ts
index b72ca72331..7332dd1857 100644
--- a/packages/backend/src/models/entities/emoji.ts
+++ b/packages/backend/src/models/entities/emoji.ts
@@ -36,6 +36,7 @@ export class Emoji {
 
 	@Column('varchar', {
 		length: 512,
+		default: '',
 	})
 	public publicUrl: string;
 
diff --git a/packages/backend/src/models/entities/instance.ts b/packages/backend/src/models/entities/instance.ts
index bb24d6b30f..7ea9234384 100644
--- a/packages/backend/src/models/entities/instance.ts
+++ b/packages/backend/src/models/entities/instance.ts
@@ -107,53 +107,53 @@ export class Instance {
 	public isSuspended: boolean;
 
 	@Column('varchar', {
-		length: 64, nullable: true, default: null,
+		length: 64, nullable: true,
 		comment: 'The software of the Instance.',
 	})
 	public softwareName: string | null;
 
 	@Column('varchar', {
-		length: 64, nullable: true, default: null,
+		length: 64, nullable: true,
 	})
 	public softwareVersion: string | null;
 
 	@Column('boolean', {
-		nullable: true, default: null,
+		nullable: true,
 	})
 	public openRegistrations: boolean | null;
 
 	@Column('varchar', {
-		length: 256, nullable: true, default: null,
+		length: 256, nullable: true,
 	})
 	public name: string | null;
 
 	@Column('varchar', {
-		length: 4096, nullable: true, default: null,
+		length: 4096, nullable: true,
 	})
 	public description: string | null;
 
 	@Column('varchar', {
-		length: 128, nullable: true, default: null,
+		length: 128, nullable: true,
 	})
 	public maintainerName: string | null;
 
 	@Column('varchar', {
-		length: 256, nullable: true, default: null,
+		length: 256, nullable: true,
 	})
 	public maintainerEmail: string | null;
 
 	@Column('varchar', {
-		length: 256, nullable: true, default: null,
+		length: 256, nullable: true,
 	})
 	public iconUrl: string | null;
 
 	@Column('varchar', {
-		length: 256, nullable: true, default: null,
+		length: 256, nullable: true,
 	})
 	public faviconUrl: string | null;
 
 	@Column('varchar', {
-		length: 64, nullable: true, default: null,
+		length: 64, nullable: true,
 	})
 	public themeColor: string | null;
 
diff --git a/packages/backend/src/models/entities/meta.ts b/packages/backend/src/models/entities/meta.ts
index 4d58b5f04f..80b5228bcd 100644
--- a/packages/backend/src/models/entities/meta.ts
+++ b/packages/backend/src/models/entities/meta.ts
@@ -78,7 +78,7 @@ export class Meta {
 	public blockedHosts: string[];
 
 	@Column('varchar', {
-		length: 512, array: true, default: '{"/featured", "/channels", "/explore", "/pages", "/about-misskey"}',
+		length: 512, array: true, default: '{/featured,/channels,/explore,/pages,/about-misskey}',
 	})
 	public pinnedPages: string[];
 
@@ -346,14 +346,12 @@ export class Meta {
 
 	@Column('varchar', {
 		length: 8192,
-		default: null,
 		nullable: true,
 	})
 	public defaultLightTheme: string | null;
 
 	@Column('varchar', {
 		length: 8192,
-		default: null,
 		nullable: true,
 	})
 	public defaultDarkTheme: string | null;
diff --git a/packages/backend/src/models/entities/muting.ts b/packages/backend/src/models/entities/muting.ts
index b3a7e7a671..8f9e69063a 100644
--- a/packages/backend/src/models/entities/muting.ts
+++ b/packages/backend/src/models/entities/muting.ts
@@ -17,7 +17,6 @@ export class Muting {
 	@Index()
 	@Column('timestamp with time zone', {
 		nullable: true,
-		default: null,
 	})
 	public expiresAt: Date | null;
 
diff --git a/packages/backend/src/models/entities/note.ts b/packages/backend/src/models/entities/note.ts
index da49d53b69..0ffeb85f69 100644
--- a/packages/backend/src/models/entities/note.ts
+++ b/packages/backend/src/models/entities/note.ts
@@ -53,8 +53,8 @@ export class Note {
 	})
 	public threadId: string | null;
 
-	@Column('varchar', {
-		length: 8192, nullable: true,
+	@Column('text', {
+		nullable: true,
 	})
 	public text: string | null;
 
@@ -179,7 +179,7 @@ export class Note {
 	@Index()
 	@Column({
 		...id(),
-		nullable: true, default: null,
+		nullable: true,
 		comment: 'The ID of source channel.',
 	})
 	public channelId: Channel['id'] | null;
diff --git a/packages/backend/src/models/entities/user-profile.ts b/packages/backend/src/models/entities/user-profile.ts
index f95cb144c5..1778742ea2 100644
--- a/packages/backend/src/models/entities/user-profile.ts
+++ b/packages/backend/src/models/entities/user-profile.ts
@@ -192,6 +192,7 @@ export class UserProfile {
 
 	@Column('jsonb', {
 		default: [],
+		comment: 'List of instances muted by the user.',
 	})
 	public mutedInstances: string[];
 
diff --git a/packages/backend/src/models/entities/user.ts b/packages/backend/src/models/entities/user.ts
index 29d9a0c2ca..df92fb8259 100644
--- a/packages/backend/src/models/entities/user.ts
+++ b/packages/backend/src/models/entities/user.ts
@@ -207,7 +207,7 @@ export class User {
 
 	@Column('boolean', {
 		default: false,
-		comment: 'Whether to show users replying to other users in the timeline',
+		comment: 'Whether to show users replying to other users in the timeline.',
 	})
 	public showTimelineReplies: boolean;
 
diff --git a/packages/backend/src/models/repositories/drive-file.ts b/packages/backend/src/models/repositories/drive-file.ts
index 69dc1721c2..0d589d4f11 100644
--- a/packages/backend/src/models/repositories/drive-file.ts
+++ b/packages/backend/src/models/repositories/drive-file.ts
@@ -1,6 +1,5 @@
 import { db } from '@/db/postgre.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
-import { Users, DriveFolders } from '../index.js';
 import { User } from '@/models/entities/user.js';
 import { toPuny } from '@/misc/convert-host.js';
 import { awaitAll, Promiseable } from '@/prelude/await-all.js';
@@ -9,6 +8,7 @@ import config from '@/config/index.js';
 import { query, appendQuery } from '@/prelude/url.js';
 import { Meta } from '@/models/entities/meta.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
+import { Users, DriveFolders } from '../index.js';
 
 type PackOptions = {
 	detail?: boolean,
@@ -29,6 +29,8 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
 
 	getPublicProperties(file: DriveFile): DriveFile['properties'] {
 		if (file.properties.orientation != null) {
+			// TODO
+			//const properties = structuredClone(file.properties);
 			const properties = JSON.parse(JSON.stringify(file.properties));
 			if (file.properties.orientation >= 5) {
 				[properties.width, properties.height] = [properties.height, properties.width];
@@ -111,7 +113,40 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
 
 	async pack(
 		src: DriveFile['id'] | DriveFile,
-		options?: PackOptions
+		options?: PackOptions,
+	): Promise<Packed<'DriveFile'>> {
+		const opts = Object.assign({
+			detail: false,
+			self: false,
+		}, options);
+
+		const file = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
+
+		return await awaitAll<Packed<'DriveFile'>>({
+			id: file.id,
+			createdAt: file.createdAt.toISOString(),
+			name: file.name,
+			type: file.type,
+			md5: file.md5,
+			size: file.size,
+			isSensitive: file.isSensitive,
+			blurhash: file.blurhash,
+			properties: opts.self ? file.properties : this.getPublicProperties(file),
+			url: opts.self ? file.url : this.getPublicUrl(file, false),
+			thumbnailUrl: this.getPublicUrl(file, true),
+			comment: file.comment,
+			folderId: file.folderId,
+			folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
+				detail: true,
+			}) : null,
+			userId: opts.withUser ? file.userId : null,
+			user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
+		});
+	},
+
+	async packNullable(
+		src: DriveFile['id'] | DriveFile,
+		options?: PackOptions,
 	): Promise<Packed<'DriveFile'> | null> {
 		const opts = Object.assign({
 			detail: false,
@@ -145,9 +180,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
 
 	async packMany(
 		files: (DriveFile['id'] | DriveFile)[],
-		options?: PackOptions
-	) {
-		const items = await Promise.all(files.map(f => this.pack(f, options)));
-		return items.filter(x => x != null);
+		options?: PackOptions,
+	): Promise<Packed<'DriveFile'>[]> {
+		const items = await Promise.all(files.map(f => this.packNullable(f, options)));
+		return items.filter((x): x is Packed<'DriveFile'> => x != null);
 	},
 });
diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index cf5fcb1787..3fefab0319 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -136,6 +136,7 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: {
 
 export const NoteRepository = db.getRepository(Note).extend({
 	async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
+		// This code must always be synchronized with the checks in generateVisibilityQuery.
 		// visibility ใŒ specified ใ‹ใค่‡ชๅˆ†ใŒๆŒ‡ๅฎšใ•ใ‚Œใฆใ„ใชใ‹ใฃใŸใ‚‰้ž่กจ็คบ
 		if (note.visibility === 'specified') {
 			if (meId == null) {
@@ -144,13 +145,7 @@ export const NoteRepository = db.getRepository(Note).extend({
 				return true;
 			} else {
 				// ๆŒ‡ๅฎšใ•ใ‚Œใฆใ„ใ‚‹ใ‹ใฉใ†ใ‹
-				const specified = note.visibleUserIds.some((id: any) => meId === id);
-
-				if (specified) {
-					return true;
-				} else {
-					return false;
-				}
+				return note.visibleUserIds.some((id: any) => meId === id);
 			}
 		}
 
@@ -168,16 +163,25 @@ export const NoteRepository = db.getRepository(Note).extend({
 				return true;
 			} else {
 				// ใƒ•ใ‚ฉใƒญใƒฏใƒผใ‹ใฉใ†ใ‹
-				const following = await Followings.findOneBy({
-					followeeId: note.userId,
-					followerId: meId,
-				});
+				const [following, user] = await Promise.all([
+					Followings.count({
+						where: {
+							followeeId: note.userId,
+							followerId: meId,
+						},
+						take: 1,
+					}),
+					Users.findOneByOrFail({ id: meId }),
+				]);
 
-				if (following == null) {
-					return false;
-				} else {
-					return true;
-				}
+				/* If we know the following, everyhting is fine.
+
+				But if we do not know the following, it might be that both the
+				author of the note and the author of the like are remote users,
+				in which case we can never know the following. Instead we have
+				to assume that the users are following each other.
+				*/
+				return following > 0 || (note.userHost != null && user.host != null);
 			}
 		}
 
diff --git a/packages/backend/src/models/repositories/page.ts b/packages/backend/src/models/repositories/page.ts
index 1bffb23fa2..092b26b396 100644
--- a/packages/backend/src/models/repositories/page.ts
+++ b/packages/backend/src/models/repositories/page.ts
@@ -1,10 +1,10 @@
 import { db } from '@/db/postgre.js';
 import { Page } from '@/models/entities/page.js';
 import { Packed } from '@/misc/schema.js';
-import { Users, DriveFiles, PageLikes } from '../index.js';
 import { awaitAll } from '@/prelude/await-all.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { User } from '@/models/entities/user.js';
+import { Users, DriveFiles, PageLikes } from '../index.js';
 
 export const PageRepository = db.getRepository(Page).extend({
 	async pack(
@@ -14,7 +14,7 @@ export const PageRepository = db.getRepository(Page).extend({
 		const meId = me ? me.id : null;
 		const page = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
 
-		const attachedFiles: Promise<DriveFile | undefined>[] = [];
+		const attachedFiles: Promise<DriveFile | null>[] = [];
 		const collectFile = (xs: any[]) => {
 			for (const x of xs) {
 				if (x.type === 'image') {
@@ -73,7 +73,7 @@ export const PageRepository = db.getRepository(Page).extend({
 			script: page.script,
 			eyeCatchingImageId: page.eyeCatchingImageId,
 			eyeCatchingImage: page.eyeCatchingImageId ? await DriveFiles.pack(page.eyeCatchingImageId) : null,
-			attachedFiles: DriveFiles.packMany(await Promise.all(attachedFiles)),
+			attachedFiles: DriveFiles.packMany((await Promise.all(attachedFiles)).filter((x): x is DriveFile => x != null)),
 			likedCount: page.likedCount,
 			isLiked: meId ? await PageLikes.findOneBy({ pageId: page.id, userId: meId }).then(x => x != null) : undefined,
 		});
diff --git a/packages/backend/src/models/repositories/user.ts b/packages/backend/src/models/repositories/user.ts
index 541fbaf003..8a4e48efdd 100644
--- a/packages/backend/src/models/repositories/user.ts
+++ b/packages/backend/src/models/repositories/user.ts
@@ -61,47 +61,58 @@ export const UserRepository = db.getRepository(User).extend({
 	//#endregion
 
 	async getRelation(me: User['id'], target: User['id']) {
-		const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
-			Followings.findOneBy({
-				followerId: me,
-				followeeId: target,
-			}),
-			Followings.findOneBy({
-				followerId: target,
-				followeeId: me,
-			}),
-			FollowRequests.findOneBy({
-				followerId: me,
-				followeeId: target,
-			}),
-			FollowRequests.findOneBy({
-				followerId: target,
-				followeeId: me,
-			}),
-			Blockings.findOneBy({
-				blockerId: me,
-				blockeeId: target,
-			}),
-			Blockings.findOneBy({
-				blockerId: target,
-				blockeeId: me,
-			}),
-			Mutings.findOneBy({
-				muterId: me,
-				muteeId: target,
-			}),
-		]);
-
-		return {
+		return awaitAll({
 			id: target,
-			isFollowing: following1 != null,
-			hasPendingFollowRequestFromYou: followReq1 != null,
-			hasPendingFollowRequestToYou: followReq2 != null,
-			isFollowed: following2 != null,
-			isBlocking: toBlocking != null,
-			isBlocked: fromBlocked != null,
-			isMuted: mute != null,
-		};
+			isFollowing: Followings.count({
+				where: {
+					followerId: me,
+					followeeId: target,
+				},
+				take: 1,
+			}).then(n => n > 0),
+			isFollowed: Followings.count({
+				where: {
+					followerId: target,
+					followeeId: me,
+				},
+				take: 1,
+			}).then(n => n > 0),
+			hasPendingFollowRequestFromYou: FollowRequests.count({
+				where: {
+					followerId: me,
+					followeeId: target,
+				},
+				take: 1,
+			}).then(n => n > 0),
+			hasPendingFollowRequestToYou: FollowRequests.count({
+				where: {
+					followerId: target,
+					followeeId: me,
+				},
+				take: 1,
+			}).then(n => n > 0),
+			isBlocking: Blockings.count({
+				where: {
+					blockerId: me,
+					blockeeId: target,
+				},
+				take: 1,
+			}).then(n => n > 0),
+			isBlocked: Blockings.count({
+				where: {
+					blockerId: target,
+					blockeeId: me,
+				},
+				take: 1,
+			}).then(n => n > 0),
+			isMuted: Mutings.count({
+				where: {
+					muterId: me,
+					muteeId: target,
+				},
+				take: 1,
+			}).then(n => n > 0),
+		});
 	},
 
 	async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts
index 2d40290e4c..c5fd7de1cb 100644
--- a/packages/backend/src/queue/index.ts
+++ b/packages/backend/src/queue/index.ts
@@ -1,4 +1,4 @@
-import httpSignature from 'http-signature';
+import httpSignature from '@peertube/http-signature';
 import { v4 as uuid } from 'uuid';
 
 import config from '@/config/index.js';
@@ -305,11 +305,13 @@ export default function() {
 	systemQueue.add('resyncCharts', {
 	}, {
 		repeat: { cron: '0 0 * * *' },
+		removeOnComplete: true,
 	});
 
 	systemQueue.add('cleanCharts', {
 	}, {
 		repeat: { cron: '0 0 * * *' },
+		removeOnComplete: true,
 	});
 
 	systemQueue.add('checkExpiredMutings', {
diff --git a/packages/backend/src/queue/processors/db/export-blocking.ts b/packages/backend/src/queue/processors/db/export-blocking.ts
index 166c9e4cd3..f5e0424a79 100644
--- a/packages/backend/src/queue/processors/db/export-blocking.ts
+++ b/packages/backend/src/queue/processors/db/export-blocking.ts
@@ -1,11 +1,11 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 
 import { queueLogger } from '../../logger.js';
 import { addFile } from '@/services/drive/add-file.js';
 import { format as dateFormat } from 'date-fns';
 import { getFullApAccount } from '@/misc/convert-host.js';
+import { createTemp } from '@/misc/create-temp.js';
 import { Users, Blockings } from '@/models/index.js';
 import { MoreThan } from 'typeorm';
 import { DbUserJobData } from '@/queue/types.js';
@@ -22,73 +22,72 @@ export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): P
 	}
 
 	// Create temp file
-	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
-		tmp.file((e, path, fd, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTemp();
 
 	logger.info(`Temp file is ${path}`);
 
-	const stream = fs.createWriteStream(path, { flags: 'a' });
+	try {
+		const stream = fs.createWriteStream(path, { flags: 'a' });
 
-	let exportedCount = 0;
-	let cursor: any = null;
+		let exportedCount = 0;
+		let cursor: any = null;
 
-	while (true) {
-		const blockings = await Blockings.find({
-			where: {
-				blockerId: user.id,
-				...(cursor ? { id: MoreThan(cursor) } : {}),
-			},
-			take: 100,
-			order: {
-				id: 1,
-			},
-		});
+		while (true) {
+			const blockings = await Blockings.find({
+				where: {
+					blockerId: user.id,
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+			});
 
-		if (blockings.length === 0) {
-			job.progress(100);
-			break;
-		}
-
-		cursor = blockings[blockings.length - 1].id;
-
-		for (const block of blockings) {
-			const u = await Users.findOneBy({ id: block.blockeeId });
-			if (u == null) {
-				exportedCount++; continue;
+			if (blockings.length === 0) {
+				job.progress(100);
+				break;
 			}
 
-			const content = getFullApAccount(u.username, u.host);
-			await new Promise<void>((res, rej) => {
-				stream.write(content + '\n', err => {
-					if (err) {
-						logger.error(err);
-						rej(err);
-					} else {
-						res();
-					}
+			cursor = blockings[blockings.length - 1].id;
+
+			for (const block of blockings) {
+				const u = await Users.findOneBy({ id: block.blockeeId });
+				if (u == null) {
+					exportedCount++; continue;
+				}
+
+				const content = getFullApAccount(u.username, u.host);
+				await new Promise<void>((res, rej) => {
+					stream.write(content + '\n', err => {
+						if (err) {
+							logger.error(err);
+							rej(err);
+						} else {
+							res();
+						}
+					});
 				});
+				exportedCount++;
+			}
+
+			const total = await Blockings.countBy({
+				blockerId: user.id,
 			});
-			exportedCount++;
+
+			job.progress(exportedCount / total);
 		}
 
-		const total = await Blockings.countBy({
-			blockerId: user.id,
-		});
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
 
-		job.progress(exportedCount / total);
+		const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
+		const driveFile = await addFile({ user, path, name: fileName, force: true });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	stream.end();
-	logger.succ(`Exported to: ${path}`);
-
-	const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-	const driveFile = await addFile({ user, path, name: fileName, force: true });
-
-	logger.succ(`Exported to: ${driveFile.id}`);
-	cleanup();
 	done();
 }
diff --git a/packages/backend/src/queue/processors/db/export-custom-emojis.ts b/packages/backend/src/queue/processors/db/export-custom-emojis.ts
index c2467fb5f0..8ce1d05272 100644
--- a/packages/backend/src/queue/processors/db/export-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/export-custom-emojis.ts
@@ -1,5 +1,4 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 
 import { ulid } from 'ulid';
@@ -10,6 +9,7 @@ import { addFile } from '@/services/drive/add-file.js';
 import { format as dateFormat } from 'date-fns';
 import { Users, Emojis } from '@/models/index.js';
 import {  } from '@/queue/types.js';
+import { createTemp, createTempDir } from '@/misc/create-temp.js';
 import { downloadUrl } from '@/misc/download-url.js';
 import config from '@/config/index.js';
 import { IsNull } from 'typeorm';
@@ -25,13 +25,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
 		return;
 	}
 
-	// Create temp dir
-	const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => {
-		tmp.dir((e, path, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTempDir();
 
 	logger.info(`Temp dir is ${path}`);
 
@@ -98,12 +92,7 @@ export async function exportCustomEmojis(job: Bull.Job, done: () => void): Promi
 	metaStream.end();
 
 	// Create archive
-	const [archivePath, archiveCleanup] = await new Promise<[string, () => void]>((res, rej) => {
-		tmp.file((e, path, fd, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [archivePath, archiveCleanup] = await createTemp();
 	const archiveStream = fs.createWriteStream(archivePath);
 	const archive = archiver('zip', {
 		zlib: { level: 0 },
diff --git a/packages/backend/src/queue/processors/db/export-following.ts b/packages/backend/src/queue/processors/db/export-following.ts
index 965500ac27..4ac165567b 100644
--- a/packages/backend/src/queue/processors/db/export-following.ts
+++ b/packages/backend/src/queue/processors/db/export-following.ts
@@ -1,11 +1,11 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 
 import { queueLogger } from '../../logger.js';
 import { addFile } from '@/services/drive/add-file.js';
 import { format as dateFormat } from 'date-fns';
 import { getFullApAccount } from '@/misc/convert-host.js';
+import { createTemp } from '@/misc/create-temp.js';
 import { Users, Followings, Mutings } from '@/models/index.js';
 import { In, MoreThan, Not } from 'typeorm';
 import { DbUserJobData } from '@/queue/types.js';
@@ -23,73 +23,72 @@ export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: () =>
 	}
 
 	// Create temp file
-	const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => {
-		tmp.file((e, path, fd, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTemp();
 
 	logger.info(`Temp file is ${path}`);
 
-	const stream = fs.createWriteStream(path, { flags: 'a' });
+	try {
+		const stream = fs.createWriteStream(path, { flags: 'a' });
 
-	let cursor: Following['id'] | null = null;
+		let cursor: Following['id'] | null = null;
 
-	const mutings = job.data.excludeMuting ? await Mutings.findBy({
-		muterId: user.id,
-	}) : [];
+		const mutings = job.data.excludeMuting ? await Mutings.findBy({
+			muterId: user.id,
+		}) : [];
 
-	while (true) {
-		const followings = await Followings.find({
-			where: {
-				followerId: user.id,
-				...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}),
-				...(cursor ? { id: MoreThan(cursor) } : {}),
-			},
-			take: 100,
-			order: {
-				id: 1,
-			},
-		}) as Following[];
+		while (true) {
+			const followings = await Followings.find({
+				where: {
+					followerId: user.id,
+					...(mutings.length > 0 ? { followeeId: Not(In(mutings.map(x => x.muteeId))) } : {}),
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+			}) as Following[];
 
-		if (followings.length === 0) {
-			break;
-		}
-
-		cursor = followings[followings.length - 1].id;
-
-		for (const following of followings) {
-			const u = await Users.findOneBy({ id: following.followeeId });
-			if (u == null) {
-				continue;
+			if (followings.length === 0) {
+				break;
 			}
 
-			if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) {
-				continue;
-			}
+			cursor = followings[followings.length - 1].id;
 
-			const content = getFullApAccount(u.username, u.host);
-			await new Promise<void>((res, rej) => {
-				stream.write(content + '\n', err => {
-					if (err) {
-						logger.error(err);
-						rej(err);
-					} else {
-						res();
-					}
+			for (const following of followings) {
+				const u = await Users.findOneBy({ id: following.followeeId });
+				if (u == null) {
+					continue;
+				}
+
+				if (job.data.excludeInactive && u.updatedAt && (Date.now() - u.updatedAt.getTime() > 1000 * 60 * 60 * 24 * 90)) {
+					continue;
+				}
+
+				const content = getFullApAccount(u.username, u.host);
+				await new Promise<void>((res, rej) => {
+					stream.write(content + '\n', err => {
+						if (err) {
+							logger.error(err);
+							rej(err);
+						} else {
+							res();
+						}
+					});
 				});
-			});
+			}
 		}
+
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
+
+		const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
+		const driveFile = await addFile({ user, path, name: fileName, force: true });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	stream.end();
-	logger.succ(`Exported to: ${path}`);
-
-	const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-	const driveFile = await addFile({ user, path, name: fileName, force: true });
-
-	logger.succ(`Exported to: ${driveFile.id}`);
-	cleanup();
 	done();
 }
diff --git a/packages/backend/src/queue/processors/db/export-mute.ts b/packages/backend/src/queue/processors/db/export-mute.ts
index 0ef81971f1..6a36cfa072 100644
--- a/packages/backend/src/queue/processors/db/export-mute.ts
+++ b/packages/backend/src/queue/processors/db/export-mute.ts
@@ -1,11 +1,11 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 
 import { queueLogger } from '../../logger.js';
 import { addFile } from '@/services/drive/add-file.js';
 import { format as dateFormat } from 'date-fns';
 import { getFullApAccount } from '@/misc/convert-host.js';
+import { createTemp } from '@/misc/create-temp.js';
 import { Users, Mutings } from '@/models/index.js';
 import { IsNull, MoreThan } from 'typeorm';
 import { DbUserJobData } from '@/queue/types.js';
@@ -22,74 +22,73 @@ export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promi
 	}
 
 	// Create temp file
-	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
-		tmp.file((e, path, fd, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTemp();
 
 	logger.info(`Temp file is ${path}`);
 
-	const stream = fs.createWriteStream(path, { flags: 'a' });
+	try {
+		const stream = fs.createWriteStream(path, { flags: 'a' });
 
-	let exportedCount = 0;
-	let cursor: any = null;
+		let exportedCount = 0;
+		let cursor: any = null;
 
-	while (true) {
-		const mutes = await Mutings.find({
-			where: {
-				muterId: user.id,
-				expiresAt: IsNull(),
-				...(cursor ? { id: MoreThan(cursor) } : {}),
-			},
-			take: 100,
-			order: {
-				id: 1,
-			},
-		});
+		while (true) {
+			const mutes = await Mutings.find({
+				where: {
+					muterId: user.id,
+					expiresAt: IsNull(),
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+			});
 
-		if (mutes.length === 0) {
-			job.progress(100);
-			break;
-		}
-
-		cursor = mutes[mutes.length - 1].id;
-
-		for (const mute of mutes) {
-			const u = await Users.findOneBy({ id: mute.muteeId });
-			if (u == null) {
-				exportedCount++; continue;
+			if (mutes.length === 0) {
+				job.progress(100);
+				break;
 			}
 
-			const content = getFullApAccount(u.username, u.host);
-			await new Promise<void>((res, rej) => {
-				stream.write(content + '\n', err => {
-					if (err) {
-						logger.error(err);
-						rej(err);
-					} else {
-						res();
-					}
+			cursor = mutes[mutes.length - 1].id;
+
+			for (const mute of mutes) {
+				const u = await Users.findOneBy({ id: mute.muteeId });
+				if (u == null) {
+					exportedCount++; continue;
+				}
+
+				const content = getFullApAccount(u.username, u.host);
+				await new Promise<void>((res, rej) => {
+					stream.write(content + '\n', err => {
+						if (err) {
+							logger.error(err);
+							rej(err);
+						} else {
+							res();
+						}
+					});
 				});
+				exportedCount++;
+			}
+
+			const total = await Mutings.countBy({
+				muterId: user.id,
 			});
-			exportedCount++;
+
+			job.progress(exportedCount / total);
 		}
 
-		const total = await Mutings.countBy({
-			muterId: user.id,
-		});
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
 
-		job.progress(exportedCount / total);
+		const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
+		const driveFile = await addFile({ user, path, name: fileName, force: true });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	stream.end();
-	logger.succ(`Exported to: ${path}`);
-
-	const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-	const driveFile = await addFile({ user, path, name: fileName, force: true });
-
-	logger.succ(`Exported to: ${driveFile.id}`);
-	cleanup();
 	done();
 }
diff --git a/packages/backend/src/queue/processors/db/export-notes.ts b/packages/backend/src/queue/processors/db/export-notes.ts
index 7e12a6fac2..051fcdf385 100644
--- a/packages/backend/src/queue/processors/db/export-notes.ts
+++ b/packages/backend/src/queue/processors/db/export-notes.ts
@@ -1,5 +1,4 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 
 import { queueLogger } from '../../logger.js';
@@ -10,6 +9,7 @@ import { MoreThan } from 'typeorm';
 import { Note } from '@/models/entities/note.js';
 import { Poll } from '@/models/entities/poll.js';
 import { DbUserJobData } from '@/queue/types.js';
+import { createTemp } from '@/misc/create-temp.js';
 
 const logger = queueLogger.createSubLogger('export-notes');
 
@@ -23,82 +23,81 @@ export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Prom
 	}
 
 	// Create temp file
-	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
-		tmp.file((e, path, fd, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTemp();
 
 	logger.info(`Temp file is ${path}`);
 
-	const stream = fs.createWriteStream(path, { flags: 'a' });
+	try {
+		const stream = fs.createWriteStream(path, { flags: 'a' });
 
-	const write = (text: string): Promise<void> => {
-		return new Promise<void>((res, rej) => {
-			stream.write(text, err => {
-				if (err) {
-					logger.error(err);
-					rej(err);
-				} else {
-					res();
-				}
+		const write = (text: string): Promise<void> => {
+			return new Promise<void>((res, rej) => {
+				stream.write(text, err => {
+					if (err) {
+						logger.error(err);
+						rej(err);
+					} else {
+						res();
+					}
+				});
 			});
-		});
-	};
+		};
 
-	await write('[');
+		await write('[');
 
-	let exportedNotesCount = 0;
-	let cursor: Note['id'] | null = null;
+		let exportedNotesCount = 0;
+		let cursor: Note['id'] | null = null;
 
-	while (true) {
-		const notes = await Notes.find({
-			where: {
-				userId: user.id,
-				...(cursor ? { id: MoreThan(cursor) } : {}),
-			},
-			take: 100,
-			order: {
-				id: 1,
-			},
-		}) as Note[];
+		while (true) {
+			const notes = await Notes.find({
+				where: {
+					userId: user.id,
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+			}) as Note[];
 
-		if (notes.length === 0) {
-			job.progress(100);
-			break;
-		}
-
-		cursor = notes[notes.length - 1].id;
-
-		for (const note of notes) {
-			let poll: Poll | undefined;
-			if (note.hasPoll) {
-				poll = await Polls.findOneByOrFail({ noteId: note.id });
+			if (notes.length === 0) {
+				job.progress(100);
+				break;
 			}
-			const content = JSON.stringify(serialize(note, poll));
-			const isFirst = exportedNotesCount === 0;
-			await write(isFirst ? content : ',\n' + content);
-			exportedNotesCount++;
+
+			cursor = notes[notes.length - 1].id;
+
+			for (const note of notes) {
+				let poll: Poll | undefined;
+				if (note.hasPoll) {
+					poll = await Polls.findOneByOrFail({ noteId: note.id });
+				}
+				const content = JSON.stringify(serialize(note, poll));
+				const isFirst = exportedNotesCount === 0;
+				await write(isFirst ? content : ',\n' + content);
+				exportedNotesCount++;
+			}
+
+			const total = await Notes.countBy({
+				userId: user.id,
+			});
+
+			job.progress(exportedNotesCount / total);
 		}
 
-		const total = await Notes.countBy({
-			userId: user.id,
-		});
+		await write(']');
 
-		job.progress(exportedNotesCount / total);
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
+
+		const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
+		const driveFile = await addFile({ user, path, name: fileName, force: true });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	await write(']');
-
-	stream.end();
-	logger.succ(`Exported to: ${path}`);
-
-	const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
-	const driveFile = await addFile({ user, path, name: fileName, force: true });
-
-	logger.succ(`Exported to: ${driveFile.id}`);
-	cleanup();
 	done();
 }
 
diff --git a/packages/backend/src/queue/processors/db/export-user-lists.ts b/packages/backend/src/queue/processors/db/export-user-lists.ts
index 45852a6038..71dd72df27 100644
--- a/packages/backend/src/queue/processors/db/export-user-lists.ts
+++ b/packages/backend/src/queue/processors/db/export-user-lists.ts
@@ -1,11 +1,11 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 
 import { queueLogger } from '../../logger.js';
 import { addFile } from '@/services/drive/add-file.js';
 import { format as dateFormat } from 'date-fns';
 import { getFullApAccount } from '@/misc/convert-host.js';
+import { createTemp } from '@/misc/create-temp.js';
 import { Users, UserLists, UserListJoinings } from '@/models/index.js';
 import { In } from 'typeorm';
 import { DbUserJobData } from '@/queue/types.js';
@@ -26,46 +26,45 @@ export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any):
 	});
 
 	// Create temp file
-	const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
-		tmp.file((e, path, fd, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTemp();
 
 	logger.info(`Temp file is ${path}`);
 
-	const stream = fs.createWriteStream(path, { flags: 'a' });
+	try {
+		const stream = fs.createWriteStream(path, { flags: 'a' });
 
-	for (const list of lists) {
-		const joinings = await UserListJoinings.findBy({ userListId: list.id });
-		const users = await Users.findBy({
-			id: In(joinings.map(j => j.userId)),
-		});
-
-		for (const u of users) {
-			const acct = getFullApAccount(u.username, u.host);
-			const content = `${list.name},${acct}`;
-			await new Promise<void>((res, rej) => {
-				stream.write(content + '\n', err => {
-					if (err) {
-						logger.error(err);
-						rej(err);
-					} else {
-						res();
-					}
-				});
+		for (const list of lists) {
+			const joinings = await UserListJoinings.findBy({ userListId: list.id });
+			const users = await Users.findBy({
+				id: In(joinings.map(j => j.userId)),
 			});
+
+			for (const u of users) {
+				const acct = getFullApAccount(u.username, u.host);
+				const content = `${list.name},${acct}`;
+				await new Promise<void>((res, rej) => {
+					stream.write(content + '\n', err => {
+						if (err) {
+							logger.error(err);
+							rej(err);
+						} else {
+							res();
+						}
+					});
+				});
+			}
 		}
+
+		stream.end();
+		logger.succ(`Exported to: ${path}`);
+
+		const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
+		const driveFile = await addFile({ user, path, name: fileName, force: true });
+
+		logger.succ(`Exported to: ${driveFile.id}`);
+	} finally {
+		cleanup();
 	}
 
-	stream.end();
-	logger.succ(`Exported to: ${path}`);
-
-	const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-	const driveFile = await addFile({ user, path, name: fileName, force: true });
-
-	logger.succ(`Exported to: ${driveFile.id}`);
-	cleanup();
 	done();
 }
diff --git a/packages/backend/src/queue/processors/db/import-custom-emojis.ts b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
index 28e0b867a4..64dfe85374 100644
--- a/packages/backend/src/queue/processors/db/import-custom-emojis.ts
+++ b/packages/backend/src/queue/processors/db/import-custom-emojis.ts
@@ -1,9 +1,9 @@
 import Bull from 'bull';
-import * as tmp from 'tmp';
 import * as fs from 'node:fs';
 import unzipper from 'unzipper';
 
 import { queueLogger } from '../../logger.js';
+import { createTempDir } from '@/misc/create-temp.js';
 import { downloadUrl } from '@/misc/download-url.js';
 import { DriveFiles, Emojis } from '@/models/index.js';
 import { DbUserImportJobData } from '@/queue/types.js';
@@ -25,13 +25,7 @@ export async function importCustomEmojis(job: Bull.Job<DbUserImportJobData>, don
 		return;
 	}
 
-	// Create temp dir
-	const [path, cleanup] = await new Promise<[string, () => void]>((res, rej) => {
-		tmp.dir((e, path, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
-		});
-	});
+	const [path, cleanup] = await createTempDir();
 
 	logger.info(`Temp dir is ${path}`);
 
diff --git a/packages/backend/src/queue/processors/inbox.ts b/packages/backend/src/queue/processors/inbox.ts
index 4fbfdb234f..198dde6050 100644
--- a/packages/backend/src/queue/processors/inbox.ts
+++ b/packages/backend/src/queue/processors/inbox.ts
@@ -1,6 +1,6 @@
 import { URL } from 'node:url';
 import Bull from 'bull';
-import httpSignature from 'http-signature';
+import httpSignature from '@peertube/http-signature';
 import perform from '@/remote/activitypub/perform.js';
 import Logger from '@/services/logger.js';
 import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index 6c0b9d9bf6..5ea4725561 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -3,7 +3,7 @@ import { Note } from '@/models/entities/note';
 import { User } from '@/models/entities/user.js';
 import { Webhook } from '@/models/entities/webhook';
 import { IActivity } from '@/remote/activitypub/type.js';
-import httpSignature from 'http-signature';
+import httpSignature from '@peertube/http-signature';
 
 export type DeliverJobData = {
 	/** Actor */
diff --git a/packages/backend/src/remote/activitypub/db-resolver.ts b/packages/backend/src/remote/activitypub/db-resolver.ts
index ef07966e42..1a02f675ca 100644
--- a/packages/backend/src/remote/activitypub/db-resolver.ts
+++ b/packages/backend/src/remote/activitypub/db-resolver.ts
@@ -5,14 +5,52 @@ import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/
 import { UserPublickey } from '@/models/entities/user-publickey.js';
 import { MessagingMessage } from '@/models/entities/messaging-message.js';
 import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
-import { IObject, getApId } from './type.js';
-import { resolvePerson } from './models/person.js';
 import { Cache } from '@/misc/cache.js';
 import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
+import { IObject, getApId } from './type.js';
+import { resolvePerson } from './models/person.js';
 
 const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
 const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
 
+export type UriParseResult = {
+	/** wether the URI was generated by us */
+	local: true;
+	/** id in DB */
+	id: string;
+	/** hint of type, e.g. "notes", "users" */
+	type: string;
+	/** any remaining text after type and id, not including the slash after id. undefined if empty */
+	rest?: string;
+} | {
+	/** wether the URI was generated by us */
+	local: false;
+	/** uri in DB */
+	uri: string;
+};
+
+export function parseUri(value: string | IObject): UriParseResult {
+	const uri = getApId(value);
+
+	// the host part of a URL is case insensitive, so use the 'i' flag.
+	const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i');
+	const matchLocal = uri.match(localRegex);
+
+	if (matchLocal) {
+		return {
+			local: true,
+			type: matchLocal[1],
+			id: matchLocal[2],
+			rest: matchLocal[3],
+		};
+	} else {
+		return {
+			local: false,
+			uri,
+		};
+	}
+}
+
 export default class DbResolver {
 	constructor() {
 	}
@@ -21,60 +59,54 @@ export default class DbResolver {
 	 * AP Note => Misskey Note in DB
 	 */
 	public async getNoteFromApId(value: string | IObject): Promise<Note | null> {
-		const parsed = this.parseUri(value);
+		const parsed = parseUri(value);
+
+		if (parsed.local) {
+			if (parsed.type !== 'notes') return null;
 
-		if (parsed.id) {
 			return await Notes.findOneBy({
 				id: parsed.id,
 			});
-		}
-
-		if (parsed.uri) {
+		} else {
 			return await Notes.findOneBy({
 				uri: parsed.uri,
 			});
 		}
-
-		return null;
 	}
 
 	public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> {
-		const parsed = this.parseUri(value);
+		const parsed = parseUri(value);
+
+		if (parsed.local) {
+			if (parsed.type !== 'notes') return null;
 
-		if (parsed.id) {
 			return await MessagingMessages.findOneBy({
 				id: parsed.id,
 			});
-		}
-
-		if (parsed.uri) {
+		} else {
 			return await MessagingMessages.findOneBy({
 				uri: parsed.uri,
 			});
 		}
-
-		return null;
 	}
 
 	/**
 	 * AP Person => Misskey User in DB
 	 */
 	public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
-		const parsed = this.parseUri(value);
+		const parsed = parseUri(value);
+
+		if (parsed.local) {
+			if (parsed.type !== 'users') return null;
 
-		if (parsed.id) {
 			return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({
 				id: parsed.id,
 			}).then(x => x ?? undefined)) ?? null;
-		}
-
-		if (parsed.uri) {
+		} else {
 			return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({
 				uri: parsed.uri,
 			}));
 		}
-
-		return null;
 	}
 
 	/**
@@ -120,31 +152,4 @@ export default class DbResolver {
 			key,
 		};
 	}
-
-	public parseUri(value: string | IObject): UriParseResult {
-		const uri = getApId(value);
-
-		const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/' + '(\\w+)' + '/' + '(\\w+)');
-		const matchLocal = uri.match(localRegex);
-
-		if (matchLocal) {
-			return {
-				type: matchLocal[1],
-				id: matchLocal[2],
-			};
-		} else {
-			return {
-				uri,
-			};
-		}
-	}
 }
-
-type UriParseResult = {
-	/** id in DB (local object only) */
-	id?: string;
-	/** uri in DB (remote object only) */
-	uri?: string;
-	/** hint of type (local object only, ex: notes, users) */
-	type?: string
-};
diff --git a/packages/backend/src/remote/activitypub/kernel/announce/note.ts b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
index 680749f4d8..759cb4ae83 100644
--- a/packages/backend/src/remote/activitypub/kernel/announce/note.ts
+++ b/packages/backend/src/remote/activitypub/kernel/announce/note.ts
@@ -9,6 +9,7 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
 import { getApLock } from '@/misc/app-lock.js';
 import { parseAudience } from '../../audience.js';
 import { StatusError } from '@/misc/fetch.js';
+import { Notes } from '@/models/index.js';
 
 const logger = apLogger;
 
@@ -52,6 +53,8 @@ export default async function(resolver: Resolver, actor: CacheableRemoteUser, ac
 			throw e;
 		}
 
+		if (!await Notes.isVisibleForMe(renote, actor.id)) return 'skip: invalid actor for this activity';
+
 		logger.info(`Creating the (Re)Note: ${uri}`);
 
 		const activityAudience = await parseAudience(actor, activity.to, activity.cc);
diff --git a/packages/backend/src/remote/activitypub/kernel/delete/index.ts b/packages/backend/src/remote/activitypub/kernel/delete/index.ts
index 4c06a9de0b..c7064f553b 100644
--- a/packages/backend/src/remote/activitypub/kernel/delete/index.ts
+++ b/packages/backend/src/remote/activitypub/kernel/delete/index.ts
@@ -13,37 +13,37 @@ export default async (actor: CacheableRemoteUser, activity: IDelete): Promise<st
 	}
 
 	// ๅ‰Š้™คๅฏพ่ฑกobjectใฎtype
-	let formarType: string | undefined;
+	let formerType: string | undefined;
 
 	if (typeof activity.object === 'string') {
 		// typeใŒไธๆ˜Žใ ใ‘ใฉใ€ใฉใ†ใ›ๆถˆใˆใฆใ‚‹ใฎใงremote resolveใ—ใชใ„
-		formarType = undefined;
+		formerType = undefined;
 	} else {
 		const object = activity.object as IObject;
 		if (isTombstone(object)) {
-			formarType = toSingle(object.formerType);
+			formerType = toSingle(object.formerType);
 		} else {
-			formarType = toSingle(object.type);
+			formerType = toSingle(object.type);
 		}
 	}
 
 	const uri = getApId(activity.object);
 
 	// typeไธๆ˜Žใงใ‚‚actorใจobjectใŒๅŒใ˜ใชใ‚‰ใฐใใ‚ŒใฏPersonใซ้•ใ„ใชใ„
-	if (!formarType && actor.uri === uri) {
-		formarType = 'Person';
+	if (!formerType && actor.uri === uri) {
+		formerType = 'Person';
 	}
 
 	// ใใ‚Œใงใ‚‚ใชใ‹ใฃใŸใ‚‰ใŠใใ‚‰ใNote
-	if (!formarType) {
-		formarType = 'Note';
+	if (!formerType) {
+		formerType = 'Note';
 	}
 
-	if (validPost.includes(formarType)) {
+	if (validPost.includes(formerType)) {
 		return await deleteNote(actor, uri);
-	} else if (validActor.includes(formarType)) {
+	} else if (validActor.includes(formerType)) {
 		return await deleteActor(actor, uri);
 	} else {
-		return `Unknown type ${formarType}`;
+		return `Unknown type ${formerType}`;
 	}
 };
diff --git a/packages/backend/src/remote/activitypub/kernel/move/index.ts b/packages/backend/src/remote/activitypub/kernel/move/index.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts
index c2ac31bf8d..417f39722f 100644
--- a/packages/backend/src/remote/activitypub/kernel/undo/announce.ts
+++ b/packages/backend/src/remote/activitypub/kernel/undo/announce.ts
@@ -8,6 +8,7 @@ export const undoAnnounce = async (actor: CacheableRemoteUser, activity: IAnnoun
 
 	const note = await Notes.findOneBy({
 		uri,
+		userId: actor.id,
 	});
 
 	if (!note) return 'skip: no such Announce';
diff --git a/packages/backend/src/remote/activitypub/misc/get-note-html.ts b/packages/backend/src/remote/activitypub/misc/get-note-html.ts
index 3800b40608..389039ebed 100644
--- a/packages/backend/src/remote/activitypub/misc/get-note-html.ts
+++ b/packages/backend/src/remote/activitypub/misc/get-note-html.ts
@@ -3,8 +3,6 @@ import { Note } from '@/models/entities/note.js';
 import { toHtml } from '../../../mfm/to-html.js';
 
 export default function(note: Note) {
-	let html = note.text ? toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)) : null;
-	if (html == null) html = '<p>.</p>';
-
-	return html;
+	if (!note.text) return '';
+	return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers));
 }
diff --git a/packages/backend/src/remote/activitypub/models/mention.ts b/packages/backend/src/remote/activitypub/models/mention.ts
index a160092969..13f77424ec 100644
--- a/packages/backend/src/remote/activitypub/models/mention.ts
+++ b/packages/backend/src/remote/activitypub/models/mention.ts
@@ -1,9 +1,9 @@
-import { toArray, unique } from '@/prelude/array.js';
-import { IObject, isMention, IApMention } from '../type.js';
-import { resolvePerson } from './person.js';
 import promiseLimit from 'promise-limit';
-import Resolver from '../resolver.js';
+import { toArray, unique } from '@/prelude/array.js';
 import { CacheableUser, User } from '@/models/entities/user.js';
+import { IObject, isMention, IApMention } from '../type.js';
+import Resolver from '../resolver.js';
+import { resolvePerson } from './person.js';
 
 export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
 	const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
@@ -12,7 +12,7 @@ export async function extractApMentions(tags: IObject | IObject[] | null | undef
 
 	const limit = promiseLimit<CacheableUser | null>(2);
 	const mentionedUsers = (await Promise.all(
-		hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
+		hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null))),
 	)).filter((x): x is CacheableUser => x != null);
 
 	return mentionedUsers;
diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts
index 097a716614..56c1a483ad 100644
--- a/packages/backend/src/remote/activitypub/models/note.ts
+++ b/packages/backend/src/remote/activitypub/models/note.ts
@@ -3,9 +3,9 @@ import promiseLimit from 'promise-limit';
 import config from '@/config/index.js';
 import Resolver from '../resolver.js';
 import post from '@/services/note/create.js';
-import { resolvePerson, updatePerson } from './person.js';
+import { resolvePerson } from './person.js';
 import { resolveImage } from './image.js';
-import { CacheableRemoteUser, IRemoteUser } from '@/models/entities/user.js';
+import { CacheableRemoteUser } from '@/models/entities/user.js';
 import { htmlToMfm } from '../misc/html-to-mfm.js';
 import { extractApHashtags } from './tag.js';
 import { unique, toArray, toSingle } from '@/prelude/array.js';
@@ -15,7 +15,7 @@ import { apLogger } from '../logger.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { deliverQuestionUpdate } from '@/services/note/polls/update.js';
 import { extractDbHost, toPuny } from '@/misc/convert-host.js';
-import { Emojis, Polls, MessagingMessages, Users } from '@/models/index.js';
+import { Emojis, Polls, MessagingMessages } from '@/models/index.js';
 import { Note } from '@/models/entities/note.js';
 import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type.js';
 import { Emoji } from '@/models/entities/emoji.js';
@@ -197,7 +197,14 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
 	const cw = note.summary === '' ? null : note.summary;
 
 	// ใƒ†ใ‚ญใ‚นใƒˆใฎใƒ‘ใƒผใ‚น
-	const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null);
+	let text: string | null = null;
+	if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') {
+		text = note.source.content;
+	} else if (typeof note._misskey_content === 'string') {
+		text = note._misskey_content;
+	} else if (typeof note.content === 'string') {
+		text = htmlToMfm(note.content, note.tag);
+	}
 
 	// vote
 	if (reply && reply.hasPoll) {
diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts
index 88661865da..6097e3b6ed 100644
--- a/packages/backend/src/remote/activitypub/models/person.ts
+++ b/packages/backend/src/remote/activitypub/models/person.ts
@@ -1,17 +1,8 @@
 import { URL } from 'node:url';
 import promiseLimit from 'promise-limit';
 
-import $, { Context } from 'cafy';
 import config from '@/config/index.js';
-import Resolver from '../resolver.js';
-import { resolveImage } from './image.js';
-import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
-import { fromHtml } from '../../../mfm/from-html.js';
-import { htmlToMfm } from '../misc/html-to-mfm.js';
-import { resolveNote, extractEmojis } from './note.js';
 import { registerOrFetchInstanceDoc } from '@/services/register-or-fetch-instance-doc.js';
-import { extractApHashtags } from './tag.js';
-import { apLogger } from '../logger.js';
 import { Note } from '@/models/entities/note.js';
 import { updateUsertags } from '@/services/update-hashtag.js';
 import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '@/models/index.js';
@@ -32,6 +23,14 @@ import { StatusError } from '@/misc/fetch.js';
 import { uriPersonCache } from '@/services/user-cache.js';
 import { publishInternalEvent } from '@/services/stream.js';
 import { db } from '@/db/postgre.js';
+import { apLogger } from '../logger.js';
+import { htmlToMfm } from '../misc/html-to-mfm.js';
+import { fromHtml } from '../../../mfm/from-html.js';
+import { isCollectionOrOrderedCollection, isCollection, IActor, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType, isActor } from '../type.js';
+import Resolver from '../resolver.js';
+import { extractApHashtags } from './tag.js';
+import { resolveNote, extractEmojis } from './note.js';
+import { resolveImage } from './image.js';
 
 const logger = apLogger;
 
@@ -54,20 +53,33 @@ function validateActor(x: IObject, uri: string): IActor {
 		throw new Error(`invalid Actor type '${x.type}'`);
 	}
 
-	const validate = (name: string, value: any, validater: Context) => {
-		const e = validater.test(value);
-		if (e) throw new Error(`invalid Actor: ${name} ${e.message}`);
-	};
+	if (!(typeof x.id === 'string' && x.id.length > 0)) {
+		throw new Error('invalid Actor: wrong id');
+	}
 
-	validate('id', x.id, $.default.str.min(1));
-	validate('inbox', x.inbox, $.default.str.min(1));
-	validate('preferredUsername', x.preferredUsername, $.default.str.min(1).max(128).match(/^\w([\w-.]*\w)?$/));
+	if (!(typeof x.inbox === 'string' && x.inbox.length > 0)) {
+		throw new Error('invalid Actor: wrong inbox');
+	}
+
+	if (!(typeof x.preferredUsername === 'string' && x.preferredUsername.length > 0 && x.preferredUsername.length <= 128 && /^\w([\w-.]*\w)?$/.test(x.preferredUsername))) {
+		throw new Error('invalid Actor: wrong username');
+	}
 
 	// These fields are only informational, and some AP software allows these
 	// fields to be very long. If they are too long, we cut them off. This way
 	// we can at least see these users and their activities.
-	validate('name', truncate(x.name, nameLength), $.default.optional.nullable.str);
-	validate('summary', truncate(x.summary, summaryLength), $.default.optional.nullable.str);
+	if (x.name) {
+		if (!(typeof x.name === 'string' && x.name.length > 0)) {
+			throw new Error('invalid Actor: wrong name');
+		}
+		x.name = truncate(x.name, nameLength);
+	}
+	if (x.summary) {
+		if (!(typeof x.summary === 'string' && x.summary.length > 0)) {
+			throw new Error('invalid Actor: wrong summary');
+		}
+		x.summary = truncate(x.summary, summaryLength);
+	}
 
 	const idHost = toPuny(new URL(x.id!).hostname);
 	if (idHost !== expectHost) {
@@ -271,7 +283,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
  * @param resolver Resolver
  * @param hint Hint of Person object (ใ“ใฎๅ€คใŒๆญฃๅฝ“ใชPersonใฎๅ ดๅˆใ€Remote resolveใ‚’ใ›ใšใซๆ›ดๆ–ฐใซๅˆฉ็”จใ—ใพใ™)
  */
-export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: Record<string, unknown>): Promise<void> {
+export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> {
 	if (typeof uri !== 'string') throw new Error('uri is not string');
 
 	// URIใŒใ“ใฎใ‚ตใƒผใƒใƒผใ‚’ๆŒ‡ใ—ใฆใ„ใ‚‹ใชใ‚‰ใ‚นใ‚ญใƒƒใƒ—
@@ -289,7 +301,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
 
 	if (resolver == null) resolver = new Resolver();
 
-	const object = hint || await resolver.resolve(uri) as any;
+	const object = hint || await resolver.resolve(uri);
 
 	const person = validateActor(object, uri);
 
@@ -400,10 +412,10 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<C
 const services: {
 		[x: string]: (id: string, username: string) => any
 	} = {
-	'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
-	'misskey:authentication:github': (id, login) => ({ id, login }),
-	'misskey:authentication:discord': (id, name) => $discord(id, name),
-};
+		'misskey:authentication:twitter': (userId, screenName) => ({ userId, screenName }),
+		'misskey:authentication:github': (id, login) => ({ id, login }),
+		'misskey:authentication:discord': (id, name) => $discord(id, name),
+	};
 
 const $discord = (id: string, name: string) => {
 	if (typeof name !== 'string') {
@@ -461,7 +473,7 @@ export async function updateFeatured(userId: User['id']) {
 
 	// Resolve to (Ordered)Collection Object
 	const collection = await resolver.resolveCollection(user.featured);
-	if (!isCollectionOrOrderedCollection(collection)) throw new Error(`Object is not Collection or OrderedCollection`);
+	if (!isCollectionOrOrderedCollection(collection)) throw new Error('Object is not Collection or OrderedCollection');
 
 	// Resolve to Object(may be Note) arrays
 	const unresolvedItems = isCollection(collection) ? collection.items : collection.orderedItems;
diff --git a/packages/backend/src/remote/activitypub/renderer/block.ts b/packages/backend/src/remote/activitypub/renderer/block.ts
index 10a4fde517..13815fb76f 100644
--- a/packages/backend/src/remote/activitypub/renderer/block.ts
+++ b/packages/backend/src/remote/activitypub/renderer/block.ts
@@ -1,8 +1,20 @@
 import config from '@/config/index.js';
-import { ILocalUser, IRemoteUser } from '@/models/entities/user.js';
+import { Blocking } from '@/models/entities/blocking.js';
 
-export default (blocker: ILocalUser, blockee: IRemoteUser) => ({
-	type: 'Block',
-	actor: `${config.url}/users/${blocker.id}`,
-	object: blockee.uri,
-});
+/**
+ * Renders a block into its ActivityPub representation.
+ *
+ * @param block The block to be rendered. The blockee relation must be loaded.
+ */
+export function renderBlock(block: Blocking) {
+	if (block.blockee?.url == null) {
+		throw new Error('renderBlock: missing blockee uri');
+	}
+
+	return {
+		type: 'Block',
+		id: `${config.url}/blocks/${block.id}`,
+		actor: `${config.url}/users/${block.blockerId}`,
+		object: block.blockee.uri,
+	};
+}
diff --git a/packages/backend/src/remote/activitypub/renderer/flag.ts b/packages/backend/src/remote/activitypub/renderer/flag.ts
index 6fbc11580f..58eadddbaa 100644
--- a/packages/backend/src/remote/activitypub/renderer/flag.ts
+++ b/packages/backend/src/remote/activitypub/renderer/flag.ts
@@ -5,7 +5,7 @@ import { getInstanceActor } from '@/services/instance-actor.js';
 
 // to anonymise reporters, the reporting actor must be a system user
 // object has to be a uri or array of uris
-export const renderFlag = (user: ILocalUser, object: [string], content: string): IActivity => {
+export const renderFlag = (user: ILocalUser, object: [string], content: string) => {
 	return {
 		type: 'Flag',
 		actor: `${config.url}/users/${user.id}`,
diff --git a/packages/backend/src/remote/activitypub/renderer/follow.ts b/packages/backend/src/remote/activitypub/renderer/follow.ts
index 9e9692b77a..00fac18ad5 100644
--- a/packages/backend/src/remote/activitypub/renderer/follow.ts
+++ b/packages/backend/src/remote/activitypub/renderer/follow.ts
@@ -4,12 +4,11 @@ import { Users } from '@/models/index.js';
 
 export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => {
 	const follow = {
+		id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`,
 		type: 'Follow',
 		actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri,
 		object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri,
 	} as any;
 
-	if (requestId) follow.id = requestId;
-
 	return follow;
 };
diff --git a/packages/backend/src/remote/activitypub/renderer/index.ts b/packages/backend/src/remote/activitypub/renderer/index.ts
index 5f69332266..f100b77ce5 100644
--- a/packages/backend/src/remote/activitypub/renderer/index.ts
+++ b/packages/backend/src/remote/activitypub/renderer/index.ts
@@ -8,7 +8,7 @@ import { User } from '@/models/entities/user.js';
 export const renderActivity = (x: any): IActivity | null => {
 	if (x == null) return null;
 
-	if (x !== null && typeof x === 'object' && x.id == null) {
+	if (typeof x === 'object' && x.id == null) {
 		x.id = `${config.url}/${uuid()}`;
 	}
 
diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts
index 679c8bbfe4..df2ae65205 100644
--- a/packages/backend/src/remote/activitypub/renderer/note.ts
+++ b/packages/backend/src/remote/activitypub/renderer/note.ts
@@ -1,15 +1,15 @@
-import renderDocument from './document.js';
-import renderHashtag from './hashtag.js';
-import renderMention from './mention.js';
-import renderEmoji from './emoji.js';
+import { In, IsNull } from 'typeorm';
 import config from '@/config/index.js';
-import toHtml from '../misc/get-note-html.js';
 import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { DriveFiles, Notes, Users, Emojis, Polls } from '@/models/index.js';
-import { In, IsNull } from 'typeorm';
 import { Emoji } from '@/models/entities/emoji.js';
 import { Poll } from '@/models/entities/poll.js';
+import toHtml from '../misc/get-note-html.js';
+import renderEmoji from './emoji.js';
+import renderMention from './mention.js';
+import renderHashtag from './hashtag.js';
+import renderDocument from './document.js';
 
 export default async function renderNote(note: Note, dive = true, isTalk = false): Promise<Record<string, unknown>> {
 	const getPromisedFiles = async (ids: string[]) => {
@@ -82,15 +82,15 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
 
 	const files = await getPromisedFiles(note.fileIds);
 
-	const text = note.text;
-	let poll: Poll | null;
+	// text should never be undefined
+	const text = note.text ?? null;
+	let poll: Poll | null = null;
 
 	if (note.hasPoll) {
 		poll = await Polls.findOneBy({ noteId: note.id });
 	}
 
-	let apText = text;
-	if (apText == null) apText = '';
+	let apText = text ?? '';
 
 	if (quote) {
 		apText += `\n\nRE: ${quote}`;
@@ -138,6 +138,10 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
 		summary,
 		content,
 		_misskey_content: text,
+		source: {
+			content: text,
+			mediaType: "text/x.misskeymarkdown",
+		},
 		_misskey_quote: quote,
 		quoteUrl: quote,
 		published: note.createdAt.toISOString(),
@@ -159,7 +163,7 @@ export async function getEmojis(names: string[]): Promise<Emoji[]> {
 		names.map(name => Emojis.findOneBy({
 			name,
 			host: IsNull(),
-		}))
+		})),
 	);
 
 	return emojis.filter(emoji => emoji != null) as Emoji[];
diff --git a/packages/backend/src/remote/activitypub/resolver.ts b/packages/backend/src/remote/activitypub/resolver.ts
index c1269c75c5..2f9af43c0c 100644
--- a/packages/backend/src/remote/activitypub/resolver.ts
+++ b/packages/backend/src/remote/activitypub/resolver.ts
@@ -2,10 +2,19 @@ import config from '@/config/index.js';
 import { getJson } from '@/misc/fetch.js';
 import { ILocalUser } from '@/models/entities/user.js';
 import { getInstanceActor } from '@/services/instance-actor.js';
+import { fetchMeta } from '@/misc/fetch-meta.js';
+import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
 import { signedGet } from './request.js';
 import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
-import { fetchMeta } from '@/misc/fetch-meta.js';
-import { extractDbHost } from '@/misc/convert-host.js';
+import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js';
+import { parseUri } from './db-resolver.js';
+import renderNote from '@/remote/activitypub/renderer/note.js';
+import { renderLike } from '@/remote/activitypub/renderer/like.js';
+import { renderPerson } from '@/remote/activitypub/renderer/person.js';
+import renderQuestion from '@/remote/activitypub/renderer/question.js';
+import renderCreate from '@/remote/activitypub/renderer/create.js';
+import { renderActivity } from '@/remote/activitypub/renderer/index.js';
+import renderFollow from '@/remote/activitypub/renderer/follow.js';
 
 export default class Resolver {
 	private history: Set<string>;
@@ -40,14 +49,25 @@ export default class Resolver {
 			return value;
 		}
 
+		if (value.includes('#')) {
+			// URLs with fragment parts cannot be resolved correctly because
+			// the fragment part does not get transmitted over HTTP(S).
+			// Avoid strange behaviour by not trying to resolve these at all.
+			throw new Error(`cannot resolve URL with fragment: ${value}`);
+		}
+
 		if (this.history.has(value)) {
 			throw new Error('cannot resolve already resolved one');
 		}
 
 		this.history.add(value);
 
-		const meta = await fetchMeta();
 		const host = extractDbHost(value);
+		if (isSelfHost(host)) {
+			return await this.resolveLocal(value);
+		}
+
+		const meta = await fetchMeta();
 		if (meta.blockedHosts.includes(host)) {
 			throw new Error('Instance is blocked');
 		}
@@ -56,13 +76,13 @@ export default class Resolver {
 			this.user = await getInstanceActor();
 		}
 
-		const object = this.user
+		const object = (this.user
 			? await signedGet(value, this.user)
-			: await getJson(value, 'application/activity+json, application/ld+json');
+			: await getJson(value, 'application/activity+json, application/ld+json')) as IObject;
 
 		if (object == null || (
 			Array.isArray(object['@context']) ?
-				!object['@context'].includes('https://www.w3.org/ns/activitystreams') :
+				!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
 				object['@context'] !== 'https://www.w3.org/ns/activitystreams'
 		)) {
 			throw new Error('invalid response');
@@ -70,4 +90,44 @@ export default class Resolver {
 
 		return object;
 	}
+
+	private resolveLocal(url: string): Promise<IObject> {
+		const parsed = parseUri(url);
+		if (!parsed.local) throw new Error('resolveLocal: not local');
+
+		switch (parsed.type) {
+			case 'notes':
+				return Notes.findOneByOrFail({ id: parsed.id })
+				.then(note => {
+					if (parsed.rest === 'activity') {
+						// this refers to the create activity and not the note itself
+						return renderActivity(renderCreate(renderNote(note)));
+					} else {
+						return renderNote(note);
+					}
+				});
+			case 'users':
+				return Users.findOneByOrFail({ id: parsed.id })
+				.then(user => renderPerson(user as ILocalUser));
+			case 'questions':
+				// Polls are indexed by the note they are attached to.
+				return Promise.all([
+					Notes.findOneByOrFail({ id: parsed.id }),
+					Polls.findOneByOrFail({ noteId: parsed.id }),
+				])
+				.then(([note, poll]) => renderQuestion({ id: note.userId }, note, poll));
+			case 'likes':
+				return NoteReactions.findOneByOrFail({ id: parsed.id }).then(reaction => renderActivity(renderLike(reaction, { uri: null })));
+			case 'follows':
+				// rest should be <followee id>
+				if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
+
+				return Promise.all(
+					[parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id }))
+				)
+				.then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url)));
+			default:
+				throw new Error(`resolveLocal: type ${type} unhandled`);
+		}
+	}
 }
diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts
index 2051d2624d..5d00481b75 100644
--- a/packages/backend/src/remote/activitypub/type.ts
+++ b/packages/backend/src/remote/activitypub/type.ts
@@ -2,7 +2,7 @@ export type obj = { [x: string]: any };
 export type ApObject = IObject | string | (IObject | string)[];
 
 export interface IObject {
-	'@context': string | obj | obj[];
+	'@context': string | string[] | obj | obj[];
 	type: string | string[];
 	id?: string;
 	summary?: string;
@@ -48,7 +48,7 @@ export function getOneApId(value: ApObject): string {
 export function getApId(value: string | IObject): string {
 	if (typeof value === 'string') return value;
 	if (typeof value.id === 'string') return value.id;
-	throw new Error(`cannot detemine id`);
+	throw new Error('cannot detemine id');
 }
 
 /**
@@ -57,7 +57,7 @@ export function getApId(value: string | IObject): string {
 export function getApType(value: IObject): string {
 	if (typeof value.type === 'string') return value.type;
 	if (Array.isArray(value.type) && typeof value.type[0] === 'string') return value.type[0];
-	throw new Error(`cannot detect type`);
+	throw new Error('cannot detect type');
 }
 
 export function getOneApHrefNullable(value: ApObject | undefined): string | undefined {
@@ -106,7 +106,10 @@ export const isPost = (object: IObject): object is IPost =>
 
 export interface IPost extends IObject {
 	type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event';
-	_misskey_content?: string;
+	source?: {
+		content: string;
+		mediaType: string;
+	};
 	_misskey_quote?: string;
 	quoteUrl?: string;
 	_misskey_talk: boolean;
@@ -114,7 +117,10 @@ export interface IPost extends IObject {
 
 export interface IQuestion extends IObject {
 	type: 'Note' | 'Question';
-	_misskey_content?: string;
+	source?: {
+		content: string;
+		mediaType: string;
+	};
 	_misskey_quote?: string;
 	quoteUrl?: string;
 	oneOf?: IQuestionChoice[];
diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts
index 133dd36066..cd5f917c40 100644
--- a/packages/backend/src/server/activitypub.ts
+++ b/packages/backend/src/server/activitypub.ts
@@ -1,6 +1,6 @@
 import Router from '@koa/router';
 import json from 'koa-json-body';
-import httpSignature from 'http-signature';
+import httpSignature from '@peertube/http-signature';
 
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import renderNote from '@/remote/activitypub/renderer/note.js';
@@ -15,9 +15,10 @@ import { inbox as processInbox } from '@/queue/index.js';
 import { isSelfHost } from '@/misc/convert-host.js';
 import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js';
 import { ILocalUser, User } from '@/models/entities/user.js';
-import { In, IsNull } from 'typeorm';
+import { In, IsNull, Not } from 'typeorm';
 import { renderLike } from '@/remote/activitypub/renderer/like.js';
 import { getUserKeypair } from '@/misc/keypair-store.js';
+import renderFollow from '@/remote/activitypub/renderer/follow.js';
 
 // Init router
 const router = new Router();
@@ -224,4 +225,30 @@ router.get('/likes/:like', async ctx => {
 	setResponseType(ctx);
 });
 
+// follow
+router.get('/follows/:follower/:followee', async ctx => {
+	// This may be used before the follow is completed, so we do not
+	// check if the following exists.
+
+	const [follower, followee] = await Promise.all([
+		Users.findOneBy({
+			id: ctx.params.follower,
+			host: IsNull(),
+		}),
+		Users.findOneBy({
+			id: ctx.params.followee,
+			host: Not(IsNull()),
+		}),
+	]);
+
+	if (follower == null || followee == null) {
+		ctx.status = 404;
+		return;
+	}
+
+	ctx.body = renderActivity(renderFollow(follower, followee));
+	ctx.set('Cache-Control', 'public, max-age=180');
+	setResponseType(ctx);
+});
+
 export default router;
diff --git a/packages/backend/src/server/activitypub/followers.ts b/packages/backend/src/server/activitypub/followers.ts
index 4d4f733162..beb48713a6 100644
--- a/packages/backend/src/server/activitypub/followers.ts
+++ b/packages/backend/src/server/activitypub/followers.ts
@@ -1,32 +1,26 @@
 import Router from '@koa/router';
+import { FindOptionsWhere, IsNull, LessThan } from 'typeorm';
 import config from '@/config/index.js';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id.js';
 import * as url from '@/prelude/url.js';
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
 import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
 import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
-import { setResponseType } from '../activitypub.js';
 import { Users, Followings, UserProfiles } from '@/models/index.js';
-import { IsNull, LessThan } from 'typeorm';
+import { Following } from '@/models/entities/following.js';
+import { setResponseType } from '../activitypub.js';
 
 export default async (ctx: Router.RouterContext) => {
 	const userId = ctx.params.user;
 
-	// Get 'cursor' parameter
-	const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor);
-
-	// Get 'page' parameter
-	const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
-	const page: boolean = ctx.request.query.page === 'true';
-
-	// Validate parameters
-	if (cursorErr || pageErr) {
+	const cursor = ctx.request.query.cursor;
+	if (cursor != null && typeof cursor !== 'string') {
 		ctx.status = 400;
 		return;
 	}
 
+	const page = ctx.request.query.page === 'true';
+
 	const user = await Users.findOneBy({
 		id: userId,
 		host: IsNull(),
@@ -57,7 +51,7 @@ export default async (ctx: Router.RouterContext) => {
 	if (page) {
 		const query = {
 			followeeId: user.id,
-		} as any;
+		} as FindOptionsWhere<Following>;
 
 		// ใ‚ซใƒผใ‚ฝใƒซใŒๆŒ‡ๅฎšใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆ
 		if (cursor) {
@@ -86,7 +80,7 @@ export default async (ctx: Router.RouterContext) => {
 			inStock ? `${partOf}?${url.query({
 				page: 'true',
 				cursor: followings[followings.length - 1].id,
-			})}` : undefined
+			})}` : undefined,
 		);
 
 		ctx.body = renderActivity(rendered);
diff --git a/packages/backend/src/server/activitypub/following.ts b/packages/backend/src/server/activitypub/following.ts
index 0af1f424f9..3a25a6316c 100644
--- a/packages/backend/src/server/activitypub/following.ts
+++ b/packages/backend/src/server/activitypub/following.ts
@@ -1,33 +1,26 @@
 import Router from '@koa/router';
+import { LessThan, IsNull, FindOptionsWhere } from 'typeorm';
 import config from '@/config/index.js';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id.js';
 import * as url from '@/prelude/url.js';
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
 import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
 import renderFollowUser from '@/remote/activitypub/renderer/follow-user.js';
-import { setResponseType } from '../activitypub.js';
 import { Users, Followings, UserProfiles } from '@/models/index.js';
-import { LessThan, IsNull, FindOptionsWhere } from 'typeorm';
 import { Following } from '@/models/entities/following.js';
+import { setResponseType } from '../activitypub.js';
 
 export default async (ctx: Router.RouterContext) => {
 	const userId = ctx.params.user;
 
-	// Get 'cursor' parameter
-	const [cursor, cursorErr] = $.default.optional.type(ID).get(ctx.request.query.cursor);
-
-	// Get 'page' parameter
-	const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
-	const page: boolean = ctx.request.query.page === 'true';
-
-	// Validate parameters
-	if (cursorErr || pageErr) {
+	const cursor = ctx.request.query.cursor;
+	if (cursor != null && typeof cursor !== 'string') {
 		ctx.status = 400;
 		return;
 	}
 
+	const page = ctx.request.query.page === 'true';
+
 	const user = await Users.findOneBy({
 		id: userId,
 		host: IsNull(),
@@ -87,7 +80,7 @@ export default async (ctx: Router.RouterContext) => {
 			inStock ? `${partOf}?${url.query({
 				page: 'true',
 				cursor: followings[followings.length - 1].id,
-			})}` : undefined
+			})}` : undefined,
 		);
 
 		ctx.body = renderActivity(rendered);
diff --git a/packages/backend/src/server/activitypub/outbox.ts b/packages/backend/src/server/activitypub/outbox.ts
index 6b9592bcf3..7a2586998a 100644
--- a/packages/backend/src/server/activitypub/outbox.ts
+++ b/packages/backend/src/server/activitypub/outbox.ts
@@ -1,36 +1,37 @@
 import Router from '@koa/router';
+import { Brackets, IsNull } from 'typeorm';
 import config from '@/config/index.js';
-import $ from 'cafy';
-import { ID } from '@/misc/cafy-id.js';
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import renderOrderedCollection from '@/remote/activitypub/renderer/ordered-collection.js';
 import renderOrderedCollectionPage from '@/remote/activitypub/renderer/ordered-collection-page.js';
-import { setResponseType } from '../activitypub.js';
 import renderNote from '@/remote/activitypub/renderer/note.js';
 import renderCreate from '@/remote/activitypub/renderer/create.js';
 import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
 import { countIf } from '@/prelude/array.js';
 import * as url from '@/prelude/url.js';
 import { Users, Notes } from '@/models/index.js';
-import { makePaginationQuery } from '../api/common/make-pagination-query.js';
-import { Brackets, IsNull } from 'typeorm';
 import { Note } from '@/models/entities/note.js';
+import { makePaginationQuery } from '../api/common/make-pagination-query.js';
+import { setResponseType } from '../activitypub.js';
 
 export default async (ctx: Router.RouterContext) => {
 	const userId = ctx.params.user;
 
-	// Get 'sinceId' parameter
-	const [sinceId, sinceIdErr] = $.default.optional.type(ID).get(ctx.request.query.since_id);
+	const sinceId = ctx.request.query.since_id;
+	if (sinceId != null && typeof sinceId !== 'string') {
+		ctx.status = 400;
+		return;
+	}
 
-	// Get 'untilId' parameter
-	const [untilId, untilIdErr] = $.default.optional.type(ID).get(ctx.request.query.until_id);
+	const untilId = ctx.request.query.until_id;
+	if (untilId != null && typeof untilId !== 'string') {
+		ctx.status = 400;
+		return;
+	}
 
-	// Get 'page' parameter
-	const pageErr = !$.default.optional.str.or(['true', 'false']).ok(ctx.request.query.page);
-	const page: boolean = ctx.request.query.page === 'true';
+	const page = ctx.request.query.page === 'true';
 
-	// Validate parameters
-	if (sinceIdErr || untilIdErr || pageErr || countIf(x => x != null, [sinceId, untilId]) > 1) {
+	if (countIf(x => x != null, [sinceId, untilId]) > 1) {
 		ctx.status = 400;
 		return;
 	}
@@ -52,8 +53,8 @@ export default async (ctx: Router.RouterContext) => {
 		const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId)
 			.andWhere('note.userId = :userId', { userId: user.id })
 			.andWhere(new Brackets(qb => { qb
-				.where(`note.visibility = 'public'`)
-				.orWhere(`note.visibility = 'home'`);
+				.where('note.visibility = \'public\'')
+				.orWhere('note.visibility = \'home\'');
 			}))
 			.andWhere('note.localOnly = FALSE');
 
@@ -76,7 +77,7 @@ export default async (ctx: Router.RouterContext) => {
 			notes.length ? `${partOf}?${url.query({
 				page: 'true',
 				until_id: notes[notes.length - 1].id,
-			})}` : undefined
+			})}` : undefined,
 		);
 
 		ctx.body = renderActivity(rendered);
@@ -85,7 +86,7 @@ export default async (ctx: Router.RouterContext) => {
 		// index page
 		const rendered = renderOrderedCollection(partOf, user.notesCount,
 			`${partOf}?page=true`,
-			`${partOf}?page=true&since_id=000000000000000000000000`
+			`${partOf}?page=true&since_id=000000000000000000000000`,
 		);
 		ctx.body = renderActivity(rendered);
 		ctx.set('Cache-Control', 'public, max-age=180');
diff --git a/packages/backend/src/server/api/2fa.ts b/packages/backend/src/server/api/2fa.ts
index dce8accaac..96b9316e47 100644
--- a/packages/backend/src/server/api/2fa.ts
+++ b/packages/backend/src/server/api/2fa.ts
@@ -1,6 +1,6 @@
 import * as crypto from 'node:crypto';
-import config from '@/config/index.js';
 import * as jsrsasign from 'jsrsasign';
+import config from '@/config/index.js';
 
 const ECC_PRELUDE = Buffer.from([0x04]);
 const NULL_BYTE = Buffer.from([0]);
@@ -145,7 +145,7 @@ export function verifyLogin({
 
 export const procedures = {
 	none: {
-		verify({ publicKey }: {publicKey: Map<number, Buffer>}) {
+		verify({ publicKey }: { publicKey: Map<number, Buffer> }) {
 			const negTwo = publicKey.get(-2);
 
 			if (!negTwo || negTwo.length !== 32) {
diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts
index 9a85e4565b..cd3e0abc06 100644
--- a/packages/backend/src/server/api/call.ts
+++ b/packages/backend/src/server/api/call.ts
@@ -2,10 +2,11 @@ import Koa from 'koa';
 import { performance } from 'perf_hooks';
 import { limiter } from './limiter.js';
 import { CacheableLocalUser, User } from '@/models/entities/user.js';
-import endpoints, { IEndpoint } from './endpoints.js';
+import endpoints, { IEndpointMeta } from './endpoints.js';
 import { ApiError } from './error.js';
 import { apiLogger } from './logger.js';
 import { AccessToken } from '@/models/entities/access-token.js';
+import { getIpHash } from '@/misc/get-ip-hash.js';
 
 const accessDenied = {
 	message: 'Access denied.',
@@ -15,6 +16,7 @@ const accessDenied = {
 
 export default async (endpoint: string, user: CacheableLocalUser | null | undefined, token: AccessToken | null | undefined, data: any, ctx?: Koa.Context) => {
 	const isSecure = user != null && token == null;
+	const isModerator = user != null && (user.isModerator || user.isAdmin);
 
 	const ep = endpoints.find(e => e.name === endpoint);
 
@@ -31,6 +33,32 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 		throw new ApiError(accessDenied);
 	}
 
+	if (ep.meta.limit && !isModerator) {
+		// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
+		let limitActor: string;
+		if (user) {
+			limitActor = user.id;
+		} else {
+			limitActor = getIpHash(ctx!.ip);
+		}
+
+		const limit = Object.assign({}, ep.meta.limit);
+
+		if (!limit.key) {
+			limit.key = ep.name;
+		}
+
+		// Rate limit
+		await limiter(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor).catch(e => {
+			throw new ApiError({
+				message: 'Rate limit exceeded. Please try again later.',
+				code: 'RATE_LIMIT_EXCEEDED',
+				id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
+				httpStatusCode: 429,
+			});
+		});
+	}
+
 	if (ep.meta.requireCredential && user == null) {
 		throw new ApiError({
 			message: 'Credential required.',
@@ -53,7 +81,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 		throw new ApiError(accessDenied, { reason: 'You are not the admin.' });
 	}
 
-	if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) {
+	if (ep.meta.requireModerator && !isModerator) {
 		throw new ApiError(accessDenied, { reason: 'You are not a moderator.' });
 	}
 
@@ -65,18 +93,6 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
 		});
 	}
 
-	if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) {
-		// Rate limit
-		await limiter(ep as IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user!).catch(e => {
-			throw new ApiError({
-				message: 'Rate limit exceeded. Please try again later.',
-				code: 'RATE_LIMIT_EXCEEDED',
-				id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
-				httpStatusCode: 429,
-			});
-		});
-	}
-
 	// Cast non JSON input
 	if (ep.meta.requireFile && ep.params.properties) {
 		for (const k of Object.keys(ep.params.properties)) {
diff --git a/packages/backend/src/server/api/common/generate-visibility-query.ts b/packages/backend/src/server/api/common/generate-visibility-query.ts
index 715982934c..b50b6812f4 100644
--- a/packages/backend/src/server/api/common/generate-visibility-query.ts
+++ b/packages/backend/src/server/api/common/generate-visibility-query.ts
@@ -3,6 +3,7 @@ import { Followings } from '@/models/index.js';
 import { Brackets, SelectQueryBuilder } from 'typeorm';
 
 export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: User['id'] } | null) {
+	// This code must always be synchronized with the checks in Notes.isVisibleForMe.
 	if (me == null) {
 		q.andWhere(new Brackets(qb => { qb
 			.where(`note.visibility = 'public'`)
@@ -11,7 +12,7 @@ export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: U
 	} else {
 		const followingQuery = Followings.createQueryBuilder('following')
 			.select('following.followeeId')
-			.where('following.followerId = :followerId', { followerId: me.id });
+			.where('following.followerId = :meId');
 
 		q.andWhere(new Brackets(qb => { qb
 			// ๅ…ฌ้–‹ๆŠ•็จฟใงใ‚ใ‚‹
@@ -20,21 +21,22 @@ export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: U
 				.orWhere(`note.visibility = 'home'`);
 			}))
 			// ใพใŸใฏ ่‡ชๅˆ†่‡ช่บซ
-			.orWhere('note.userId = :userId1', { userId1: me.id })
+			.orWhere('note.userId = :meId')
 			// ใพใŸใฏ ่‡ชๅˆ†ๅฎ›ใฆ
-			.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`)
+			.orWhere(':meId = ANY(note.visibleUserIds)')
+			.orWhere(':meId = ANY(note.mentions)')
 			.orWhere(new Brackets(qb => { qb
 				// ใพใŸใฏ ใƒ•ใ‚ฉใƒญใƒฏใƒผๅฎ›ใฆใฎๆŠ•็จฟใงใ‚ใ‚Šใ€
-				.where('note.visibility = \'followers\'')
+				.where(`note.visibility = 'followers'`)
 				.andWhere(new Brackets(qb => { qb
 					// ่‡ชๅˆ†ใŒใƒ•ใ‚ฉใƒญใƒฏใƒผใงใ‚ใ‚‹
 					.where(`note.userId IN (${ followingQuery.getQuery() })`)
 					// ใพใŸใฏ ่‡ชๅˆ†ใฎๆŠ•็จฟใธใฎใƒชใƒ—ใƒฉใ‚ค
-					.orWhere('note.replyUserId = :userId3', { userId3: me.id });
+					.orWhere('note.replyUserId = :meId');
 				}));
 			}));
 		}));
 
-		q.setParameters(followingQuery.getParameters());
+		q.setParameters({ meId: me.id });
 	}
 }
diff --git a/packages/backend/src/server/api/common/read-messaging-message.ts b/packages/backend/src/server/api/common/read-messaging-message.ts
index 3638518e67..c4c18ffa06 100644
--- a/packages/backend/src/server/api/common/read-messaging-message.ts
+++ b/packages/backend/src/server/api/common/read-messaging-message.ts
@@ -1,6 +1,7 @@
 import { publishMainStream, publishGroupMessagingStream } from '@/services/stream.js';
 import { publishMessagingStream } from '@/services/stream.js';
 import { publishMessagingIndexStream } from '@/services/stream.js';
+import { pushNotification } from '@/services/push-notification.js';
 import { User, IRemoteUser } from '@/models/entities/user.js';
 import { MessagingMessage } from '@/models/entities/messaging-message.js';
 import { MessagingMessages, UserGroupJoinings, Users } from '@/models/index.js';
@@ -50,6 +51,21 @@ export async function readUserMessagingMessage(
 	if (!await Users.getHasUnreadMessagingMessage(userId)) {
 		// ๅ…จใฆใฎ(ใ„ใพใพใงๆœช่ชญใ ใฃใŸ)่‡ชๅˆ†ๅฎ›ใฆใฎใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’(ใ“ใ‚Œใง)่ชญใฟใพใ—ใŸใ‚ˆใจใ„ใ†ใ‚คใƒ™ใƒณใƒˆใ‚’็™บ่กŒ
 		publishMainStream(userId, 'readAllMessagingMessages');
+		pushNotification(userId, 'readAllMessagingMessages', undefined);
+	} else {
+		// ใใฎใƒฆใƒผใ‚ถใƒผใจใฎใƒกใƒƒใ‚ปใƒผใ‚ธใงๆœช่ชญใŒใชใ‘ใ‚Œใฐใ‚คใƒ™ใƒณใƒˆ็™บ่กŒ
+		const count = await MessagingMessages.count({
+			where: {
+				userId: otherpartyId,
+				recipientId: userId,
+				isRead: false,
+			},
+			take: 1
+		});
+
+		if (!count) {
+			pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId });
+		}
 	}
 }
 
@@ -104,6 +120,19 @@ export async function readGroupMessagingMessage(
 	if (!await Users.getHasUnreadMessagingMessage(userId)) {
 		// ๅ…จใฆใฎ(ใ„ใพใพใงๆœช่ชญใ ใฃใŸ)่‡ชๅˆ†ๅฎ›ใฆใฎใƒกใƒƒใ‚ปใƒผใ‚ธใ‚’(ใ“ใ‚Œใง)่ชญใฟใพใ—ใŸใ‚ˆใจใ„ใ†ใ‚คใƒ™ใƒณใƒˆใ‚’็™บ่กŒ
 		publishMainStream(userId, 'readAllMessagingMessages');
+		pushNotification(userId, 'readAllMessagingMessages', undefined);
+	} else {
+		// ใใฎใ‚ฐใƒซใƒผใƒ—ใซใŠใ„ใฆๆœช่ชญใŒใชใ‘ใ‚Œใฐใ‚คใƒ™ใƒณใƒˆ็™บ่กŒ
+		const unreadExist = await MessagingMessages.createQueryBuilder('message')
+			.where(`message.groupId = :groupId`, { groupId: groupId })
+			.andWhere('message.userId != :userId', { userId: userId })
+			.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
+			.andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // ่‡ชๅˆ†ใŒๅŠ ๅ…ฅใ™ใ‚‹ๅ‰ใฎไผš่ฉฑใซใคใ„ใฆใฏใ€ๆœช่ชญๆ‰ฑใ„ใ—ใชใ„
+			.getOne().then(x => x != null);
+
+		if (!unreadExist) {
+			pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId });
+		}
 	}
 }
 
diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts
index 1f575042a0..0dad35bcc2 100644
--- a/packages/backend/src/server/api/common/read-notification.ts
+++ b/packages/backend/src/server/api/common/read-notification.ts
@@ -1,4 +1,5 @@
 import { publishMainStream } from '@/services/stream.js';
+import { pushNotification } from '@/services/push-notification.js';
 import { User } from '@/models/entities/user.js';
 import { Notification } from '@/models/entities/notification.js';
 import { Notifications, Users } from '@/models/index.js';
@@ -16,28 +17,29 @@ export async function readNotification(
 		isRead: true,
 	});
 
-	post(userId);
+	if (!await Users.getHasUnreadNotification(userId)) return postReadAllNotifications(userId);
+	else return postReadNotifications(userId, notificationIds);
 }
 
 export async function readNotificationByQuery(
 	userId: User['id'],
 	query: Record<string, any>
 ) {
-	// Update documents
-	await Notifications.update({
+	const notificationIds = await Notifications.find({
 		...query,
 		notifieeId: userId,
 		isRead: false,
-	}, {
-		isRead: true,
-	});
+	}).then(notifications => notifications.map(notification => notification.id));
 
-	post(userId);
+	return readNotification(userId, notificationIds);
 }
 
-async function post(userId: User['id']) {
-	if (!await Users.getHasUnreadNotification(userId)) {
-		// ๅ…จใฆใฎ(ใ„ใพใพใงๆœช่ชญใ ใฃใŸ)้€š็Ÿฅใ‚’(ใ“ใ‚Œใง)่ชญใฟใพใ—ใŸใ‚ˆใจใ„ใ†ใ‚คใƒ™ใƒณใƒˆใ‚’็™บ่กŒ
-		publishMainStream(userId, 'readAllNotifications');
-	}
+function postReadAllNotifications(userId: User['id']) {
+	publishMainStream(userId, 'readAllNotifications');
+	return pushNotification(userId, 'readAllNotifications', undefined);
+}
+
+function postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
+	publishMainStream(userId, 'readNotifications', notificationIds);
+	return pushNotification(userId, 'readNotifications', { notificationIds });
 }
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index e2db03f13a..1e7afd8cdd 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -654,7 +654,6 @@ export interface IEndpointMeta {
 	/**
 	 * ใ‚จใƒณใƒ‰ใƒใ‚คใƒณใƒˆใฎใƒชใƒŸใƒ†ใƒผใ‚ทใƒงใƒณใซ้–ขใ™ใ‚‹ใ‚„ใค
 	 * ็œ็•ฅใ—ใŸๅ ดๅˆใฏใƒชใƒŸใƒ†ใƒผใ‚ทใƒงใƒณใฏ็„กใ„ใ‚‚ใฎใจใ—ใฆ่งฃ้‡ˆใ•ใ‚Œใพใ™ใ€‚
-	 * ใพใŸใ€withCredential ใŒ false ใฎๅ ดๅˆใฏใƒชใƒŸใƒ†ใƒผใ‚ทใƒงใƒณใ‚’่กŒใ†ใ“ใจใฏใงใใพใ›ใ‚“ใ€‚
 	 */
 	readonly limit?: {
 
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
index 1d8eb1d618..7a5758d75b 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
@@ -1,5 +1,6 @@
-import define from '../../../define.js';
 import { Announcements, AnnouncementReads } from '@/models/index.js';
+import { Announcement } from '@/models/entities/announcement.js';
+import define from '../../../define.js';
 import { makePaginationQuery } from '../../../common/make-pagination-query.js';
 
 export const meta = {
@@ -68,11 +69,21 @@ export default define(meta, paramDef, async (ps) => {
 
 	const announcements = await query.take(ps.limit).getMany();
 
+	const reads = new Map<Announcement, number>();
+
 	for (const announcement of announcements) {
-		(announcement as any).reads = await AnnouncementReads.countBy({
+		reads.set(announcement, await AnnouncementReads.countBy({
 			announcementId: announcement.id,
-		});
+		}));
 	}
 
-	return announcements;
+	return announcements.map(announcement => ({
+		id: announcement.id,
+		createdAt: announcement.createdAt.toISOString(),
+		updatedAt: announcement.updatedAt?.toISOString() ?? null,
+		title: announcement.title,
+		text: announcement.text,
+		imageUrl: announcement.imageUrl,
+		reads: reads.get(announcement)!,
+	}));
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index bf6cc16532..78033aed58 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -1,5 +1,5 @@
+import { Signins, UserProfiles, Users } from '@/models/index.js';
 import define from '../../define.js';
-import { Users } from '@/models/index.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -23,9 +23,12 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, me) => {
-	const user = await Users.findOneBy({ id: ps.userId });
+	const [user, profile] = await Promise.all([
+		Users.findOneBy({ id: ps.userId }),
+		UserProfiles.findOneBy({ userId: ps.userId })
+	]);
 
-	if (user == null) {
+	if (user == null || profile == null) {
 		throw new Error('user not found');
 	}
 
@@ -34,8 +37,37 @@ export default define(meta, paramDef, async (ps, me) => {
 		throw new Error('cannot show info of admin');
 	}
 
+	if (!_me.isAdmin) {
+		return {
+			isModerator: user.isModerator,
+			isSilenced: user.isSilenced,
+			isSuspended: user.isSuspended,
+		};
+	}
+
+	const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken'];
+	Object.keys(profile.integrations).forEach(integration => {
+		maskedKeys.forEach(key => profile.integrations[integration][key] = '<MASKED>');
+	});
+
+	const signins = await Signins.findBy({ userId: user.id });
+
 	return {
-		...user,
-		token: user.token != null ? '<MASKED>' : user.token,
+		email: profile.email,
+		emailVerified: profile.emailVerified,
+		autoAcceptFollowed: profile.autoAcceptFollowed,
+		noCrawle: profile.noCrawle,
+		alwaysMarkNsfw: profile.alwaysMarkNsfw,
+		carefulBot: profile.carefulBot,
+		injectFeaturedNote: profile.injectFeaturedNote,
+		receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
+		integrations: profile.integrations,
+		mutedWords: profile.mutedWords,
+		mutedInstances: profile.mutedInstances,
+		mutingNotificationTypes: profile.mutingNotificationTypes,
+		isModerator: user.isModerator,
+		isSilenced: user.isSilenced,
+		isSuspended: user.isSuspended,
+		signins,
 	};
 });
diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts
index 2703b4b9db..1575d81d5d 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -1,5 +1,5 @@
-import define from '../../define.js';
 import { Users } from '@/models/index.js';
+import define from '../../define.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -24,8 +24,8 @@ export const paramDef = {
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		offset: { type: 'integer', default: 0 },
 		sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] },
-		state: { type: 'string', enum: ['all', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: "all" },
-		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" },
+		state: { type: 'string', enum: ['all', 'alive', 'available', 'admin', 'moderator', 'adminOrModerator', 'silenced', 'suspended'], default: 'all' },
+		origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' },
 		username: { type: 'string', nullable: true, default: null },
 		hostname: {
 			type: 'string',
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index b23ee9e3df..09e43301b7 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -27,7 +27,7 @@ export const paramDef = {
 		blockedHosts: { type: 'array', nullable: true, items: {
 			type: 'string',
 		} },
-		themeColor: { type: 'string', nullable: true },
+		themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
 		mascotImageUrl: { type: 'string', nullable: true },
 		bannerUrl: { type: 'string', nullable: true },
 		errorImageUrl: { type: 'string', nullable: true },
diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
index 7ffe89a1e5..415a8cc693 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'read:drive',
 
+	description: 'Find the notes to which the given file is attached.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
index 80293df5d9..bbae9bf4e4 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
@@ -8,6 +8,8 @@ export const meta = {
 
 	kind: 'read:drive',
 
+	description: 'Check if a given file exists.',
+
 	res: {
 		type: 'boolean',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index 0939ae3365..7397fd9ce9 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -20,6 +20,8 @@ export const meta = {
 
 	kind: 'write:drive',
 
+	description: 'Upload a new drive file.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
index 61c56e6314..6108ae7da9 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'write:drive',
 
+	description: 'Delete an existing drive file.',
+
 	errors: {
 		noSuchFile: {
 			message: 'No such file.',
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
index f9b4ea89ea..f2bc7348c6 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
@@ -1,5 +1,5 @@
-import define from '../../../define.js';
 import { DriveFiles } from '@/models/index.js';
+import define from '../../../define.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -8,6 +8,8 @@ export const meta = {
 
 	kind: 'read:drive',
 
+	description: 'Search for a drive file by a hash of the contents.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts
index 4938a69d11..245fb45a65 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'read:drive',
 
+	description: 'Search for a drive file by the given parameters.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index a2bc0c7aa4..2c604c54c8 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -1,7 +1,7 @@
-import define from '../../../define.js';
-import { ApiError } from '../../../error.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { DriveFiles, Users } from '@/models/index.js';
+import define from '../../../define.js';
+import { ApiError } from '../../../error.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -10,6 +10,8 @@ export const meta = {
 
 	kind: 'read:drive',
 
+	description: 'Show the properties of a drive file.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -51,7 +53,7 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
-	let file: DriveFile | undefined;
+	let file: DriveFile | null = null;
 
 	if (ps.fileId) {
 		file = await DriveFiles.findOneBy({ id: ps.fileId });
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index 4b3f5f2dc9..e3debe0b4f 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'write:drive',
 
+	description: 'Update the properties of a drive file.',
+
 	errors: {
 		invalidFileName: {
 			message: 'Invalid file name.',
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index 3bfecac802..53f2298f21 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -13,6 +13,8 @@ export const meta = {
 		max: 60,
 	},
 
+	description: 'Request the server to download a new drive file from the specified URL.',
+
 	requireCredential: true,
 
 	kind: 'write:drive',
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
index d5e1b19e54..33f5717728 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -2,8 +2,8 @@ import bcrypt from 'bcryptjs';
 import * as speakeasy from 'speakeasy';
 import * as QRCode from 'qrcode';
 import config from '@/config/index.js';
-import define from '../../../define.js';
 import { UserProfiles } from '@/models/index.js';
+import define from '../../../define.js';
 
 export const meta = {
 	requireCredential: true,
@@ -40,15 +40,17 @@ export default define(meta, paramDef, async (ps, user) => {
 	});
 
 	// Get the data URL of the authenticator URL
-	const dataUrl = await QRCode.toDataURL(speakeasy.otpauthURL({
+	const url = speakeasy.otpauthURL({
 		secret: secret.base32,
 		encoding: 'base32',
 		label: user.username,
 		issuer: config.host,
-	}));
+	});
+	const dataUrl = await QRCode.toDataURL(url);
 
 	return {
 		qr: dataUrl,
+		url,
 		secret: secret.base32,
 		label: user.username,
 		issuer: config.host,
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 9de05918c0..a133294169 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -1,15 +1,15 @@
 import ms from 'ms';
+import { In } from 'typeorm';
 import create from '@/services/note/create.js';
-import define from '../../define.js';
-import { ApiError } from '../../error.js';
 import { User } from '@/models/entities/user.js';
 import { Users, DriveFiles, Notes, Channels, Blockings } from '@/models/index.js';
 import { DriveFile } from '@/models/entities/drive-file.js';
 import { Note } from '@/models/entities/note.js';
-import { noteVisibilities } from '../../../../types.js';
 import { Channel } from '@/models/entities/channel.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { In } from 'typeorm';
+import { noteVisibilities } from '../../../../types.js';
+import { ApiError } from '../../error.js';
+import define from '../../define.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -83,7 +83,7 @@ export const meta = {
 export const paramDef = {
 	type: 'object',
 	properties: {
-		visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: "public" },
+		visibility: { type: 'string', enum: ['public', 'home', 'followers', 'specified'], default: 'public' },
 		visibleUserIds: { type: 'array', uniqueItems: true, items: {
 			type: 'string', format: 'misskey:id',
 		} },
@@ -134,7 +134,7 @@ export const paramDef = {
 		{
 			// (re)note with text, files and poll are optional
 			properties: {
-				text: { type: 'string', maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false },
+				text: { type: 'string', minLength: 1, maxLength: MAX_NOTE_TEXT_LENGTH, nullable: false },
 			},
 			required: ['text'],
 		},
@@ -149,7 +149,7 @@ export const paramDef = {
 		{
 			// (re)note with poll, text and files are optional
 			properties: {
-				poll: { type: 'object', nullable: false, },
+				poll: { type: 'object', nullable: false },
 			},
 			required: ['poll'],
 		},
@@ -172,20 +172,24 @@ export default define(meta, paramDef, async (ps, user) => {
 	let files: DriveFile[] = [];
 	const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null;
 	if (fileIds != null) {
-		files = await DriveFiles.findBy({
-			userId: user.id,
-			id: In(fileIds),
-		});
+		files = await DriveFiles.createQueryBuilder('file')
+			.where('file.userId = :userId AND file.id IN (:...fileIds)', {
+				userId: user.id,
+				fileIds,
+			})
+			.orderBy('array_position(ARRAY[:...fileIds], "id"::text)')
+			.setParameters({ fileIds })
+			.getMany();
 	}
 
-	let renote: Note | null;
+	let renote: Note | null = null;
 	if (ps.renoteId != null) {
 		// Fetch renote to note
 		renote = await Notes.findOneBy({ id: ps.renoteId });
 
 		if (renote == null) {
 			throw new ApiError(meta.errors.noSuchRenoteTarget);
-		} else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.poll) {
+		} else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
 			throw new ApiError(meta.errors.cannotReRenote);
 		}
 
@@ -201,14 +205,14 @@ export default define(meta, paramDef, async (ps, user) => {
 		}
 	}
 
-	let reply: Note | null;
+	let reply: Note | null = null;
 	if (ps.replyId != null) {
 		// Fetch reply
 		reply = await Notes.findOneBy({ id: ps.replyId });
 
 		if (reply == null) {
 			throw new ApiError(meta.errors.noSuchReplyTarget);
-		} else if (reply.renoteId && !reply.text && !reply.fileIds && !renote.poll) {
+		} else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
 			throw new ApiError(meta.errors.cannotReplyToPureRenote);
 		}
 
@@ -234,7 +238,7 @@ export default define(meta, paramDef, async (ps, user) => {
 		}
 	}
 
-	let channel: Channel | undefined;
+	let channel: Channel | null = null;
 	if (ps.channelId != null) {
 		channel = await Channels.findOneBy({ id: ps.channelId });
 
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index 3555424fa6..fbb065329c 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -1,8 +1,8 @@
+import { DeepPartial, FindOptionsWhere } from 'typeorm';
+import { NoteReactions } from '@/models/index.js';
+import { NoteReaction } from '@/models/entities/note-reaction.js';
 import define from '../../define.js';
 import { ApiError } from '../../error.js';
-import { NoteReactions } from '@/models/index.js';
-import { DeepPartial } from 'typeorm';
-import { NoteReaction } from '@/models/entities/note-reaction.js';
 
 export const meta = {
 	tags: ['notes', 'reactions'],
@@ -45,7 +45,7 @@ export const paramDef = {
 export default define(meta, paramDef, async (ps, user) => {
 	const query = {
 		noteId: ps.noteId,
-	} as DeepPartial<NoteReaction>;
+	} as FindOptionsWhere<NoteReaction>;
 
 	if (ps.type) {
 		// ใƒญใƒผใ‚ซใƒซใƒชใ‚ขใ‚ฏใ‚ทใƒงใƒณใฏใƒ›ใ‚นใƒˆๅใŒ . ใจใ•ใ‚Œใฆใ„ใ‚‹ใŒ
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index c602981b30..5e40e7106f 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -1,12 +1,12 @@
-import define from '../../define.js';
-import { getNote } from '../../common/getters.js';
-import { ApiError } from '../../error.js';
+import { URLSearchParams } from 'node:url';
 import fetch from 'node-fetch';
 import config from '@/config/index.js';
 import { getAgentByUrl } from '@/misc/fetch.js';
-import { URLSearchParams } from 'node:url';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { Notes } from '@/models/index.js';
+import { ApiError } from '../../error.js';
+import { getNote } from '../../common/getters.js';
+import define from '../../define.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -80,7 +80,12 @@ export default define(meta, paramDef, async (ps, user) => {
 		agent: getAgentByUrl,
 	});
 
-	const json = await res.json();
+	const json = (await res.json()) as {
+		translations: {
+			detected_source_language: string;
+			text: string;
+		}[];
+	};
 
 	return {
 		sourceLang: json.translations[0].detected_source_language,
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index abefe07be6..4575cba43f 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -1,4 +1,5 @@
 import { publishMainStream } from '@/services/stream.js';
+import { pushNotification } from '@/services/push-notification.js';
 import define from '../../define.js';
 import { Notifications } from '@/models/index.js';
 
@@ -28,4 +29,5 @@ export default define(meta, paramDef, async (ps, user) => {
 
 	// ๅ…จใฆใฎ้€š็Ÿฅใ‚’่ชญใฟใพใ—ใŸใ‚ˆใจใ„ใ†ใ‚คใƒ™ใƒณใƒˆใ‚’็™บ่กŒ
 	publishMainStream(user.id, 'readAllNotifications');
+	pushNotification(user.id, 'readAllNotifications', undefined);
 });
diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts
index c7bc5dc0a5..65e96d4862 100644
--- a/packages/backend/src/server/api/endpoints/notifications/read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/read.ts
@@ -1,10 +1,12 @@
-import { publishMainStream } from '@/services/stream.js';
 import define from '../../define.js';
-import { Notifications } from '@/models/index.js';
 import { readNotification } from '../../common/read-notification.js';
-import { ApiError } from '../../error.js';
 
 export const meta = {
+	desc: {
+		'ja-JP': '้€š็Ÿฅใ‚’ๆ—ข่ชญใซใ—ใพใ™ใ€‚',
+		'en-US': 'Mark a notification as read.'
+	},
+
 	tags: ['notifications', 'account'],
 
 	requireCredential: true,
@@ -21,23 +23,26 @@ export const meta = {
 } as const;
 
 export const paramDef = {
-	type: 'object',
-	properties: {
-		notificationId: { type: 'string', format: 'misskey:id' },
-	},
-	required: ['notificationId'],
+	oneOf: [
+		{
+			type: 'object',
+			properties: {
+				notificationId: { type: 'string', format: 'misskey:id' },
+			},
+			required: ['notificationId'],
+		},
+		{
+			type: 'object',
+			properties: {
+				notificationIds: { type: 'array', items: { type: 'string', format: 'misskey:id' } },
+			},
+			required: ['notificationIds'],
+		},
+	],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
-	const notification = await Notifications.findOneBy({
-		notifieeId: user.id,
-		id: ps.notificationId,
-	});
-
-	if (notification == null) {
-		throw new ApiError(meta.errors.noSuchNotification);
-	}
-
-	readNotification(user.id, [notification.id]);
+	if ('notificationId' in ps) return readNotification(user.id, [ps.notificationId]);
+	return readNotification(user.id, ps.notificationIds);
 });
diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts
index 3dcce8550f..5d37e86b91 100644
--- a/packages/backend/src/server/api/endpoints/pages/show.ts
+++ b/packages/backend/src/server/api/endpoints/pages/show.ts
@@ -1,8 +1,8 @@
-import define from '../../define.js';
-import { ApiError } from '../../error.js';
+import { IsNull } from 'typeorm';
 import { Pages, Users } from '@/models/index.js';
 import { Page } from '@/models/entities/page.js';
-import { IsNull } from 'typeorm';
+import define from '../../define.js';
+import { ApiError } from '../../error.js';
 
 export const meta = {
 	tags: ['pages'],
@@ -45,7 +45,7 @@ export const paramDef = {
 
 // eslint-disable-next-line import/no-default-export
 export default define(meta, paramDef, async (ps, user) => {
-	let page: Page | undefined;
+	let page: Page | null = null;
 
 	if (ps.pageId) {
 		page = await Pages.findOneBy({ id: ps.pageId });
diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts
index 046337f040..12ce7a9834 100644
--- a/packages/backend/src/server/api/endpoints/request-reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts
@@ -10,8 +10,12 @@ import { genId } from '@/misc/gen-id.js';
 import { IsNull } from 'typeorm';
 
 export const meta = {
+	tags: ['reset password'],
+
 	requireCredential: false,
 
+	description: 'Request a users password to be reset.',
+
 	limit: {
 		duration: ms('1hour'),
 		max: 3,
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index dbe64e9a13..5ff115dab5 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -3,8 +3,12 @@ import { ApiError } from '../error.js';
 import { resetDb } from '@/db/postgre.js';
 
 export const meta = {
+	tags: ['non-productive'],
+
 	requireCredential: false,
 
+	description: 'Only available when running with <code>NODE_ENV=testing</code>. Reset the database and flush Redis.',
+
 	errors: {
 
 	},
diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts
index 7acc545c40..3dcb0b9b83 100644
--- a/packages/backend/src/server/api/endpoints/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/reset-password.ts
@@ -5,8 +5,12 @@ import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js';
 import { ApiError } from '../error.js';
 
 export const meta = {
+	tags: ['reset password'],
+
 	requireCredential: false,
 
+	description: 'Complete the password reset that was previously requested.',
+
 	errors: {
 
 	},
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index a48973a0df..5bc3b9b6a1 100644
--- a/packages/backend/src/server/api/endpoints/sw/register.ts
+++ b/packages/backend/src/server/api/endpoints/sw/register.ts
@@ -8,6 +8,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	description: 'Register to receive push notifications.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts
index 9748f2a222..c21856d28f 100644
--- a/packages/backend/src/server/api/endpoints/sw/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts
@@ -5,6 +5,8 @@ export const meta = {
 	tags: ['account'],
 
 	requireCredential: true,
+
+	description: 'Unregister from receiving push notifications.',
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts
index 256da1a66f..9949237a7e 100644
--- a/packages/backend/src/server/api/endpoints/test.ts
+++ b/packages/backend/src/server/api/endpoints/test.ts
@@ -1,6 +1,10 @@
 import define from '../define.js';
 
 export const meta = {
+	tags: ['non-productive'],
+
+	description: 'Endpoint for testing input validation.',
+
 	requireCredential: false,
 } as const;
 
diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts
index 424c594749..37d4153950 100644
--- a/packages/backend/src/server/api/endpoints/users/clips.ts
+++ b/packages/backend/src/server/api/endpoints/users/clips.ts
@@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js';
 
 export const meta = {
 	tags: ['users', 'clips'],
+
+	description: 'Show all clips this user owns.',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'Clip',
+		},
+	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 26b1f20df0..b1fb656208 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Show everyone that follows this user.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 42cf5216e8..429a5e80e5 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Show everyone that this user is following.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
index d7c435256c..35bf2df598 100644
--- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
@@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../../common/make-pagination-query.js';
 
 export const meta = {
 	tags: ['users', 'gallery'],
+
+	description: 'Show all gallery posts by the given user.',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'GalleryPost',
+		},
+	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
index 73cadc0df7..ab5837b3f3 100644
--- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
+++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Get a list of other users that the specified user frequently replies to.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts
index fc775d7cc1..fcaf4af3c3 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Create a new group.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
index f68006994c..1bf253ae3f 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Delete an existing group.',
+
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
index 75c1acc302..eafd7f592c 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Join a group the authenticated user has been invited to.',
+
 	errors: {
 		noSuchInvitation: {
 			message: 'No such invitation.',
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
index 46bc780ab0..08d3a3804b 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Delete an existing group invitation for the authenticated user without joining the group.',
+
 	errors: {
 		noSuchInvitation: {
 			message: 'No such invitation.',
diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
index 30a5beb1d9..cc82e43f21 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Invite a user to an existing group.',
+
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts
index 77dc59d3e5..6a2862ee5a 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'read:user-groups',
 
+	description: 'List the groups that the authenticated user is a member of.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
index 33abd5439f..2343cdf857 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.',
+
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts
index b1289e601f..de030193cc 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts
@@ -8,6 +8,8 @@ export const meta = {
 
 	kind: 'read:user-groups',
 
+	description: 'List the groups that the authenticated user is the owner of.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
index b31990b2e3..703dad6d3b 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Removes a specified user from a group. The owner can not be removed.',
+
 	errors: {
 		noSuchGroup: {
 			message: 'No such group.',
diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts
index 3ffb0f5ba9..e1cee5fcf7 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'read:user-groups',
 
+	description: 'Show the properties of a group.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
index 41ceee3b2e..1496e766ca 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Transfer ownership of a group from the authenticated user to another user.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts
index 1016aa8926..43cf3e484e 100644
--- a/packages/backend/src/server/api/endpoints/users/groups/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'write:user-groups',
 
+	description: 'Update the properties of a group.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts
index d5260256d5..d2941a0af5 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	kind: 'write:account',
 
+	description: 'Create a new list of users.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
index b7ad96eef0..8cd02ee02a 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'write:account',
 
+	description: 'Delete an existing list of users.',
+
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts
index 78311292cb..b337f879b1 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/list.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts
@@ -8,6 +8,8 @@ export const meta = {
 
 	kind: 'read:account',
 
+	description: 'Show all lists that the authenticated user has created.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
index 76863f07d1..fa7033b02e 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'write:account',
 
+	description: 'Remove a user from a list.',
+
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index 260665c63a..1db10afc80 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'write:account',
 
+	description: 'Add a user to an existing list.',
+
 	errors: {
 		noSuchList: {
 			message: 'No such list.',
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index 5f51980e95..94d24e1274 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'read:account',
 
+	description: 'Show the properties of a list.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts
index 52353a14cc..c21cdcf679 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	kind: 'write:account',
 
+	description: 'Update the properties of a list.',
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index 16318d2225..57dcdfaa88 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -12,6 +12,8 @@ import { generateMutedInstanceQuery } from '../../common/generate-muted-instance
 export const meta = {
 	tags: ['users', 'notes'],
 
+	description: 'Show all notes that this user created.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts
index b8b3e8192e..85d122c24f 100644
--- a/packages/backend/src/server/api/endpoints/users/pages.ts
+++ b/packages/backend/src/server/api/endpoints/users/pages.ts
@@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../common/make-pagination-query.js';
 
 export const meta = {
 	tags: ['users', 'pages'],
+
+	description: 'Show all pages this user created.',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'Page',
+		},
+	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index c2d1994343..64994aae49 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Show all reactions this user made.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts
index a8f18de522..6fff94ddcf 100644
--- a/packages/backend/src/server/api/endpoints/users/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	kind: 'read:account',
 
+	description: 'Show users that the authenticated user might be interested to follow.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts
index c6262122d4..87cab5fcf1 100644
--- a/packages/backend/src/server/api/endpoints/users/relation.ts
+++ b/packages/backend/src/server/api/endpoints/users/relation.ts
@@ -6,6 +6,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	description: 'Show the different kinds of relations between the authenticated user and the specified user(s).',
+
 	res: {
 		optional: false, nullable: false,
 		oneOf: [
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index 0be385dbbf..c7c7a3f591 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	description: 'File a report.',
+
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index f74d80e2ae..6cbf12b3b5 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Search for a user by username and/or host.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index a72a58a843..19c1a2c690 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -8,6 +8,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Search for users.',
+
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -61,7 +63,14 @@ export default define(meta, paramDef, async (ps, me) => {
 			.getMany();
 	} else {
 		const nameQuery = Users.createQueryBuilder('user')
-			.where('user.name ILIKE :query', { query: '%' + ps.query + '%' })
+			.where(new Brackets(qb => { 
+				qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' });
+
+				// Also search username if it qualifies as username
+				if (Users.validateLocalUsername(ps.query)) {
+					qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' });
+				}
+			}))
 			.andWhere(new Brackets(qb => { qb
 				.where('user.updatedAt IS NULL')
 				.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 183ff1b8bb..b31ca30647 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	requireCredential: false,
 
+	description: 'Show the properties of a user.',
+
 	res: {
 		optional: false, nullable: false,
 		oneOf: [
diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts
index d138019a72..d17e8b64b5 100644
--- a/packages/backend/src/server/api/endpoints/users/stats.ts
+++ b/packages/backend/src/server/api/endpoints/users/stats.ts
@@ -1,12 +1,15 @@
 import define from '../../define.js';
 import { ApiError } from '../../error.js';
 import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js';
+import { awaitAll } from '@/prelude/await-all.js';
 
 export const meta = {
 	tags: ['users'],
 
 	requireCredential: false,
 
+	description: 'Show statistics about a user.',
+
 	errors: {
 		noSuchUser: {
 			message: 'No such user.',
@@ -14,6 +17,94 @@ export const meta = {
 			id: '9e638e45-3b25-4ef7-8f95-07e8498f1819',
 		},
 	},
+
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		properties: {
+			notesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			repliesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			renotesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			repliedCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			renotedCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			pollVotesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			pollVotedCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			localFollowingCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			remoteFollowingCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			localFollowersCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			remoteFollowersCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			followingCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			followersCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			sentReactionsCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			receivedReactionsCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			noteFavoritesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			pageLikesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			pageLikedCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			driveFilesCount: {
+				type: 'integer',
+				optional: false, nullable: false,
+			},
+			driveUsage: {
+				type: 'integer',
+				optional: false, nullable: false,
+				description: 'Drive usage in bytes',
+			},
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -31,109 +122,72 @@ export default define(meta, paramDef, async (ps, me) => {
 		throw new ApiError(meta.errors.noSuchUser);
 	}
 
-	const [
-		notesCount,
-		repliesCount,
-		renotesCount,
-		repliedCount,
-		renotedCount,
-		pollVotesCount,
-		pollVotedCount,
-		localFollowingCount,
-		remoteFollowingCount,
-		localFollowersCount,
-		remoteFollowersCount,
-		sentReactionsCount,
-		receivedReactionsCount,
-		noteFavoritesCount,
-		pageLikesCount,
-		pageLikedCount,
-		driveFilesCount,
-		driveUsage,
-	] = await Promise.all([
-		Notes.createQueryBuilder('note')
+	const result = await awaitAll({
+		notesCount: Notes.createQueryBuilder('note')
 			.where('note.userId = :userId', { userId: user.id })
 			.getCount(),
-		Notes.createQueryBuilder('note')
+		repliesCount: Notes.createQueryBuilder('note')
 			.where('note.userId = :userId', { userId: user.id })
 			.andWhere('note.replyId IS NOT NULL')
 			.getCount(),
-		Notes.createQueryBuilder('note')
+		renotesCount: Notes.createQueryBuilder('note')
 			.where('note.userId = :userId', { userId: user.id })
 			.andWhere('note.renoteId IS NOT NULL')
 			.getCount(),
-		Notes.createQueryBuilder('note')
+		repliedCount: Notes.createQueryBuilder('note')
 			.where('note.replyUserId = :userId', { userId: user.id })
 			.getCount(),
-		Notes.createQueryBuilder('note')
+		renotedCount: Notes.createQueryBuilder('note')
 			.where('note.renoteUserId = :userId', { userId: user.id })
 			.getCount(),
-		PollVotes.createQueryBuilder('vote')
+		pollVotesCount: PollVotes.createQueryBuilder('vote')
 			.where('vote.userId = :userId', { userId: user.id })
 			.getCount(),
-		PollVotes.createQueryBuilder('vote')
+		pollVotedCount: PollVotes.createQueryBuilder('vote')
 			.innerJoin('vote.note', 'note')
 			.where('note.userId = :userId', { userId: user.id })
 			.getCount(),
-		Followings.createQueryBuilder('following')
+		localFollowingCount: Followings.createQueryBuilder('following')
 			.where('following.followerId = :userId', { userId: user.id })
 			.andWhere('following.followeeHost IS NULL')
 			.getCount(),
-		Followings.createQueryBuilder('following')
+		remoteFollowingCount: Followings.createQueryBuilder('following')
 			.where('following.followerId = :userId', { userId: user.id })
 			.andWhere('following.followeeHost IS NOT NULL')
 			.getCount(),
-		Followings.createQueryBuilder('following')
+		localFollowersCount: Followings.createQueryBuilder('following')
 			.where('following.followeeId = :userId', { userId: user.id })
 			.andWhere('following.followerHost IS NULL')
 			.getCount(),
-		Followings.createQueryBuilder('following')
+		remoteFollowersCount: Followings.createQueryBuilder('following')
 			.where('following.followeeId = :userId', { userId: user.id })
 			.andWhere('following.followerHost IS NOT NULL')
 			.getCount(),
-		NoteReactions.createQueryBuilder('reaction')
+		sentReactionsCount: NoteReactions.createQueryBuilder('reaction')
 			.where('reaction.userId = :userId', { userId: user.id })
 			.getCount(),
-		NoteReactions.createQueryBuilder('reaction')
+		receivedReactionsCount: NoteReactions.createQueryBuilder('reaction')
 			.innerJoin('reaction.note', 'note')
 			.where('note.userId = :userId', { userId: user.id })
 			.getCount(),
-		NoteFavorites.createQueryBuilder('favorite')
+		noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite')
 			.where('favorite.userId = :userId', { userId: user.id })
 			.getCount(),
-		PageLikes.createQueryBuilder('like')
+		pageLikesCount: PageLikes.createQueryBuilder('like')
 			.where('like.userId = :userId', { userId: user.id })
 			.getCount(),
-		PageLikes.createQueryBuilder('like')
+		pageLikedCount: PageLikes.createQueryBuilder('like')
 			.innerJoin('like.page', 'page')
 			.where('page.userId = :userId', { userId: user.id })
 			.getCount(),
-		DriveFiles.createQueryBuilder('file')
+		driveFilesCount: DriveFiles.createQueryBuilder('file')
 			.where('file.userId = :userId', { userId: user.id })
 			.getCount(),
-		DriveFiles.calcDriveUsageOf(user),
-	]);
+		driveUsage: DriveFiles.calcDriveUsageOf(user),
+	});
 
-	return {
-		notesCount,
-		repliesCount,
-		renotesCount,
-		repliedCount,
-		renotedCount,
-		pollVotesCount,
-		pollVotedCount,
-		localFollowingCount,
-		remoteFollowingCount,
-		localFollowersCount,
-		remoteFollowersCount,
-		followingCount: localFollowingCount + remoteFollowingCount,
-		followersCount: localFollowersCount + remoteFollowersCount,
-		sentReactionsCount,
-		receivedReactionsCount,
-		noteFavoritesCount,
-		pageLikesCount,
-		pageLikedCount,
-		driveFilesCount,
-		driveUsage,
-	};
+	result.followingCount = result.localFollowingCount + result.remoteFollowingCount;
+	result.followersCount = result.localFollowersCount + result.remoteFollowersCount;
+
+	return result;
 });
diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts
index e74db8466e..23430cf8b6 100644
--- a/packages/backend/src/server/api/limiter.ts
+++ b/packages/backend/src/server/api/limiter.ts
@@ -1,25 +1,17 @@
 import Limiter from 'ratelimiter';
 import { redisClient } from '../../db/redis.js';
-import { IEndpoint } from './endpoints.js';
-import * as Acct from '@/misc/acct.js';
+import { IEndpointMeta } from './endpoints.js';
 import { CacheableLocalUser, User } from '@/models/entities/user.js';
 import Logger from '@/services/logger.js';
 
 const logger = new Logger('limiter');
 
-export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndpoint['meta']['limit']> } }, user: CacheableLocalUser) => new Promise<void>((ok, reject) => {
-	const limitation = endpoint.meta.limit;
-
-	const key = Object.prototype.hasOwnProperty.call(limitation, 'key')
-		? limitation.key
-		: endpoint.name;
-
-	const hasShortTermLimit =
-		Object.prototype.hasOwnProperty.call(limitation, 'minInterval');
+export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((ok, reject) => {
+	const hasShortTermLimit = typeof limitation.minInterval === 'number';
 
 	const hasLongTermLimit =
-		Object.prototype.hasOwnProperty.call(limitation, 'duration') &&
-		Object.prototype.hasOwnProperty.call(limitation, 'max');
+		typeof limitation.duration === 'number' &&
+		typeof limitation.max === 'number';
 
 	if (hasShortTermLimit) {
 		min();
@@ -32,7 +24,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp
 	// Short-term limit
 	function min(): void {
 		const minIntervalLimiter = new Limiter({
-			id: `${user.id}:${key}:min`,
+			id: `${actor}:${limitation.key}:min`,
 			duration: limitation.minInterval,
 			max: 1,
 			db: redisClient,
@@ -43,7 +35,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp
 				return reject('ERR');
 			}
 
-			logger.debug(`@${Acct.toString(user)} ${endpoint.name} min remaining: ${info.remaining}`);
+			logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
 
 			if (info.remaining === 0) {
 				reject('BRIEF_REQUEST_INTERVAL');
@@ -60,7 +52,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp
 	// Long term limit
 	function max(): void {
 		const limiter = new Limiter({
-			id: `${user.id}:${key}`,
+			id: `${actor}:${limitation.key}`,
 			duration: limitation.duration,
 			max: limitation.max,
 			db: redisClient,
@@ -71,7 +63,7 @@ export const limiter = (endpoint: IEndpoint & { meta: { limit: NonNullable<IEndp
 				return reject('ERR');
 			}
 
-			logger.debug(`@${Acct.toString(user)} ${endpoint.name} max remaining: ${info.remaining}`);
+			logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
 
 			if (info.remaining === 0) {
 				reject('RATE_LIMIT_EXCEEDED');
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index c6e557aefb..3929fff3f7 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -59,6 +59,18 @@ export function genOpenapiSpec(lang = 'ja-JP') {
 			desc += ` / **Permission**: *${kind}*`;
 		}
 
+		const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
+		const schema = endpoint.params;
+
+		if (endpoint.meta.requireFile) {
+			schema.properties.file = {
+				type: 'string',
+				format: 'binary',
+				description: 'The file contents.',
+			};
+			schema.required.push('file');
+		}
+
 		const info = {
 			operationId: endpoint.name,
 			summary: endpoint.name,
@@ -78,8 +90,8 @@ export function genOpenapiSpec(lang = 'ja-JP') {
 			requestBody: {
 				required: true,
 				content: {
-					'application/json': {
-						schema: endpoint.params,
+					[requestType]: {
+						schema,
 					},
 				},
 			},
diff --git a/packages/backend/src/server/api/private/signin.ts b/packages/backend/src/server/api/private/signin.ts
index 7b66657ad8..79b31764fd 100644
--- a/packages/backend/src/server/api/private/signin.ts
+++ b/packages/backend/src/server/api/private/signin.ts
@@ -9,6 +9,8 @@ import { genId } from '@/misc/gen-id.js';
 import { verifyLogin, hash } from '../2fa.js';
 import { randomBytes } from 'node:crypto';
 import { IsNull } from 'typeorm';
+import { limiter } from '../limiter.js';
+import { getIpHash } from '@/misc/get-ip-hash.js';
 
 export default async (ctx: Koa.Context) => {
 	ctx.set('Access-Control-Allow-Origin', config.url);
@@ -24,6 +26,21 @@ export default async (ctx: Koa.Context) => {
 		ctx.body = { error };
 	}
 
+	try {
+		// not more than 1 attempt per second and not more than 10 attempts per hour
+		await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip));
+	} catch (err) {
+		ctx.status = 429;
+		ctx.body = {
+			error: {
+				message: 'Too many failed attempts to sign in. Try again later.',
+				code: 'TOO_MANY_AUTHENTICATION_FAILURES',
+				id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
+			},
+		};
+		return;
+	}
+
 	if (typeof username !== 'string') {
 		ctx.status = 400;
 		return;
diff --git a/packages/backend/src/server/api/service/discord.ts b/packages/backend/src/server/api/service/discord.ts
index 04197574c2..97cbcbecdb 100644
--- a/packages/backend/src/server/api/service/discord.ts
+++ b/packages/backend/src/server/api/service/discord.ts
@@ -1,16 +1,16 @@
 import Koa from 'koa';
 import Router from '@koa/router';
-import { getJson } from '@/misc/fetch.js';
 import { OAuth2 } from 'oauth';
+import { v4 as uuid } from 'uuid';
+import { IsNull } from 'typeorm';
+import { getJson } from '@/misc/fetch.js';
 import config from '@/config/index.js';
 import { publishMainStream } from '@/services/stream.js';
-import { redisClient } from '../../../db/redis.js';
-import { v4 as uuid } from 'uuid';
-import signin from '../common/signin.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { Users, UserProfiles } from '@/models/index.js';
 import { ILocalUser } from '@/models/entities/user.js';
-import { IsNull } from 'typeorm';
+import { redisClient } from '../../../db/redis.js';
+import signin from '../common/signin.js';
 
 function getUserToken(ctx: Koa.BaseContext): string | null {
 	return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
@@ -54,7 +54,7 @@ router.get('/disconnect/discord', async ctx => {
 		integrations: profile.integrations,
 	});
 
-	ctx.body = `Discordใฎ้€ฃๆบใ‚’่งฃ้™คใ—ใพใ—ใŸ :v:`;
+	ctx.body = 'Discordใฎ้€ฃๆบใ‚’่งฃ้™คใ—ใพใ—ใŸ :v:';
 
 	// Publish i updated event
 	publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
@@ -140,7 +140,7 @@ router.get('/dc/cb', async ctx => {
 
 		const code = ctx.query.code;
 
-		if (!code) {
+		if (!code || typeof code !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
@@ -174,17 +174,17 @@ router.get('/dc/cb', async ctx => {
 				}
 			}));
 
-		const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
+		const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
 			'Authorization': `Bearer ${accessToken}`,
-		});
+		})) as Record<string, unknown>;
 
-		if (!id || !username || !discriminator) {
+		if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
 
 		const profile = await UserProfiles.createQueryBuilder()
-			.where(`"integrations"->'discord'->>'id' = :id`, { id: id })
+			.where('"integrations"->\'discord\'->>\'id\' = :id', { id: id })
 			.andWhere('"userHost" IS NULL')
 			.getOne();
 
@@ -211,7 +211,7 @@ router.get('/dc/cb', async ctx => {
 	} else {
 		const code = ctx.query.code;
 
-		if (!code) {
+		if (!code || typeof code !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
@@ -245,10 +245,10 @@ router.get('/dc/cb', async ctx => {
 				}
 			}));
 
-		const { id, username, discriminator } = await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
+		const { id, username, discriminator } = (await getJson('https://discord.com/api/users/@me', '*/*', 10 * 1000, {
 			'Authorization': `Bearer ${accessToken}`,
-		});
-		if (!id || !username || !discriminator) {
+		})) as Record<string, unknown>;
+		if (typeof id !== 'string' || typeof username !== 'string' || typeof discriminator !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
diff --git a/packages/backend/src/server/api/service/github.ts b/packages/backend/src/server/api/service/github.ts
index 61bb768a63..04dbd1f7ab 100644
--- a/packages/backend/src/server/api/service/github.ts
+++ b/packages/backend/src/server/api/service/github.ts
@@ -1,16 +1,16 @@
 import Koa from 'koa';
 import Router from '@koa/router';
-import { getJson } from '@/misc/fetch.js';
 import { OAuth2 } from 'oauth';
+import { v4 as uuid } from 'uuid';
+import { IsNull } from 'typeorm';
+import { getJson } from '@/misc/fetch.js';
 import config from '@/config/index.js';
 import { publishMainStream } from '@/services/stream.js';
-import { redisClient } from '../../../db/redis.js';
-import { v4 as uuid } from 'uuid';
-import signin from '../common/signin.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { Users, UserProfiles } from '@/models/index.js';
 import { ILocalUser } from '@/models/entities/user.js';
-import { IsNull } from 'typeorm';
+import { redisClient } from '../../../db/redis.js';
+import signin from '../common/signin.js';
 
 function getUserToken(ctx: Koa.BaseContext): string | null {
 	return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
@@ -54,7 +54,7 @@ router.get('/disconnect/github', async ctx => {
 		integrations: profile.integrations,
 	});
 
-	ctx.body = `GitHubใฎ้€ฃๆบใ‚’่งฃ้™คใ—ใพใ—ใŸ :v:`;
+	ctx.body = 'GitHubใฎ้€ฃๆบใ‚’่งฃ้™คใ—ใพใ—ใŸ :v:';
 
 	// Publish i updated event
 	publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
@@ -138,7 +138,7 @@ router.get('/gh/cb', async ctx => {
 
 		const code = ctx.query.code;
 
-		if (!code) {
+		if (!code || typeof code !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
@@ -167,16 +167,16 @@ router.get('/gh/cb', async ctx => {
 				}
 			}));
 
-		const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
+		const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
 			'Authorization': `bearer ${accessToken}`,
-		});
-		if (!login || !id) {
+		})) as Record<string, unknown>;
+		if (typeof login !== 'string' || typeof id !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
 
 		const link = await UserProfiles.createQueryBuilder()
-			.where(`"integrations"->'github'->>'id' = :id`, { id: id })
+			.where('"integrations"->\'github\'->>\'id\' = :id', { id: id })
 			.andWhere('"userHost" IS NULL')
 			.getOne();
 
@@ -189,7 +189,7 @@ router.get('/gh/cb', async ctx => {
 	} else {
 		const code = ctx.query.code;
 
-		if (!code) {
+		if (!code || typeof code !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
@@ -219,11 +219,11 @@ router.get('/gh/cb', async ctx => {
 					}
 				}));
 
-		const { login, id } = await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
+		const { login, id } = (await getJson('https://api.github.com/user', 'application/vnd.github.v3+json', 10 * 1000, {
 			'Authorization': `bearer ${accessToken}`,
-		});
+		})) as Record<string, unknown>;
 
-		if (!login || !id) {
+		if (typeof login !== 'string' || typeof id !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
diff --git a/packages/backend/src/server/api/service/twitter.ts b/packages/backend/src/server/api/service/twitter.ts
index e72b71e2f7..2b4f9f6daa 100644
--- a/packages/backend/src/server/api/service/twitter.ts
+++ b/packages/backend/src/server/api/service/twitter.ts
@@ -2,14 +2,14 @@ import Koa from 'koa';
 import Router from '@koa/router';
 import { v4 as uuid } from 'uuid';
 import autwh from 'autwh';
-import { redisClient } from '../../../db/redis.js';
+import { IsNull } from 'typeorm';
 import { publishMainStream } from '@/services/stream.js';
 import config from '@/config/index.js';
-import signin from '../common/signin.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { Users, UserProfiles } from '@/models/index.js';
 import { ILocalUser } from '@/models/entities/user.js';
-import { IsNull } from 'typeorm';
+import signin from '../common/signin.js';
+import { redisClient } from '../../../db/redis.js';
 
 function getUserToken(ctx: Koa.BaseContext): string | null {
 	return ((ctx.headers['cookie'] || '').match(/igi=(\w+)/) || [null, null])[1];
@@ -53,7 +53,7 @@ router.get('/disconnect/twitter', async ctx => {
 		integrations: profile.integrations,
 	});
 
-	ctx.body = `Twitterใฎ้€ฃๆบใ‚’่งฃ้™คใ—ใพใ—ใŸ :v:`;
+	ctx.body = 'Twitterใฎ้€ฃๆบใ‚’่งฃ้™คใ—ใพใ—ใŸ :v:';
 
 	// Publish i updated event
 	publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, {
@@ -132,10 +132,16 @@ router.get('/tw/cb', async ctx => {
 
 		const twCtx = await get;
 
-		const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
+		const verifier = ctx.query.oauth_verifier;
+		if (!verifier || typeof verifier !== 'string') {
+			ctx.throw(400, 'invalid session');
+			return;
+		}
+
+		const result = await twAuth!.done(JSON.parse(twCtx), verifier);
 
 		const link = await UserProfiles.createQueryBuilder()
-			.where(`"integrations"->'twitter'->>'userId' = :id`, { id: result.userId })
+			.where('"integrations"->\'twitter\'->>\'userId\' = :id', { id: result.userId })
 			.andWhere('"userHost" IS NULL')
 			.getOne();
 
@@ -148,7 +154,7 @@ router.get('/tw/cb', async ctx => {
 	} else {
 		const verifier = ctx.query.oauth_verifier;
 
-		if (verifier == null) {
+		if (!verifier || typeof verifier !== 'string') {
 			ctx.throw(400, 'invalid session');
 			return;
 		}
diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts
index 043d03ab8d..b67600474b 100644
--- a/packages/backend/src/server/api/stream/channels/queue-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts
@@ -1,7 +1,7 @@
-import { default as Xev } from 'xev';
+import Xev from 'xev';
 import Channel from '../channel.js';
 
-const ev = new Xev.default();
+const ev = new Xev();
 
 export default class extends Channel {
 	public readonly chName = 'queueStats';
diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts
index 0da1895767..db75a6fa38 100644
--- a/packages/backend/src/server/api/stream/channels/server-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/server-stats.ts
@@ -1,7 +1,7 @@
-import { default as Xev } from 'xev';
+import Xev from 'xev';
 import Channel from '../channel.js';
 
-const ev = new Xev.default();
+const ev = new Xev();
 
 export default class extends Channel {
 	public readonly chName = 'serverStats';
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index b803478281..2d23145f14 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -1,27 +1,25 @@
-import * as websocket from 'websocket';
-import { readNotification } from '../common/read-notification.js';
-import call from '../call.js';
-import readNote from '@/services/note/read.js';
-import Channel from './channel.js';
-import channels from './channels/index.js';
 import { EventEmitter } from 'events';
+import * as websocket from 'websocket';
+import readNote from '@/services/note/read.js';
 import { User } from '@/models/entities/user.js';
 import { Channel as ChannelModel } from '@/models/entities/channel.js';
 import { Users, Followings, Mutings, UserProfiles, ChannelFollowings, Blockings } from '@/models/index.js';
-import { ApiError } from '../error.js';
 import { AccessToken } from '@/models/entities/access-token.js';
 import { UserProfile } from '@/models/entities/user-profile.js';
 import { publishChannelStream, publishGroupMessagingStream, publishMessagingStream } from '@/services/stream.js';
 import { UserGroup } from '@/models/entities/user-group.js';
-import { StreamEventEmitter, StreamMessages } from './types.js';
 import { Packed } from '@/misc/schema.js';
+import { readNotification } from '../common/read-notification.js';
+import channels from './channels/index.js';
+import Channel from './channel.js';
+import { StreamEventEmitter, StreamMessages } from './types.js';
 
 /**
  * Main stream connection
  */
 export default class Connection {
 	public user?: User;
-	public userProfile?: UserProfile;
+	public userProfile?: UserProfile | null;
 	public following: Set<User['id']> = new Set();
 	public muting: Set<User['id']> = new Set();
 	public blocking: Set<User['id']> = new Set(); // "่ขซ"blocking
@@ -84,7 +82,7 @@ export default class Connection {
 				this.muting.delete(data.body.id);
 				break;
 
-			// TODO: block events
+				// TODO: block events
 
 			case 'followChannel':
 				this.followingChannels.add(data.body.id);
@@ -126,7 +124,6 @@ export default class Connection {
 		const { type, body } = obj;
 
 		switch (type) {
-			case 'api': this.onApiRequest(body); break;
 			case 'readNotification': this.onReadNotification(body); break;
 			case 'subNote': this.onSubscribeNote(body); break;
 			case 's': this.onSubscribeNote(body); break; // alias
@@ -183,31 +180,6 @@ export default class Connection {
 		}
 	}
 
-	/**
-	 * APIใƒชใ‚ฏใ‚จใ‚นใƒˆ่ฆๆฑ‚ๆ™‚
-	 */
-	private async onApiRequest(payload: any) {
-		// ๆ–ฐ้ฎฎใชใƒ‡ใƒผใ‚ฟใ‚’ๅˆฉ็”จใ™ใ‚‹ใŸใ‚ใซใƒฆใƒผใ‚ถใƒผใ‚’ใƒ•ใ‚งใƒƒใƒ
-		const user = this.user ? await Users.findOneBy({ id: this.user.id }) : null;
-
-		const endpoint = payload.endpoint || payload.ep; // alias
-
-		// ๅ‘ผใณๅ‡บใ—
-		call(endpoint, user, this.token, payload.data).then(res => {
-			this.sendMessageToWs(`api:${payload.id}`, { res });
-		}).catch((e: ApiError) => {
-			this.sendMessageToWs(`api:${payload.id}`, {
-				error: {
-					message: e.message,
-					code: e.code,
-					id: e.id,
-					kind: e.kind,
-					...(e.info ? { info: e.info } : {}),
-				},
-			});
-		});
-	}
-
 	private onReadNotification(payload: any) {
 		if (!payload.id) return;
 		readNotification(this.user!.id, [payload.id]);
diff --git a/packages/backend/src/server/api/streaming.ts b/packages/backend/src/server/api/streaming.ts
index 2a34edac67..f8e42d27fe 100644
--- a/packages/backend/src/server/api/streaming.ts
+++ b/packages/backend/src/server/api/streaming.ts
@@ -1,4 +1,4 @@
-import * as http from 'http';
+import * as http from 'node:http';
 import * as websocket from 'websocket';
 
 import MainStreamConnection from './stream/index.js';
diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts
index 6bc220b362..c34e043145 100644
--- a/packages/backend/src/server/file/send-drive-file.ts
+++ b/packages/backend/src/server/file/send-drive-file.ts
@@ -4,14 +4,14 @@ import { dirname } from 'node:path';
 import Koa from 'koa';
 import send from 'koa-send';
 import rename from 'rename';
-import * as tmp from 'tmp';
 import { serverLogger } from '../index.js';
 import { contentDisposition } from '@/misc/content-disposition.js';
 import { DriveFiles } from '@/models/index.js';
 import { InternalStorage } from '@/services/drive/internal-storage.js';
+import { createTemp } from '@/misc/create-temp.js';
 import { downloadUrl } from '@/misc/download-url.js';
 import { detectType } from '@/misc/get-file-info.js';
-import { convertToJpeg, convertToPng, convertToPngOrJpeg } from '@/services/drive/image-processor.js';
+import { convertToWebp, convertToJpeg, convertToPng } from '@/services/drive/image-processor.js';
 import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail.js';
 import { StatusError } from '@/misc/fetch.js';
 import { FILE_TYPE_BROWSERSAFE } from '@/const.js';
@@ -50,12 +50,7 @@ export default async function(ctx: Koa.Context) {
 
 	if (!file.storedInternal) {
 		if (file.isLink && file.uri) {	// ๆœŸ้™ๅˆ‡ใ‚Œใƒชใƒขใƒผใƒˆใƒ•ใ‚กใ‚คใƒซ
-			const [path, cleanup] = await new Promise<[string, any]>((res, rej) => {
-				tmp.file((e, path, fd, cleanup) => {
-					if (e) return rej(e);
-					res([path, cleanup]);
-				});
-			});
+			const [path, cleanup] = await createTemp();
 
 			try {
 				await downloadUrl(file.uri, path);
@@ -64,10 +59,8 @@ export default async function(ctx: Koa.Context) {
 
 				const convertFile = async () => {
 					if (isThumbnail) {
-						if (['image/jpeg', 'image/webp'].includes(mime)) {
-							return await convertToJpeg(path, 498, 280);
-						} else if (['image/png', 'image/svg+xml'].includes(mime)) {
-							return await convertToPngOrJpeg(path, 498, 280);
+						if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(mime)) {
+							return await convertToWebp(path, 498, 280);
 						} else if (mime.startsWith('video/')) {
 							return await GenerateVideoThumbnail(path);
 						}
diff --git a/packages/backend/src/server/index.ts b/packages/backend/src/server/index.ts
index b50e38a63b..f31de2b7f4 100644
--- a/packages/backend/src/server/index.ts
+++ b/packages/backend/src/server/index.ts
@@ -2,8 +2,9 @@
  * Core Server
  */
 
+import cluster from 'node:cluster';
 import * as fs from 'node:fs';
-import * as http from 'http';
+import * as http from 'node:http';
 import Koa from 'koa';
 import Router from '@koa/router';
 import mount from 'koa-mount';
@@ -88,10 +89,10 @@ router.get('/avatar/@:acct', async ctx => {
 });
 
 router.get('/identicon/:x', async ctx => {
-	const [temp] = await createTemp();
+	const [temp, cleanup] = await createTemp();
 	await genIdenticon(ctx.params.x, fs.createWriteStream(temp));
 	ctx.set('Content-Type', 'image/png');
-	ctx.body = fs.createReadStream(temp);
+	ctx.body = fs.createReadStream(temp).on('close', () => cleanup());
 });
 
 router.get('/verify-email/:code', async ctx => {
@@ -142,5 +143,26 @@ export default () => new Promise(resolve => {
 
 	initializeStreamingServer(server);
 
+	server.on('error', e => {
+		switch ((e as any).code) {
+			case 'EACCES':
+				serverLogger.error(`You do not have permission to listen on port ${config.port}.`);
+				break;
+			case 'EADDRINUSE':
+				serverLogger.error(`Port ${config.port} is already in use by another process.`);
+				break;
+			default:
+				serverLogger.error(e);
+				break;
+		}
+
+		if (cluster.isWorker) {
+			process.send!('listenFailed');
+		} else {
+			// disableClustering
+			process.exit(1);
+		}
+	});
+
 	server.listen(config.port, resolve);
 });
diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts
index 3cc5b827a6..48887bf12f 100644
--- a/packages/backend/src/server/proxy/proxy-media.ts
+++ b/packages/backend/src/server/proxy/proxy-media.ts
@@ -1,7 +1,7 @@
 import * as fs from 'node:fs';
 import Koa from 'koa';
 import { serverLogger } from '../index.js';
-import { IImage, convertToPng, convertToJpeg } from '@/services/drive/image-processor.js';
+import { IImage, convertToWebp } from '@/services/drive/image-processor.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { downloadUrl } from '@/misc/download-url.js';
 import { detectType } from '@/misc/get-file-info.js';
@@ -27,11 +27,11 @@ export async function proxyMedia(ctx: Koa.Context) {
 		let image: IImage;
 
 		if ('static' in ctx.query && ['image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/webp', 'image/svg+xml'].includes(mime)) {
-			image = await convertToPng(path, 498, 280);
+			image = await convertToWebp(path, 498, 280);
 		} else if ('preview' in ctx.query && ['image/jpeg', 'image/png', 'image/gif', 'image/apng', 'image/vnd.mozilla.apng', 'image/svg+xml'].includes(mime)) {
-			image = await convertToJpeg(path, 200, 200);
+			image = await convertToWebp(path, 200, 200);
 		}	else if (['image/svg+xml'].includes(mime)) {
-			image = await convertToPng(path, 2048, 2048);
+			image = await convertToWebp(path, 2048, 2048, 1);
 		} else if (!mime.startsWith('image/') || !FILE_TYPE_BROWSERSAFE.includes(mime)) {
 			throw new StatusError('Rejected type', 403, 'Rejected type');
 		} else {
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 751e8619bf..94329e11c9 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -54,19 +54,11 @@
 	//#endregion
 
 	//#region Script
-	const salt = localStorage.getItem('salt')
-		? `?salt=${localStorage.getItem('salt')}`
-		: '';
-
-	const script = document.createElement('script');
-	script.setAttribute('src', `/assets/app.${v}.js${salt}`);
-	script.setAttribute('async', 'true');
-	script.setAttribute('defer', 'true');
-	script.addEventListener('error', async () => {
-		await checkUpdate();
-		renderError('APP_FETCH_FAILED');
-	});
-	document.head.appendChild(script);
+	import(`/assets/${CLIENT_ENTRY}`)
+		.catch(async e => {
+			await checkUpdate();
+			renderError('APP_FETCH_FAILED', JSON.stringify(e));
+		})
 	//#endregion
 
 	//#region Theme
@@ -146,9 +138,6 @@
 
 	// eslint-disable-next-line no-inner-declarations
 	function refresh() {
-		// Random
-		localStorage.setItem('salt', Math.random().toString().substr(2, 8));
-
 		// Clear cache (service worker)
 		try {
 			navigator.serviceWorker.controller.postMessage('clear');
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 34d56cfd0c..2feee72be7 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -4,6 +4,7 @@
 
 import { dirname } from 'node:path';
 import { fileURLToPath } from 'node:url';
+import { PathOrFileDescriptor, readFileSync } from 'node:fs';
 import ms from 'ms';
 import Koa from 'koa';
 import Router from '@koa/router';
@@ -14,7 +15,7 @@ import { createBullBoard } from '@bull-board/api';
 import { BullAdapter } from '@bull-board/api/bullAdapter.js';
 import { KoaAdapter } from '@bull-board/koa';
 
-import { IsNull } from 'typeorm';
+import { In, IsNull } from 'typeorm';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import config from '@/config/index.js';
 import { Users, Notes, UserProfiles, Pages, Channels, Clips, GalleryPosts } from '@/models/index.js';
@@ -32,6 +33,7 @@ const _dirname = dirname(_filename);
 const staticAssets = `${_dirname}/../../../assets/`;
 const clientAssets = `${_dirname}/../../../../client/assets/`;
 const assets = `${_dirname}/../../../../../built/_client_dist_/`;
+const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
 
 // Init app
 const app = new Koa();
@@ -72,6 +74,9 @@ app.use(views(_dirname + '/views', {
 	extension: 'pug',
 	options: {
 		version: config.version,
+		getClientEntry: () => process.env.NODE_ENV === 'production' ?
+			config.clientEntry :
+			JSON.parse(readFileSync(`${_dirname}/../../../../../built/_client_dist_/manifest.json`, 'utf-8'))['src/init.ts'],
 		config,
 	},
 }));
@@ -136,9 +141,10 @@ router.get('/twemoji/(.*)', async ctx => {
 });
 
 // ServiceWorker
-router.get('/sw.js', async ctx => {
-	await send(ctx as any, `/sw.${config.version}.js`, {
-		root: assets,
+router.get(`/sw.js`, async ctx => {
+	await send(ctx as any, `/sw.js`, {
+		root: swAssets,
+		maxage: ms('10 minutes'),
 	});
 });
 
@@ -241,7 +247,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
 			icon: meta.iconUrl,
 			themeColor: meta.themeColor,
 		});
-		ctx.set('Cache-Control', 'public, max-age=30');
+		ctx.set('Cache-Control', 'public, max-age=15');
 	} else {
 		// ใƒชใƒขใƒผใƒˆใƒฆใƒผใ‚ถใƒผใชใฎใง
 		// ใƒขใƒ‡ใƒฌใƒผใ‚ฟใŒAPI็ตŒ็”ฑใงๅ‚็…งๅฏ่ƒฝใซใ™ใ‚‹ใŸใ‚ใซ404ใซใฏใ—ใชใ„
@@ -266,7 +272,10 @@ router.get('/users/:user', async ctx => {
 
 // Note
 router.get('/notes/:note', async (ctx, next) => {
-	const note = await Notes.findOneBy({ id: ctx.params.note });
+	const note = await Notes.findOneBy({
+		id: ctx.params.note,
+		visibility: In(['public', 'home']),
+	});
 
 	if (note) {
 		const _note = await Notes.pack(note);
@@ -283,11 +292,7 @@ router.get('/notes/:note', async (ctx, next) => {
 			themeColor: meta.themeColor,
 		});
 
-		if (['public', 'home'].includes(note.visibility)) {
-			ctx.set('Cache-Control', 'public, max-age=180');
-		} else {
-			ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
-		}
+		ctx.set('Cache-Control', 'public, max-age=15');
 
 		return;
 	}
@@ -324,7 +329,7 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
 		});
 
 		if (['public'].includes(page.visibility)) {
-			ctx.set('Cache-Control', 'public, max-age=180');
+			ctx.set('Cache-Control', 'public, max-age=15');
 		} else {
 			ctx.set('Cache-Control', 'private, max-age=0, must-revalidate');
 		}
@@ -355,7 +360,7 @@ router.get('/clips/:clip', async (ctx, next) => {
 			themeColor: meta.themeColor,
 		});
 
-		ctx.set('Cache-Control', 'public, max-age=180');
+		ctx.set('Cache-Control', 'public, max-age=15');
 
 		return;
 	}
@@ -380,7 +385,7 @@ router.get('/gallery/:post', async (ctx, next) => {
 			themeColor: meta.themeColor,
 		});
 
-		ctx.set('Cache-Control', 'public, max-age=180');
+		ctx.set('Cache-Control', 'public, max-age=15');
 
 		return;
 	}
@@ -404,7 +409,7 @@ router.get('/channels/:channel', async (ctx, next) => {
 			themeColor: meta.themeColor,
 		});
 
-		ctx.set('Cache-Control', 'public, max-age=180');
+		ctx.set('Cache-Control', 'public, max-age=15');
 
 		return;
 	}
@@ -463,7 +468,7 @@ router.get('(.*)', async ctx => {
 		icon: meta.iconUrl,
 		themeColor: meta.themeColor,
 	});
-	ctx.set('Cache-Control', 'public, max-age=300');
+	ctx.set('Cache-Control', 'public, max-age=15');
 });
 
 // Register router
diff --git a/packages/backend/src/server/web/manifest.ts b/packages/backend/src/server/web/manifest.ts
index bcbf9b76a7..ee568b8077 100644
--- a/packages/backend/src/server/web/manifest.ts
+++ b/packages/backend/src/server/web/manifest.ts
@@ -1,16 +1,18 @@
 import Koa from 'koa';
-import manifest from './manifest.json' assert { type: 'json' };
 import { fetchMeta } from '@/misc/fetch-meta.js';
+import manifest from './manifest.json' assert { type: 'json' };
 
 export const manifestHandler = async (ctx: Koa.Context) => {
-	const json = JSON.parse(JSON.stringify(manifest));
+	// TODO
+	//const res = structuredClone(manifest);
+	const res = JSON.parse(JSON.stringify(manifest));
 
 	const instance = await fetchMeta(true);
 
-	json.short_name = instance.name || 'Misskey';
-	json.name = instance.name || 'Misskey';
-	if (instance.themeColor) json.theme_color = instance.themeColor;
+	res.short_name = instance.name || 'Misskey';
+	res.name = instance.name || 'Misskey';
+	if (instance.themeColor) res.theme_color = instance.themeColor;
 
 	ctx.set('Cache-Control', 'max-age=300');
-	ctx.body = json;
+	ctx.body = res;
 };
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index 9c4cd4b9bf..d59f00fe16 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -39,28 +39,24 @@ html {
 	width: 28px;
 	height: 28px;
 	transform: translateY(70px);
+	color: var(--accent);
 }
-
-#splashSpinner:before,
-#splashSpinner:after {
-	content: " ";
-	display: block;
-	box-sizing: border-box;
-	width: 28px;
-	height: 28px;
-	border-radius: 50%;
-	border: solid 4px;
-}
-
-#splashSpinner:before {
-	border-color: currentColor;
-	opacity: 0.3;
-}
-
-#splashSpinner:after {
+#splashSpinner > .spinner {
 	position: absolute;
 	top: 0;
-	border-color: currentColor transparent transparent transparent;
+	left: 0;
+	width: 28px;
+	height: 28px;
+	fill-rule: evenodd;
+	clip-rule: evenodd;
+	stroke-linecap: round;
+	stroke-linejoin: round;
+	stroke-miterlimit: 1.5;
+}
+#splashSpinner > .spinner.bg {
+	opacity: 0.275;
+}
+#splashSpinner > .spinner.fg {
 	animation: splashSpinner 0.5s linear infinite;
 }
 
diff --git a/packages/backend/src/server/web/url-preview.ts b/packages/backend/src/server/web/url-preview.ts
index 6bd8ead5b5..1e259649f9 100644
--- a/packages/backend/src/server/web/url-preview.ts
+++ b/packages/backend/src/server/web/url-preview.ts
@@ -56,7 +56,7 @@ export const urlPreviewHandler = async (ctx: Koa.Context) => {
 function wrap(url?: string): string | null {
 	return url != null
 		? url.match(/^https?:\/\//)
-			? `${config.url}/proxy/preview.jpg?${query({
+			? `${config.url}/proxy/preview.webp?${query({
 				url,
 				preview: '1',
 			})}`
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 1513208310..5bb156f0f4 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -1,17 +1,21 @@
 block vars
 
+block loadClientEntry
+	- const clientEntry = getClientEntry();
+
 doctype html
 
-!= '<!--\n'
-!= '  _____ _         _           \n'
-!= ' |     |_|___ ___| |_ ___ _ _ \n'
-!= ' | | | | |_ -|_ -| \'_| -_| | |\n'
-!= ' |_|_|_|_|___|___|_,_|___|_  |\n'
-!= '                         |___|\n'
-!= ' Thank you for using Misskey!\n'
-!= ' If you are reading this message... how about joining the development?\n'
-!= ' https://github.com/misskey-dev/misskey'
-!= '\n-->\n'
+//
+	-
+	  _____ _         _           
+	 |     |_|___ ___| |_ ___ _ _ 
+	 | | | | |_ -|_ -| '_| -_| | |
+	 |_|_|_|_|___|___|_,_|___|_  |
+	                         |___|
+	 Thank you for using Misskey!
+	 If you are reading this message... how about joining the development?
+	 https://github.com/misskey-dev/misskey
+	 
 
 html
 
@@ -30,8 +34,14 @@ html
 		link(rel='prefetch' href='https://xn--931a.moe/assets/info.jpg')
 		link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
 		link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')
-		link(rel='preload' href='/assets/fontawesome/css/all.css' as='style')
 		link(rel='stylesheet' href='/assets/fontawesome/css/all.css')
+		link(rel='modulepreload' href=`/assets/${clientEntry.file}`)
+
+		each href in clientEntry.css
+			link(rel='preload' href=`/assets/${href}` as='style')
+
+		each href in clientEntry.css
+			link(rel='preload' href=`/assets/${href}` as='style')
 
 		title
 			block title
@@ -50,6 +60,10 @@ html
 		style
 			include ../style.css
 
+		script.
+			var VERSION = "#{version}";
+			var CLIENT_ENTRY = "#{clientEntry.file}";
+
 		script
 			include ../boot.js
 
@@ -61,4 +75,14 @@ html
 		div#splash
 			img#splashIcon(src= icon || '/static-assets/splash.png')
 			div#splashSpinner
+				<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
+					<g transform="matrix(1,0,0,1,12,12)">
+						<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
+					</g>
+				</svg>
+				<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
+					<g transform="matrix(1,0,0,1,12,12)">
+						<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
+					</g>
+				</svg>
 		block content
diff --git a/packages/backend/src/server/well-known.ts b/packages/backend/src/server/well-known.ts
index 7530b4e0ba..1d094f2edd 100644
--- a/packages/backend/src/server/well-known.ts
+++ b/packages/backend/src/server/well-known.ts
@@ -41,6 +41,7 @@ router.options(allPath, async ctx => {
 router.get('/.well-known/host-meta', async ctx => {
 	ctx.set('Content-Type', xrd);
 	ctx.body = XRD({ element: 'Link', attributes: {
+		rel: 'lrdd',
 		type: xrd,
 		template: `${config.url}${webFingerPath}?resource={uri}`,
 	} });
diff --git a/packages/backend/src/services/blocking/create.ts b/packages/backend/src/services/blocking/create.ts
index 5e96e5037f..a2c61cca22 100644
--- a/packages/backend/src/services/blocking/create.ts
+++ b/packages/backend/src/services/blocking/create.ts
@@ -2,9 +2,10 @@ import { publishMainStream, publishUserEvent } from '@/services/stream.js';
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import renderFollow from '@/remote/activitypub/renderer/follow.js';
 import renderUndo from '@/remote/activitypub/renderer/undo.js';
-import renderBlock from '@/remote/activitypub/renderer/block.js';
+import { renderBlock } from '@/remote/activitypub/renderer/block.js';
 import { deliver } from '@/queue/index.js';
 import renderReject from '@/remote/activitypub/renderer/reject.js';
+import { Blocking } from '@/models/entities/blocking.js';
 import { User } from '@/models/entities/user.js';
 import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js';
 import { perUserFollowingChart } from '@/services/chart/index.js';
@@ -22,15 +23,19 @@ export default async function(blocker: User, blockee: User) {
 		removeFromList(blockee, blocker),
 	]);
 
-	await Blockings.insert({
+	const blocking = {
 		id: genId(),
 		createdAt: new Date(),
+		blocker,
 		blockerId: blocker.id,
+		blockee,
 		blockeeId: blockee.id,
-	});
+	} as Blocking;
+
+	await Blockings.insert(blocking);
 
 	if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
-		const content = renderActivity(renderBlock(blocker, blockee));
+		const content = renderActivity(renderBlock(blocking));
 		deliver(blocker, content, blockee.inbox);
 	}
 }
@@ -95,17 +100,12 @@ async function unFollow(follower: User, followee: User) {
 		return;
 	}
 
-	Followings.delete(following.id);
-
-	//#region Decrement following count
-	Users.decrement({ id: follower.id }, 'followingCount', 1);
-	//#endregion
-
-	//#region Decrement followers count
-	Users.decrement({ id: followee.id }, 'followersCount', 1);
-	//#endregion
-
-	perUserFollowingChart.update(follower, followee, false);
+	await Promise.all([
+		Followings.delete(following.id),
+		Users.decrement({ id: follower.id }, 'followingCount', 1),
+		Users.decrement({ id: followee.id }, 'followersCount', 1),
+		perUserFollowingChart.update(follower, followee, false),
+	]);
 
 	// Publish unfollow event
 	if (Users.isLocalUser(follower)) {
diff --git a/packages/backend/src/services/blocking/delete.ts b/packages/backend/src/services/blocking/delete.ts
index d7b5ddd5ff..cb16651bc0 100644
--- a/packages/backend/src/services/blocking/delete.ts
+++ b/packages/backend/src/services/blocking/delete.ts
@@ -1,5 +1,5 @@
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
-import renderBlock from '@/remote/activitypub/renderer/block.js';
+import { renderBlock } from '@/remote/activitypub/renderer/block.js';
 import renderUndo from '@/remote/activitypub/renderer/undo.js';
 import { deliver } from '@/queue/index.js';
 import Logger from '../logger.js';
@@ -19,11 +19,16 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) {
 		return;
 	}
 
+	// Since we already have the blocker and blockee, we do not need to fetch
+	// them in the query above and can just manually insert them here.
+	blocking.blocker = blocker;
+	blocking.blockee = blockee;
+
 	Blockings.delete(blocking.id);
 
 	// deliver if remote bloking
 	if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
-		const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker));
+		const content = renderActivity(renderUndo(renderBlock(blocking), blocker));
 		deliver(blocker, content, blockee.inbox);
 	}
 }
diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts
index cf69e2194d..2960bac8f7 100644
--- a/packages/backend/src/services/chart/core.ts
+++ b/packages/backend/src/services/chart/core.ts
@@ -91,27 +91,20 @@ type ToJsonSchema<S> = {
 };
 
 export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> {
-	const object = {};
-	for (const [k, v] of Object.entries(schema)) {
-		nestedProperty.set(object, k, null);
-	}
+	const jsonSchema = {
+		type: 'object',
+		properties: {} as Record<string, unknown>,
+		required: [],
+	};
 
-	function f(obj: Record<string, null | Record<string, unknown>>) {
-		const jsonSchema = {
-			type: 'object',
-			properties: {} as Record<string, unknown>,
-			required: [],
+	for (const k in schema) {
+		jsonSchema.properties[k] = {
+			type: 'array',
+			items: { type: 'number' },
 		};
-		for (const [k, v] of Object.entries(obj)) {
-			jsonSchema.properties[k] = v === null ? {
-				type: 'array',
-				items: { type: 'number' },
-			} : f(v as Record<string, null | Record<string, unknown>>);
-		}
-		return jsonSchema;
 	}
 
-	return f(object) as ToJsonSchema<Unflatten<ChartResult<S>>>;
+	return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>;
 }
 
 /**
diff --git a/packages/backend/src/services/chart/entities.ts b/packages/backend/src/services/chart/entities.ts
index 13e994cb65..a9eeabd639 100644
--- a/packages/backend/src/services/chart/entities.ts
+++ b/packages/backend/src/services/chart/entities.ts
@@ -11,6 +11,11 @@ import { entity as PerUserFollowingChart } from './charts/entities/per-user-foll
 import { entity as PerUserDriveChart } from './charts/entities/per-user-drive.js';
 import { entity as ApRequestChart } from './charts/entities/ap-request.js';
 
+import { entity as TestChart } from './charts/entities/test.js';
+import { entity as TestGroupedChart } from './charts/entities/test-grouped.js';
+import { entity as TestUniqueChart } from './charts/entities/test-unique.js';
+import { entity as TestIntersectionChart } from './charts/entities/test-intersection.js';
+
 export const entities = [
 	FederationChart.hour, FederationChart.day,
 	NotesChart.hour, NotesChart.day,
@@ -24,4 +29,11 @@ export const entities = [
 	PerUserFollowingChart.hour, PerUserFollowingChart.day,
 	PerUserDriveChart.hour, PerUserDriveChart.day,
 	ApRequestChart.hour, ApRequestChart.day,
+
+	...(process.env.NODE_ENV === 'test' ? [
+		TestChart.hour, TestChart.day,
+		TestGroupedChart.hour, TestGroupedChart.day,
+		TestUniqueChart.hour, TestUniqueChart.day,
+		TestIntersectionChart.hour, TestIntersectionChart.day,
+	] : []),
 ];
diff --git a/packages/backend/src/services/create-notification.ts b/packages/backend/src/services/create-notification.ts
index 9a53db1f38..d53a4235b8 100644
--- a/packages/backend/src/services/create-notification.ts
+++ b/packages/backend/src/services/create-notification.ts
@@ -1,5 +1,5 @@
 import { publishMainStream } from '@/services/stream.js';
-import pushSw from './push-notification.js';
+import { pushNotification } from '@/services/push-notification.js';
 import { Notifications, Mutings, UserProfiles, Users } from '@/models/index.js';
 import { genId } from '@/misc/gen-id.js';
 import { User } from '@/models/entities/user.js';
@@ -52,8 +52,8 @@ export async function createNotification(
 		//#endregion
 
 		publishMainStream(notifieeId, 'unreadNotification', packed);
+		pushNotification(notifieeId, 'notification', packed);
 
-		pushSw(notifieeId, 'notification', packed);
 		if (type === 'follow') sendEmailNotification.follow(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! }));
 		if (type === 'receiveFollowRequest') sendEmailNotification.receiveFollowRequest(notifieeId, await Users.findOneByOrFail({ id: data.notifierId! }));
 	}, 2000);
diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts
index 549b11c9fe..cfbcb60ddf 100644
--- a/packages/backend/src/services/drive/add-file.ts
+++ b/packages/backend/src/services/drive/add-file.ts
@@ -7,7 +7,7 @@ import { deleteFile } from './delete-file.js';
 import { fetchMeta } from '@/misc/fetch-meta.js';
 import { GenerateVideoThumbnail } from './generate-video-thumbnail.js';
 import { driveLogger } from './logger.js';
-import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor.js';
+import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng } from './image-processor.js';
 import { contentDisposition } from '@/misc/content-disposition.js';
 import { getFileInfo } from '@/misc/get-file-info.js';
 import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '@/models/index.js';
@@ -179,6 +179,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 	}
 
 	let img: sharp.Sharp | null = null;
+	let satisfyWebpublic: boolean;
 
 	try {
 		img = sharp(path);
@@ -192,6 +193,13 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 				thumbnail: null,
 			};
 		}
+
+		satisfyWebpublic = !!(
+			type !== 'image/svg+xml' && type !== 'image/webp' &&
+			!(metadata.exif || metadata.iptc || metadata.xmp || metadata.tifftagPhotoshop) &&
+			metadata.width && metadata.width <= 2048 &&
+			metadata.height && metadata.height <= 2048
+		);
 	} catch (err) {
 		logger.warn(`sharp failed: ${err}`);
 		return {
@@ -203,15 +211,15 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 	// #region webpublic
 	let webpublic: IImage | null = null;
 
-	if (generateWeb) {
+	if (generateWeb && !satisfyWebpublic) {
 		logger.info(`creating web image`);
 
 		try {
-			if (['image/jpeg'].includes(type)) {
+			if (['image/jpeg', 'image/webp'].includes(type)) {
 				webpublic = await convertSharpToJpeg(img, 2048, 2048);
-			} else if (['image/webp'].includes(type)) {
-				webpublic = await convertSharpToWebp(img, 2048, 2048);
-			} else if (['image/png', 'image/svg+xml'].includes(type)) {
+			} else if (['image/png'].includes(type)) {
+				webpublic = await convertSharpToPng(img, 2048, 2048);
+			} else if (['image/svg+xml'].includes(type)) {
 				webpublic = await convertSharpToPng(img, 2048, 2048);
 			} else {
 				logger.debug(`web image not created (not an required image)`);
@@ -220,7 +228,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 			logger.warn(`web image not created (an error occured)`, err as Error);
 		}
 	} else {
-		logger.info(`web image not created (from remote)`);
+		if (satisfyWebpublic) logger.info(`web image not created (original satisfies webpublic)`);
+		else logger.info(`web image not created (from remote)`);
 	}
 	// #endregion webpublic
 
@@ -228,10 +237,8 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
 	let thumbnail: IImage | null = null;
 
 	try {
-		if (['image/jpeg', 'image/webp'].includes(type)) {
-			thumbnail = await convertSharpToJpeg(img, 498, 280);
-		} else if (['image/png', 'image/svg+xml'].includes(type)) {
-			thumbnail = await convertSharpToPngOrJpeg(img, 498, 280);
+		if (['image/jpeg', 'image/webp', 'image/png', 'image/svg+xml'].includes(type)) {
+			thumbnail = await convertSharpToWebp(img, 498, 280);
 		} else {
 			logger.debug(`thumbnail not created (not an required file)`);
 		}
diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts
index 04a7a83346..ca12ab8d3d 100644
--- a/packages/backend/src/services/drive/generate-video-thumbnail.ts
+++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts
@@ -1,37 +1,31 @@
 import * as fs from 'node:fs';
-import * as tmp from 'tmp';
+import * as path from 'node:path';
+import { createTemp } from '@/misc/create-temp.js';
 import { IImage, convertToJpeg } from './image-processor.js';
-import * as FFmpeg from 'fluent-ffmpeg';
+import FFmpeg from 'fluent-ffmpeg';
 
-export async function GenerateVideoThumbnail(path: string): Promise<IImage> {
-	const [outDir, cleanup] = await new Promise<[string, any]>((res, rej) => {
-		tmp.dir((e, path, cleanup) => {
-			if (e) return rej(e);
-			res([path, cleanup]);
+export async function GenerateVideoThumbnail(source: string): Promise<IImage> {
+	const [file, cleanup] = await createTemp();
+	const parsed = path.parse(file);
+
+	try {
+		await new Promise((res, rej) => {
+			FFmpeg({
+				source,
+			})
+			.on('end', res)
+			.on('error', rej)
+			.screenshot({
+				folder: parsed.dir,
+				filename: parsed.base,
+				count: 1,
+				timestamps: ['5%'],
+			});
 		});
-	});
 
-	await new Promise((res, rej) => {
-		FFmpeg({
-			source: path,
-		})
-		.on('end', res)
-		.on('error', rej)
-		.screenshot({
-			folder: outDir,
-			filename: 'output.png',
-			count: 1,
-			timestamps: ['5%'],
-		});
-	});
-
-	const outPath = `${outDir}/output.png`;
-
-	const thumbnail = await convertToJpeg(outPath, 498, 280);
-
-	// cleanup
-	await fs.promises.unlink(outPath);
-	cleanup();
-
-	return thumbnail;
+		// JPEGใซๅค‰ๆ› (Webpใงใ‚‚ใ„ใ„ใŒใ€MastodonใฏWebpใ‚’ใ‚ตใƒใƒผใƒˆใ›ใš่กจ็คบใงใใชใใชใ‚‹)
+		return await convertToJpeg(498, 280);
+	} finally {
+		cleanup();
+	}
 }
diff --git a/packages/backend/src/services/drive/image-processor.ts b/packages/backend/src/services/drive/image-processor.ts
index 146dcfb6ca..2c564ea595 100644
--- a/packages/backend/src/services/drive/image-processor.ts
+++ b/packages/backend/src/services/drive/image-processor.ts
@@ -38,11 +38,11 @@ export async function convertSharpToJpeg(sharp: sharp.Sharp, width: number, heig
  * Convert to WebP
  *   with resize, remove metadata, resolve orientation, stop animation
  */
-export async function convertToWebp(path: string, width: number, height: number): Promise<IImage> {
-	return convertSharpToWebp(await sharp(path), width, height);
+export async function convertToWebp(path: string, width: number, height: number, quality: number = 85): Promise<IImage> {
+	return convertSharpToWebp(await sharp(path), width, height, quality);
 }
 
-export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> {
+export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, height: number, quality: number = 85): Promise<IImage> {
 	const data = await sharp
 		.resize(width, height, {
 			fit: 'inside',
@@ -50,7 +50,7 @@ export async function convertSharpToWebp(sharp: sharp.Sharp, width: number, heig
 		})
 		.rotate()
 		.webp({
-			quality: 85,
+			quality,
 		})
 		.toBuffer();
 
@@ -85,23 +85,3 @@ export async function convertSharpToPng(sharp: sharp.Sharp, width: number, heigh
 		type: 'image/png',
 	};
 }
-
-/**
- * Convert to PNG or JPEG
- *   with resize, remove metadata, resolve orientation, stop animation
- */
-export async function convertToPngOrJpeg(path: string, width: number, height: number): Promise<IImage> {
-	return convertSharpToPngOrJpeg(await sharp(path), width, height);
-}
-
-export async function convertSharpToPngOrJpeg(sharp: sharp.Sharp, width: number, height: number): Promise<IImage> {
-	const stats = await sharp.stats();
-	const metadata = await sharp.metadata();
-
-	// ไธ้€ๆ˜Žใง300x300pxใฎ็ฏ„ๅ›ฒใ‚’่ถ…ใˆใฆใ„ใ‚ŒใฐJPEG
-	if (stats.isOpaque && ((metadata.width && metadata.width >= 300) || (metadata.height && metadata!.height >= 300))) {
-		return await convertSharpToJpeg(sharp, width, height);
-	} else {
-		return await convertSharpToPng(sharp, width, height);
-	}
-}
diff --git a/packages/backend/src/services/drive/upload-from-url.ts b/packages/backend/src/services/drive/upload-from-url.ts
index 79b1b8c2e1..001fc49ee4 100644
--- a/packages/backend/src/services/drive/upload-from-url.ts
+++ b/packages/backend/src/services/drive/upload-from-url.ts
@@ -45,29 +45,20 @@ export async function uploadFromUrl({
 	// Create temp file
 	const [path, cleanup] = await createTemp();
 
-	// write content at URL to temp file
-	await downloadUrl(url, path);
-
-	let driveFile: DriveFile;
-	let error;
-
 	try {
-		driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive });
+		// write content at URL to temp file
+		await downloadUrl(url, path);
+
+		const driveFile = await addFile({ user, path, name, comment, folderId, force, isLink, url, uri, sensitive });
 		logger.succ(`Got: ${driveFile.id}`);
+		return driveFile!;
 	} catch (e) {
-		error = e;
 		logger.error(`Failed to create drive file: ${e}`, {
 			url: url,
 			e: e,
 		});
-	}
-
-	// clean-up
-	cleanup();
-
-	if (error) {
-		throw error;
-	} else {
-		return driveFile!;
+		throw e;
+	} finally {
+		cleanup();
 	}
 }
diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts
index d5294c5fe8..029c388dc2 100644
--- a/packages/backend/src/services/fetch-instance-metadata.ts
+++ b/packages/backend/src/services/fetch-instance-metadata.ts
@@ -1,5 +1,6 @@
 import { DOMWindow, JSDOM } from 'jsdom';
 import fetch from 'node-fetch';
+import tinycolor from 'tinycolor2';
 import { getJson, getHtml, getAgentByUrl } from '@/misc/fetch.js';
 import { Instance } from '@/models/entities/instance.js';
 import { Instances } from '@/models/index.js';
@@ -208,16 +209,11 @@ async function fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | nul
 }
 
 async function getThemeColor(doc: DOMWindow['document'] | null, manifest: Record<string, any> | null): Promise<string | null> {
-	if (doc) {
-		const themeColor = doc.querySelector('meta[name="theme-color"]')?.getAttribute('content');
+	const themeColor = doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') || manifest?.theme_color;
 
-		if (themeColor) {
-			return themeColor;
-		}
-	}
-
-	if (manifest) {
-		return manifest.theme_color;
+	if (themeColor) {
+		const color = new tinycolor(themeColor);
+		if (color.isValid()) return color.toHexString();
 	}
 
 	return null;
diff --git a/packages/backend/src/services/following/create.ts b/packages/backend/src/services/following/create.ts
index 7491c44f83..72c24676bb 100644
--- a/packages/backend/src/services/following/create.ts
+++ b/packages/backend/src/services/following/create.ts
@@ -67,8 +67,10 @@ export async function insertFollowingDoc(followee: { id: User['id']; host: User[
 	if (alreadyFollowed) return;
 
 	//#region Increment counts
-	Users.increment({ id: follower.id }, 'followingCount', 1);
-	Users.increment({ id: followee.id }, 'followersCount', 1);
+	await Promise.all([
+		Users.increment({ id: follower.id }, 'followingCount', 1),
+		Users.increment({ id: followee.id }, 'followersCount', 1),
+	]);
 	//#endregion
 
 	//#region Update instance stats
diff --git a/packages/backend/src/services/following/delete.ts b/packages/backend/src/services/following/delete.ts
index 241f9606e5..91b5a3d61d 100644
--- a/packages/backend/src/services/following/delete.ts
+++ b/packages/backend/src/services/following/delete.ts
@@ -58,12 +58,11 @@ export default async function(follower: { id: User['id']; host: User['host']; ur
 }
 
 export async function decrementFollowing(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }) {
-	//#region Decrement following count
-	Users.decrement({ id: follower.id }, 'followingCount', 1);
-	//#endregion
-
-	//#region Decrement followers count
-	Users.decrement({ id: followee.id }, 'followersCount', 1);
+	//#region Decrement following / followers counts
+	await Promise.all([
+		Users.decrement({ id: follower.id }, 'followingCount', 1),
+		Users.decrement({ id: followee.id }, 'followersCount', 1),
+	]);
 	//#endregion
 
 	//#region Update instance stats
diff --git a/packages/backend/src/services/messages/create.ts b/packages/backend/src/services/messages/create.ts
index e5cd5a30d2..e6b3204922 100644
--- a/packages/backend/src/services/messages/create.ts
+++ b/packages/backend/src/services/messages/create.ts
@@ -5,7 +5,7 @@ import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '@/models/i
 import { genId } from '@/misc/gen-id.js';
 import { MessagingMessage } from '@/models/entities/messaging-message.js';
 import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '@/services/stream.js';
-import pushNotification from '../push-notification.js';
+import { pushNotification } from '@/services/push-notification.js';
 import { Not } from 'typeorm';
 import { Note } from '@/models/entities/note.js';
 import renderNote from '@/remote/activitypub/renderer/note.js';
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index f14bc2059b..e2bf9d5b59 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -187,6 +187,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
 
 	if (data.text) {
 		data.text = data.text.trim();
+	} else {
+		data.text = null;
 	}
 
 	let tags = data.apHashtags;
@@ -310,7 +312,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
 		endedPollNotificationQueue.add({
 			noteId: note.id,
 		}, {
-			delay
+			delay,
+			removeOnComplete: true,
 		});
 	}
 
diff --git a/packages/backend/src/services/note/delete.ts b/packages/backend/src/services/note/delete.ts
index ffd609dd84..4963200161 100644
--- a/packages/backend/src/services/note/delete.ts
+++ b/packages/backend/src/services/note/delete.ts
@@ -1,3 +1,4 @@
+import { Brackets, In } from 'typeorm';
 import { publishNoteStream } from '@/services/stream.js';
 import renderDelete from '@/remote/activitypub/renderer/delete.js';
 import renderAnnounce from '@/remote/activitypub/renderer/announce.js';
@@ -5,15 +6,14 @@ import renderUndo from '@/remote/activitypub/renderer/undo.js';
 import { renderActivity } from '@/remote/activitypub/renderer/index.js';
 import renderTombstone from '@/remote/activitypub/renderer/tombstone.js';
 import config from '@/config/index.js';
-import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
 import { User, ILocalUser, IRemoteUser } from '@/models/entities/user.js';
 import { Note, IMentionedRemoteUsers } from '@/models/entities/note.js';
 import { Notes, Users, Instances } from '@/models/index.js';
 import { notesChart, perUserNotesChart, instanceChart } from '@/services/chart/index.js';
 import { deliverToFollowers, deliverToUser } from '@/remote/activitypub/deliver-manager.js';
 import { countSameRenotes } from '@/misc/count-same-renotes.js';
+import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc.js';
 import { deliverToRelays } from '../relay.js';
-import { Brackets, In } from 'typeorm';
 
 /**
  * ๆŠ•็จฟใ‚’ๅ‰Š้™คใ—ใพใ™ใ€‚
@@ -40,7 +40,7 @@ export default async function(user: { id: User['id']; uri: User['uri']; host: Us
 
 		//#region ใƒญใƒผใ‚ซใƒซใฎๆŠ•็จฟใชใ‚‰ๅ‰Š้™คใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ“ใƒ†ใ‚ฃใ‚’้…้€
 		if (Users.isLocalUser(user) && !note.localOnly) {
-			let renote: Note | null;
+			let renote: Note | null = null;
 
 			// if deletd note is renote
 			if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
@@ -113,7 +113,7 @@ async function getMentionedRemoteUsers(note: Note) {
 	const uris = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
 	if (uris.length > 0) {
 		where.push(
-			{ uri: In(uris) }
+			{ uri: In(uris) },
 		);
 	}
 
diff --git a/packages/backend/src/services/note/reaction/create.ts b/packages/backend/src/services/note/reaction/create.ts
index 5a0948bca9..83d302826a 100644
--- a/packages/backend/src/services/note/reaction/create.ts
+++ b/packages/backend/src/services/note/reaction/create.ts
@@ -27,6 +27,11 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note,
 		}
 	}
 
+	// check visibility
+	if (!await Notes.isVisibleForMe(note, user.id)) {
+		throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
+	}
+
 	// TODO: cache
 	reaction = await toDbReaction(reaction, user.host);
 
diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts
index 41122c92e8..5c3bafbb34 100644
--- a/packages/backend/src/services/push-notification.ts
+++ b/packages/backend/src/services/push-notification.ts
@@ -5,8 +5,15 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
 import { Packed } from '@/misc/schema.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
 
-type notificationType = 'notification' | 'unreadMessagingMessage';
-type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>;
+// Defined also packages/sw/types.ts#L14-L21
+type pushNotificationsTypes = {
+	'notification': Packed<'Notification'>;
+	'unreadMessagingMessage': Packed<'MessagingMessage'>;
+	'readNotifications': { notificationIds: string[] };
+	'readAllNotifications': undefined;
+	'readAllMessagingMessages': undefined;
+	'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string };
+};
 
 // ใƒ—ใƒƒใ‚ทใƒฅใƒกใƒƒใ‚ปใƒผใ‚ธใ‚ตใƒผใƒใƒผใซใฏๆ–‡ๅญ—ๆ•ฐๅˆถ้™ใŒใ‚ใ‚‹ใŸใ‚ใ€ๅ†…ๅฎนใ‚’ๅ‰Šๆธ›ใ—ใพใ™
 function truncateNotification(notification: Packed<'Notification'>): any {
@@ -17,12 +24,11 @@ function truncateNotification(notification: Packed<'Notification'>): any {
 				...notification.note,
 				// textใ‚’getNoteSummaryใ—ใŸใ‚‚ใฎใซ็ฝฎใๆ›ใˆใ‚‹
 				text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note),
-				...{
-					cw: undefined,
-					reply: undefined,
-					renote: undefined,
-					user: undefined as any, // ้€š็Ÿฅใ‚’ๅ—ใ‘ๅ–ใฃใŸใƒฆใƒผใ‚ถใƒผใงใ‚ใ‚‹ๅ ดๅˆใŒๅคšใ„ใฎใงใ“ใ‚Œใ‚‚ๆจใฆใ‚‹
-				}
+
+				cw: undefined,
+				reply: undefined,
+				renote: undefined,
+				user: undefined as any, // ้€š็Ÿฅใ‚’ๅ—ใ‘ๅ–ใฃใŸใƒฆใƒผใ‚ถใƒผใงใ‚ใ‚‹ๅ ดๅˆใŒๅคšใ„ใฎใงใ“ใ‚Œใ‚‚ๆจใฆใ‚‹
 			}
 		};
 	}
@@ -30,7 +36,7 @@ function truncateNotification(notification: Packed<'Notification'>): any {
 	return notification;
 }
 
-export default async function(userId: string, type: notificationType, body: notificationBody) {
+export async function pushNotification<T extends keyof pushNotificationsTypes>(userId: string, type: T, body: pushNotificationsTypes[T]) {
 	const meta = await fetchMeta();
 
 	if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
diff --git a/packages/backend/src/services/relay.ts b/packages/backend/src/services/relay.ts
index 1ab45588da..6bc4304436 100644
--- a/packages/backend/src/services/relay.ts
+++ b/packages/backend/src/services/relay.ts
@@ -1,4 +1,4 @@
-import { createSystemUser } from './create-system-user.js';
+import { IsNull } from 'typeorm';
 import { renderFollowRelay } from '@/remote/activitypub/renderer/follow-relay.js';
 import { renderActivity, attachLdSignature } from '@/remote/activitypub/renderer/index.js';
 import renderUndo from '@/remote/activitypub/renderer/undo.js';
@@ -8,7 +8,7 @@ import { Users, Relays } from '@/models/index.js';
 import { genId } from '@/misc/gen-id.js';
 import { Cache } from '@/misc/cache.js';
 import { Relay } from '@/models/entities/relay.js';
-import { IsNull } from 'typeorm';
+import { createSystemUser } from './create-system-user.js';
 
 const ACTOR_USERNAME = 'relay.actor' as const;
 
@@ -88,6 +88,8 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
 	}));
 	if (relays.length === 0) return;
 
+	// TODO
+	//const copy = structuredClone(activity);
 	const copy = JSON.parse(JSON.stringify(activity));
 	if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
 
diff --git a/packages/backend/test/.eslintrc b/packages/backend/test/.eslintrc
deleted file mode 100644
index cea1b11388..0000000000
--- a/packages/backend/test/.eslintrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-	"env": {
-		"node": true,
-		"mocha": true,
-		"commonjs": true
-	}
-}
diff --git a/packages/backend/test/.eslintrc.cjs b/packages/backend/test/.eslintrc.cjs
new file mode 100644
index 0000000000..d83dc37d2f
--- /dev/null
+++ b/packages/backend/test/.eslintrc.cjs
@@ -0,0 +1,11 @@
+module.exports = {
+	parserOptions: {
+		tsconfigRootDir: __dirname,
+		project: ['./tsconfig.json'],
+	},
+	extends: ['../.eslintrc.cjs'],
+	env: {
+		node: true,
+		mocha: true,
+	},
+};
diff --git a/packages/backend/test/activitypub.ts b/packages/backend/test/activitypub.ts
index 70f35cafd8..f4ae27e5ec 100644
--- a/packages/backend/test/activitypub.ts
+++ b/packages/backend/test/activitypub.ts
@@ -1,12 +1,14 @@
 process.env.NODE_ENV = 'test';
 
-import rndstr from 'rndstr';
 import * as assert from 'assert';
+import rndstr from 'rndstr';
+import { initDb } from '../src/db/postgre.js';
 import { initTestDb } from './utils.js';
 
 describe('ActivityPub', () => {
 	before(async () => {
-		await initTestDb();
+		//await initTestDb();
+		await initDb();
 	});
 
 	describe('Parse minimum object', () => {
@@ -57,8 +59,8 @@ describe('ActivityPub', () => {
 			const note = await createNote(post.id, resolver, true);
 
 			assert.deepStrictEqual(note?.uri, post.id);
-			assert.deepStrictEqual(note?.visibility, 'public');
-			assert.deepStrictEqual(note?.text, post.content);
+			assert.deepStrictEqual(note.visibility, 'public');
+			assert.deepStrictEqual(note.text, post.content);
 		});
 	});
 
diff --git a/packages/backend/test/ap-request.ts b/packages/backend/test/ap-request.ts
index 48f4fceb51..da95c421f3 100644
--- a/packages/backend/test/ap-request.ts
+++ b/packages/backend/test/ap-request.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert';
+import httpSignature from 'http-signature';
 import { genRsaKeyPair } from '../src/misc/gen-key-pair.js';
 import { createSignedPost, createSignedGet } from '../src/remote/activitypub/ap-request.js';
-import httpSignature from 'http-signature';
 
 export const buildParsedSignature = (signingString: string, signature: string, algorithm: string) => {
 	return {
@@ -13,7 +13,7 @@ export const buildParsedSignature = (signingString: string, signature: string, a
 			signature: signature,
 		},
 		signingString: signingString,
-		algorithm: algorithm?.toUpperCase(),
+		algorithm: algorithm.toUpperCase(),
 		keyId: 'KeyID',	// dummy, not used for verify
 	};
 };
@@ -26,7 +26,7 @@ describe('ap-request', () => {
 		const activity = { a: 1 };
 		const body = JSON.stringify(activity);
 		const headers = {
-			'User-Agent': 'UA'
+			'User-Agent': 'UA',
 		};
 
 		const req = createSignedPost({ key, url, body, additionalHeaders: headers });
@@ -42,7 +42,7 @@ describe('ap-request', () => {
 		const key = { keyId: 'x', 'privateKeyPem': keypair.privateKey };
 		const url = 'https://example.com/outbox';
 		const headers = {
-			'User-Agent': 'UA'
+			'User-Agent': 'UA',
 		};
 
 		const req = createSignedGet({ key, url, additionalHeaders: headers });
diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.ts
index d946191be8..b155549f98 100644
--- a/packages/backend/test/api-visibility.ts
+++ b/packages/backend/test/api-visibility.ts
@@ -61,40 +61,40 @@ describe('API visibility', () => {
 
 		const show = async (noteId: any, by: any) => {
 			return await request('/notes/show', {
-				noteId
+				noteId,
 			}, by);
 		};
 
 		before(async () => {
 			//#region prepare
 			// signup
-			alice    = await signup({ username: 'alice' });
+			alice = await signup({ username: 'alice' });
 			follower = await signup({ username: 'follower' });
-			other    = await signup({ username: 'other' });
-			target   = await signup({ username: 'target' });
-			target2  = await signup({ username: 'target2' });
+			other = await signup({ username: 'other' });
+			target = await signup({ username: 'target' });
+			target2 = await signup({ username: 'target2' });
 
 			// follow alice <= follower
 			await request('/following/create', { userId: alice.id }, follower);
 
 			// normal posts
-			pub  = await post(alice, { text: 'x', visibility: 'public' });
+			pub = await post(alice, { text: 'x', visibility: 'public' });
 			home = await post(alice, { text: 'x', visibility: 'home' });
-			fol  = await post(alice, { text: 'x', visibility: 'followers' });
-			spe  = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] });
+			fol = await post(alice, { text: 'x', visibility: 'followers' });
+			spe = await post(alice, { text: 'x', visibility: 'specified', visibleUserIds: [target.id] });
 
 			// replies
 			tgt = await post(target, { text: 'y', visibility: 'public' });
-			pubR  = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' });
+			pubR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'public' });
 			homeR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'home' });
-			folR  = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' });
-			speR  = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' });
+			folR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'followers' });
+			speR = await post(alice, { text: 'x', replyId: tgt.id, visibility: 'specified' });
 
 			// mentions
-			pubM  = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' });
+			pubM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'public' });
 			homeM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'home' });
-			folM  = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' });
-			speM  = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' });
+			folM = await post(alice, { text: '@target x', replyId: tgt.id, visibility: 'followers' });
+			speM = await post(alice, { text: '@target2 x', replyId: tgt.id, visibility: 'specified' });
 			//#endregion
 		});
 
diff --git a/packages/backend/test/block.ts b/packages/backend/test/block.ts
index 103eec991d..b3343813cd 100644
--- a/packages/backend/test/block.ts
+++ b/packages/backend/test/block.ts
@@ -25,7 +25,7 @@ describe('Block', () => {
 
 	it('Blockไฝœๆˆ', async(async () => {
 		const res = await request('/blocking/create', {
-			userId: bob.id
+			userId: bob.id,
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
diff --git a/packages/backend/test/chart.ts b/packages/backend/test/chart.ts
index c8cea874f0..ac0844679f 100644
--- a/packages/backend/test/chart.ts
+++ b/packages/backend/test/chart.ts
@@ -2,30 +2,21 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as lolex from '@sinonjs/fake-timers';
-import { async, initTestDb } from './utils.js';
 import TestChart from '../src/services/chart/charts/test.js';
 import TestGroupedChart from '../src/services/chart/charts/test-grouped.js';
 import TestUniqueChart from '../src/services/chart/charts/test-unique.js';
 import TestIntersectionChart from '../src/services/chart/charts/test-intersection.js';
-import * as _TestChart from '../src/services/chart/charts/entities/test.js';
-import * as _TestGroupedChart from '../src/services/chart/charts/entities/test-grouped.js';
-import * as _TestUniqueChart from '../src/services/chart/charts/entities/test-unique.js';
-import * as _TestIntersectionChart from '../src/services/chart/charts/entities/test-intersection.js';
+import { initDb } from '../src/db/postgre.js';
 
 describe('Chart', () => {
 	let testChart: TestChart;
 	let testGroupedChart: TestGroupedChart;
 	let testUniqueChart: TestUniqueChart;
 	let testIntersectionChart: TestIntersectionChart;
-	let clock: lolex.Clock;
+	let clock: lolex.InstalledClock;
 
-	beforeEach(async(async () => {
-		await initTestDb(false, [
-			_TestChart.entity.hour, _TestChart.entity.day,
-			_TestGroupedChart.entity.hour, _TestGroupedChart.entity.day,
-			_TestUniqueChart.entity.hour, _TestUniqueChart.entity.day,
-			_TestIntersectionChart.entity.hour, _TestIntersectionChart.entity.day,
-		]);
+	beforeEach(async () => {
+		await initDb(true);
 
 		testChart = new TestChart();
 		testGroupedChart = new TestGroupedChart();
@@ -33,15 +24,16 @@ describe('Chart', () => {
 		testIntersectionChart = new TestIntersectionChart();
 
 		clock = lolex.install({
-			now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0))
+			now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)),
+			shouldClearNativeTimers: true,
 		});
-	}));
+	});
 
-	afterEach(async(async () => {
+	afterEach(() => {
 		clock.uninstall();
-	}));
+	});
 
-	it('Can updates', async(async () => {
+	it('Can updates', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -52,7 +44,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -60,12 +52,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can updates (dec)', async(async () => {
+	it('Can updates (dec)', async () => {
 		await testChart.decrement();
 		await testChart.save();
 
@@ -76,7 +68,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [1, 0, 0],
 				inc: [0, 0, 0],
-				total: [-1, 0, 0]
+				total: [-1, 0, 0],
 			},
 		});
 
@@ -84,12 +76,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [1, 0, 0],
 				inc: [0, 0, 0],
-				total: [-1, 0, 0]
+				total: [-1, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Empty chart', async(async () => {
+	it('Empty chart', async () => {
 		const chartHours = await testChart.getChart('hour', 3, null);
 		const chartDays = await testChart.getChart('day', 3, null);
 
@@ -97,7 +89,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [0, 0, 0],
-				total: [0, 0, 0]
+				total: [0, 0, 0],
 			},
 		});
 
@@ -105,12 +97,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [0, 0, 0],
-				total: [0, 0, 0]
+				total: [0, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can updates at multiple times at same time', async(async () => {
+	it('Can updates at multiple times at same time', async () => {
 		await testChart.increment();
 		await testChart.increment();
 		await testChart.increment();
@@ -123,7 +115,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [3, 0, 0],
-				total: [3, 0, 0]
+				total: [3, 0, 0],
 			},
 		});
 
@@ -131,12 +123,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [3, 0, 0],
-				total: [3, 0, 0]
+				total: [3, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('่ค‡ๆ•ฐๅ›žsaveใ•ใ‚Œใฆใ‚‚ใƒ‡ใƒผใ‚ฟใฎๆ›ดๆ–ฐใฏไธ€ๅบฆใ ใ‘', async(async () => {
+	it('่ค‡ๆ•ฐๅ›žsaveใ•ใ‚Œใฆใ‚‚ใƒ‡ใƒผใ‚ฟใฎๆ›ดๆ–ฐใฏไธ€ๅบฆใ ใ‘', async () => {
 		await testChart.increment();
 		await testChart.save();
 		await testChart.save();
@@ -149,7 +141,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -157,12 +149,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can updates at different times', async(async () => {
+	it('Can updates at different times', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -178,7 +170,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 1, 0],
-				total: [2, 1, 0]
+				total: [2, 1, 0],
 			},
 		});
 
@@ -186,14 +178,14 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
 	// ไป•ๆง˜ไธŠใฏใ“ใ†ใชใฃใฆใปใ—ใ„ใ‘ใฉใ€ๅฎŸ่ฃ…ใฏ้›ฃใ—ใใ†ใชใฎใงskip
 	/*
-	it('Can updates at different times without save', async(async () => {
+	it('Can updates at different times without save', async () => {
 		await testChart.increment();
 
 		clock.tick('01:00:00');
@@ -219,10 +211,10 @@ describe('Chart', () => {
 				total: [2, 0, 0]
 			},
 		});
-	}));
+	});
 	*/
 
-	it('Can padding', async(async () => {
+	it('Can padding', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -238,7 +230,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 1],
-				total: [2, 1, 1]
+				total: [2, 1, 1],
 			},
 		});
 
@@ -246,13 +238,13 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
 	// ่ฆๆฑ‚ใ•ใ‚ŒใŸ็ฏ„ๅ›ฒใซใƒญใ‚ฐใŒใฒใจใคใ‚‚ใชใ„ๅ ดๅˆใงใ‚‚ใƒ‘ใƒ‡ใ‚ฃใƒณใ‚ฐใงใใ‚‹
-	it('Can padding from past range', async(async () => {
+	it('Can padding from past range', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -265,7 +257,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [0, 0, 0],
-				total: [1, 1, 1]
+				total: [1, 1, 1],
 			},
 		});
 
@@ -273,14 +265,14 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
-	}));
+	});
 
 	// ่ฆๆฑ‚ใ•ใ‚ŒใŸ็ฏ„ๅ›ฒใฎๆœ€ใ‚‚ๅคใ„็ฎ‡ๆ‰€ใซไฝ็ฝฎใ™ใ‚‹ใƒญใ‚ฐใŒๅญ˜ๅœจใ—ใชใ„ๅ ดๅˆใงใ‚‚ใƒ‘ใƒ‡ใ‚ฃใƒณใ‚ฐใงใใ‚‹
 	// Issue #3190
-	it('Can padding from past range 2', async(async () => {
+	it('Can padding from past range 2', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -296,7 +288,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [2, 1, 1]
+				total: [2, 1, 1],
 			},
 		});
 
@@ -304,12 +296,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can specify offset', async(async () => {
+	it('Can specify offset', async () => {
 		await testChart.increment();
 		await testChart.save();
 
@@ -325,7 +317,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -333,12 +325,12 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
-	it('Can specify offset (floor time)', async(async () => {
+	it('Can specify offset (floor time)', async () => {
 		clock.tick('00:30:00');
 
 		await testChart.increment();
@@ -356,7 +348,7 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [1, 0, 0],
-				total: [1, 0, 0]
+				total: [1, 0, 0],
 			},
 		});
 
@@ -364,13 +356,13 @@ describe('Chart', () => {
 			foo: {
 				dec: [0, 0, 0],
 				inc: [2, 0, 0],
-				total: [2, 0, 0]
+				total: [2, 0, 0],
 			},
 		});
-	}));
+	});
 
 	describe('Grouped', () => {
-		it('Can updates', async(async () => {
+		it('Can updates', async () => {
 			await testGroupedChart.increment('alice');
 			await testGroupedChart.save();
 
@@ -383,7 +375,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [1, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 
@@ -391,7 +383,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [1, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 
@@ -399,7 +391,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [0, 0, 0]
+					total: [0, 0, 0],
 				},
 			});
 
@@ -407,14 +399,14 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [0, 0, 0]
+					total: [0, 0, 0],
 				},
 			});
-		}));
+		});
 	});
 
 	describe('Unique increment', () => {
-		it('Can updates', async(async () => {
+		it('Can updates', async () => {
 			await testUniqueChart.uniqueIncrement('alice');
 			await testUniqueChart.uniqueIncrement('alice');
 			await testUniqueChart.uniqueIncrement('bob');
@@ -430,10 +422,10 @@ describe('Chart', () => {
 			assert.deepStrictEqual(chartDays, {
 				foo: [2, 0, 0],
 			});
-		}));
+		});
 
 		describe('Intersection', () => {
-			it('ๆกไปถใŒๆบ€ใŸใ•ใ‚Œใฆใ„ใชใ„ๅ ดๅˆใฏใ‚ซใ‚ฆใƒณใƒˆใ•ใ‚Œใชใ„', async(async () => {
+			it('ๆกไปถใŒๆบ€ใŸใ•ใ‚Œใฆใ„ใชใ„ๅ ดๅˆใฏใ‚ซใ‚ฆใƒณใƒˆใ•ใ‚Œใชใ„', async () => {
 				await testIntersectionChart.addA('alice');
 				await testIntersectionChart.addA('bob');
 				await testIntersectionChart.addB('carol');
@@ -453,9 +445,9 @@ describe('Chart', () => {
 					b: [1, 0, 0],
 					aAndB: [0, 0, 0],
 				});
-			}));
+			});
 
-			it('ๆกไปถใŒๆบ€ใŸใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆใซใ‚ซใ‚ฆใƒณใƒˆใ•ใ‚Œใ‚‹', async(async () => {
+			it('ๆกไปถใŒๆบ€ใŸใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆใซใ‚ซใ‚ฆใƒณใƒˆใ•ใ‚Œใ‚‹', async () => {
 				await testIntersectionChart.addA('alice');
 				await testIntersectionChart.addA('bob');
 				await testIntersectionChart.addB('carol');
@@ -476,12 +468,12 @@ describe('Chart', () => {
 					b: [2, 0, 0],
 					aAndB: [1, 0, 0],
 				});
-			}));
+			});
 		});
 	});
 
 	describe('Resync', () => {
-		it('Can resync', async(async () => {
+		it('Can resync', async () => {
 			testChart.total = 1;
 
 			await testChart.resync();
@@ -493,7 +485,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
 
@@ -501,12 +493,12 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 0, 0],
-					total: [1, 0, 0]
+					total: [1, 0, 0],
 				},
 			});
-		}));
+		});
 
-		it('Can resync (2)', async(async () => {
+		it('Can resync (2)', async () => {
 			await testChart.increment();
 			await testChart.save();
 
@@ -523,7 +515,7 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [0, 1, 0],
-					total: [100, 1, 0]
+					total: [100, 1, 0],
 				},
 			});
 
@@ -531,9 +523,9 @@ describe('Chart', () => {
 				foo: {
 					dec: [0, 0, 0],
 					inc: [1, 0, 0],
-					total: [100, 0, 0]
+					total: [100, 0, 0],
 				},
 			});
-		}));
+		});
 	});
 });
diff --git a/packages/backend/test/extract-mentions.ts b/packages/backend/test/extract-mentions.ts
index 9bfbc4192a..85afb098d8 100644
--- a/packages/backend/test/extract-mentions.ts
+++ b/packages/backend/test/extract-mentions.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert';
 
-import { extractMentions } from '../src/misc/extract-mentions.js';
 import { parse } from 'mfm-js';
+import { extractMentions } from '../src/misc/extract-mentions.js';
 
 describe('Extract mentions', () => {
 	it('simple', () => {
@@ -10,15 +10,15 @@ describe('Extract mentions', () => {
 		assert.deepStrictEqual(mentions, [{
 			username: 'foo',
 			acct: '@foo',
-			host: null
+			host: null,
 		}, {
 			username: 'bar',
 			acct: '@bar',
-			host: null
+			host: null,
 		}, {
 			username: 'baz',
 			acct: '@baz',
-			host: null
+			host: null,
 		}]);
 	});
 
@@ -28,15 +28,15 @@ describe('Extract mentions', () => {
 		assert.deepStrictEqual(mentions, [{
 			username: 'foo',
 			acct: '@foo',
-			host: null
+			host: null,
 		}, {
 			username: 'bar',
 			acct: '@bar',
-			host: null
+			host: null,
 		}, {
 			username: 'baz',
 			acct: '@baz',
-			host: null
+			host: null,
 		}]);
 	});
 });
diff --git a/packages/backend/test/fetch-resource.ts b/packages/backend/test/fetch-resource.ts
index 4cb4b42562..ddb0e94b86 100644
--- a/packages/backend/test/fetch-resource.ts
+++ b/packages/backend/test/fetch-resource.ts
@@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as childProcess from 'child_process';
-import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js';
 import * as openapi from '@redocly/openapi-core';
+import { async, startServer, signup, post, request, simpleGet, port, shutdownServer } from './utils.js';
 
 // Request Accept
 const ONLY_AP = 'application/activity+json';
@@ -26,7 +26,7 @@ describe('Fetch resource', () => {
 		p = await startServer();
 		alice = await signup({ username: 'alice' });
 		alicesPost = await post(alice, {
-			text: 'test'
+			text: 'test',
 		});
 	});
 
@@ -70,7 +70,7 @@ describe('Fetch resource', () => {
 			const config = await openapi.loadConfig();
 			const result = await openapi.bundle({
 				config,
-				ref: `http://localhost:${port}/api.json`
+				ref: `http://localhost:${port}/api.json`,
 			});
 
 			for (const problem of result.problems) {
diff --git a/packages/backend/test/get-file-info.ts b/packages/backend/test/get-file-info.ts
index 20061b8708..7ce98db50f 100644
--- a/packages/backend/test/get-file-info.ts
+++ b/packages/backend/test/get-file-info.ts
@@ -1,10 +1,15 @@
 import * as assert from 'assert';
-import { async } from './utils.js';
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
 import { getFileInfo } from '../src/misc/get-file-info.js';
+import { async } from './utils.js';
+
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
 
 describe('Get file info', () => {
 	it('Empty file', async (async () => {
-		const path = `${__dirname}/resources/emptyfile`;
+		const path = `${_dirname}/resources/emptyfile`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -13,7 +18,7 @@ describe('Get file info', () => {
 			md5: 'd41d8cd98f00b204e9800998ecf8427e',
 			type: {
 				mime: 'application/octet-stream',
-				ext: null
+				ext: null,
 			},
 			width: undefined,
 			height: undefined,
@@ -22,7 +27,7 @@ describe('Get file info', () => {
 	}));
 
 	it('Generic JPEG', async (async () => {
-		const path = `${__dirname}/resources/Lenna.jpg`;
+		const path = `${_dirname}/resources/Lenna.jpg`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -31,7 +36,7 @@ describe('Get file info', () => {
 			md5: '091b3f259662aa31e2ffef4519951168',
 			type: {
 				mime: 'image/jpeg',
-				ext: 'jpg'
+				ext: 'jpg',
 			},
 			width: 512,
 			height: 512,
@@ -40,7 +45,7 @@ describe('Get file info', () => {
 	}));
 
 	it('Generic APNG', async (async () => {
-		const path = `${__dirname}/resources/anime.png`;
+		const path = `${_dirname}/resources/anime.png`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -49,7 +54,7 @@ describe('Get file info', () => {
 			md5: '08189c607bea3b952704676bb3c979e0',
 			type: {
 				mime: 'image/apng',
-				ext: 'apng'
+				ext: 'apng',
 			},
 			width: 256,
 			height: 256,
@@ -58,7 +63,7 @@ describe('Get file info', () => {
 	}));
 
 	it('Generic AGIF', async (async () => {
-		const path = `${__dirname}/resources/anime.gif`;
+		const path = `${_dirname}/resources/anime.gif`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -67,7 +72,7 @@ describe('Get file info', () => {
 			md5: '32c47a11555675d9267aee1a86571e7e',
 			type: {
 				mime: 'image/gif',
-				ext: 'gif'
+				ext: 'gif',
 			},
 			width: 256,
 			height: 256,
@@ -76,7 +81,7 @@ describe('Get file info', () => {
 	}));
 
 	it('PNG with alpha', async (async () => {
-		const path = `${__dirname}/resources/with-alpha.png`;
+		const path = `${_dirname}/resources/with-alpha.png`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -85,7 +90,7 @@ describe('Get file info', () => {
 			md5: 'f73535c3e1e27508885b69b10cf6e991',
 			type: {
 				mime: 'image/png',
-				ext: 'png'
+				ext: 'png',
 			},
 			width: 256,
 			height: 256,
@@ -94,7 +99,7 @@ describe('Get file info', () => {
 	}));
 
 	it('Generic SVG', async (async () => {
-		const path = `${__dirname}/resources/image.svg`;
+		const path = `${_dirname}/resources/image.svg`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -103,7 +108,7 @@ describe('Get file info', () => {
 			md5: 'b6f52b4b021e7b92cdd04509c7267965',
 			type: {
 				mime: 'image/svg+xml',
-				ext: 'svg'
+				ext: 'svg',
 			},
 			width: 256,
 			height: 256,
@@ -113,7 +118,7 @@ describe('Get file info', () => {
 
 	it('SVG with XML definition', async (async () => {
 		// https://github.com/misskey-dev/misskey/issues/4413
-		const path = `${__dirname}/resources/with-xml-def.svg`;
+		const path = `${_dirname}/resources/with-xml-def.svg`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -122,7 +127,7 @@ describe('Get file info', () => {
 			md5: '4b7a346cde9ccbeb267e812567e33397',
 			type: {
 				mime: 'image/svg+xml',
-				ext: 'svg'
+				ext: 'svg',
 			},
 			width: 256,
 			height: 256,
@@ -131,7 +136,7 @@ describe('Get file info', () => {
 	}));
 
 	it('Dimension limit', async (async () => {
-		const path = `${__dirname}/resources/25000x25000.png`;
+		const path = `${_dirname}/resources/25000x25000.png`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -140,7 +145,7 @@ describe('Get file info', () => {
 			md5: '268c5dde99e17cf8fe09f1ab3f97df56',
 			type: {
 				mime: 'application/octet-stream',	// do not treat as image
-				ext: null
+				ext: null,
 			},
 			width: 25000,
 			height: 25000,
@@ -149,7 +154,7 @@ describe('Get file info', () => {
 	}));
 
 	it('Rotate JPEG', async (async () => {
-		const path = `${__dirname}/resources/rotate.jpg`;
+		const path = `${_dirname}/resources/rotate.jpg`;
 		const info = await getFileInfo(path) as any;
 		delete info.warnings;
 		delete info.blurhash;
@@ -158,7 +163,7 @@ describe('Get file info', () => {
 			md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
 			type: {
 				mime: 'image/jpeg',
-				ext: 'jpg'
+				ext: 'jpg',
 			},
 			width: 512,
 			height: 256,
diff --git a/packages/backend/test/loader.js b/packages/backend/test/loader.js
index 016f32f1a8..6b21587e32 100644
--- a/packages/backend/test/loader.js
+++ b/packages/backend/test/loader.js
@@ -1,37 +1,34 @@
-import path from 'path'
-import typescript from 'typescript'
-import { createMatchPath } from 'tsconfig-paths'
-import { resolve as BaseResolve, getFormat, transformSource } from 'ts-node/esm'
+/**
+ * ts-node/esmใƒญใƒผใƒ€ใƒผใซๆŠ•ใ’ใ‚‹ๅ‰ใซpath mappingใ‚’่งฃๆฑบใ™ใ‚‹
+ * ๅ‚่€ƒ
+ * - https://github.com/TypeStrong/ts-node/discussions/1450#discussioncomment-1806115
+ * - https://nodejs.org/api/esm.html#loaders
+ * โ€ป https://github.com/TypeStrong/ts-node/pull/1585 ใŒๅ–ใ‚Š่พผใพใ‚ŒใŸใ‚‰ใ“ใฎใ‚ซใ‚นใ‚ฟใƒ ใƒญใƒผใƒ€ใƒผใฏๅฟ…่ฆใชใใชใ‚‹
+ */
 
-const { readConfigFile, parseJsonConfigFileContent, sys } = typescript
+import { resolve as resolveTs, load } from 'ts-node/esm';
+import { loadConfig, createMatchPath } from 'tsconfig-paths';
+import { pathToFileURL } from 'url';
 
-const __dirname = path.dirname(new URL(import.meta.url).pathname)
+const tsconfig = loadConfig();
+const matchPath = createMatchPath(tsconfig.absoluteBaseUrl, tsconfig.paths);
 
-const configFile = readConfigFile('./test/tsconfig.json', sys.readFile)
-if (typeof configFile.error !== 'undefined') {
-  throw new Error(`Failed to load tsconfig: ${configFile.error}`)
+export function resolve(specifier, ctx, defaultResolve) {
+	let resolvedSpecifier;
+	if (specifier.endsWith('.js')) {
+		// maybe transpiled
+		const specifierWithoutExtension = specifier.substring(0, specifier.length - '.js'.length);
+		const matchedSpecifier = matchPath(specifierWithoutExtension);
+		if (matchedSpecifier) {
+			resolvedSpecifier = pathToFileURL(`${matchedSpecifier}.js`).href;
+		}
+	} else {
+		const matchedSpecifier = matchPath(specifier);
+		if (matchedSpecifier) {
+			resolvedSpecifier = pathToFileURL(matchedSpecifier).href;
+		}
+	}
+	return resolveTs(resolvedSpecifier ?? specifier, ctx, defaultResolve);
 }
 
-const { options } = parseJsonConfigFileContent(
-  configFile.config,
-  {
-    fileExists: sys.fileExists,
-    readFile: sys.readFile,
-    readDirectory: sys.readDirectory,
-    useCaseSensitiveFileNames: true,
-  },
-  __dirname
-)
-
-export { getFormat, transformSource }  // ใ“ใ„ใคใ‚‰ใฏใใฎใพใพไฝฟใฃใฆใปใ—ใ„ใฎใง re-export ใ™ใ‚‹
-
-const matchPath = createMatchPath(options.baseUrl, options.paths)
-
-export async function resolve(specifier, context, defaultResolve) {
-  const matchedSpecifier = matchPath(specifier.replace('.js', '.ts'))
-  return BaseResolve(  // ts-node/esm ใฎ resolve ใซ tsconfig-paths ใง่งฃๆฑบใ—ใŸใƒ‘ใ‚นใ‚’ๆธกใ™
-    matchedSpecifier ? `${matchedSpecifier}.ts` : specifier,
-    context,
-    defaultResolve
-  )
-}
+export { load };
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 5a46daf49f..ba89ac329a 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -11,7 +11,7 @@ export class MockResolver extends Resolver {
 	public async _register(uri: string, content: string | Record<string, any>, type = 'application/activity+json') {
 		this._rs.set(uri, {
 			type,
-			content: typeof content === 'string' ? content : JSON.stringify(content)
+			content: typeof content === 'string' ? content : JSON.stringify(content),
 		});
 	}
 
@@ -22,9 +22,9 @@ export class MockResolver extends Resolver {
 
 		if (!r) {
 			throw {
-				name: `StatusError`,
+				name: 'StatusError',
 				statusCode: 404,
-				message: `Not registed for mock`
+				message: 'Not registed for mock',
 			};
 		}
 
diff --git a/packages/backend/test/mute.ts b/packages/backend/test/mute.ts
index 288e8a8055..2be70f2b65 100644
--- a/packages/backend/test/mute.ts
+++ b/packages/backend/test/mute.ts
@@ -25,7 +25,7 @@ describe('Mute', () => {
 
 	it('ใƒŸใƒฅใƒผใƒˆไฝœๆˆ', async(async () => {
 		const res = await request('/mute/create', {
-			userId: carol.id
+			userId: carol.id,
 		}, alice);
 
 		assert.strictEqual(res.status, 204);
@@ -117,7 +117,7 @@ describe('Mute', () => {
 			const aliceNote = await post(alice);
 			const carolNote = await post(carol);
 			const bobNote = await post(bob, {
-				renoteId: carolNote.id
+				renoteId: carolNote.id,
 			});
 
 			const res = await request('/notes/local-timeline', {}, alice);
diff --git a/packages/backend/test/note.ts b/packages/backend/test/note.ts
index 942b2709df..1183e9e4f1 100644
--- a/packages/backend/test/note.ts
+++ b/packages/backend/test/note.ts
@@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as childProcess from 'child_process';
-import { async, signup, request, post, uploadFile, startServer, shutdownServer, initTestDb } from './utils.js';
 import { Note } from '../src/models/entities/note.js';
+import { async, signup, request, post, uploadFile, startServer, shutdownServer, initTestDb } from './utils.js';
 
 describe('Note', () => {
 	let p: childProcess.ChildProcess;
@@ -26,7 +26,7 @@ describe('Note', () => {
 
 	it('ๆŠ•็จฟใงใใ‚‹', async(async () => {
 		const post = {
-			text: 'test'
+			text: 'test',
 		};
 
 		const res = await request('/notes/create', post, alice);
@@ -40,7 +40,7 @@ describe('Note', () => {
 		const file = await uploadFile(alice);
 
 		const res = await request('/notes/create', {
-			fileIds: [file.id]
+			fileIds: [file.id],
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
@@ -53,7 +53,7 @@ describe('Note', () => {
 
 		const res = await request('/notes/create', {
 			text: 'test',
-			fileIds: [file.id]
+			fileIds: [file.id],
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
@@ -64,7 +64,7 @@ describe('Note', () => {
 	it('ๅญ˜ๅœจใ—ใชใ„ใƒ•ใ‚กใ‚คใƒซใฏ็„ก่ฆ–', async(async () => {
 		const res = await request('/notes/create', {
 			text: 'test',
-			fileIds: ['000000000000000000000000']
+			fileIds: ['000000000000000000000000'],
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
@@ -74,19 +74,19 @@ describe('Note', () => {
 
 	it('ไธๆญฃใชใƒ•ใ‚กใ‚คใƒซIDใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const res = await request('/notes/create', {
-			fileIds: ['kyoppie']
+			fileIds: ['kyoppie'],
 		}, alice);
 		assert.strictEqual(res.status, 400);
 	}));
 
 	it('่ฟ”ไฟกใงใใ‚‹', async(async () => {
 		const bobPost = await post(bob, {
-			text: 'foo'
+			text: 'foo',
 		});
 
 		const alicePost = {
 			text: 'bar',
-			replyId: bobPost.id
+			replyId: bobPost.id,
 		};
 
 		const res = await request('/notes/create', alicePost, alice);
@@ -100,11 +100,11 @@ describe('Note', () => {
 
 	it('renoteใงใใ‚‹', async(async () => {
 		const bobPost = await post(bob, {
-			text: 'test'
+			text: 'test',
 		});
 
 		const alicePost = {
-			renoteId: bobPost.id
+			renoteId: bobPost.id,
 		};
 
 		const res = await request('/notes/create', alicePost, alice);
@@ -117,12 +117,12 @@ describe('Note', () => {
 
 	it('ๅผ•็”จrenoteใงใใ‚‹', async(async () => {
 		const bobPost = await post(bob, {
-			text: 'test'
+			text: 'test',
 		});
 
 		const alicePost = {
 			text: 'test',
-			renoteId: bobPost.id
+			renoteId: bobPost.id,
 		};
 
 		const res = await request('/notes/create', alicePost, alice);
@@ -136,7 +136,7 @@ describe('Note', () => {
 
 	it('ๆ–‡ๅญ—ๆ•ฐใŽใ‚ŠใŽใ‚Šใงๆ€’ใ‚‰ใ‚Œใชใ„', async(async () => {
 		const post = {
-			text: '!'.repeat(500)
+			text: '!'.repeat(500),
 		};
 		const res = await request('/notes/create', post, alice);
 		assert.strictEqual(res.status, 200);
@@ -144,7 +144,7 @@ describe('Note', () => {
 
 	it('ๆ–‡ๅญ—ๆ•ฐใ‚ชใƒผใƒใƒผใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const post = {
-			text: '!'.repeat(501)
+			text: '!'.repeat(501),
 		};
 		const res = await request('/notes/create', post, alice);
 		assert.strictEqual(res.status, 400);
@@ -153,7 +153,7 @@ describe('Note', () => {
 	it('ๅญ˜ๅœจใ—ใชใ„ใƒชใƒ—ใƒฉใ‚คๅ…ˆใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const post = {
 			text: 'test',
-			replyId: '000000000000000000000000'
+			replyId: '000000000000000000000000',
 		};
 		const res = await request('/notes/create', post, alice);
 		assert.strictEqual(res.status, 400);
@@ -161,7 +161,7 @@ describe('Note', () => {
 
 	it('ๅญ˜ๅœจใ—ใชใ„renoteๅฏพ่ฑกใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const post = {
-			renoteId: '000000000000000000000000'
+			renoteId: '000000000000000000000000',
 		};
 		const res = await request('/notes/create', post, alice);
 		assert.strictEqual(res.status, 400);
@@ -170,7 +170,7 @@ describe('Note', () => {
 	it('ไธๆญฃใชใƒชใƒ—ใƒฉใ‚คๅ…ˆIDใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const post = {
 			text: 'test',
-			replyId: 'foo'
+			replyId: 'foo',
 		};
 		const res = await request('/notes/create', post, alice);
 		assert.strictEqual(res.status, 400);
@@ -178,7 +178,7 @@ describe('Note', () => {
 
 	it('ไธๆญฃใชrenoteๅฏพ่ฑกIDใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const post = {
-			renoteId: 'foo'
+			renoteId: 'foo',
 		};
 		const res = await request('/notes/create', post, alice);
 		assert.strictEqual(res.status, 400);
@@ -186,7 +186,7 @@ describe('Note', () => {
 
 	it('ๅญ˜ๅœจใ—ใชใ„ใƒฆใƒผใ‚ถใƒผใซใƒกใƒณใ‚ทใƒงใƒณใงใใ‚‹', async(async () => {
 		const post = {
-			text: '@ghost yo'
+			text: '@ghost yo',
 		};
 
 		const res = await request('/notes/create', post, alice);
@@ -198,7 +198,7 @@ describe('Note', () => {
 
 	it('ๅŒใ˜ใƒฆใƒผใ‚ถใƒผใซ่ค‡ๆ•ฐใƒกใƒณใ‚ทใƒงใƒณใ—ใฆใ‚‚ๅ†…้ƒจ็š„ใซใพใจใ‚ใ‚‰ใ‚Œใ‚‹', async(async () => {
 		const post = {
-			text: '@bob @bob @bob yo'
+			text: '@bob @bob @bob yo',
 		};
 
 		const res = await request('/notes/create', post, alice);
@@ -216,8 +216,8 @@ describe('Note', () => {
 			const res = await request('/notes/create', {
 				text: 'test',
 				poll: {
-					choices: ['foo', 'bar']
-				}
+					choices: ['foo', 'bar'],
+				},
 			}, alice);
 
 			assert.strictEqual(res.status, 200);
@@ -227,7 +227,7 @@ describe('Note', () => {
 
 		it('ๆŠ•็ฅจใฎ้ธๆŠž่‚ขใŒ็„กใใฆๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 			const res = await request('/notes/create', {
-				poll: {}
+				poll: {},
 			}, alice);
 			assert.strictEqual(res.status, 400);
 		}));
@@ -235,8 +235,8 @@ describe('Note', () => {
 		it('ๆŠ•็ฅจใฎ้ธๆŠž่‚ขใŒ็„กใใฆๆ€’ใ‚‰ใ‚Œใ‚‹ (็ฉบใฎ้…ๅˆ—)', async(async () => {
 			const res = await request('/notes/create', {
 				poll: {
-					choices: []
-				}
+					choices: [],
+				},
 			}, alice);
 			assert.strictEqual(res.status, 400);
 		}));
@@ -244,8 +244,8 @@ describe('Note', () => {
 		it('ๆŠ•็ฅจใฎ้ธๆŠž่‚ขใŒ1ใคใงๆ€’ใ‚‰ใ‚Œใ‚‹', async(async () => {
 			const res = await request('/notes/create', {
 				poll: {
-					choices: ['Strawberry Pasta']
-				}
+					choices: ['Strawberry Pasta'],
+				},
 			}, alice);
 			assert.strictEqual(res.status, 400);
 		}));
@@ -254,13 +254,13 @@ describe('Note', () => {
 			const { body } = await request('/notes/create', {
 				text: 'test',
 				poll: {
-					choices: ['sakura', 'izumi', 'ako']
-				}
+					choices: ['sakura', 'izumi', 'ako'],
+				},
 			}, alice);
 
 			const res = await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 1
+				choice: 1,
 			}, alice);
 
 			assert.strictEqual(res.status, 204);
@@ -270,18 +270,18 @@ describe('Note', () => {
 			const { body } = await request('/notes/create', {
 				text: 'test',
 				poll: {
-					choices: ['sakura', 'izumi', 'ako']
-				}
+					choices: ['sakura', 'izumi', 'ako'],
+				},
 			}, alice);
 
 			await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 0
+				choice: 0,
 			}, alice);
 
 			const res = await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 2
+				choice: 2,
 			}, alice);
 
 			assert.strictEqual(res.status, 400);
@@ -292,23 +292,23 @@ describe('Note', () => {
 				text: 'test',
 				poll: {
 					choices: ['sakura', 'izumi', 'ako'],
-					multiple: true
-				}
+					multiple: true,
+				},
 			}, alice);
 
 			await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 0
+				choice: 0,
 			}, alice);
 
 			await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 1
+				choice: 1,
 			}, alice);
 
 			const res = await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 2
+				choice: 2,
 			}, alice);
 
 			assert.strictEqual(res.status, 204);
@@ -319,15 +319,15 @@ describe('Note', () => {
 				text: 'test',
 				poll: {
 					choices: ['sakura', 'izumi', 'ako'],
-					expiredAfter: 1
-				}
+					expiredAfter: 1,
+				},
 			}, alice);
 
 			await new Promise(x => setTimeout(x, 2));
 
 			const res = await request('/notes/polls/vote', {
 				noteId: body.createdNote.id,
-				choice: 1
+				choice: 1,
 			}, alice);
 
 			assert.strictEqual(res.status, 400);
@@ -341,11 +341,11 @@ describe('Note', () => {
 			}, alice);
 			const replyOneRes = await request('/notes/create', {
 				text: 'reply one',
-				replyId: mainNoteRes.body.createdNote.id
+				replyId: mainNoteRes.body.createdNote.id,
 			}, alice);
 			const replyTwoRes = await request('/notes/create', {
 				text: 'reply two',
-				replyId: mainNoteRes.body.createdNote.id
+				replyId: mainNoteRes.body.createdNote.id,
 			}, alice);
 
 			const deleteOneRes = await request('/notes/delete', {
@@ -353,7 +353,7 @@ describe('Note', () => {
 			}, alice);
 
 			assert.strictEqual(deleteOneRes.status, 204);
-			let mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id});
+			let mainNote = await Notes.findOne({ id: mainNoteRes.body.createdNote.id });
 			assert.strictEqual(mainNote.repliesCount, 1);
 
 			const deleteTwoRes = await request('/notes/delete', {
@@ -361,7 +361,7 @@ describe('Note', () => {
 			}, alice);
 
 			assert.strictEqual(deleteTwoRes.status, 204);
-			mainNote = await Notes.findOne({id: mainNoteRes.body.createdNote.id});
+			mainNote = await Notes.findOne({ id: mainNoteRes.body.createdNote.id });
 			assert.strictEqual(mainNote.repliesCount, 0);
 		}));
 	});
diff --git a/packages/backend/test/prelude/url.ts b/packages/backend/test/prelude/url.ts
index 84e43d26c2..df102c8dfe 100644
--- a/packages/backend/test/prelude/url.ts
+++ b/packages/backend/test/prelude/url.ts
@@ -6,7 +6,7 @@ describe('url', () => {
 		const s = query({
 			foo: 'ใตใ…',
 			bar: 'b a r',
-			baz: undefined
+			baz: undefined,
 		});
 		assert.deepStrictEqual(s, 'foo=%E3%81%B5%E3%81%85&bar=b%20a%20r');
 	});
diff --git a/packages/backend/test/streaming.ts b/packages/backend/test/streaming.ts
index 8d22b6d3d3..f080b71dd4 100644
--- a/packages/backend/test/streaming.ts
+++ b/packages/backend/test/streaming.ts
@@ -2,8 +2,8 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as childProcess from 'child_process';
-import { connectStream, signup, request, post, startServer, shutdownServer, initTestDb } from './utils.js';
 import { Following } from '../src/models/entities/following.js';
+import { connectStream, signup, request, post, startServer, shutdownServer, initTestDb } from './utils.js';
 
 describe('Streaming', () => {
 	let p: childProcess.ChildProcess;
@@ -30,7 +30,7 @@ describe('Streaming', () => {
 			followerSharedInbox: null,
 			followeeHost: followee.host,
 			followeeInbox: null,
-			followeeSharedInbox: null
+			followeeSharedInbox: null,
 		});
 	};
 
@@ -47,7 +47,7 @@ describe('Streaming', () => {
 		});
 
 		post(alice, {
-			text: 'foo @bob bar'
+			text: 'foo @bob bar',
 		});
 	}));
 
@@ -55,7 +55,7 @@ describe('Streaming', () => {
 		const alice = await signup({ username: 'alice' });
 		const bob = await signup({ username: 'bob' });
 		const bobNote = await post(bob, {
-			text: 'foo'
+			text: 'foo',
 		});
 
 		const ws = await connectStream(bob, 'main', ({ type, body }) => {
@@ -67,14 +67,14 @@ describe('Streaming', () => {
 		});
 
 		post(alice, {
-			renoteId: bobNote.id
+			renoteId: bobNote.id,
 		});
 	}));
 
 	describe('Home Timeline', () => {
 		it('่‡ชๅˆ†ใฎๆŠ•็จฟใŒๆตใ‚Œใ‚‹', () => new Promise(async done => {
 			const post = {
-				text: 'foo'
+				text: 'foo',
 			};
 
 			const me = await signup();
@@ -96,7 +96,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => {
@@ -108,7 +108,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -125,7 +125,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 
 			setTimeout(() => {
@@ -141,7 +141,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			const ws = await connectStream(alice, 'homeTimeline', ({ type, body }) => {
@@ -157,7 +157,7 @@ describe('Streaming', () => {
 			post(bob, {
 				text: 'foo',
 				visibility: 'specified',
-				visibleUserIds: [alice.id]
+				visibleUserIds: [alice.id],
 			});
 		}));
 
@@ -168,7 +168,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			let fired = false;
@@ -183,7 +183,7 @@ describe('Streaming', () => {
 			post(bob, {
 				text: 'foo',
 				visibility: 'specified',
-				visibleUserIds: [carol.id]
+				visibleUserIds: [carol.id],
 			});
 
 			setTimeout(() => {
@@ -207,7 +207,7 @@ describe('Streaming', () => {
 			});
 
 			post(me, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -224,7 +224,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -241,7 +241,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 
 			setTimeout(() => {
@@ -257,7 +257,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			let fired = false;
@@ -269,7 +269,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 
 			setTimeout(() => {
@@ -294,7 +294,7 @@ describe('Streaming', () => {
 			// ใƒ›ใƒผใƒ ๆŒ‡ๅฎš
 			post(bob, {
 				text: 'foo',
-				visibility: 'home'
+				visibility: 'home',
 			});
 
 			setTimeout(() => {
@@ -310,7 +310,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			let fired = false;
@@ -325,7 +325,7 @@ describe('Streaming', () => {
 			post(bob, {
 				text: 'foo',
 				visibility: 'specified',
-				visibleUserIds: [alice.id]
+				visibleUserIds: [alice.id],
 			});
 
 			setTimeout(() => {
@@ -350,7 +350,7 @@ describe('Streaming', () => {
 			// ใƒ•ใ‚ฉใƒญใƒฏใƒผๅฎ›ใฆๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
-				visibility: 'followers'
+				visibility: 'followers',
 			});
 
 			setTimeout(() => {
@@ -374,7 +374,7 @@ describe('Streaming', () => {
 			});
 
 			post(me, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -391,7 +391,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -411,7 +411,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -428,7 +428,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 
 			setTimeout(() => {
@@ -444,7 +444,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
@@ -460,7 +460,7 @@ describe('Streaming', () => {
 			post(bob, {
 				text: 'foo',
 				visibility: 'specified',
-				visibleUserIds: [alice.id]
+				visibleUserIds: [alice.id],
 			});
 		}));
 
@@ -470,7 +470,7 @@ describe('Streaming', () => {
 
 			// Alice ใŒ Bob ใ‚’ใƒ•ใ‚ฉใƒญใƒผ
 			await request('/following/create', {
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
@@ -485,7 +485,7 @@ describe('Streaming', () => {
 			// ใƒ›ใƒผใƒ ๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
-				visibility: 'home'
+				visibility: 'home',
 			});
 		}));
 
@@ -504,7 +504,7 @@ describe('Streaming', () => {
 			// ใƒ›ใƒผใƒ ๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
-				visibility: 'home'
+				visibility: 'home',
 			});
 
 			setTimeout(() => {
@@ -529,7 +529,7 @@ describe('Streaming', () => {
 			// ใƒ•ใ‚ฉใƒญใƒฏใƒผๅฎ›ใฆๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
-				visibility: 'followers'
+				visibility: 'followers',
 			});
 
 			setTimeout(() => {
@@ -554,7 +554,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -571,7 +571,7 @@ describe('Streaming', () => {
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -590,7 +590,7 @@ describe('Streaming', () => {
 			// ใƒ›ใƒผใƒ ๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
-				visibility: 'home'
+				visibility: 'home',
 			});
 
 			setTimeout(() => {
@@ -608,13 +608,13 @@ describe('Streaming', () => {
 
 			// ใƒชใ‚นใƒˆไฝœๆˆ
 			const list = await request('/users/lists/create', {
-				name: 'my list'
+				name: 'my list',
 			}, alice).then(x => x.body);
 
 			// Alice ใŒ Bob ใ‚’ใƒชใ‚นใ‚คใƒณ
 			await request('/users/lists/push', {
 				listId: list.id,
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			const ws = await connectStream(alice, 'userList', ({ type, body }) => {
@@ -624,11 +624,11 @@ describe('Streaming', () => {
 					done();
 				}
 			}, {
-				listId: list.id
+				listId: list.id,
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 		}));
 
@@ -638,7 +638,7 @@ describe('Streaming', () => {
 
 			// ใƒชใ‚นใƒˆไฝœๆˆ
 			const list = await request('/users/lists/create', {
-				name: 'my list'
+				name: 'my list',
 			}, alice).then(x => x.body);
 
 			let fired = false;
@@ -648,11 +648,11 @@ describe('Streaming', () => {
 					fired = true;
 				}
 			}, {
-				listId: list.id
+				listId: list.id,
 			});
 
 			post(bob, {
-				text: 'foo'
+				text: 'foo',
 			});
 
 			setTimeout(() => {
@@ -669,13 +669,13 @@ describe('Streaming', () => {
 
 			// ใƒชใ‚นใƒˆไฝœๆˆ
 			const list = await request('/users/lists/create', {
-				name: 'my list'
+				name: 'my list',
 			}, alice).then(x => x.body);
 
 			// Alice ใŒ Bob ใ‚’ใƒชใ‚นใ‚คใƒณ
 			await request('/users/lists/push', {
 				listId: list.id,
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			const ws = await connectStream(alice, 'userList', ({ type, body }) => {
@@ -686,14 +686,14 @@ describe('Streaming', () => {
 					done();
 				}
 			}, {
-				listId: list.id
+				listId: list.id,
 			});
 
 			// Bob ใŒ Alice ๅฎ›ใฆใฎใƒ€ใ‚คใƒฌใ‚ฏใƒˆๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
 				visibility: 'specified',
-				visibleUserIds: [alice.id]
+				visibleUserIds: [alice.id],
 			});
 		}));
 
@@ -704,13 +704,13 @@ describe('Streaming', () => {
 
 			// ใƒชใ‚นใƒˆไฝœๆˆ
 			const list = await request('/users/lists/create', {
-				name: 'my list'
+				name: 'my list',
 			}, alice).then(x => x.body);
 
 			// Alice ใŒ Bob ใ‚’ใƒชใ‚นใ‚คใƒณ
 			await request('/users/lists/push', {
 				listId: list.id,
-				userId: bob.id
+				userId: bob.id,
 			}, alice);
 
 			let fired = false;
@@ -720,13 +720,13 @@ describe('Streaming', () => {
 					fired = true;
 				}
 			}, {
-				listId: list.id
+				listId: list.id,
 			});
 
 			// ใƒ•ใ‚ฉใƒญใƒฏใƒผๅฎ›ใฆๆŠ•็จฟ
 			post(bob, {
 				text: 'foo',
-				visibility: 'followers'
+				visibility: 'followers',
 			});
 
 			setTimeout(() => {
@@ -749,12 +749,12 @@ describe('Streaming', () => {
 				}
 			}, {
 				q: [
-					['foo']
-				]
+					['foo'],
+				],
 			});
 
 			post(me, {
-				text: '#foo'
+				text: '#foo',
 			});
 		}));
 
@@ -773,20 +773,20 @@ describe('Streaming', () => {
 				}
 			}, {
 				q: [
-					['foo', 'bar']
-				]
+					['foo', 'bar'],
+				],
 			});
 
 			post(me, {
-				text: '#foo'
+				text: '#foo',
 			});
 
 			post(me, {
-				text: '#bar'
+				text: '#bar',
 			});
 
 			post(me, {
-				text: '#foo #bar'
+				text: '#foo #bar',
 			});
 
 			setTimeout(() => {
@@ -816,24 +816,24 @@ describe('Streaming', () => {
 			}, {
 				q: [
 					['foo'],
-					['bar']
-				]
+					['bar'],
+				],
 			});
 
 			post(me, {
-				text: '#foo'
+				text: '#foo',
 			});
 
 			post(me, {
-				text: '#bar'
+				text: '#bar',
 			});
 
 			post(me, {
-				text: '#foo #bar'
+				text: '#foo #bar',
 			});
 
 			post(me, {
-				text: '#piyo'
+				text: '#piyo',
 			});
 
 			setTimeout(() => {
@@ -866,28 +866,28 @@ describe('Streaming', () => {
 			}, {
 				q: [
 					['foo', 'bar'],
-					['piyo']
-				]
+					['piyo'],
+				],
 			});
 
 			post(me, {
-				text: '#foo'
+				text: '#foo',
 			});
 
 			post(me, {
-				text: '#bar'
+				text: '#bar',
 			});
 
 			post(me, {
-				text: '#foo #bar'
+				text: '#foo #bar',
 			});
 
 			post(me, {
-				text: '#piyo'
+				text: '#piyo',
 			});
 
 			post(me, {
-				text: '#waaa'
+				text: '#waaa',
 			});
 
 			setTimeout(() => {
diff --git a/packages/backend/test/user-notes.ts b/packages/backend/test/user-notes.ts
index 25ffe04756..5b7933da67 100644
--- a/packages/backend/test/user-notes.ts
+++ b/packages/backend/test/user-notes.ts
@@ -2,8 +2,13 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as childProcess from 'child_process';
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
 import { async, signup, request, post, uploadFile, startServer, shutdownServer } from './utils.js';
 
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
+
 describe('users/notes', () => {
 	let p: childProcess.ChildProcess;
 
@@ -15,16 +20,16 @@ describe('users/notes', () => {
 	before(async () => {
 		p = await startServer();
 		alice = await signup({ username: 'alice' });
-		const jpg = await uploadFile(alice, __dirname + '/resources/Lenna.jpg');
-		const png = await uploadFile(alice, __dirname + '/resources/Lenna.png');
+		const jpg = await uploadFile(alice, _dirname + '/resources/Lenna.jpg');
+		const png = await uploadFile(alice, _dirname + '/resources/Lenna.png');
 		jpgNote = await post(alice, {
-			fileIds: [jpg.id]
+			fileIds: [jpg.id],
 		});
 		pngNote = await post(alice, {
-			fileIds: [png.id]
+			fileIds: [png.id],
 		});
 		jpgPngNote = await post(alice, {
-			fileIds: [jpg.id, png.id]
+			fileIds: [jpg.id, png.id],
 		});
 	});
 
@@ -35,7 +40,7 @@ describe('users/notes', () => {
 	it('ใƒ•ใ‚กใ‚คใƒซใ‚ฟใ‚คใƒ—ๆŒ‡ๅฎš (jpg)', async(async () => {
 		const res = await request('/users/notes', {
 			userId: alice.id,
-			fileType: ['image/jpeg']
+			fileType: ['image/jpeg'],
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
@@ -48,7 +53,7 @@ describe('users/notes', () => {
 	it('ใƒ•ใ‚กใ‚คใƒซใ‚ฟใ‚คใƒ—ๆŒ‡ๅฎš (jpg or png)', async(async () => {
 		const res = await request('/users/notes', {
 			userId: alice.id,
-			fileType: ['image/jpeg', 'image/png']
+			fileType: ['image/jpeg', 'image/png'],
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 32a030f933..5eb4ed3b01 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -1,14 +1,20 @@
 import * as fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
+import * as childProcess from 'child_process';
+import * as http from 'node:http';
+import { SIGKILL } from 'constants';
 import * as WebSocket from 'ws';
 import * as misskey from 'misskey-js';
 import fetch from 'node-fetch';
 import FormData from 'form-data';
-import * as childProcess from 'child_process';
-import * as http from 'http';
+import { DataSource } from 'typeorm';
 import loadConfig from '../src/config/load.js';
-import { SIGKILL } from 'constants';
 import { entities } from '../src/db/postgre.js';
 
+const _filename = fileURLToPath(import.meta.url);
+const _dirname = dirname(_filename);
+
 const config = loadConfig();
 export const port = config.port;
 
@@ -22,29 +28,29 @@ export const async = (fn: Function) => (done: Function) => {
 
 export const request = async (endpoint: string, params: any, me?: any): Promise<{ body: any, status: number }> => {
 	const auth = me ? {
-		i: me.token
+		i: me.token,
 	} : {};
 
 	const res = await fetch(`http://localhost:${port}/api${endpoint}`, {
 		method: 'POST',
 		headers: {
-			'Content-Type': 'application/json'
+			'Content-Type': 'application/json',
 		},
-		body: JSON.stringify(Object.assign(auth, params))
+		body: JSON.stringify(Object.assign(auth, params)),
 	});
 
 	const status = res.status;
 	const body = res.status !== 204 ? await res.json().catch() : null;
 
 	return {
-		body, status
+		body, status,
 	};
 };
 
 export const signup = async (params?: any): Promise<any> => {
 	const q = Object.assign({
 		username: 'test',
-		password: 'test'
+		password: 'test',
 	}, params);
 
 	const res = await request('/signup', q);
@@ -54,7 +60,7 @@ export const signup = async (params?: any): Promise<any> => {
 
 export const post = async (user: any, params?: misskey.Endpoints['notes/create']['req']): Promise<misskey.entities.Note> => {
 	const q = Object.assign({
-		text: 'test'
+		text: 'test',
 	}, params);
 
 	const res = await request('/notes/create', q, user);
@@ -65,26 +71,26 @@ export const post = async (user: any, params?: misskey.Endpoints['notes/create']
 export const react = async (user: any, note: any, reaction: string): Promise<any> => {
 	await request('/notes/reactions/create', {
 		noteId: note.id,
-		reaction: reaction
+		reaction: reaction,
 	}, user);
 };
 
 export const uploadFile = (user: any, path?: string): Promise<any> => {
-		const formData = new FormData();
-		formData.append('i', user.token);
-		formData.append('file', fs.createReadStream(path || __dirname + '/resources/Lenna.png'));
+	const formData = new FormData();
+	formData.append('i', user.token);
+	formData.append('file', fs.createReadStream(path || _dirname + '/resources/Lenna.png'));
 
-		return fetch(`http://localhost:${port}/api/drive/files/create`, {
-			method: 'post',
-			body: formData,
-			timeout: 30 * 1000,
-		}).then(res => {
-			if (!res.ok) {
-				throw `${res.status} ${res.statusText}`;
-			} else {
-				return res.json();
-			}
-		});
+	return fetch(`http://localhost:${port}/api/drive/files/create`, {
+		method: 'post',
+		body: formData,
+		timeout: 30 * 1000,
+	}).then(res => {
+		if (!res.ok) {
+			throw `${res.status} ${res.statusText}`;
+		} else {
+			return res.json();
+		}
+	});
 };
 
 export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
@@ -94,9 +100,9 @@ export function connectStream(user: any, channel: string, listener: (message: Re
 		ws.on('open', () => {
 			ws.on('message', data => {
 				const msg = JSON.parse(data.toString());
-				if (msg.type == 'channel' && msg.body.id == 'a') {
+				if (msg.type === 'channel' && msg.body.id === 'a') {
 					listener(msg.body);
-				} else if (msg.type == 'connected' && msg.body.id == 'a') {
+				} else if (msg.type === 'connected' && msg.body.id === 'a') {
 					res(ws);
 				}
 			});
@@ -107,8 +113,8 @@ export function connectStream(user: any, channel: string, listener: (message: Re
 					channel: channel,
 					id: 'a',
 					pong: true,
-					params: params
-				}
+					params: params,
+				},
 			}));
 		});
 	});
@@ -119,8 +125,8 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?
 	return await new Promise((resolve, reject) => {
 		const req = http.request(`http://localhost:${port}${path}`, {
 			headers: {
-				Accept: accept
-			}
+				Accept: accept,
+			},
 		}, res => {
 			if (res.statusCode! >= 400) {
 				reject(res);
@@ -139,9 +145,9 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?
 
 export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProcess) => void, moreProcess: () => Promise<void> = async () => {}) {
 	return (done: (err?: Error) => any) => {
-		const p = childProcess.spawn('node', [__dirname + '/../index.js'], {
+		const p = childProcess.spawn('node', [_dirname + '/../index.js'], {
 			stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
-			env: { NODE_ENV: 'test', PATH: process.env.PATH }
+			env: { NODE_ENV: 'test', PATH: process.env.PATH },
 		});
 		callbackSpawnedProcess(p);
 		p.on('message', message => {
@@ -153,12 +159,7 @@ export function launchServer(callbackSpawnedProcess: (p: childProcess.ChildProce
 export async function initTestDb(justBorrow = false, initEntities?: any[]) {
 	if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
 
-	try {
-		const conn = await getConnection();
-		await conn.close();
-	} catch (e) {}
-
-	return await createConnection({
+	const db = new DataSource({
 		type: 'postgres',
 		host: config.db.host,
 		port: config.db.port,
@@ -167,8 +168,12 @@ export async function initTestDb(justBorrow = false, initEntities?: any[]) {
 		database: config.db.db,
 		synchronize: true && !justBorrow,
 		dropSchema: true && !justBorrow,
-		entities: initEntities || entities
+		entities: initEntities || entities,
 	});
+
+	await db.initialize();
+
+	return db;
 }
 
 export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProcess> {
@@ -178,9 +183,9 @@ export function startServer(timeout = 30 * 1000): Promise<childProcess.ChildProc
 			rej('timeout to start');
 		}, timeout);
 
-		const p = childProcess.spawn('node', [__dirname + '/../built/index.js'], {
+		const p = childProcess.spawn('node', [_dirname + '/../built/index.js'], {
 			stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
-			env: { NODE_ENV: 'test', PATH: process.env.PATH }
+			env: { NODE_ENV: 'test', PATH: process.env.PATH },
 		});
 
 		p.on('error', e => rej(e));
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
index 3120851aae..22338a4976 100644
--- a/packages/backend/tsconfig.json
+++ b/packages/backend/tsconfig.json
@@ -25,9 +25,14 @@
 		"rootDir": "./src",
 		"baseUrl": "./",
 		"paths": {
-			"@/*": ["./src/*"]
+			"@/*": [
+				"./src/*"
+			]
 		},
 		"outDir": "./built",
+		"types": [
+			"node"
+		],
 		"typeRoots": [
 			"./node_modules/@types",
 			"./src/@types"
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 5cd71acf9c..5ce87c34eb 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -12,20 +12,6 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.9.tgz#ca34cb95e1c2dd126863a84465ae8ef66114be99"
   integrity sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==
 
-"@babel/runtime@^7.16.0":
-  version "7.16.3"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
-  integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
-  dependencies:
-    regenerator-runtime "^0.13.4"
-
-"@babel/runtime@^7.6.2":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d"
-  integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==
-  dependencies:
-    regenerator-runtime "^0.13.4"
-
 "@babel/types@^7.6.1", "@babel/types@^7.9.6":
   version "7.13.0"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
@@ -35,68 +21,63 @@
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
-"@bull-board/api@3.10.3":
-  version "3.10.3"
-  resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.10.3.tgz#c6aad9f5cfb3acbe02c57e823ee81c1ae575849d"
-  integrity sha512-kV6EPwi9j71qBmozvDmtT01j986r4cFqNmBgq7HApYXW0G2U8Brmv0Ut0iMQZRc/X7aA5KYL3qXcEsriFnq+jw==
+"@bull-board/api@3.11.1":
+  version "3.11.1"
+  resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-3.11.1.tgz#98b2c9556f643718bb5bde4a1306e6706af8192e"
+  integrity sha512-ElwX7sM+Ng4ZL9KUsbDubRE+r2hu/gss85OsROeE9bmyfkW14jOJkgr5MKUyjTTgPEeMs1Mw55TgQs2vxoWBiA==
   dependencies:
     redis-info "^3.0.8"
 
-"@bull-board/koa@3.10.3":
-  version "3.10.3"
-  resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.10.3.tgz#b9f02629f96f056d6a038c3c58fc339d58e55abb"
-  integrity sha512-DK8m09MwcRwUR3tz3xI0iSK/Ih2huQ2MAWm8krYjO5deswP2yBaCWE4OtpiULLfVpf8z4zB3Oqa0xNJrKRHTOQ==
+"@bull-board/koa@3.11.1":
+  version "3.11.1"
+  resolved "https://registry.yarnpkg.com/@bull-board/koa/-/koa-3.11.1.tgz#1872aba2c65d116d1183b3003e4a2cb2c1e2fbbf"
+  integrity sha512-F/thrTuC1JWpdBO7DPdKD/wr8c+d7MJGu0sr5ARsT1WXhng7sU7OqBEP/5Y7HhByurjDFXDxcgk/mc78Tmeb/Q==
   dependencies:
-    "@bull-board/api" "3.10.3"
-    "@bull-board/ui" "3.10.3"
-    ejs "^3.1.6"
+    "@bull-board/api" "3.11.1"
+    "@bull-board/ui" "3.11.1"
+    ejs "^3.1.7"
     koa "^2.13.1"
     koa-mount "^4.0.0"
     koa-router "^10.0.0"
     koa-static "^5.0.0"
     koa-views "^7.0.1"
 
-"@bull-board/ui@3.10.3":
-  version "3.10.3"
-  resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.10.3.tgz#b921199d42b32d8ddd9bbf0e35c25be0d64403e9"
-  integrity sha512-6zYW3FqySg+4IKEeM1jt/5ixNVBKQjtZLG9W81ADVcHk8YceQ++7URWzDb8nQEct3rEW4bjR6nicVWNXMSN7Lw==
+"@bull-board/ui@3.11.1":
+  version "3.11.1"
+  resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-3.11.1.tgz#17a2af5573f31811a543105b9a96249c95e93ce7"
+  integrity sha512-SRrfvxHF/WaBICiAFuWAoAlTvoBYUBmX94oRbSKzVILRFZMe3gs0hN071BFohrn4yOTFHAkWPN7cjMbaqHwCag==
   dependencies:
-    "@bull-board/api" "3.10.3"
+    "@bull-board/api" "3.11.1"
 
-"@cspotcode/source-map-consumer@0.8.0":
-  version "0.8.0"
-  resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
-  integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
-
-"@cspotcode/source-map-support@0.7.0":
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
-  integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
+"@cspotcode/source-map-support@^0.8.0":
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
+  integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
   dependencies:
-    "@cspotcode/source-map-consumer" "0.8.0"
+    "@jridgewell/trace-mapping" "0.3.9"
 
 "@cto.af/textdecoder@^0.0.0":
   version "0.0.0"
   resolved "https://registry.yarnpkg.com/@cto.af/textdecoder/-/textdecoder-0.0.0.tgz#e1e8d84c936c30a0f4619971f19ca41941af9fdc"
   integrity sha512-sJpx3F5xcVV/9jNYJQtvimo4Vfld/nD3ph+ZWtQzZ03Zo8rJC7QKQTRcIGS13Rcz80DwFNthCWMrd58vpY4ZAQ==
 
-"@digitalbazaar/http-client@^1.1.0":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-1.1.0.tgz#cac383b24ace04b18b919deab773462b03d3d7b0"
-  integrity sha512-ks7hqa6hm9NyULdbm9qL6TRS8rADzBw8R0lETvUgvdNXu9H62XG2YqoKRDThtfgWzWxLwRJ3Z2o4ev81dZZbyQ==
+"@digitalbazaar/http-client@^3.2.0":
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/@digitalbazaar/http-client/-/http-client-3.2.0.tgz#b85ea09028c7d0f288f976c852d0a8f3875f0fcf"
+  integrity sha512-NhYXcWE/JDE7AnJikNX7q0S6zNuUPA2NuIoRdUpmvHlarjmRqyr6hIO3Awu2FxlUzbdiI1uzuWrZyB9mD1tTvw==
   dependencies:
-    esm "^3.2.22"
-    ky "^0.25.1"
-    ky-universal "^0.8.2"
+    ky "^0.30.0"
+    ky-universal "^0.10.1"
+    undici "^5.2.0"
 
-"@discordapp/twemoji@13.1.1":
-  version "13.1.1"
-  resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91"
-  integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A==
+"@discordapp/twemoji@14.0.2":
+  version "14.0.2"
+  resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-14.0.2.tgz#50cc19f6f3769dc6b36eb251421b5f5d4629e837"
+  integrity sha512-eYJpFsjViDTYwq3f6v+tRu8iRc+yLAeGrlh6kmNRvvC6rroUE2bMlBfEQ/WNh+2Q1FtSEFXpxzuQPOHzRzbAyA==
   dependencies:
     fs-extra "^8.0.1"
     jsonfile "^5.0.0"
-    twemoji-parser "13.1.0"
+    twemoji-parser "14.0.0"
     universalify "^0.1.2"
 
 "@elastic/elasticsearch@7.11.0":
@@ -110,19 +91,19 @@
     pump "^3.0.0"
     secure-json-parse "^2.1.0"
 
-"@eslint/eslintrc@^1.2.1":
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6"
-  integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==
+"@eslint/eslintrc@^1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
+  integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
-    espree "^9.3.1"
-    globals "^13.9.0"
+    espree "^9.3.2"
+    globals "^13.15.0"
     ignore "^5.2.0"
     import-fresh "^3.2.1"
     js-yaml "^4.1.0"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
 "@gar/promisify@^1.0.1":
@@ -144,6 +125,24 @@
   resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
   integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
 
+"@jridgewell/resolve-uri@^3.0.3":
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz#30cd49820a962aff48c8fffc5cd760151fca61fe"
+  integrity sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+  version "1.4.13"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c"
+  integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==
+
+"@jridgewell/trace-mapping@0.3.9":
+  version "0.3.9"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
+  integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+  dependencies:
+    "@jridgewell/resolve-uri" "^3.0.3"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+
 "@koa/cors@3.1.0":
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/@koa/cors/-/cors-3.1.0.tgz#618bb073438cfdbd3ebd0e648a76e33b84f3a3b2"
@@ -234,6 +233,15 @@
     mkdirp "^1.0.4"
     rimraf "^3.0.2"
 
+"@peertube/http-signature@1.6.0":
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/@peertube/http-signature/-/http-signature-1.6.0.tgz#22bef028384e6437e8dbd94052ba7b8bd7f7f1ae"
+  integrity sha512-Bx780c7FPYtkV4LgCoaJcXYcKQqaMef2iQR2V2r5klkYkIQWFxbTOpyhKxvVXYIBIFpj5Cb8DGVDAmhkm7aavg==
+  dependencies:
+    assert-plus "^1.0.0"
+    jsprim "^1.2.2"
+    sshpk "^1.14.1"
+
 "@redocly/ajv@^8.6.4":
   version "8.6.4"
   resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.6.4.tgz#94053e7a9d4146d1a4feacd3813892873f229a85"
@@ -244,10 +252,10 @@
     require-from-string "^2.0.2"
     uri-js "^4.2.2"
 
-"@redocly/openapi-core@1.0.0-beta.93":
-  version "1.0.0-beta.93"
-  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.93.tgz#882db8684598217f621adc7349288229253a0038"
-  integrity sha512-xQj7UnjPj3mKtkyRrm+bjzEoyo0CVNjGP4pV6BzQ0vgKf0Jqq7apFC703psyBH+JscYr7NKK1hPQU76ylhFDdg==
+"@redocly/openapi-core@1.0.0-beta.97":
+  version "1.0.0-beta.97"
+  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.0.0-beta.97.tgz#324ed46e9a9aee4c615be22ee348c53f7bb5f180"
+  integrity sha512-3WW9/6flosJuRtU3GI0Vw39OYFZqqXMDCp5TLa3EjXOb7Nm6AZTWRb3Y+I/+UdNJ/NTszVJkQczoa1t476ekiQ==
   dependencies:
     "@redocly/ajv" "^8.6.4"
     "@types/node" "^14.11.8"
@@ -255,7 +263,7 @@
     js-levenshtein "^1.1.6"
     js-yaml "^4.1.0"
     lodash.isequal "^4.5.0"
-    minimatch "^3.0.4"
+    minimatch "^5.0.1"
     node-fetch "^2.6.1"
     pluralize "^8.0.0"
     yaml-ast-parser "0.0.43"
@@ -277,10 +285,10 @@
   dependencies:
     type-detect "4.0.8"
 
-"@sinonjs/fake-timers@9.1.1":
-  version "9.1.1"
-  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.1.tgz#7b698e0b9d12d93611f06ee143c30ced848e2840"
-  integrity sha512-Wp5vwlZ0lOqpSYGKqr53INws9HLkt6JDc/pDZcPf7bchQnrXJMXPns8CXx0hFikMSGSWfvtvvpb2gtMVfkWagA==
+"@sinonjs/fake-timers@9.1.2":
+  version "9.1.2"
+  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c"
+  integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==
   dependencies:
     "@sinonjs/commons" "^1.7.0"
 
@@ -527,6 +535,11 @@
   resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4"
   integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg==
 
+"@types/jsrsasign@10.5.1":
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.1.tgz#6f9defd46dfcf324b1cff08a06be639858deee3b"
+  integrity sha512-QqM03IXHY6SX835mWdx7Vp8ZOxw/hcnMjGjapUQf+pgFPRyGdjg3jxFsr4p+rolKcdRhptm3mtVQNk4OMhCQcA==
+
 "@types/keygrip@*":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
@@ -649,10 +662,10 @@
   resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
   integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
 
-"@types/mocha@9.1.0":
-  version "9.1.0"
-  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5"
-  integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==
+"@types/mocha@9.1.1":
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4"
+  integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
 
 "@types/node-fetch@3.0.3":
   version "3.0.3"
@@ -666,10 +679,10 @@
   resolved "https://registry.yarnpkg.com/@types/node/-/node-16.6.2.tgz#331b7b9f8621c638284787c5559423822fdffc50"
   integrity sha512-LSw8TZt12ZudbpHc6EkIyDM3nHVWKYrAvGy6EAJfNfjusbwnThqjqxUKKRwuV3iWYeW/LYMzNgaq3MaLffQ2xA==
 
-"@types/node@17.0.23":
-  version "17.0.23"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da"
-  integrity sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==
+"@types/node@17.0.41":
+  version "17.0.41"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b"
+  integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==
 
 "@types/node@^14.11.8":
   version "14.17.9"
@@ -700,11 +713,6 @@
   resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
   integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
 
-"@types/portscanner@2.1.1":
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/@types/portscanner/-/portscanner-2.1.1.tgz#89d5094e16f3d941f20f3889dfa5d3a164b3dd3b"
-  integrity sha512-1NsVIbgBKvrqxwtMN0V6CLji1ERwKSI/RWz0J3y++CzSwYNGBStCfpIFgxV3ZwxsDR5PoZqoUWhwraDm+Ztn0Q==
-
 "@types/pug@2.0.6":
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6"
@@ -777,6 +785,11 @@
   dependencies:
     htmlparser2 "^6.0.0"
 
+"@types/semver@7.3.9":
+  version "7.3.9"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc"
+  integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==
+
 "@types/serve-static@*":
   version "1.13.3"
   resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
@@ -785,10 +798,10 @@
     "@types/express-serve-static-core" "*"
     "@types/mime" "*"
 
-"@types/sharp@0.30.1":
-  version "0.30.1"
-  resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.1.tgz#31bd128f2437e8fc31424eb23d8284aa127bfa8d"
-  integrity sha512-LxzQsKo2YtvA2DlqACNXmlbLGMVJCSU/HhV4N9RrStClUEf02iN+AakD/zUOpZkbo1OG+lHk2LeqoHedLwln2w==
+"@types/sharp@0.30.2":
+  version "0.30.2"
+  resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.2.tgz#df5ff34140b3bad165482e6f3d26b08e42a0503a"
+  integrity sha512-uLCBwjDg/BTcQit0dpNGvkIjvH3wsb8zpaJePCjvONBBSfaKHoxXBIuq1MT8DMQEfk2fKYnpC9QExCgFhkGkMQ==
   dependencies:
     "@types/node" "*"
 
@@ -845,85 +858,85 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d"
-  integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A==
+"@typescript-eslint/eslint-plugin@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758"
+  integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/type-utils" "5.18.0"
-    "@typescript-eslint/utils" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/type-utils" "5.27.1"
+    "@typescript-eslint/utils" "5.27.1"
+    debug "^4.3.4"
     functional-red-black-tree "^1.0.1"
-    ignore "^5.1.8"
+    ignore "^5.2.0"
     regexpp "^3.2.0"
-    semver "^7.3.5"
+    semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6"
-  integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ==
+"@typescript-eslint/parser@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639"
+  integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/typescript-estree" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/typescript-estree" "5.27.1"
+    debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505"
-  integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ==
+"@typescript-eslint/scope-manager@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d"
+  integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
 
-"@typescript-eslint/type-utils@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74"
-  integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA==
+"@typescript-eslint/type-utils@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166"
+  integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw==
   dependencies:
-    "@typescript-eslint/utils" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/utils" "5.27.1"
+    debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e"
-  integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw==
+"@typescript-eslint/types@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1"
+  integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==
 
-"@typescript-eslint/typescript-estree@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474"
-  integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ==
+"@typescript-eslint/typescript-estree@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808"
+  integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
-    debug "^4.3.2"
-    globby "^11.0.4"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
+    debug "^4.3.4"
+    globby "^11.1.0"
     is-glob "^4.0.3"
-    semver "^7.3.5"
+    semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855"
-  integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA==
+"@typescript-eslint/utils@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f"
+  integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/typescript-estree" "5.18.0"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/typescript-estree" "5.27.1"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60"
-  integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg==
+"@typescript-eslint/visitor-keys@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af"
+  integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    eslint-visitor-keys "^3.0.0"
+    "@typescript-eslint/types" "5.27.1"
+    eslint-visitor-keys "^3.3.0"
 
 "@ungap/promise-all-settled@1.1.2":
   version "1.1.2"
@@ -963,10 +976,10 @@ acorn-globals@^6.0.0:
     acorn "^7.1.1"
     acorn-walk "^7.1.1"
 
-acorn-jsx@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
-  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+acorn-jsx@^5.3.2:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
 acorn-walk@^7.1.1:
   version "7.1.1"
@@ -988,11 +1001,16 @@ acorn@^8.4.1:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c"
   integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==
 
-acorn@^8.5.0, acorn@^8.7.0:
+acorn@^8.5.0:
   version "8.7.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
   integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
 
+acorn@^8.7.1:
+  version "8.7.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
+  integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
+
 agent-base@6, agent-base@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -1074,7 +1092,7 @@ ansi-styles@^3.2.1:
   dependencies:
     color-convert "^1.9.0"
 
-ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ansi-styles@^4.0.0:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
   integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
@@ -1082,6 +1100,13 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
     "@types/color-name" "^1.1.1"
     color-convert "^2.0.1"
 
+ansi-styles@^4.1.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+  dependencies:
+    color-convert "^2.0.1"
+
 any-promise@^1.0.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f"
@@ -1131,13 +1156,13 @@ archiver-utils@^2.1.0:
     normalize-path "^3.0.0"
     readable-stream "^2.0.0"
 
-archiver@5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.0.tgz#dd3e097624481741df626267564f7dd8640a45ba"
-  integrity sha512-iUw+oDwK0fgNpvveEsdQ0Ase6IIKztBJU2U0E9MzszMfmVVUyv1QJhS2ITW9ZCqx8dktAxVAjWWkKehuZE8OPg==
+archiver@5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.1.tgz#21e92811d6f09ecfce649fbefefe8c79e57cbbb6"
+  integrity sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==
   dependencies:
     archiver-utils "^2.1.0"
-    async "^3.2.0"
+    async "^3.2.3"
     buffer-crc32 "^0.2.1"
     readable-stream "^3.6.0"
     readdir-glob "^1.0.0"
@@ -1227,27 +1252,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
   resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
   integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
 
-async@0.9.x:
-  version "0.9.2"
-  resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d"
-  integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=
-
-async@>=0.2.9:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
-  integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
-
-async@^2.6.0:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
-  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
-  dependencies:
-    lodash "^4.17.14"
-
-async@^3.2.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8"
-  integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==
+async@>=0.2.9, async@^3.2.3:
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
+  integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
 
 asynckit@^0.4.0:
   version "0.4.0"
@@ -1266,10 +1274,10 @@ autwh@0.1.0:
   dependencies:
     oauth "0.9.15"
 
-aws-sdk@2.1111.0:
-  version "2.1111.0"
-  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1111.0.tgz#02b1e5c530ef8140235ee7c48c710bb2dbd7dc84"
-  integrity sha512-WRyNcCckzmu1djTAWfR2r+BuI/PbuLrhG3oa+oH39v4NZ4EecYWFL1CoCPlC2kRUML4maSba5T4zlxjcNl7ELQ==
+aws-sdk@2.1152.0:
+  version "2.1152.0"
+  resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1152.0.tgz#73e4fb81b3a9c289234b5d6848bcdb854f169bdf"
+  integrity sha512-Lqwk0bDhm3vzpYb3AAM9VgGHeDpbB8+o7UJnP9R+CO23kJfi/XRpKihAcbyKDD/AUQ+O1LJaUVpvaJYLS9Am7w==
   dependencies:
     buffer "4.9.2"
     events "1.1.1"
@@ -1278,7 +1286,7 @@ aws-sdk@2.1111.0:
     querystring "0.2.0"
     sax "1.2.1"
     url "0.10.3"
-    uuid "3.3.2"
+    uuid "8.0.0"
     xml2js "0.4.19"
 
 axios@^0.24.0:
@@ -1296,9 +1304,9 @@ babel-walk@3.0.0-canary-5:
     "@babel/types" "^7.9.6"
 
 balanced-match@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
-  integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
 base32.js@0.0.1:
   version "0.0.1"
@@ -1327,11 +1335,6 @@ bcryptjs@2.4.3:
   resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
   integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=
 
-big-integer@^1.6.16:
-  version "1.6.48"
-  resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
-  integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
-
 big-integer@^1.6.17:
   version "1.6.51"
   resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
@@ -1397,27 +1400,20 @@ brace-expansion@^1.1.7:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
-braces@^3.0.1, braces@~3.0.2:
+brace-expansion@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+  integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+  dependencies:
+    balanced-match "^1.0.0"
+
+braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
   integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
   dependencies:
     fill-range "^7.0.1"
 
-broadcast-channel@4.10.0:
-  version "4.10.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198"
-  integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ==
-  dependencies:
-    "@babel/runtime" "^7.16.0"
-    detect-node "^2.1.0"
-    microseconds "0.2.0"
-    nano-time "1.0.0"
-    oblivious-set "1.0.0"
-    p-queue "6.6.2"
-    rimraf "3.0.2"
-    unload "2.3.1"
-
 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"
@@ -1490,10 +1486,10 @@ bufferutil@^4.0.1:
   dependencies:
     node-gyp-build "~3.7.0"
 
-bull@4.8.1:
-  version "4.8.1"
-  resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.1.tgz#83daaefc3118876450b21d7a02bc11ea28a2440e"
-  integrity sha512-ojH5AfOchKQsQwwE+thViS1pMpvREGC+Ov1+3HXsQqn5Q27ZSGkgMriMqc6c9J9rvQ/+D732pZE+TN1+2LRWVg==
+bull@4.8.3:
+  version "4.8.3"
+  resolved "https://registry.yarnpkg.com/bull/-/bull-4.8.3.tgz#4ab67029fee1183dcb7185895b20dc08c02d6bf2"
+  integrity sha512-oOHr+KTLu3JM5V9TXsg18/1xyVQceoYCFiGrXZOpu9abZn3W3vXJtMBrwB6Yvl/RxSKVVBpoa25RF/ya3750qg==
   dependencies:
     cron-parser "^4.2.1"
     debuglog "^1.0.0"
@@ -1586,11 +1582,6 @@ cacheable-request@^7.0.2:
     normalize-url "^6.0.1"
     responselike "^2.0.0"
 
-cafy@15.2.1:
-  version "15.2.1"
-  resolved "https://registry.yarnpkg.com/cafy/-/cafy-15.2.1.tgz#5a55eaeb721c604c7dca652f3d555c392e5f995a"
-  integrity sha512-g2zOmFb63p6XcZ/zeMWKYP8YKQYNWnhJmi6K71Ql4EAFTAay31xF0PBPtdBCCfQ0fiETgWTMxKtySAVI/Od6aQ==
-
 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"
@@ -1678,7 +1669,7 @@ chalk@^4.0.0, chalk@^4.1.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@^4.1.2:
+chalk@^4.0.2, chalk@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
   integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@@ -1720,7 +1711,7 @@ cheerio@0.22.0:
     lodash.reject "^4.4.0"
     lodash.some "^4.4.0"
 
-chokidar@3.5.3, chokidar@^3.3.1, chokidar@^3.5.2:
+chokidar@3.5.3, chokidar@^3.3.1, chokidar@^3.5.3:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
   integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
@@ -1859,10 +1850,10 @@ color-support@^1.1.2:
   resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
   integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
 
-color@^4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884"
-  integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==
+color@^4.0.1:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a"
+  integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==
   dependencies:
     color-convert "^2.0.1"
     color-string "^1.9.0"
@@ -1884,10 +1875,10 @@ commander@^2.19.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
   integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
 
-commander@^8.2.0:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
-  integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
+commander@^9.0.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9"
+  integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==
 
 compress-commons@^4.1.0:
   version "4.1.1"
@@ -2079,11 +2070,6 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
-data-uri-to-buffer@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
-  integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==
-
 data-uri-to-buffer@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b"
@@ -2124,6 +2110,13 @@ debug@4.3.3:
   dependencies:
     ms "2.1.2"
 
+debug@4.3.4, debug@^4.3.3, 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"
+
 debug@^3.1.0, debug@^3.2.7:
   version "3.2.7"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@@ -2145,13 +2138,6 @@ debug@^4.3.2:
   dependencies:
     ms "2.1.2"
 
-debug@^4.3.3:
-  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"
-
 debuglog@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -2261,26 +2247,16 @@ destroy@^1.0.4:
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
   integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
 
-detect-file@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
-  integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
+detect-libc@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
 
 detect-libc@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.0.tgz#c528bc09bc6d1aa30149228240917c225448f204"
   integrity sha512-S55LzUl8HUav8l9E2PBTlC5PAJrHK7tkM+XXFGD+fbsbkTzhCpG6K05LxJcUOEWzMa4v6ptcMZ9s3fOdJDu0Zw==
 
-detect-libc@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd"
-  integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==
-
-detect-node@2.1.0, detect-node@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
-  integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
-
 dicer@0.2.5:
   version "0.2.5"
   resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
@@ -2470,12 +2446,12 @@ ee-first@1.1.1:
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
   integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
 
-ejs@^3.1.6:
-  version "3.1.6"
-  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.6.tgz#5bfd0a0689743bb5268b3550cceeebbc1702822a"
-  integrity sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==
+ejs@^3.1.7:
+  version "3.1.8"
+  resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.8.tgz#758d32910c78047585c7ef1f92f9ee041c1c190b"
+  integrity sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==
   dependencies:
-    jake "^10.6.1"
+    jake "^10.8.5"
 
 emoji-regex@^8.0.0:
   version "8.0.0"
@@ -2700,22 +2676,17 @@ eslint-visitor-keys@^2.0.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
   integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
 
-eslint-visitor-keys@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186"
-  integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==
-
 eslint-visitor-keys@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
   integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
 
-eslint@8.13.0:
-  version "8.13.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7"
-  integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==
+eslint@8.17.0:
+  version "8.17.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.17.0.tgz#1cfc4b6b6912f77d24b874ca1506b0fe09328c21"
+  integrity sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==
   dependencies:
-    "@eslint/eslintrc" "^1.2.1"
+    "@eslint/eslintrc" "^1.3.0"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -2726,14 +2697,14 @@ eslint@8.13.0:
     eslint-scope "^7.1.1"
     eslint-utils "^3.0.0"
     eslint-visitor-keys "^3.3.0"
-    espree "^9.3.1"
+    espree "^9.3.2"
     esquery "^1.4.0"
     esutils "^2.0.2"
     fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
     functional-red-black-tree "^1.0.1"
     glob-parent "^6.0.1"
-    globals "^13.6.0"
+    globals "^13.15.0"
     ignore "^5.2.0"
     import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
@@ -2742,7 +2713,7 @@ eslint@8.13.0:
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.4.1"
     lodash.merge "^4.6.2"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     natural-compare "^1.4.0"
     optionator "^0.9.1"
     regexpp "^3.2.0"
@@ -2751,18 +2722,13 @@ eslint@8.13.0:
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
-esm@^3.2.22:
-  version "3.2.25"
-  resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
-  integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
-
-espree@^9.3.1:
-  version "9.3.1"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd"
-  integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==
+espree@^9.3.2:
+  version "9.3.2"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
+  integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
   dependencies:
-    acorn "^8.7.0"
-    acorn-jsx "^5.3.1"
+    acorn "^8.7.1"
+    acorn-jsx "^5.3.2"
     eslint-visitor-keys "^3.3.0"
 
 esprima@^4.0.1:
@@ -2809,7 +2775,7 @@ event-target-shim@^5.0.0:
   resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
   integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
 
-eventemitter3@^4.0.4, eventemitter3@^4.0.7:
+eventemitter3@^4.0.7:
   version "4.0.7"
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
@@ -2844,13 +2810,6 @@ expand-template@^2.0.3:
   resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
   integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
 
-expand-tilde@^2.0.0, expand-tilde@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
-  integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
-  dependencies:
-    homedir-polyfill "^1.0.1"
-
 ext@^1.1.2:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
@@ -2897,6 +2856,17 @@ fast-glob@^3.1.1:
     micromatch "^4.0.2"
     picomatch "^2.2.1"
 
+fast-glob@^3.2.9:
+  version "3.2.11"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+  integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+  dependencies:
+    "@nodelib/fs.stat" "^2.0.2"
+    "@nodelib/fs.walk" "^1.2.3"
+    glob-parent "^5.1.2"
+    merge2 "^1.3.0"
+    micromatch "^4.0.4"
+
 fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -2926,11 +2896,6 @@ feed@4.2.2:
   dependencies:
     xml-js "^1.6.11"
 
-fetch-blob@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-2.1.2.tgz#a7805db1361bd44c1ef62bb57fb5fe8ea173ef3c"
-  integrity sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==
-
 fetch-blob@^3.1.2, fetch-blob@^3.1.4:
   version "3.1.4"
   resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.1.4.tgz#e8c6567f80ad7fc22fd302e7dcb72bafde9c1717"
@@ -2946,21 +2911,21 @@ file-entry-cache@^6.0.1:
   dependencies:
     flat-cache "^3.0.4"
 
-file-type@17.1.1:
-  version "17.1.1"
-  resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.1.tgz#24c59bc663df0c0c181b31dfacde25e06431afbe"
-  integrity sha512-heRUMZHby2Qj6wZAA3YHeMlRmZNQTcb6VxctkGmM+mcM6ROQKvHpr7SS6EgdfEhH+s25LDshBjvPx/Ecm+bOVQ==
+file-type@17.1.2:
+  version "17.1.2"
+  resolved "https://registry.yarnpkg.com/file-type/-/file-type-17.1.2.tgz#9257437a64e0c3623f70d9f27430522d978b1384"
+  integrity sha512-3thBUSfa9YEUEGO/NAAiQGvjujZxZiJTF6xNwyDn6kB0NcEtwMn5ttkGG9jGwm/Nt/t8U1bpBNqyBNZCz4F4ig==
   dependencies:
     readable-web-to-node-stream "^3.0.2"
     strtok3 "^7.0.0-alpha.7"
     token-types "^5.0.0-alpha.2"
 
 filelist@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.2.tgz#80202f21462d4d1c2e214119b1807c1bc0380e5b"
-  integrity sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83"
+  integrity sha512-LwjCsruLWQULGYKy7TX0OPtrL9kLpojOFKc5VCTxdFTV7w5zbsgqVKfnkKG7Qgjtq50gKfO56hJv88OfcGb70Q==
   dependencies:
-    minimatch "^3.0.4"
+    minimatch "^5.0.1"
 
 fill-range@^7.0.1:
   version "7.0.1"
@@ -2969,14 +2934,6 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
-find-node-modules@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c"
-  integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug==
-  dependencies:
-    findup-sync "^4.0.0"
-    merge "^2.1.0"
-
 find-up@5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
@@ -3000,16 +2957,6 @@ find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
-findup-sync@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0"
-  integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==
-  dependencies:
-    detect-file "^1.0.0"
-    is-glob "^4.0.0"
-    micromatch "^4.0.2"
-    resolve-dir "^1.0.1"
-
 flat-cache@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@@ -3210,7 +3157,7 @@ github-from-package@0.0.0:
   resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
   integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=
 
-glob-parent@^5.1.0, glob-parent@~5.1.0:
+glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -3248,37 +3195,10 @@ glob@^7.1.3, glob@^7.1.4:
     once "^1.3.0"
     path-is-absolute "^1.0.0"
 
-global-modules@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
-  integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
-  dependencies:
-    global-prefix "^1.0.1"
-    is-windows "^1.0.1"
-    resolve-dir "^1.0.0"
-
-global-prefix@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
-  integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
-  dependencies:
-    expand-tilde "^2.0.2"
-    homedir-polyfill "^1.0.1"
-    ini "^1.3.4"
-    is-windows "^1.0.1"
-    which "^1.2.14"
-
-globals@^13.6.0:
-  version "13.7.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795"
-  integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==
-  dependencies:
-    type-fest "^0.20.2"
-
-globals@^13.9.0:
-  version "13.9.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb"
-  integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==
+globals@^13.15.0:
+  version "13.15.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac"
+  integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==
   dependencies:
     type-fest "^0.20.2"
 
@@ -3294,6 +3214,18 @@ globby@^11.0.4:
     merge2 "^1.3.0"
     slash "^3.0.0"
 
+globby@^11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+  integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+  dependencies:
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.2.9"
+    ignore "^5.2.0"
+    merge2 "^1.4.1"
+    slash "^3.0.0"
+
 got@11.5.1:
   version "11.5.1"
   resolved "https://registry.yarnpkg.com/got/-/got-11.5.1.tgz#bf098a270fe80b3fb88ffd5a043a59ebb0a391db"
@@ -3311,10 +3243,10 @@ got@11.5.1:
     p-cancelable "^2.0.0"
     responselike "^2.0.0"
 
-got@12.0.3:
-  version "12.0.3"
-  resolved "https://registry.yarnpkg.com/got/-/got-12.0.3.tgz#c7314daab26d42039e624adbf98f6d442e5de749"
-  integrity sha512-hmdcXi/S0gcAtDg4P8j/rM7+j3o1Aq6bXhjxkDhRY2ipe7PHpvx/14DgTY2czHOLaGeU8VRvRecidwfu9qdFug==
+got@12.1.0:
+  version "12.1.0"
+  resolved "https://registry.yarnpkg.com/got/-/got-12.1.0.tgz#099f3815305c682be4fd6b0ee0726d8e4c6b0af4"
+  integrity sha512-hBv2ty9QN2RdbJJMK3hesmSkFTjVIHyIDDbssCKnSmq62edGgImJWD10Eb1k77TiV1bxloxqcFAVK8+9pkhOig==
   dependencies:
     "@sindresorhus/is" "^4.6.0"
     "@szmarczak/http-timer" "^5.0.1"
@@ -3345,11 +3277,6 @@ graceful-fs@^4.2.6:
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
   integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==
 
-growl@1.10.5:
-  version "1.10.5"
-  resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
-  integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
-
 has-bigints@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
@@ -3404,13 +3331,6 @@ highlight.js@^10.7.1:
   resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360"
   integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg==
 
-homedir-polyfill@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
-  integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
-  dependencies:
-    parse-passwd "^1.0.0"
-
 hpagent@0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/hpagent/-/hpagent-0.1.2.tgz#cab39c66d4df2d4377dbd212295d878deb9bdaa9"
@@ -3507,15 +3427,6 @@ http-proxy-agent@^5.0.0:
     agent-base "6"
     debug "4"
 
-http-signature@1.3.6:
-  version "1.3.6"
-  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"
-  integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==
-  dependencies:
-    assert-plus "^1.0.0"
-    jsprim "^2.0.2"
-    sshpk "^1.14.1"
-
 http2-wrapper@^1.0.0-beta.5.0:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d"
@@ -3600,11 +3511,6 @@ ignore@^5.1.4:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
   integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
-ignore@^5.1.8:
-  version "5.1.9"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb"
-  integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==
-
 ignore@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
@@ -3700,10 +3606,10 @@ ip-address@^7.1.0:
     jsbn "1.1.0"
     sprintf-js "1.1.2"
 
-ip-cidr@3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375"
-  integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ==
+ip-cidr@3.0.10:
+  version "3.0.10"
+  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.10.tgz#e1a039705196d84b43858f81a243fd70def9cefc"
+  integrity sha512-PXSsrRYirsuaCI1qBVyVXRLUIpNzxm76eHd3UvN5NXTMUG85GWGZpr6P+70mimc5e7Nfh/tShmjk0oSywErMWg==
   dependencies:
     ip-address "^7.1.0"
     jsbn "^1.1.0"
@@ -3848,13 +3754,6 @@ is-negative-zero@^2.0.1:
   resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
   integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
 
-is-number-like@^1.0.3:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3"
-  integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==
-  dependencies:
-    lodash.isfinite "^3.3.2"
-
 is-number-object@^1.0.4:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
@@ -3962,11 +3861,6 @@ is-whitespace@^0.3.0:
   resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f"
   integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38=
 
-is-windows@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
 isarray@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -3982,13 +3876,13 @@ isexe@^2.0.0:
   resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
   integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
 
-jake@^10.6.1:
-  version "10.8.2"
-  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.2.tgz#ebc9de8558160a66d82d0eadc6a2e58fbc500a7b"
-  integrity sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==
+jake@^10.8.5:
+  version "10.8.5"
+  resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
+  integrity sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==
   dependencies:
-    async "0.9.x"
-    chalk "^2.4.2"
+    async "^3.2.3"
+    chalk "^4.0.2"
     filelist "^1.0.1"
     minimatch "^3.0.4"
 
@@ -4117,7 +4011,7 @@ json5-loader@4.0.1:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
-json5@2.2.1:
+json5@2.2.1, json5@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
   integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
@@ -4152,30 +4046,30 @@ jsonfile@^5.0.0:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
-jsonld@5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-5.2.0.tgz#d1e8af38a334cb95edf0f2ae4e2b58baf8d2b5a9"
-  integrity sha512-JymgT6Xzk5CHEmHuEyvoTNviEPxv6ihLWSPu1gFdtjSAyM6cFqNrv02yS/SIur3BBIkCf0HjizRc24d8/FfQKw==
+jsonld@6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-6.0.0.tgz#560a8a871dce72aba5d4c6b08356438d863d62fb"
+  integrity sha512-1SkN2RXhMCTCSkX+bzHvr9ycM2HTmjWyV41hn2xG7k6BqlCgRjw0zHmuqfphjBRPqi1gKMIqgBCe/0RZMcWrAA==
   dependencies:
-    "@digitalbazaar/http-client" "^1.1.0"
+    "@digitalbazaar/http-client" "^3.2.0"
     canonicalize "^1.0.1"
     lru-cache "^6.0.0"
     rdf-canonize "^3.0.0"
 
-jsprim@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
-  integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==
+jsprim@^1.2.2:
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
+  integrity sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==
   dependencies:
     assert-plus "1.0.0"
     extsprintf "1.3.0"
     json-schema "0.4.0"
     verror "1.10.0"
 
-jsrsasign@8.0.20:
-  version "8.0.20"
-  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-8.0.20.tgz#37d8029c9d8f794d8ac8d8998bce319921491f11"
-  integrity sha512-JTXt9+nqdynIB8wFsS6e8ffHhIjilhywXwdaEVHSj9OVmwldG2H0EoCqkQ+KXkm2tVqREfH/HEmklY4k1/6Rcg==
+jsrsasign@10.5.24:
+  version "10.5.24"
+  resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.5.24.tgz#2d159e1756b2268682c6eb5e147184e33e946b1c"
+  integrity sha512-0i/UHRgJZifp/YmoXHyNQXUY4eKWiSd7YxuD7oKEw9mlqgr51hg9lZQw2nlEDvwHDh7pyj6ZjYlxldlW27xb/Q==
 
 jstransformer@1.0.0:
   version "1.0.0"
@@ -4368,18 +4262,18 @@ koa@2.13.4, koa@^2.13.1:
     type-is "^1.6.16"
     vary "^1.1.2"
 
-ky-universal@^0.8.2:
-  version "0.8.2"
-  resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.8.2.tgz#edc398d54cf495d7d6830aa1ab69559a3cc7f824"
-  integrity sha512-xe0JaOH9QeYxdyGLnzUOVGK4Z6FGvDVzcXFTdrYA1f33MZdEa45sUDaMBy98xQMcsd2XIBrTXRrRYnegcSdgVQ==
+ky-universal@^0.10.1:
+  version "0.10.1"
+  resolved "https://registry.yarnpkg.com/ky-universal/-/ky-universal-0.10.1.tgz#778881e098f6e3c52a87b382d9acca54d22bb0d3"
+  integrity sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g==
   dependencies:
     abort-controller "^3.0.0"
-    node-fetch "3.0.0-beta.9"
+    node-fetch "^3.2.2"
 
-ky@^0.25.1:
-  version "0.25.1"
-  resolved "https://registry.yarnpkg.com/ky/-/ky-0.25.1.tgz#0df0bd872a9cc57e31acd5dbc1443547c881bfbc"
-  integrity sha512-PjpCEWlIU7VpiMVrTwssahkYXX1by6NCT0fhTUX34F3DTinARlgMpriuroolugFPcMgpPWrOW4mTb984Qm1RXA==
+ky@^0.30.0:
+  version "0.30.0"
+  resolved "https://registry.yarnpkg.com/ky/-/ky-0.30.0.tgz#a3d293e4f6c4604a9a4694eceb6ce30e73d27d64"
+  integrity sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==
 
 lazystream@^1.0.0:
   version "1.0.1"
@@ -4485,11 +4379,6 @@ lodash.isequal@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
   integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
 
-lodash.isfinite@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3"
-  integrity sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=
-
 lodash.isplainobject@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
@@ -4535,7 +4424,7 @@ lodash.union@^4.6.0:
   resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88"
   integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=
 
-lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21:
+lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.21:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -4573,11 +4462,6 @@ lru-cache@^6.0.0:
   dependencies:
     yallist "^4.0.0"
 
-lru-cache@^7.4.0:
-  version "7.8.1"
-  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.8.1.tgz#68ee3f4807a57d2ba185b7fd90827d5c21ce82bb"
-  integrity sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==
-
 luxon@^1.28.0:
   version "1.28.0"
   resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf"
@@ -4625,27 +4509,22 @@ merge-stream@^2.0.0:
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
-merge2@^1.3.0:
+merge2@^1.3.0, merge2@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
-merge@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98"
-  integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==
-
 methods@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
   integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
 
-mfm-js@0.21.0:
-  version "0.21.0"
-  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772"
-  integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA==
+mfm-js@0.22.1:
+  version "0.22.1"
+  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.1.tgz#ad5f0b95cc903ca5a5e414e2edf64ac4648dc8c2"
+  integrity sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ==
   dependencies:
-    twemoji-parser "13.1.x"
+    twemoji-parser "14.0.x"
 
 micromatch@^4.0.0, micromatch@^4.0.2:
   version "4.0.2"
@@ -4655,10 +4534,13 @@ micromatch@^4.0.0, micromatch@^4.0.2:
     braces "^3.0.1"
     picomatch "^2.0.5"
 
-microseconds@0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
-  integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
+micromatch@^4.0.4:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+  integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+  dependencies:
+    braces "^3.0.2"
+    picomatch "^2.3.1"
 
 mime-db@1.44.0:
   version "1.44.0"
@@ -4704,21 +4586,14 @@ minimalistic-assert@^1.0.0:
   resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
   integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
 
-minimatch@4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"
-  integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==
+minimatch@5.0.1, minimatch@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+  integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
   dependencies:
-    brace-expansion "^1.1.7"
+    brace-expansion "^2.0.1"
 
-minimatch@^3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
-  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-minimatch@^3.1.2:
+minimatch@^3.0.4, minimatch@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
   integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -4815,40 +4690,38 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.3:
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
   integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
 
-mocha@9.2.2:
-  version "9.2.2"
-  resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9"
-  integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==
+mocha@10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9"
+  integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==
   dependencies:
     "@ungap/promise-all-settled" "1.1.2"
     ansi-colors "4.1.1"
     browser-stdout "1.3.1"
     chokidar "3.5.3"
-    debug "4.3.3"
+    debug "4.3.4"
     diff "5.0.0"
     escape-string-regexp "4.0.0"
     find-up "5.0.0"
     glob "7.2.0"
-    growl "1.10.5"
     he "1.2.0"
     js-yaml "4.1.0"
     log-symbols "4.1.0"
-    minimatch "4.2.1"
+    minimatch "5.0.1"
     ms "2.1.3"
-    nanoid "3.3.1"
+    nanoid "3.3.3"
     serialize-javascript "6.0.0"
     strip-json-comments "3.1.1"
     supports-color "8.1.1"
-    which "2.0.2"
-    workerpool "6.2.0"
+    workerpool "6.2.1"
     yargs "16.2.0"
     yargs-parser "20.2.4"
     yargs-unparser "2.0.0"
 
 moment@^2.22.2:
-  version "2.24.0"
-  resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
-  integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
+  version "2.29.3"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.3.tgz#edd47411c322413999f7a5940d526de183c031f3"
+  integrity sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==
 
 ms@2.0.0:
   version "2.0.0"
@@ -4899,10 +4772,10 @@ multer@1.4.4:
     type-is "^1.6.4"
     xtend "^4.0.0"
 
-mylas@^2.1.4:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.5.tgz#7ccf41ec5a93ab2d63fc3678abf1942c0e7bdeb1"
-  integrity sha512-7ZyrJux1lipSR45IxDvWz7zJOXWTazTFCqD4/p8XBF4O+mtJwf7QpMWTH+jE4lV9O2I38xcpS0KTIp7GwhUTmA==
+mylas@^2.1.9:
+  version "2.1.9"
+  resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.9.tgz#8329626f95c0ce522ca7d3c192eca6221d172cdc"
+  integrity sha512-pa+cQvmhoM8zzgitPYZErmDt9EdTNVnXsH1XFjMeM4TyG4FFcgxrvK1+jwabVFwUOEDaSWuXBMjg43kqt/Ydlg==
 
 mz@^2.4.0, mz@^2.7.0:
   version "2.7.0"
@@ -4918,14 +4791,12 @@ nan@^2.14.2, nan@^2.15.0:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee"
   integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==
 
-nano-time@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
-  integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=
-  dependencies:
-    big-integer "^1.6.16"
+nanoid@3.3.3:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+  integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
 
-nanoid@3.3.1, nanoid@^3.1.30:
+nanoid@^3.1.30:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
   integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
@@ -4976,7 +4847,7 @@ node-abi@^3.3.0:
   dependencies:
     semver "^7.3.5"
 
-node-addon-api@^4.3.0:
+node-addon-api@^4.2.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f"
   integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==
@@ -4995,18 +4866,10 @@ node-fetch@*:
     fetch-blob "^3.1.4"
     formdata-polyfill "^4.0.10"
 
-node-fetch@3.0.0-beta.9:
-  version "3.0.0-beta.9"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.0.0-beta.9.tgz#0a7554cfb824380dd6812864389923c783c80d9b"
-  integrity sha512-RdbZCEynH2tH46+tj0ua9caUHVWrd/RHnRfvly2EVdqGmI3ndS1Vn/xjm5KuGejDt2RNDQsVRLPNd2QPwcewVg==
-  dependencies:
-    data-uri-to-buffer "^3.0.1"
-    fetch-blob "^2.1.1"
-
-node-fetch@3.2.3:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.3.tgz#a03c9cc2044d21d1a021566bd52f080f333719a6"
-  integrity sha512-AXP18u4pidSZ1xYXRDPY/8jdv3RAozIt/WLNR/MBGZAz+xjtlr90RvCnsvHQRiXyWliZF/CpytExp32UU67/SA==
+node-fetch@3.2.6, node-fetch@^3.2.2:
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.6.tgz#6d4627181697a9d9674aae0d61548e0d629b31b9"
+  integrity sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==
   dependencies:
     data-uri-to-buffer "^4.0.0"
     fetch-blob "^3.1.4"
@@ -5045,10 +4908,10 @@ node-gyp@^8.4.1:
     tar "^6.1.2"
     which "^2.0.2"
 
-nodemailer@6.7.3:
-  version "6.7.3"
-  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.3.tgz#b73f9a81b9c8fa8acb4ea14b608f5e725ea8e018"
-  integrity sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==
+nodemailer@6.7.5:
+  version "6.7.5"
+  resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.7.5.tgz#b30b1566f5fa2249f7bd49ced4c58bec6b25915e"
+  integrity sha512-6VtMpwhsrixq1HDYSBBHvW0GwiWawE75dS3oal48VqRhUvKJNnKnJo2RI/bCVQubj1vgrgscMNW4DHaD6xtMCg==
 
 nofilter@^2.0.3:
   version "2.0.3"
@@ -5175,11 +5038,6 @@ object.values@^1.1.5:
     define-properties "^1.1.3"
     es-abstract "^1.19.1"
 
-oblivious-set@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
-  integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
-
 on-finished@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
@@ -5327,14 +5185,6 @@ p-map@^4.0.0:
   dependencies:
     aggregate-error "^3.0.0"
 
-p-queue@6.6.2:
-  version "6.6.2"
-  resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
-  integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
-  dependencies:
-    eventemitter3 "^4.0.4"
-    p-timeout "^3.2.0"
-
 p-timeout@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
@@ -5364,11 +5214,6 @@ parent-module@^1.0.0:
   dependencies:
     callsites "^3.0.0"
 
-parse-passwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
-  integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
-
 parse-srcset@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
@@ -5507,11 +5352,23 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1:
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
   integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
 
+picomatch@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
 pify@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
   integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
 
+plimit-lit@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.2.6.tgz#8c1336f26a042b6e9f1acc665be5eee4c2a55fb3"
+  integrity sha512-EuVnKyDeFgr58aidKf2G7DI41r23bxphlvBKAZ8e8dT9of0Ez2g9w6JbJGUP1YBNC2yG9+ZCCbjLj4yS1P5Gzw==
+  dependencies:
+    queue-lit "^1.2.7"
+
 pluralize@^8.0.0:
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
@@ -5527,14 +5384,6 @@ pngjs@^5.0.0:
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
   integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
 
-portscanner@2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1"
-  integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==
-  dependencies:
-    async "^2.6.0"
-    is-number-like "^1.0.3"
-
 postcss@^8.3.11:
   version "8.3.11"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.11.tgz#c3beca7ea811cd5e1c4a3ec6d2e7599ef1f8f858"
@@ -5566,10 +5415,10 @@ postgres-interval@^1.1.0:
   dependencies:
     xtend "^4.0.0"
 
-prebuild-install@^7.0.1:
-  version "7.0.1"
-  resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870"
-  integrity sha512-QBSab31WqkyxpnMWQxubYAHR5S9B2+r81ucocew34Fkl98FhvKIF50jIJnNOBmAZfyNV7vE5T6gd3hTVWgY6tg==
+prebuild-install@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.0.tgz#991b6ac16c81591ba40a6d5de93fb33673ac1370"
+  integrity sha512-CNcMgI1xBypOyGqjp3wOc8AAo1nMhZS3Cwd3iHIxOdAUbb+YxdNuM4Z5iIrZ8RLvOsf3F3bl7b7xGq6DjQoNYA==
   dependencies:
     detect-libc "^2.0.0"
     expand-template "^2.0.3"
@@ -5828,6 +5677,11 @@ querystring@0.2.0:
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
   integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=
 
+queue-lit@^1.2.7:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.2.7.tgz#69081656c9e7b81f09770bb2de6aa007f1a90763"
+  integrity sha512-K/rTdggORRcmf3+c89ijPlgJ/ldGP4oBj6Sm7VcTup4B2clf03Jo8QaXTnMst4EEQwkUbOZFN4frKocq2I85gw==
+
 quick-lru@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
@@ -6006,11 +5860,6 @@ reflect-metadata@0.1.13, reflect-metadata@^0.1.13:
   resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
   integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==
 
-regenerator-runtime@^0.13.4:
-  version "0.13.7"
-  resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
-  integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
-
 regexpp@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
@@ -6053,14 +5902,6 @@ resolve-alpn@^1.2.0:
   resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
   integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==
 
-resolve-dir@^1.0.0, resolve-dir@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
-  integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
-  dependencies:
-    expand-tilde "^2.0.0"
-    global-modules "^1.0.0"
-
 resolve-from@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -6115,7 +5956,7 @@ rimraf@2:
   dependencies:
     glob "^7.1.3"
 
-rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
+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==
@@ -6213,12 +6054,12 @@ seedrandom@3.0.5:
   resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
   integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
 
-semver@7.3.6:
-  version "7.3.6"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.6.tgz#5d73886fb9c0c6602e79440b97165c29581cbb2b"
-  integrity sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==
+semver@7.3.7, 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 "^7.4.0"
+    lru-cache "^6.0.0"
 
 semver@^5.6.0:
   version "5.7.1"
@@ -6274,17 +6115,17 @@ sha.js@^2.4.11:
     inherits "^2.0.1"
     safe-buffer "^5.0.1"
 
-sharp@0.30.3:
-  version "0.30.3"
-  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37"
-  integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg==
+sharp@0.29.3:
+  version "0.29.3"
+  resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2"
+  integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA==
   dependencies:
-    color "^4.2.1"
-    detect-libc "^2.0.1"
-    node-addon-api "^4.3.0"
-    prebuild-install "^7.0.1"
+    color "^4.0.1"
+    detect-libc "^1.0.3"
+    node-addon-api "^4.2.0"
+    prebuild-install "^7.0.0"
     semver "^7.3.5"
-    simple-get "^4.0.1"
+    simple-get "^4.0.0"
     tar-fs "^2.1.1"
     tunnel-agent "^0.6.0"
 
@@ -6329,7 +6170,7 @@ simple-concat@^1.0.0:
   resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
   integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
 
-simple-get@^4.0.0, simple-get@^4.0.1:
+simple-get@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
   integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
@@ -6588,16 +6429,17 @@ style-loader@3.3.1:
   resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
   integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==
 
-summaly@2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.0.tgz#ec5af6e84857efcb6c844d896e83569e64a923ea"
-  integrity sha512-IzvO2s7yj/PUyH42qWjVjSPpIiPlgTRWGh33t4cIZKOqPQJ2INo7e83hXhHFr4hXTb3JRcIdCuM1ELjlrujiUQ==
+summaly@2.5.1:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/summaly/-/summaly-2.5.1.tgz#742fe6631987f84ad2e95d2b0f7902ec57e0f6b3"
+  integrity sha512-WWvl7rLs3wm61Xc2JqgTbSuqtIOmGqKte+rkbnxe6ISy4089lQ+7F2ajooQNee6PWHl9kZ27SDd1ZMoL3/6R4A==
   dependencies:
     cheerio "0.22.0"
     debug "4.3.3"
     escape-regexp "0.0.1"
     got "11.5.1"
     html-entities "2.3.2"
+    iconv-lite "0.6.3"
     jschardet "3.0.0"
     koa "2.13.4"
     private-ip "2.3.3"
@@ -6642,10 +6484,10 @@ syslog-pro@1.0.0:
   dependencies:
     moment "^2.22.2"
 
-systeminformation@5.11.9:
-  version "5.11.9"
-  resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d"
-  integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA==
+systeminformation@5.11.16:
+  version "5.11.16"
+  resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.16.tgz#5f6fda2447fafe204bd2ab543475f1ffa8c14a85"
+  integrity sha512-/a1VfP9WELKLT330yhAHJ4lWCXRYynel1kMMHKc/qdzCgDt3BIcMlo+3tKcTiRHFefjV3fz4AvqMx7dGO/72zw==
 
 tapable@^2.2.0:
   version "2.2.0"
@@ -6803,22 +6645,22 @@ trace-redirect@1.0.6:
   resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
   integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
 
-ts-loader@9.2.8:
-  version "9.2.8"
-  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48"
-  integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==
+ts-loader@9.3.0:
+  version "9.3.0"
+  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.0.tgz#980f4dbfb60e517179e15e10ed98e454b132159f"
+  integrity sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog==
   dependencies:
     chalk "^4.1.0"
     enhanced-resolve "^5.0.0"
     micromatch "^4.0.0"
     semver "^7.3.4"
 
-ts-node@10.7.0:
-  version "10.7.0"
-  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5"
-  integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
+ts-node@10.8.1:
+  version "10.8.1"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.8.1.tgz#ea2bd3459011b52699d7e88daa55a45a1af4f066"
+  integrity sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==
   dependencies:
-    "@cspotcode/source-map-support" "0.7.0"
+    "@cspotcode/source-map-support" "^0.8.0"
     "@tsconfig/node10" "^1.0.7"
     "@tsconfig/node12" "^1.0.7"
     "@tsconfig/node14" "^1.0.0"
@@ -6829,22 +6671,31 @@ ts-node@10.7.0:
     create-require "^1.1.0"
     diff "^4.0.1"
     make-error "^1.1.1"
-    v8-compile-cache-lib "^3.0.0"
+    v8-compile-cache-lib "^3.0.1"
     yn "3.1.1"
 
-tsc-alias@1.4.1:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.4.1.tgz#6a6075dd94267d9befdad1431f410bd0b8819805"
-  integrity sha512-nHTR8qvM/LiYI8Fx6UrzAQXRngAuE2PEK+n9uXmQY6fN+oLZhweNFkCLbyxKDmlLfYnclSuaR+dSuvRd7FUu8Q==
+tsc-alias@1.6.9:
+  version "1.6.9"
+  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.9.tgz#d04d95124b95ad8eea55e52d45cf65a744c26baa"
+  integrity sha512-5lv5uAHn0cgxY1XfpXIdquUSz2xXq3ryQyNtxC6DYH7YT5rt/W+9Gsft2uyLFTh+ozk4qU8iCSP3VemjT69xlQ==
   dependencies:
-    chokidar "^3.5.2"
-    commander "^8.2.0"
-    find-node-modules "^2.1.2"
+    chokidar "^3.5.3"
+    commander "^9.0.0"
     globby "^11.0.4"
-    mylas "^2.1.4"
+    mylas "^2.1.9"
     normalize-path "^3.0.0"
+    plimit-lit "^1.2.6"
 
-tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1:
+tsconfig-paths@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64"
+  integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q==
+  dependencies:
+    json5 "^2.2.1"
+    minimist "^1.2.6"
+    strip-bom "^3.0.0"
+
+tsconfig-paths@^3.14.1:
   version "3.14.1"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
   integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==
@@ -6888,12 +6739,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
   integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
 
-twemoji-parser@13.1.0, twemoji-parser@13.1.x:
-  version "13.1.0"
-  resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4"
-  integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg==
-
-twemoji-parser@14.0.0:
+twemoji-parser@14.0.0, twemoji-parser@14.0.x:
   version "14.0.0"
   resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62"
   integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==
@@ -6952,10 +6798,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typeorm@0.3.5:
-  version "0.3.5"
-  resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.5.tgz#8fe50d517de5ec6f4b38856ea0f180e4a60cf7e4"
-  integrity sha512-KL4c8nQqouHaXs4m1J3xh7oXWqX4+A9poExbceLxBRtlavpJQYqiSnqt3JYGpy7Tl9vD5DG5DrmZrSslTkkW5Q==
+typeorm@0.3.6:
+  version "0.3.6"
+  resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.6.tgz#65203443a1b684bb746785913fe2b0877aa991c0"
+  integrity sha512-DRqgfqcelMiGgWSMbBmVoJNFN2nPNA3EeY2gC324ndr2DZoGRTb9ILtp2oGVGnlA+cu5zgQ6it5oqKFNkte7Aw==
   dependencies:
     "@sqltools/formatter" "^1.2.2"
     app-root-path "^3.0.0"
@@ -6975,10 +6821,10 @@ typeorm@0.3.5:
     xml2js "^0.4.23"
     yargs "^17.3.1"
 
-typescript@4.6.3:
-  version "4.6.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
-  integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
+typescript@4.7.3:
+  version "4.7.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
+  integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
 
 ulid@2.3.0:
   version "2.3.0"
@@ -6995,6 +6841,11 @@ unbox-primitive@^1.0.1:
     has-symbols "^1.0.2"
     which-boxed-primitive "^1.0.2"
 
+undici@^5.2.0:
+  version "5.4.0"
+  resolved "https://registry.yarnpkg.com/undici/-/undici-5.4.0.tgz#c474fae02743d4788b96118d46008a24195024d2"
+  integrity sha512-A1SRXysDg7J+mVP46jF+9cKANw0kptqSFZ8tGyL+HBiv0K1spjxPX8Z4EGu+Eu6pjClJUBdnUPlxrOafR668/g==
+
 unique-filename@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
@@ -7014,14 +6865,6 @@ universalify@^0.1.0, universalify@^0.1.2:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
-unload@2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/unload/-/unload-2.3.1.tgz#9d16862d372a5ce5cb630ad1309c2fd6e35dacfe"
-  integrity sha512-MUZEiDqvAN9AIDRbbBnVYVvfcR6DrjCqeU2YQMmliFZl9uaBUjTkhuDQkBiyAy8ad5bx1TXVbqZ3gg7namsWjA==
-  dependencies:
-    "@babel/runtime" "^7.6.2"
-    detect-node "2.1.0"
-
 unpipe@1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -7075,25 +6918,25 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
 
-uuid@3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
-  integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
-
 uuid@7.0.3:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
   integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==
 
+uuid@8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.0.0.tgz#bc6ccf91b5ff0ac07bbcdbf1c7c4e150db4dbb6c"
+  integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
+
 uuid@8.3.2, uuid@^8.3.0, uuid@^8.3.2:
   version "8.3.2"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
   integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
 
-v8-compile-cache-lib@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8"
-  integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==
+v8-compile-cache-lib@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
+  integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
 
 v8-compile-cache@^2.0.3:
   version "2.2.0"
@@ -7133,10 +6976,10 @@ w3c-xmlserializer@^3.0.0:
   dependencies:
     xml-name-validator "^4.0.0"
 
-web-push@3.4.5:
-  version "3.4.5"
-  resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.4.5.tgz#f94074ff150538872c7183e4d8881c8305920cf1"
-  integrity sha512-2njbTqZ6Q7ZqqK14YpK1GGmaZs3NmuGYF5b7abCXulUIWFSlSYcZ3NBJQRFcMiQDceD7vQknb8FUuvI1F7Qe/g==
+web-push@3.5.0:
+  version "3.5.0"
+  resolved "https://registry.yarnpkg.com/web-push/-/web-push-3.5.0.tgz#4576533746052eda3bd50414b54a1b0a21eeaeae"
+  integrity sha512-JC0V9hzKTqlDYJ+LTZUXtW7B175qwwaqzbbMSWDxHWxZvd3xY0C2rcotMGDavub2nAAFw+sXTsqR65/KY2A5AQ==
   dependencies:
     asn1.js "^5.3.0"
     http_ece "1.1.0"
@@ -7216,20 +7059,20 @@ which-module@^2.0.0:
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
   integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
-which@2.0.2, which@^2.0.1, which@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
-  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
-  dependencies:
-    isexe "^2.0.0"
-
-which@^1.1.1, which@^1.2.14:
+which@^1.1.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
   integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
   dependencies:
     isexe "^2.0.0"
 
+which@^2.0.1, which@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+  dependencies:
+    isexe "^2.0.0"
+
 wide-align@^1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
@@ -7259,10 +7102,10 @@ word-wrap@^1.2.3, word-wrap@~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==
 
-workerpool@6.2.0:
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
-  integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
+workerpool@6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+  integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
 
 wrap-ansi@^6.2.0:
   version "6.2.0"
@@ -7287,20 +7130,20 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-ws@8.5.0:
-  version "8.5.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
-  integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
+ws@8.8.0:
+  version "8.8.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"
+  integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==
 
 ws@^8.2.3:
   version "8.4.2"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
   integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
 
-xev@2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/xev/-/xev-2.0.1.tgz#24484173a22115bc8a990ef5d4d5129695b827a7"
-  integrity sha512-icDf9M67bDge0F2qf02WKZq+s7mMO/SbPv67ZQPym6JThLEOdlWWLdB7VTVgRJp3ekgaiVItCAyH6aoKCPvfIA==
+xev@3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/xev/-/xev-3.0.2.tgz#3f4080bd8bed0d3479c674050e3696da98d22a4d"
+  integrity sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==
 
 xml-js@^1.6.11:
   version "1.6.11"
diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js
index a6e23e5171..10f0e5a9cb 100644
--- a/packages/client/.eslintrc.js
+++ b/packages/client/.eslintrc.js
@@ -1,68 +1,85 @@
 module.exports = {
 	root: true,
 	env: {
-		"node": false
+		'node': false,
 	},
-	parser: "vue-eslint-parser",
+	parser: 'vue-eslint-parser',
 	parserOptions: {
-		"parser": "@typescript-eslint/parser",
+		'parser': '@typescript-eslint/parser',
 		tsconfigRootDir: __dirname,
-		//project: ['./tsconfig.json'],
+		project: ['./tsconfig.json'],
+		extraFileExtensions: ['.vue'],
 	},
 	extends: [
-		//"../shared/.eslintrc.js",
-		"plugin:vue/vue3-recommended"
+		'../shared/.eslintrc.js',
+		'plugin:vue/vue3-recommended',
 	],
 	rules: {
+		'@typescript-eslint/no-empty-interface': [
+			'error',
+			{
+				'allowSingleExtends': true,
+			},
+		],
 		// window ใฎ็ฆๆญข็†็”ฑ: ใ‚ฐใƒญใƒผใƒใƒซใ‚นใ‚ณใƒผใƒ—ใจ่ก็ชใ—ใ€ไบˆๆœŸใ›ใฌ็ตๆžœใ‚’ๆ‹›ใใŸใ‚
 		// data ใฎ็ฆๆญข็†็”ฑ: ๆŠฝ่ฑก็š„ใ™ใŽใ‚‹ใŸใ‚
 		// e ใฎ็ฆๆญข็†็”ฑ: error ใ‚„ event ใชใฉใ€่ค‡ๆ•ฐใฎใ‚ญใƒผใƒฏใƒผใƒ‰ใฎ้ ญๆ–‡ๅญ—ใงใ‚ใ‚Šๅˆ†ใ‹ใ‚Šใซใใ„ใŸใ‚
-		"id-denylist": ["error", "window", "data", "e"],
+		'id-denylist': ['error', 'window', 'data', 'e'],
 		'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
-		"no-shadow": ["warn"],
-		"vue/attributes-order": ["error", {
-			"alphabetical": false
+		'no-shadow': ['warn'],
+		'vue/attributes-order': ['error', {
+			'alphabetical': false,
 		}],
-		"vue/no-use-v-if-with-v-for": ["error", {
-			"allowUsingIterationVar": false
+		'vue/no-use-v-if-with-v-for': ['error', {
+			'allowUsingIterationVar': false,
 		}],
-		"vue/no-ref-as-operand": "error",
-		"vue/no-multi-spaces": ["error", {
-			"ignoreProperties": false
+		'vue/no-ref-as-operand': 'error',
+		'vue/no-multi-spaces': ['error', {
+			'ignoreProperties': false,
 		}],
-		"vue/no-v-html": "error",
-		"vue/order-in-components": "error",
-		"vue/html-indent": ["warn", "tab", {
-			"attribute": 1,
-			"baseIndent": 0,
-			"closeBracket": 0,
-			"alignAttributesVertically": true,
-			"ignores": []
+		'vue/no-v-html': 'error',
+		'vue/order-in-components': 'error',
+		'vue/html-indent': ['warn', 'tab', {
+			'attribute': 1,
+			'baseIndent': 0,
+			'closeBracket': 0,
+			'alignAttributesVertically': true,
+			'ignores': [],
 		}],
-		"vue/html-closing-bracket-spacing": ["warn", {
-			"startTag": "never",
-			"endTag": "never",
-			"selfClosingTag": "never"
+		'vue/html-closing-bracket-spacing': ['warn', {
+			'startTag': 'never',
+			'endTag': 'never',
+			'selfClosingTag': 'never',
 		}],
-		"vue/multi-word-component-names": "warn",
-		"vue/require-v-for-key": "warn",
-		"vue/no-unused-components": "warn",
-		"vue/valid-v-for": "warn",
-		"vue/return-in-computed-property": "warn",
-		"vue/no-setup-props-destructure": "warn",
-		"vue/max-attributes-per-line": "off",
-		"vue/html-self-closing": "off",
-		"vue/singleline-html-element-content-newline": "off",
+		'vue/multi-word-component-names': 'warn',
+		'vue/require-v-for-key': 'warn',
+		'vue/no-unused-components': 'warn',
+		'vue/valid-v-for': 'warn',
+		'vue/return-in-computed-property': 'warn',
+		'vue/no-setup-props-destructure': 'warn',
+		'vue/max-attributes-per-line': 'off',
+		'vue/html-self-closing': 'off',
+		'vue/singleline-html-element-content-newline': 'off',
 	},
 	globals: {
-		"require": false,
-		"_DEV_": false,
-		"_LANGS_": false,
-		"_VERSION_": false,
-		"_ENV_": false,
-		"_PERF_PREFIX_": false,
-		"_DATA_TRANSFER_DRIVE_FILE_": false,
-		"_DATA_TRANSFER_DRIVE_FOLDER_": false,
-		"_DATA_TRANSFER_DECK_COLUMN_": false
-	}
-}
+		// Node.js
+		'module': false,
+		'require': false,
+		'__dirname': false,
+
+		// Vue
+		'$$': false,
+		'$ref': false,
+		'$computed': false,
+
+		// Misskey
+		'_DEV_': false,
+		'_LANGS_': false,
+		'_VERSION_': false,
+		'_ENV_': false,
+		'_PERF_PREFIX_': false,
+		'_DATA_TRANSFER_DRIVE_FILE_': false,
+		'_DATA_TRANSFER_DRIVE_FOLDER_': false,
+		'_DATA_TRANSFER_DECK_COLUMN_': false,
+	},
+};
diff --git a/packages/client/@types/theme.d.ts b/packages/client/@types/theme.d.ts
new file mode 100644
index 0000000000..67f724a9aa
--- /dev/null
+++ b/packages/client/@types/theme.d.ts
@@ -0,0 +1,7 @@
+declare module '@/themes/*.json5' {
+	import { Theme } from "@/scripts/theme";
+
+	const theme: Theme;
+
+	export default theme;
+}
diff --git a/packages/client/package.json b/packages/client/package.json
index 9de500f3ab..83c8086e23 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -1,80 +1,52 @@
 {
 	"private": true,
 	"scripts": {
-		"watch": "webpack --watch",
-		"build": "webpack",
-		"lint": "eslint --quiet 'src/**/*.{ts,vue}'"
+		"watch": "vite build --watch --mode development",
+		"build": "vite build",
+		"lint": "eslint --quiet \"src/**/*.{ts,vue}\""
 	},
 	"resolutions": {
 		"chokidar": "^3.3.1",
 		"lodash": "^4.17.21"
 	},
 	"dependencies": {
-		"@discordapp/twemoji": "13.1.1",
+		"@discordapp/twemoji": "14.0.2",
 		"@fortawesome/fontawesome-free": "6.1.1",
+		"@rollup/plugin-alias": "3.1.9",
+		"@rollup/plugin-json": "4.1.0",
 		"@syuilo/aiscript": "0.11.1",
-		"@types/escape-regexp": "0.0.1",
-		"@types/glob": "7.2.0",
-		"@types/gulp": "4.0.9",
-		"@types/gulp-rename": "2.0.1",
-		"@types/is-url": "1.2.30",
-		"@types/katex": "0.14.0",
-		"@types/matter-js": "0.17.7",
-		"@types/mocha": "9.1.0",
-		"@types/oauth": "0.9.1",
-		"@types/parse5": "6.0.3",
-		"@types/punycode": "2.1.0",
-		"@types/qrcode": "1.4.2",
-		"@types/random-seed": "0.3.3",
-		"@types/seedrandom": "3.0.2",
-		"@types/throttle-debounce": "2.1.0",
-		"@types/tinycolor2": "1.4.3",
-		"@types/uuid": "8.3.4",
-		"@types/webpack": "5.28.0",
-		"@types/webpack-stream": "3.2.12",
-		"@types/websocket": "1.0.5",
-		"@types/ws": "8.5.3",
-		"@typescript-eslint/parser": "5.18.0",
-		"@vue/compiler-sfc": "3.2.31",
+		"@vitejs/plugin-vue": "2.3.3",
+		"@vue/compiler-sfc": "3.2.37",
 		"abort-controller": "3.0.0",
 		"autobind-decorator": "2.4.0",
 		"autosize": "5.0.1",
 		"autwh": "0.1.0",
 		"blurhash": "1.1.5",
-		"broadcast-channel": "4.10.0",
-		"chart.js": "3.7.1",
+		"broadcast-channel": "4.13.0",
+		"browser-image-resizer": "git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2",
+		"chart.js": "3.8.0",
 		"chartjs-adapter-date-fns": "2.0.0",
-		"chartjs-plugin-gradient": "0.2.2",
+		"chartjs-plugin-gradient": "0.5.0",
 		"chartjs-plugin-zoom": "1.2.1",
 		"compare-versions": "4.1.3",
 		"content-disposition": "0.5.4",
-		"css-loader": "6.7.1",
-		"cssnano": "5.1.7",
+		"cropperjs": "2.0.0-beta",
 		"date-fns": "2.28.0",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.13.0",
-		"eslint-plugin-vue": "8.6.0",
 		"eventemitter3": "4.0.7",
 		"feed": "4.2.2",
-		"glob": "7.2.0",
 		"idb-keyval": "6.1.0",
 		"insert-text-at-cursor": "0.3.0",
-		"ip-cidr": "3.0.4",
 		"json5": "2.2.1",
-		"json5-loader": "4.0.1",
-		"katex": "0.15.3",
+		"katex": "0.15.6",
 		"matter-js": "0.18.0",
-		"mfm-js": "0.21.0",
+		"mfm-js": "0.22.1",
 		"misskey-js": "0.0.14",
-		"mocha": "9.2.2",
+		"mocha": "10.0.0",
 		"ms": "2.1.3",
 		"nested-property": "4.0.0",
-		"parse5": "6.0.1",
-		"photoswipe": "5.2.4",
-		"portscanner": "2.2.0",
-		"postcss": "8.4.12",
-		"postcss-loader": "6.2.1",
-		"prismjs": "1.27.0",
+		"photoswipe": "5.2.7",
+		"prismjs": "1.28.0",
 		"private-ip": "2.3.3",
 		"promise-limit": "2.7.0",
 		"pug": "3.0.2",
@@ -84,43 +56,58 @@
 		"random-seed": "0.3.0",
 		"reflect-metadata": "0.1.13",
 		"rndstr": "1.0.0",
+		"rollup": "2.75.6",
 		"s-age": "1.1.2",
-		"sass": "1.50.0",
-		"sass-loader": "12.6.0",
+		"sass": "1.52.3",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
-		"style-loader": "3.3.1",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
-		"three": "0.139.2",
-		"throttle-debounce": "4.0.0",
+		"three": "0.141.0",
+		"throttle-debounce": "5.0.0",
 		"tinycolor2": "1.4.2",
-		"ts-loader": "9.2.8",
-		"tsc-alias": "1.5.0",
-		"tsconfig-paths": "3.14.1",
+		"tsc-alias": "1.6.9",
+		"tsconfig-paths": "4.0.0",
 		"twemoji-parser": "14.0.0",
-		"typescript": "4.6.3",
+		"typescript": "4.7.3",
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
-		"vue": "3.2.31",
-		"vue-loader": "17.0.0",
+		"vite": "2.9.10",
+		"vue": "3.2.37",
 		"vue-prism-editor": "2.0.0-alpha.2",
-		"vue-router": "4.0.14",
-		"vue-style-loader": "4.1.3",
-		"vue-svg-loader": "0.17.0-beta.2",
+		"vue-router": "4.0.16",
 		"vuedraggable": "4.0.1",
-		"webpack": "5.72.0",
-		"webpack-cli": "4.9.2",
 		"websocket": "1.0.34",
-		"ws": "8.5.0"
+		"ws": "8.8.0"
 	},
 	"devDependencies": {
-		"@typescript-eslint/eslint-plugin": "5.18.0",
+		"@types/escape-regexp": "0.0.1",
+		"@types/glob": "7.2.0",
+		"@types/gulp": "4.0.9",
+		"@types/gulp-rename": "2.0.1",
+		"@types/is-url": "1.2.30",
+		"@types/katex": "0.14.0",
+		"@types/matter-js": "0.17.7",
+		"@types/mocha": "9.1.1",
+		"@types/oauth": "0.9.1",
+		"@types/punycode": "2.1.0",
+		"@types/qrcode": "1.4.2",
+		"@types/random-seed": "0.3.3",
+		"@types/seedrandom": "3.0.2",
+		"@types/throttle-debounce": "5.0.0",
+		"@types/tinycolor2": "1.4.3",
+		"@types/uuid": "8.3.4",
+		"@types/websocket": "1.0.5",
+		"@types/ws": "8.5.3",
+		"@typescript-eslint/eslint-plugin": "5.27.1",
+		"@typescript-eslint/parser": "5.27.1",
 		"cross-env": "7.0.3",
-		"cypress": "9.5.3",
+		"cypress": "10.0.3",
+		"eslint": "8.17.0",
 		"eslint-plugin-import": "2.26.0",
+		"eslint-plugin-vue": "9.1.0",
 		"start-server-and-test": "1.14.0"
 	}
 }
diff --git a/packages/client/src/account.ts b/packages/client/src/account.ts
index f4dcab319c..ce4af61f18 100644
--- a/packages/client/src/account.ts
+++ b/packages/client/src/account.ts
@@ -1,5 +1,5 @@
 import { del, get, set } from '@/scripts/idb-proxy';
-import { reactive } from 'vue';
+import { defineAsyncComponent, reactive } from 'vue';
 import * as misskey from 'misskey-js';
 import { apiUrl } from '@/config';
 import { waiting, api, popup, popupMenu, success, alert } from '@/os';
@@ -11,10 +11,10 @@ import { i18n } from './i18n';
 
 type Account = misskey.entities.MeDetailed;
 
-const data = localStorage.getItem('account');
+const accountData = localStorage.getItem('account');
 
 // TODO: ๅค–้ƒจใ‹ใ‚‰ใฏreadonlyใซ
-export const $i = data ? reactive(JSON.parse(data) as Account) : null;
+export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
 
 export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator);
 
@@ -52,7 +52,7 @@ export async function signout() {
 					return Promise.all(registrations.map(registration => registration.unregister()));
 				});
 		}
-	} catch (e) {}
+	} catch (err) {}
 	//#endregion
 
 	document.cookie = `igi=; path=/`;
@@ -104,8 +104,8 @@ function fetchAccount(token: string): Promise<Account> {
 	});
 }
 
-export function updateAccount(data) {
-	for (const [key, value] of Object.entries(data)) {
+export function updateAccount(accountData) {
+	for (const [key, value] of Object.entries(accountData)) {
 		$i[key] = value;
 	}
 	localStorage.setItem('account', JSON.stringify($i));
@@ -141,7 +141,7 @@ export async function openAccountMenu(opts: {
 	onChoose?: (account: misskey.entities.UserDetailed) => void;
 }, ev: MouseEvent) {
 	function showSigninDialog() {
-		popup(import('@/components/signin-dialog.vue'), {}, {
+		popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
 			done: res => {
 				addAccount(res.id, res.i);
 				success();
@@ -150,7 +150,7 @@ export async function openAccountMenu(opts: {
 	}
 
 	function createAccount() {
-		popup(import('@/components/signup-dialog.vue'), {}, {
+		popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
 			done: res => {
 				addAccount(res.id, res.i);
 				switchAccountWithToken(res.i);
diff --git a/packages/client/src/components/abuse-report-window.vue b/packages/client/src/components/abuse-report-window.vue
index f2cb369802..5114349620 100644
--- a/packages/client/src/components/abuse-report-window.vue
+++ b/packages/client/src/components/abuse-report-window.vue
@@ -37,7 +37,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'closed'): void;
+	(ev: 'closed'): void;
 }>();
 
 const window = ref<InstanceType<typeof XWindow>>();
diff --git a/packages/client/src/components/abuse-report.vue b/packages/client/src/components/abuse-report.vue
index b67cef209b..a947406f88 100644
--- a/packages/client/src/components/abuse-report.vue
+++ b/packages/client/src/components/abuse-report.vue
@@ -2,7 +2,7 @@
 <div class="bcekxzvu _card _gap">
 	<div class="_content target">
 		<MkAvatar class="avatar" :user="report.targetUser" :show-indicator="true"/>
-		<MkA class="info" :to="userPage(report.targetUser)" v-user-preview="report.targetUserId">
+		<MkA v-user-preview="report.targetUserId" class="info" :to="userPage(report.targetUser)">
 			<MkUserName class="name" :user="report.targetUser"/>
 			<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
 		</MkA>
@@ -43,20 +43,20 @@ export default defineComponent({
 		MkSwitch,
 	},
 
-	emits: ['resolved'],
-
 	props: {
 		report: {
 			type: Object,
 			required: true,
 		}
-	}
+	},
+
+	emits: ['resolved'],
 
 	data() {
 		return {
 			forward: this.report.forwarded,
 		};
-	}
+	},
 
 	methods: {
 		acct,
diff --git a/packages/client/src/components/analog-clock.vue b/packages/client/src/components/analog-clock.vue
index 59b8e97304..18dd1e3f41 100644
--- a/packages/client/src/components/analog-clock.vue
+++ b/packages/client/src/components/analog-clock.vue
@@ -42,7 +42,7 @@
 
 <script lang="ts" setup>
 import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 
 withDefaults(defineProps<{
 	thickness: number;
diff --git a/packages/client/src/components/autocomplete.vue b/packages/client/src/components/autocomplete.vue
index adeac4e050..1e4a4506f7 100644
--- a/packages/client/src/components/autocomplete.vue
+++ b/packages/client/src/components/autocomplete.vue
@@ -131,8 +131,8 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'done', v: { type: string; value: any }): void;
-	(e: 'closed'): void;
+	(event: 'done', value: { type: string; value: any }): void;
+	(event: 'closed'): void;
 }>();
 
 const suggests = ref<Element>();
@@ -152,7 +152,7 @@ function complete(type: string, value: any) {
 	emit('closed');
 	if (type === 'emoji') {
 		let recents = defaultStore.state.recentlyUsedEmojis;
-		recents = recents.filter((e: any) => e !== value);
+		recents = recents.filter((emoji: any) => emoji !== value);
 		recents.unshift(value);
 		defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
 	}
@@ -232,7 +232,7 @@ function exec() {
 	} else if (props.type === 'emoji') {
 		if (!props.q || props.q === '') {
 			// ๆœ€่ฟ‘ไฝฟใฃใŸ็ตตๆ–‡ๅญ—ใ‚’ใ‚ตใ‚ธใ‚งใ‚นใƒˆ
-			emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji === emoji)).filter(x => x) as EmojiDef[];
+			emojis.value = defaultStore.state.recentlyUsedEmojis.map(emoji => emojiDb.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
 			return;
 		}
 
@@ -269,17 +269,17 @@ function exec() {
 	}
 }
 
-function onMousedown(e: Event) {
-	if (!contains(rootEl.value, e.target) && (rootEl.value !== e.target)) props.close();
+function onMousedown(event: Event) {
+	if (!contains(rootEl.value, event.target) && (rootEl.value !== event.target)) props.close();
 }
 
-function onKeydown(e: KeyboardEvent) {
+function onKeydown(event: KeyboardEvent) {
 	const cancel = () => {
-		e.preventDefault();
-		e.stopPropagation();
+		event.preventDefault();
+		event.stopPropagation();
 	};
 
-	switch (e.key) {
+	switch (event.key) {
 		case 'Enter':
 			if (select.value !== -1) {
 				cancel();
@@ -310,7 +310,7 @@ function onKeydown(e: KeyboardEvent) {
 			break;
 
 		default:
-			e.stopPropagation();
+			event.stopPropagation();
 			props.textarea.focus();
 	}
 }
diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue
index ccd8880df8..183658471b 100644
--- a/packages/client/src/components/captcha.vue
+++ b/packages/client/src/components/captcha.vue
@@ -27,8 +27,7 @@ type CaptchaContainer = {
 };
 
 declare global {
-	interface Window extends CaptchaContainer {
-	}
+	interface Window extends CaptchaContainer { }
 }
 
 const props = defineProps<{
diff --git a/packages/client/src/components/channel-follow-button.vue b/packages/client/src/components/channel-follow-button.vue
index 7bbf5ae663..dff02beec0 100644
--- a/packages/client/src/components/channel-follow-button.vue
+++ b/packages/client/src/components/channel-follow-button.vue
@@ -48,8 +48,8 @@ async function onClick() {
 			});
 			isFollowing.value = true;
 		}
-	} catch (e) {
-		console.error(e);
+	} catch (err) {
+		console.error(err);
 	} finally {
 		wait.value = false;
 	}
diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue
index cc1aa9c20a..4e9c4e587a 100644
--- a/packages/client/src/components/chart.vue
+++ b/packages/client/src/components/chart.vue
@@ -7,8 +7,13 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, ref, watch, PropType, onUnmounted, shallowRef } from 'vue';
+<script lang="ts" setup>
+/* eslint-disable id-denylist --
+  Chart.js has a `data` attribute in most chart definitions, which triggers the
+  id-denylist violation when setting it. This is causing about 60+ lint issues.
+  As this is part of Chart.js's API it makes sense to disable the check here.
+*/
+import { defineProps, onMounted, ref, watch, PropType, onUnmounted } from 'vue';
 import {
 	Chart,
 	ArcElement,
@@ -29,11 +34,53 @@ import {
 import 'chartjs-adapter-date-fns';
 import { enUS } from 'date-fns/locale';
 import zoomPlugin from 'chartjs-plugin-zoom';
-import gradient from 'chartjs-plugin-gradient';
+// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114242002
+// We can't use gradient because Vite throws a error.
+//import gradient from 'chartjs-plugin-gradient';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
 import MkChartTooltip from '@/components/chart-tooltip.vue';
 
+const props = defineProps({
+	src: {
+		type: String,
+		required: true,
+	},
+	args: {
+		type: Object,
+		required: false,
+	},
+	limit: {
+		type: Number,
+		required: false,
+		default: 90
+	},
+	span: {
+		type: String as PropType<'hour' | 'day'>,
+		required: true,
+	},
+	detailed: {
+		type: Boolean,
+		required: false,
+		default: false
+	},
+	stacked: {
+		type: Boolean,
+		required: false,
+		default: false
+	},
+	bar: {
+		type: Boolean,
+		required: false,
+		default: false
+	},
+	aspectRatio: {
+		type: Number,
+		required: false,
+		default: null
+	},
+});
+
 Chart.register(
 	ArcElement,
 	LineElement,
@@ -50,7 +97,7 @@ Chart.register(
 	SubTitle,
 	Filler,
 	zoomPlugin,
-	gradient,
+	//gradient,
 );
 
 const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
@@ -78,826 +125,777 @@ const getColor = (i) => {
 	return colorSets[i % colorSets.length];
 };
 
-export default defineComponent({
-	props: {
-		src: {
-			type: String,
-			required: true,
-		},
-		args: {
-			type: Object,
-			required: false,
-		},
-		limit: {
-			type: Number,
-			required: false,
-			default: 90
-		},
-		span: {
-			type: String as PropType<'hour' | 'day'>,
-			required: true,
-		},
-		detailed: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		stacked: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		bar: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		aspectRatio: {
-			type: Number,
-			required: false,
-			default: null
-		},
-	},
+const now = new Date();
+let chartInstance: Chart = null;
+let chartData: {
+	series: {
+		name: string;
+		type: 'line' | 'area';
+		color?: string;
+		dashed?: boolean;
+		hidden?: boolean;
+		data: {
+			x: number;
+			y: number;
+		}[];
+	}[];
+} = null;
 
-	setup(props) {
-		const now = new Date();
-		let chartInstance: Chart = null;
-		let data: {
-			series: {
-				name: string;
-				type: 'line' | 'area';
-				color?: string;
-				dashed?: boolean;
-				hidden?: boolean;
-				data: {
-					x: number;
-					y: number;
-				}[];
-			}[];
-		} = null;
+const chartEl = ref<HTMLCanvasElement>(null);
+const fetching = ref(true);
 
-		const chartEl = ref<HTMLCanvasElement>(null);
-		const fetching = ref(true);
+const getDate = (ago: number) => {
+	const y = now.getFullYear();
+	const m = now.getMonth();
+	const d = now.getDate();
+	const h = now.getHours();
 
-		const getDate = (ago: number) => {
-			const y = now.getFullYear();
-			const m = now.getMonth();
-			const d = now.getDate();
-			const h = now.getHours();
+	return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago);
+};
 
-			return props.span === 'day' ? new Date(y, m, d - ago) : new Date(y, m, d, h - ago);
-		};
+const format = (arr) => {
+	return arr.map((v, i) => ({
+		x: getDate(i).getTime(),
+		y: v
+	}));
+};
 
-		const format = (arr) => {
-			return arr.map((v, i) => ({
-				x: getDate(i).getTime(),
-				y: v
-			}));
-		};
+const tooltipShowing = ref(false);
+const tooltipX = ref(0);
+const tooltipY = ref(0);
+const tooltipTitle = ref(null);
+const tooltipSeries = ref(null);
+let disposeTooltipComponent;
 
-		const tooltipShowing = ref(false);
-		const tooltipX = ref(0);
-		const tooltipY = ref(0);
-		const tooltipTitle = ref(null);
-		const tooltipSeries = ref(null);
-		let disposeTooltipComponent;
+os.popup(MkChartTooltip, {
+	showing: tooltipShowing,
+	x: tooltipX,
+	y: tooltipY,
+	title: tooltipTitle,
+	series: tooltipSeries,
+}, {}).then(({ dispose }) => {
+	disposeTooltipComponent = dispose;
+});
 
-		os.popup(MkChartTooltip, {
-			showing: tooltipShowing,
-			x: tooltipX,
-			y: tooltipY,
-			title: tooltipTitle,
-			series: tooltipSeries,
-		}, {}).then(({ dispose }) => {
-			disposeTooltipComponent = dispose;
-		});
+function externalTooltipHandler(context) {
+	if (context.tooltip.opacity === 0) {
+		tooltipShowing.value = false;
+		return;
+	}
 
-		function externalTooltipHandler(context) {
-			if (context.tooltip.opacity === 0) {
-				tooltipShowing.value = false;
-				return;
-			}
+	tooltipTitle.value = context.tooltip.title[0];
+	tooltipSeries.value = context.tooltip.body.map((b, i) => ({
+		backgroundColor: context.tooltip.labelColors[i].backgroundColor,
+		borderColor: context.tooltip.labelColors[i].borderColor,
+		text: b.lines[0],
+	}));
 
-			tooltipTitle.value = context.tooltip.title[0];
-			tooltipSeries.value = context.tooltip.body.map((b, i) => ({
-				backgroundColor: context.tooltip.labelColors[i].backgroundColor,
-				borderColor: context.tooltip.labelColors[i].borderColor,
-				text: b.lines[0],
-			}));
+	const rect = context.chart.canvas.getBoundingClientRect();
 
-			const rect = context.chart.canvas.getBoundingClientRect();
+	tooltipShowing.value = true;
+	tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX;
+	tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY;
+}
 
-			tooltipShowing.value = true;
-			tooltipX.value = rect.left + window.pageXOffset + context.tooltip.caretX;
-			tooltipY.value = rect.top + window.pageYOffset + context.tooltip.caretY;
-		}
+const render = () => {
+	if (chartInstance) {
+		chartInstance.destroy();
+	}
 
-		const render = () => {
-			if (chartInstance) {
-				chartInstance.destroy();
-			}
+	const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
+	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
-			const gridColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
-			const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	// ใƒ•ใ‚ฉใƒณใƒˆใ‚ซใƒฉใƒผ
+	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
 
-			// ใƒ•ใ‚ฉใƒณใƒˆใ‚ซใƒฉใƒผ
-			Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
+	const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
 
-			const maxes = data.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
-
-			chartInstance = new Chart(chartEl.value, {
-				type: props.bar ? 'bar' : 'line',
-				data: {
-					labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
-					datasets: data.series.map((x, i) => ({
-						parsing: false,
-						label: x.name,
-						data: x.data.slice().reverse(),
-						tension: 0.3,
-						pointRadius: 0,
-						borderWidth: props.bar ? 0 : 2,
-						borderColor: x.color ? x.color : getColor(i),
-						borderDash: x.dashed ? [5, 5] : [],
-						borderJoinStyle: 'round',
-						borderRadius: props.bar ? 3 : undefined,
-						backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1),
-						gradient: props.bar ? undefined : {
-							backgroundColor: {
-								axis: 'y',
-								colors: {
-									0: alpha(x.color ? x.color : getColor(i), 0),
-									[maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2),
-								},
-							},
-						},
-						barPercentage: 0.9,
-						categoryPercentage: 0.9,
-						fill: x.type === 'area',
-						clip: 8,
-						hidden: !!x.hidden,
-					})),
-				},
-				options: {
-					aspectRatio: props.aspectRatio || 2.5,
-					layout: {
-						padding: {
-							left: 0,
-							right: 8,
-							top: 0,
-							bottom: 0,
+	chartInstance = new Chart(chartEl.value, {
+		type: props.bar ? 'bar' : 'line',
+		data: {
+			labels: new Array(props.limit).fill(0).map((_, i) => getDate(i).toLocaleString()).slice().reverse(),
+			datasets: chartData.series.map((x, i) => ({
+				parsing: false,
+				label: x.name,
+				data: x.data.slice().reverse(),
+				tension: 0.3,
+				pointRadius: 0,
+				borderWidth: props.bar ? 0 : 2,
+				borderColor: x.color ? x.color : getColor(i),
+				borderDash: x.dashed ? [5, 5] : [],
+				borderJoinStyle: 'round',
+				borderRadius: props.bar ? 3 : undefined,
+				backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1),
+				/*gradient: props.bar ? undefined : {
+					backgroundColor: {
+						axis: 'y',
+						colors: {
+							0: alpha(x.color ? x.color : getColor(i), 0),
+							[maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.2),
 						},
 					},
-					scales: {
+				},*/
+				barPercentage: 0.9,
+				categoryPercentage: 0.9,
+				fill: x.type === 'area',
+				clip: 8,
+				hidden: !!x.hidden,
+			})),
+		},
+		options: {
+			aspectRatio: props.aspectRatio || 2.5,
+			layout: {
+				padding: {
+					left: 0,
+					right: 8,
+					top: 0,
+					bottom: 0,
+				},
+			},
+			scales: {
+				x: {
+					type: 'time',
+					stacked: props.stacked,
+					offset: false,
+					time: {
+						stepSize: 1,
+						unit: props.span === 'day' ? 'month' : 'day',
+					},
+					grid: {
+						color: gridColor,
+						borderColor: 'rgb(0, 0, 0, 0)',
+					},
+					ticks: {
+						display: props.detailed,
+						maxRotation: 0,
+						autoSkipPadding: 16,
+					},
+					adapters: {
+						date: {
+							locale: enUS,
+						},
+					},
+					min: getDate(props.limit).getTime(),
+				},
+				y: {
+					position: 'left',
+					stacked: props.stacked,
+					suggestedMax: 50,
+					grid: {
+						color: gridColor,
+						borderColor: 'rgb(0, 0, 0, 0)',
+					},
+					ticks: {
+						display: props.detailed,
+						//mirror: true,
+					},
+				},
+			},
+			interaction: {
+				intersect: false,
+				mode: 'index',
+			},
+			elements: {
+				point: {
+					hoverRadius: 5,
+					hoverBorderWidth: 2,
+				},
+			},
+			animation: false,
+			plugins: {
+				legend: {
+					display: props.detailed,
+					position: 'bottom',
+					labels: {
+						boxWidth: 16,
+					},
+				},
+				tooltip: {
+					enabled: false,
+					mode: 'index',
+					animation: {
+						duration: 0,
+					},
+					external: externalTooltipHandler,
+				},
+				zoom: props.detailed ? {
+					pan: {
+						enabled: true,
+					},
+					zoom: {
+						wheel: {
+							enabled: true,
+						},
+						pinch: {
+							enabled: true,
+						},
+						drag: {
+							enabled: false,
+						},
+						mode: 'x',
+					},
+					limits: {
 						x: {
-							type: 'time',
-							stacked: props.stacked,
-							offset: false,
-							time: {
-								stepSize: 1,
-								unit: props.span === 'day' ? 'month' : 'day',
-							},
-							grid: {
-								color: gridColor,
-								borderColor: 'rgb(0, 0, 0, 0)',
-							},
-							ticks: {
-								display: props.detailed,
-								maxRotation: 0,
-								autoSkipPadding: 16,
-							},
-							adapters: {
-								date: {
-									locale: enUS,
-								},
-							},
-							min: getDate(props.limit).getTime(),
+							min: 'original',
+							max: 'original',
 						},
 						y: {
-							position: 'left',
-							stacked: props.stacked,
-							suggestedMax: 50,
-							grid: {
-								color: gridColor,
-								borderColor: 'rgb(0, 0, 0, 0)',
-							},
-							ticks: {
-								display: props.detailed,
-								//mirror: true,
-							},
+							min: 'original',
+							max: 'original',
 						},
-					},
-					interaction: {
-						intersect: false,
-						mode: 'index',
-					},
-					elements: {
-						point: {
-							hoverRadius: 5,
-							hoverBorderWidth: 2,
-						},
-					},
-					animation: false,
-					plugins: {
-						legend: {
-							display: props.detailed,
-							position: 'bottom',
-							labels: {
-								boxWidth: 16,
-							},
-						},
-						tooltip: {
-							enabled: false,
-							mode: 'index',
-							animation: {
-								duration: 0,
-							},
-							external: externalTooltipHandler,
-						},
-						zoom: props.detailed ? {
-							pan: {
-								enabled: true,
-							},
-							zoom: {
-								wheel: {
-									enabled: true,
-								},
-								pinch: {
-									enabled: true,
-								},
-								drag: {
-									enabled: false,
-								},
-								mode: 'x',
-							},
-							limits: {
-								x: {
-									min: 'original',
-									max: 'original',
-								},
-								y: {
-									min: 'original',
-									max: 'original',
-								},
-							}
-						} : undefined,
-						gradient,
-					},
-				},
-				plugins: [{
-					id: 'vLine',
-					beforeDraw(chart, args, options) {
-						if (chart.tooltip._active && chart.tooltip._active.length) {
-							const activePoint = chart.tooltip._active[0];
-							const ctx = chart.ctx;
-							const x = activePoint.element.x;
-							const topY = chart.scales.y.top;
-							const bottomY = chart.scales.y.bottom;
-
-							ctx.save();
-							ctx.beginPath();
-							ctx.moveTo(x, bottomY);
-							ctx.lineTo(x, topY);
-							ctx.lineWidth = 1;
-							ctx.strokeStyle = vLineColor;
-							ctx.stroke();
-							ctx.restore();
-						}
 					}
-				}]
-			});
-		};
+				} : undefined,
+				//gradient,
+			},
+		},
+		plugins: [{
+			id: 'vLine',
+			beforeDraw(chart, args, options) {
+				if (chart.tooltip._active && chart.tooltip._active.length) {
+					const activePoint = chart.tooltip._active[0];
+					const ctx = chart.ctx;
+					const x = activePoint.element.x;
+					const topY = chart.scales.y.top;
+					const bottomY = chart.scales.y.bottom;
 
-		const exportData = () => {
-			// TODO
-		};
-
-		const fetchFederationChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/federation', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Received',
-					type: 'area',
-					data: format(raw.inboxInstances),
-					color: colors.blue,
-				}, {
-					name: 'Delivered',
-					type: 'area',
-					data: format(raw.deliveredInstances),
-					color: colors.green,
-				}, {
-					name: 'Stalled',
-					type: 'area',
-					data: format(raw.stalled),
-					color: colors.red,
-				}, {
-					name: 'Pub Active',
-					type: 'line',
-					data: format(raw.pubActive),
-					color: colors.purple,
-				}, {
-					name: 'Sub Active',
-					type: 'line',
-					data: format(raw.subActive),
-					color: colors.orange,
-				}, {
-					name: 'Pub & Sub',
-					type: 'line',
-					data: format(raw.pubsub),
-					dashed: true,
-					color: colors.cyan,
-				}, {
-					name: 'Pub',
-					type: 'line',
-					data: format(raw.pub),
-					dashed: true,
-					color: colors.purple,
-				}, {
-					name: 'Sub',
-					type: 'line',
-					data: format(raw.sub),
-					dashed: true,
-					color: colors.orange,
-				}],
-			};
-		};
-
-		const fetchApRequestChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'In',
-					type: 'area',
-					color: '#008FFB',
-					data: format(raw.inboxReceived)
-				}, {
-					name: 'Out (succ)',
-					type: 'area',
-					color: '#00E396',
-					data: format(raw.deliverSucceeded)
-				}, {
-					name: 'Out (fail)',
-					type: 'area',
-					color: '#FEB019',
-					data: format(raw.deliverFailed)
-				}]
-			};
-		};
-
-		const fetchNotesChart = async (type: string): Promise<typeof data> => {
-			const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'All',
-					type: 'line',
-					data: format(type == 'combined'
-						? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
-						: sum(raw[type].inc, negate(raw[type].dec))
-					),
-					color: '#888888',
-				}, {
-					name: 'Renotes',
-					type: 'area',
-					data: format(type == 'combined'
-						? sum(raw.local.diffs.renote, raw.remote.diffs.renote)
-						: raw[type].diffs.renote
-					),
-					color: colors.green,
-				}, {
-					name: 'Replies',
-					type: 'area',
-					data: format(type == 'combined'
-						? sum(raw.local.diffs.reply, raw.remote.diffs.reply)
-						: raw[type].diffs.reply
-					),
-					color: colors.yellow,
-				}, {
-					name: 'Normal',
-					type: 'area',
-					data: format(type == 'combined'
-						? sum(raw.local.diffs.normal, raw.remote.diffs.normal)
-						: raw[type].diffs.normal
-					),
-					color: colors.blue,
-				}, {
-					name: 'With file',
-					type: 'area',
-					data: format(type == 'combined'
-						? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile)
-						: raw[type].diffs.withFile
-					),
-					color: colors.purple,
-				}],
-			};
-		};
-
-		const fetchNotesTotalChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Combined',
-					type: 'line',
-					data: format(sum(raw.local.total, raw.remote.total)),
-				}, {
-					name: 'Local',
-					type: 'area',
-					data: format(raw.local.total),
-				}, {
-					name: 'Remote',
-					type: 'area',
-					data: format(raw.remote.total),
-				}],
-			};
-		};
-
-		const fetchUsersChart = async (total: boolean): Promise<typeof data> => {
-			const raw = await os.api('charts/users', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Combined',
-					type: 'line',
-					data: format(total
-						? sum(raw.local.total, raw.remote.total)
-						: sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
-					),
-				}, {
-					name: 'Local',
-					type: 'area',
-					data: format(total
-						? raw.local.total
-						: sum(raw.local.inc, negate(raw.local.dec))
-					),
-				}, {
-					name: 'Remote',
-					type: 'area',
-					data: format(total
-						? raw.remote.total
-						: sum(raw.remote.inc, negate(raw.remote.dec))
-					),
-				}],
-			};
-		};
-
-		const fetchActiveUsersChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Read & Write',
-					type: 'area',
-					data: format(raw.readWrite),
-					color: colors.orange,
-				}, {
-					name: 'Write',
-					type: 'area',
-					data: format(raw.write),
-					color: colors.lime,
-				}, {
-					name: 'Read',
-					type: 'area',
-					data: format(raw.read),
-					color: colors.blue,
-				}, {
-					name: '< Week',
-					type: 'area',
-					data: format(raw.registeredWithinWeek),
-					color: colors.green,
-				}, {
-					name: '< Month',
-					type: 'area',
-					data: format(raw.registeredWithinMonth),
-					color: colors.yellow,
-				}, {
-					name: '< Year',
-					type: 'area',
-					data: format(raw.registeredWithinYear),
-					color: colors.red,
-				}, {
-					name: '> Week',
-					type: 'area',
-					data: format(raw.registeredOutsideWeek),
-					color: colors.yellow,
-				}, {
-					name: '> Month',
-					type: 'area',
-					data: format(raw.registeredOutsideMonth),
-					color: colors.red,
-				}, {
-					name: '> Year',
-					type: 'area',
-					data: format(raw.registeredOutsideYear),
-					color: colors.purple,
-				}],
-			};
-		};
-
-		const fetchDriveChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
-			return {
-				bytes: true,
-				series: [{
-					name: 'All',
-					type: 'line',
-					dashed: true,
-					data: format(
-						sum(
-							raw.local.incSize,
-							negate(raw.local.decSize),
-							raw.remote.incSize,
-							negate(raw.remote.decSize)
-						)
-					),
-				}, {
-					name: 'Local +',
-					type: 'area',
-					data: format(raw.local.incSize),
-				}, {
-					name: 'Local -',
-					type: 'area',
-					data: format(negate(raw.local.decSize)),
-				}, {
-					name: 'Remote +',
-					type: 'area',
-					data: format(raw.remote.incSize),
-				}, {
-					name: 'Remote -',
-					type: 'area',
-					data: format(negate(raw.remote.decSize)),
-				}],
-			};
-		};
-
-		const fetchDriveFilesChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'All',
-					type: 'line',
-					dashed: true,
-					data: format(
-						sum(
-							raw.local.incCount,
-							negate(raw.local.decCount),
-							raw.remote.incCount,
-							negate(raw.remote.decCount)
-						)
-					),
-				}, {
-					name: 'Local +',
-					type: 'area',
-					data: format(raw.local.incCount),
-				}, {
-					name: 'Local -',
-					type: 'area',
-					data: format(negate(raw.local.decCount)),
-				}, {
-					name: 'Remote +',
-					type: 'area',
-					data: format(raw.remote.incCount),
-				}, {
-					name: 'Remote -',
-					type: 'area',
-					data: format(negate(raw.remote.decCount)),
-				}],
-			};
-		};
-
-		const fetchInstanceRequestsChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'In',
-					type: 'area',
-					color: '#008FFB',
-					data: format(raw.requests.received)
-				}, {
-					name: 'Out (succ)',
-					type: 'area',
-					color: '#00E396',
-					data: format(raw.requests.succeeded)
-				}, {
-					name: 'Out (fail)',
-					type: 'area',
-					color: '#FEB019',
-					data: format(raw.requests.failed)
-				}]
-			};
-		};
-
-		const fetchInstanceUsersChart = async (total: boolean): Promise<typeof data> => {
-			const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Users',
-					type: 'area',
-					color: '#008FFB',
-					data: format(total
-						? raw.users.total
-						: sum(raw.users.inc, negate(raw.users.dec))
-					)
-				}]
-			};
-		};
-
-		const fetchInstanceNotesChart = async (total: boolean): Promise<typeof data> => {
-			const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Notes',
-					type: 'area',
-					color: '#008FFB',
-					data: format(total
-						? raw.notes.total
-						: sum(raw.notes.inc, negate(raw.notes.dec))
-					)
-				}]
-			};
-		};
-
-		const fetchInstanceFfChart = async (total: boolean): Promise<typeof data> => {
-			const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Following',
-					type: 'area',
-					color: '#008FFB',
-					data: format(total
-						? raw.following.total
-						: sum(raw.following.inc, negate(raw.following.dec))
-					)
-				}, {
-					name: 'Followers',
-					type: 'area',
-					color: '#00E396',
-					data: format(total
-						? raw.followers.total
-						: sum(raw.followers.inc, negate(raw.followers.dec))
-					)
-				}]
-			};
-		};
-
-		const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof data> => {
-			const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
-			return {
-				bytes: true,
-				series: [{
-					name: 'Drive usage',
-					type: 'area',
-					color: '#008FFB',
-					data: format(total
-						? raw.drive.totalUsage
-						: sum(raw.drive.incUsage, negate(raw.drive.decUsage))
-					)
-				}]
-			};
-		};
-
-		const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof data> => {
-			const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Drive files',
-					type: 'area',
-					color: '#008FFB',
-					data: format(total
-						? raw.drive.totalFiles
-						: sum(raw.drive.incFiles, negate(raw.drive.decFiles))
-					)
-				}]
-			};
-		};
-
-		const fetchPerUserNotesChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span });
-			return {
-				series: [...(props.args.withoutAll ? [] : [{
-					name: 'All',
-					type: 'line',
-					data: format(sum(raw.inc, negate(raw.dec))),
-					color: '#888888',
-				}]), {
-					name: 'With file',
-					type: 'area',
-					data: format(raw.diffs.withFile),
-					color: colors.purple,
-				}, {
-					name: 'Renotes',
-					type: 'area',
-					data: format(raw.diffs.renote),
-					color: colors.green,
-				}, {
-					name: 'Replies',
-					type: 'area',
-					data: format(raw.diffs.reply),
-					color: colors.yellow,
-				}, {
-					name: 'Normal',
-					type: 'area',
-					data: format(raw.diffs.normal),
-					color: colors.blue,
-				}],
-			};
-		};
-
-		const fetchPerUserFollowingChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Local',
-					type: 'area',
-					data: format(raw.local.followings.total),
-				}, {
-					name: 'Remote',
-					type: 'area',
-					data: format(raw.remote.followings.total),
-				}],
-			};
-		};
-
-		const fetchPerUserFollowersChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Local',
-					type: 'area',
-					data: format(raw.local.followers.total),
-				}, {
-					name: 'Remote',
-					type: 'area',
-					data: format(raw.remote.followers.total),
-				}],
-			};
-		};
-
-		const fetchPerUserDriveChart = async (): Promise<typeof data> => {
-			const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
-			return {
-				series: [{
-					name: 'Inc',
-					type: 'area',
-					data: format(raw.incSize),
-				}, {
-					name: 'Dec',
-					type: 'area',
-					data: format(raw.decSize),
-				}],
-			};
-		};
-
-		const fetchAndRender = async () => {
-			const fetchData = () => {
-				switch (props.src) {
-					case 'federation': return fetchFederationChart();
-					case 'ap-request': return fetchApRequestChart();
-					case 'users': return fetchUsersChart(false);
-					case 'users-total': return fetchUsersChart(true);
-					case 'active-users': return fetchActiveUsersChart();
-					case 'notes': return fetchNotesChart('combined');
-					case 'local-notes': return fetchNotesChart('local');
-					case 'remote-notes': return fetchNotesChart('remote');
-					case 'notes-total': return fetchNotesTotalChart();
-					case 'drive': return fetchDriveChart();
-					case 'drive-files': return fetchDriveFilesChart();
-					
-					case 'instance-requests': return fetchInstanceRequestsChart();
-					case 'instance-users': return fetchInstanceUsersChart(false);
-					case 'instance-users-total': return fetchInstanceUsersChart(true);
-					case 'instance-notes': return fetchInstanceNotesChart(false);
-					case 'instance-notes-total': return fetchInstanceNotesChart(true);
-					case 'instance-ff': return fetchInstanceFfChart(false);
-					case 'instance-ff-total': return fetchInstanceFfChart(true);
-					case 'instance-drive-usage': return fetchInstanceDriveUsageChart(false);
-					case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true);
-					case 'instance-drive-files': return fetchInstanceDriveFilesChart(false);
-					case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true);
-
-					case 'per-user-notes': return fetchPerUserNotesChart();
-					case 'per-user-following': return fetchPerUserFollowingChart();
-					case 'per-user-followers': return fetchPerUserFollowersChart();
-					case 'per-user-drive': return fetchPerUserDriveChart();
+					ctx.save();
+					ctx.beginPath();
+					ctx.moveTo(x, bottomY);
+					ctx.lineTo(x, topY);
+					ctx.lineWidth = 1;
+					ctx.strokeStyle = vLineColor;
+					ctx.stroke();
+					ctx.restore();
 				}
-			};
-			fetching.value = true;
-			data = await fetchData();
-			fetching.value = false;
-			render();
-		};
+			}
+		}]
+	});
+};
 
-		watch(() => [props.src, props.span], fetchAndRender);
+const exportData = () => {
+	// TODO
+};
 
-		onMounted(() => {
-			fetchAndRender();
-		});
+const fetchFederationChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/federation', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Received',
+			type: 'area',
+			data: format(raw.inboxInstances),
+			color: colors.blue,
+		}, {
+			name: 'Delivered',
+			type: 'area',
+			data: format(raw.deliveredInstances),
+			color: colors.green,
+		}, {
+			name: 'Stalled',
+			type: 'area',
+			data: format(raw.stalled),
+			color: colors.red,
+		}, {
+			name: 'Pub Active',
+			type: 'line',
+			data: format(raw.pubActive),
+			color: colors.purple,
+		}, {
+			name: 'Sub Active',
+			type: 'line',
+			data: format(raw.subActive),
+			color: colors.orange,
+		}, {
+			name: 'Pub & Sub',
+			type: 'line',
+			data: format(raw.pubsub),
+			dashed: true,
+			color: colors.cyan,
+		}, {
+			name: 'Pub',
+			type: 'line',
+			data: format(raw.pub),
+			dashed: true,
+			color: colors.purple,
+		}, {
+			name: 'Sub',
+			type: 'line',
+			data: format(raw.sub),
+			dashed: true,
+			color: colors.orange,
+		}],
+	};
+};
 
-		onUnmounted(() => {
-			if (disposeTooltipComponent) disposeTooltipComponent();
-		});
+const fetchApRequestChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/ap-request', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'In',
+			type: 'area',
+			color: '#008FFB',
+			data: format(raw.inboxReceived)
+		}, {
+			name: 'Out (succ)',
+			type: 'area',
+			color: '#00E396',
+			data: format(raw.deliverSucceeded)
+		}, {
+			name: 'Out (fail)',
+			type: 'area',
+			color: '#FEB019',
+			data: format(raw.deliverFailed)
+		}]
+	};
+};
 
-		return {
-			chartEl,
-			fetching,
-		};
-	},
+const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
+	const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'All',
+			type: 'line',
+			data: format(type === 'combined'
+				? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
+				: sum(raw[type].inc, negate(raw[type].dec))
+			),
+			color: '#888888',
+		}, {
+			name: 'Renotes',
+			type: 'area',
+			data: format(type === 'combined'
+				? sum(raw.local.diffs.renote, raw.remote.diffs.renote)
+				: raw[type].diffs.renote
+			),
+			color: colors.green,
+		}, {
+			name: 'Replies',
+			type: 'area',
+			data: format(type === 'combined'
+				? sum(raw.local.diffs.reply, raw.remote.diffs.reply)
+				: raw[type].diffs.reply
+			),
+			color: colors.yellow,
+		}, {
+			name: 'Normal',
+			type: 'area',
+			data: format(type === 'combined'
+				? sum(raw.local.diffs.normal, raw.remote.diffs.normal)
+				: raw[type].diffs.normal
+			),
+			color: colors.blue,
+		}, {
+			name: 'With file',
+			type: 'area',
+			data: format(type === 'combined'
+				? sum(raw.local.diffs.withFile, raw.remote.diffs.withFile)
+				: raw[type].diffs.withFile
+			),
+			color: colors.purple,
+		}],
+	};
+};
+
+const fetchNotesTotalChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/notes', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Combined',
+			type: 'line',
+			data: format(sum(raw.local.total, raw.remote.total)),
+		}, {
+			name: 'Local',
+			type: 'area',
+			data: format(raw.local.total),
+		}, {
+			name: 'Remote',
+			type: 'area',
+			data: format(raw.remote.total),
+		}],
+	};
+};
+
+const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
+	const raw = await os.api('charts/users', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Combined',
+			type: 'line',
+			data: format(total
+				? sum(raw.local.total, raw.remote.total)
+				: sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec))
+			),
+		}, {
+			name: 'Local',
+			type: 'area',
+			data: format(total
+				? raw.local.total
+				: sum(raw.local.inc, negate(raw.local.dec))
+			),
+		}, {
+			name: 'Remote',
+			type: 'area',
+			data: format(total
+				? raw.remote.total
+				: sum(raw.remote.inc, negate(raw.remote.dec))
+			),
+		}],
+	};
+};
+
+const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/active-users', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Read & Write',
+			type: 'area',
+			data: format(raw.readWrite),
+			color: colors.orange,
+		}, {
+			name: 'Write',
+			type: 'area',
+			data: format(raw.write),
+			color: colors.lime,
+		}, {
+			name: 'Read',
+			type: 'area',
+			data: format(raw.read),
+			color: colors.blue,
+		}, {
+			name: '< Week',
+			type: 'area',
+			data: format(raw.registeredWithinWeek),
+			color: colors.green,
+		}, {
+			name: '< Month',
+			type: 'area',
+			data: format(raw.registeredWithinMonth),
+			color: colors.yellow,
+		}, {
+			name: '< Year',
+			type: 'area',
+			data: format(raw.registeredWithinYear),
+			color: colors.red,
+		}, {
+			name: '> Week',
+			type: 'area',
+			data: format(raw.registeredOutsideWeek),
+			color: colors.yellow,
+		}, {
+			name: '> Month',
+			type: 'area',
+			data: format(raw.registeredOutsideMonth),
+			color: colors.red,
+		}, {
+			name: '> Year',
+			type: 'area',
+			data: format(raw.registeredOutsideYear),
+			color: colors.purple,
+		}],
+	};
+};
+
+const fetchDriveChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
+	return {
+		bytes: true,
+		series: [{
+			name: 'All',
+			type: 'line',
+			dashed: true,
+			data: format(
+				sum(
+					raw.local.incSize,
+					negate(raw.local.decSize),
+					raw.remote.incSize,
+					negate(raw.remote.decSize)
+				)
+			),
+		}, {
+			name: 'Local +',
+			type: 'area',
+			data: format(raw.local.incSize),
+		}, {
+			name: 'Local -',
+			type: 'area',
+			data: format(negate(raw.local.decSize)),
+		}, {
+			name: 'Remote +',
+			type: 'area',
+			data: format(raw.remote.incSize),
+		}, {
+			name: 'Remote -',
+			type: 'area',
+			data: format(negate(raw.remote.decSize)),
+		}],
+	};
+};
+
+const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/drive', { limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'All',
+			type: 'line',
+			dashed: true,
+			data: format(
+				sum(
+					raw.local.incCount,
+					negate(raw.local.decCount),
+					raw.remote.incCount,
+					negate(raw.remote.decCount)
+				)
+			),
+		}, {
+			name: 'Local +',
+			type: 'area',
+			data: format(raw.local.incCount),
+		}, {
+			name: 'Local -',
+			type: 'area',
+			data: format(negate(raw.local.decCount)),
+		}, {
+			name: 'Remote +',
+			type: 'area',
+			data: format(raw.remote.incCount),
+		}, {
+			name: 'Remote -',
+			type: 'area',
+			data: format(negate(raw.remote.decCount)),
+		}],
+	};
+};
+
+const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'In',
+			type: 'area',
+			color: '#008FFB',
+			data: format(raw.requests.received)
+		}, {
+			name: 'Out (succ)',
+			type: 'area',
+			color: '#00E396',
+			data: format(raw.requests.succeeded)
+		}, {
+			name: 'Out (fail)',
+			type: 'area',
+			color: '#FEB019',
+			data: format(raw.requests.failed)
+		}]
+	};
+};
+
+const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => {
+	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Users',
+			type: 'area',
+			color: '#008FFB',
+			data: format(total
+				? raw.users.total
+				: sum(raw.users.inc, negate(raw.users.dec))
+			)
+		}]
+	};
+};
+
+const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => {
+	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Notes',
+			type: 'area',
+			color: '#008FFB',
+			data: format(total
+				? raw.notes.total
+				: sum(raw.notes.inc, negate(raw.notes.dec))
+			)
+		}]
+	};
+};
+
+const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => {
+	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Following',
+			type: 'area',
+			color: '#008FFB',
+			data: format(total
+				? raw.following.total
+				: sum(raw.following.inc, negate(raw.following.dec))
+			)
+		}, {
+			name: 'Followers',
+			type: 'area',
+			color: '#00E396',
+			data: format(total
+				? raw.followers.total
+				: sum(raw.followers.inc, negate(raw.followers.dec))
+			)
+		}]
+	};
+};
+
+const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => {
+	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	return {
+		bytes: true,
+		series: [{
+			name: 'Drive usage',
+			type: 'area',
+			color: '#008FFB',
+			data: format(total
+				? raw.drive.totalUsage
+				: sum(raw.drive.incUsage, negate(raw.drive.decUsage))
+			)
+		}]
+	};
+};
+
+const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => {
+	const raw = await os.api('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Drive files',
+			type: 'area',
+			color: '#008FFB',
+			data: format(total
+				? raw.drive.totalFiles
+				: sum(raw.drive.incFiles, negate(raw.drive.decFiles))
+			)
+		}]
+	};
+};
+
+const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	return {
+		series: [...(props.args.withoutAll ? [] : [{
+			name: 'All',
+			type: 'line',
+			data: format(sum(raw.inc, negate(raw.dec))),
+			color: '#888888',
+		}]), {
+			name: 'With file',
+			type: 'area',
+			data: format(raw.diffs.withFile),
+			color: colors.purple,
+		}, {
+			name: 'Renotes',
+			type: 'area',
+			data: format(raw.diffs.renote),
+			color: colors.green,
+		}, {
+			name: 'Replies',
+			type: 'area',
+			data: format(raw.diffs.reply),
+			color: colors.yellow,
+		}, {
+			name: 'Normal',
+			type: 'area',
+			data: format(raw.diffs.normal),
+			color: colors.blue,
+		}],
+	};
+};
+
+const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Local',
+			type: 'area',
+			data: format(raw.local.followings.total),
+		}, {
+			name: 'Remote',
+			type: 'area',
+			data: format(raw.remote.followings.total),
+		}],
+	};
+};
+
+const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Local',
+			type: 'area',
+			data: format(raw.local.followers.total),
+		}, {
+			name: 'Remote',
+			type: 'area',
+			data: format(raw.remote.followers.total),
+		}],
+	};
+};
+
+const fetchPerUserDriveChart = async (): Promise<typeof chartData> => {
+	const raw = await os.api('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	return {
+		series: [{
+			name: 'Inc',
+			type: 'area',
+			data: format(raw.incSize),
+		}, {
+			name: 'Dec',
+			type: 'area',
+			data: format(raw.decSize),
+		}],
+	};
+};
+
+const fetchAndRender = async () => {
+	const fetchData = () => {
+		switch (props.src) {
+			case 'federation': return fetchFederationChart();
+			case 'ap-request': return fetchApRequestChart();
+			case 'users': return fetchUsersChart(false);
+			case 'users-total': return fetchUsersChart(true);
+			case 'active-users': return fetchActiveUsersChart();
+			case 'notes': return fetchNotesChart('combined');
+			case 'local-notes': return fetchNotesChart('local');
+			case 'remote-notes': return fetchNotesChart('remote');
+			case 'notes-total': return fetchNotesTotalChart();
+			case 'drive': return fetchDriveChart();
+			case 'drive-files': return fetchDriveFilesChart();
+			case 'instance-requests': return fetchInstanceRequestsChart();
+			case 'instance-users': return fetchInstanceUsersChart(false);
+			case 'instance-users-total': return fetchInstanceUsersChart(true);
+			case 'instance-notes': return fetchInstanceNotesChart(false);
+			case 'instance-notes-total': return fetchInstanceNotesChart(true);
+			case 'instance-ff': return fetchInstanceFfChart(false);
+			case 'instance-ff-total': return fetchInstanceFfChart(true);
+			case 'instance-drive-usage': return fetchInstanceDriveUsageChart(false);
+			case 'instance-drive-usage-total': return fetchInstanceDriveUsageChart(true);
+			case 'instance-drive-files': return fetchInstanceDriveFilesChart(false);
+			case 'instance-drive-files-total': return fetchInstanceDriveFilesChart(true);
+
+			case 'per-user-notes': return fetchPerUserNotesChart();
+			case 'per-user-following': return fetchPerUserFollowingChart();
+			case 'per-user-followers': return fetchPerUserFollowersChart();
+			case 'per-user-drive': return fetchPerUserDriveChart();
+		}
+	};
+	fetching.value = true;
+	chartData = await fetchData();
+	fetching.value = false;
+	render();
+};
+
+watch(() => [props.src, props.span], fetchAndRender);
+
+onMounted(() => {
+	fetchAndRender();
 });
+
+onUnmounted(() => {
+	if (disposeTooltipComponent) disposeTooltipComponent();
+});
+/* eslint-enable id-denylist */
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue
new file mode 100644
index 0000000000..a8bde6ea05
--- /dev/null
+++ b/packages/client/src/components/cropper-dialog.vue
@@ -0,0 +1,175 @@
+<template>
+<XModalWindow
+	ref="dialogEl"
+	:width="800"
+	:height="500"
+	:scroll="false"
+	:with-ok-button="true"
+	@close="cancel()"
+	@ok="ok()"
+	@closed="$emit('closed')"
+>
+	<template #header>{{ $ts.cropImage }}</template>
+	<template #default="{ width, height }">
+		<div class="mk-cropper-dialog" :style="`--vw: ${width}px; --vh: ${height}px;`">
+			<Transition name="fade">
+				<div v-if="loading" class="loading">
+					<MkLoading/>
+				</div>
+			</Transition>
+			<div class="container">
+				<img ref="imgEl" :src="imgUrl" style="display: none;" @load="onImageLoad">
+			</div>
+		</div>
+	</template>
+</XModalWindow>
+</template>
+
+<script lang="ts" setup>
+import { nextTick, onMounted } from 'vue';
+import * as misskey from 'misskey-js';
+import Cropper from 'cropperjs';
+import tinycolor from 'tinycolor2';
+import XModalWindow from '@/components/ui/modal-window.vue';
+import * as os from '@/os';
+import { $i } from '@/account';
+import { defaultStore } from '@/store';
+import { apiUrl, url } from '@/config';
+import { query } from '@/scripts/url';
+
+const emit = defineEmits<{
+	(ev: 'ok', cropped: misskey.entities.DriveFile): void;
+	(ev: 'cancel'): void;
+	(ev: 'closed'): void;
+}>();
+
+const props = defineProps<{
+	file: misskey.entities.DriveFile;
+	aspectRatio: number;
+}>();
+
+const imgUrl = `${url}/proxy/image.webp?${query({
+	url: props.file.url,
+})}`;
+let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
+let imgEl = $ref<HTMLImageElement>();
+let cropper: Cropper | null = null;
+let loading = $ref(true);
+
+const ok = async () => {
+	const promise = new Promise<misskey.entities.DriveFile>(async (res) => {
+		const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
+		croppedCanvas.toBlob(blob => {
+			const formData = new FormData();
+			formData.append('file', blob);
+			formData.append('i', $i.token);
+			if (defaultStore.state.uploadFolder) {
+				formData.append('folderId', defaultStore.state.uploadFolder);
+			}
+
+			fetch(apiUrl + '/drive/files/create', {
+				method: 'POST',
+				body: formData,
+			})
+			.then(response => response.json())
+			.then(f => {
+				res(f);
+			});
+		});
+	});
+
+	os.promiseDialog(promise);
+
+	const f = await promise;
+
+	emit('ok', f);
+	dialogEl.close();
+};
+
+const cancel = () => {
+	emit('cancel');
+	dialogEl.close();
+};
+
+const onImageLoad = () => {
+	loading = false;
+
+	if (cropper) {
+		cropper.getCropperImage()!.$center('contain');
+		cropper.getCropperSelection()!.$center();
+	}
+};
+
+onMounted(() => {
+	cropper = new Cropper(imgEl, {
+	});
+
+	const computedStyle = getComputedStyle(document.documentElement);
+
+	const selection = cropper.getCropperSelection()!;
+	selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+	selection.aspectRatio = props.aspectRatio;
+	selection.initialAspectRatio = props.aspectRatio;
+	selection.outlined = true;
+
+	window.setTimeout(() => {
+		cropper.getCropperImage()!.$center('contain');
+		selection.$center();
+	}, 100);
+
+	// ใƒขใƒผใƒ€ใƒซใ‚ชใƒผใƒ—ใƒณใ‚ขใƒ‹ใƒกใƒผใ‚ทใƒงใƒณใŒ็ต‚ใ‚ใฃใŸใ‚ใจใงๅ†ๅบฆ่ชฟๆ•ด
+	window.setTimeout(() => {
+		cropper.getCropperImage()!.$center('contain');
+		selection.$center();
+	}, 500);
+});
+</script>
+
+<style lang="scss" scoped>
+.fade-enter-active,
+.fade-leave-active {
+	transition: opacity 0.5s ease 0.5s;
+}
+.fade-enter-from,
+.fade-leave-to {
+	opacity: 0;
+}
+
+.mk-cropper-dialog {
+	display: flex;
+	flex-direction: column;
+	width: var(--vw);
+	height: var(--vh);
+	position: relative;
+
+	> .loading {
+		position: absolute;
+		z-index: 10;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		-webkit-backdrop-filter: var(--blur, blur(10px));
+		backdrop-filter: var(--blur, blur(10px));
+		background: rgba(0, 0, 0, 0.5);
+	}
+
+	> .container {
+		flex: 1;
+		width: 100%;
+		height: 100%;
+
+		> ::v-deep(cropper-canvas) {
+			width: 100%;
+			height: 100%;
+
+			> cropper-selection > cropper-handle[action="move"] {
+				background: transparent;
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/client/src/components/cw-button.vue b/packages/client/src/components/cw-button.vue
index e7c9aabe4e..dd906f9bf3 100644
--- a/packages/client/src/components/cw-button.vue
+++ b/packages/client/src/components/cw-button.vue
@@ -18,7 +18,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'update:modelValue', v: boolean): void;
+	(ev: 'update:modelValue', v: boolean): void;
 }>();
 
 const label = computed(() => {
diff --git a/packages/client/src/components/dialog.vue b/packages/client/src/components/dialog.vue
index 3e106a4f0c..b090f3cb4e 100644
--- a/packages/client/src/components/dialog.vue
+++ b/packages/client/src/components/dialog.vue
@@ -90,8 +90,8 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'done', v: { canceled: boolean; result: any }): void;
-	(e: 'closed'): void;
+	(ev: 'done', v: { canceled: boolean; result: any }): void;
+	(ev: 'closed'): void;
 }>();
 
 const modal = ref<InstanceType<typeof MkModal>>();
@@ -122,14 +122,14 @@ function onBgClick() {
 	if (props.cancelableByBgClick) cancel();
 }
 */
-function onKeydown(e: KeyboardEvent) {
-	if (e.key === 'Escape') cancel();
+function onKeydown(evt: KeyboardEvent) {
+	if (evt.key === 'Escape') cancel();
 }
 
-function onInputKeydown(e: KeyboardEvent) {
-	if (e.key === 'Enter') {
-		e.preventDefault();
-		e.stopPropagation();
+function onInputKeydown(evt: KeyboardEvent) {
+	if (evt.key === 'Enter') {
+		evt.preventDefault();
+		evt.stopPropagation();
 		ok();
 	}
 }
diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue
index 81b80e7e8e..dd24440e82 100644
--- a/packages/client/src/components/drive-file-thumbnail.vue
+++ b/packages/client/src/components/drive-file-thumbnail.vue
@@ -42,7 +42,7 @@ const is = computed(() => {
 			"application/x-tar",
 			"application/gzip",
 			"application/x-7z-compressed"
-		].some(e => e === props.file.type)) return 'archive';
+		].some(archiveType => archiveType === props.file.type)) return 'archive';
 	return 'unknown';
 });
 
diff --git a/packages/client/src/components/drive-select-dialog.vue b/packages/client/src/components/drive-select-dialog.vue
index f6c59457d1..03974559d2 100644
--- a/packages/client/src/components/drive-select-dialog.vue
+++ b/packages/client/src/components/drive-select-dialog.vue
@@ -33,8 +33,8 @@ withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'done', r?: Misskey.entities.DriveFile[]): void;
-	(e: 'closed'): void;
+	(ev: 'done', r?: Misskey.entities.DriveFile[]): void;
+	(ev: 'closed'): void;
 }>();
 
 const dialog = ref<InstanceType<typeof XModalWindow>>();
diff --git a/packages/client/src/components/drive-window.vue b/packages/client/src/components/drive-window.vue
index d08c5fb674..5bbfca83c9 100644
--- a/packages/client/src/components/drive-window.vue
+++ b/packages/client/src/components/drive-window.vue
@@ -24,6 +24,6 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'closed'): void;
+	(ev: 'closed'): void;
 }>();
 </script>
diff --git a/packages/client/src/components/drive.file.vue b/packages/client/src/components/drive.file.vue
index 262eae0de1..aaf7ca3ca3 100644
--- a/packages/client/src/components/drive.file.vue
+++ b/packages/client/src/components/drive.file.vue
@@ -31,7 +31,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue';
+import { computed, defineAsyncComponent, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import MkDriveFileThumbnail from './drive-file-thumbnail.vue';
@@ -50,9 +50,9 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'chosen', r: Misskey.entities.DriveFile): void;
-	(e: 'dragstart'): void;
-	(e: 'dragend'): void;
+	(ev: 'chosen', r: Misskey.entities.DriveFile): void;
+	(ev: 'dragstart'): void;
+	(ev: 'dragend'): void;
 }>();
 
 const isDragging = ref(false);
@@ -99,14 +99,14 @@ function onClick(ev: MouseEvent) {
 	}
 }
 
-function onContextmenu(e: MouseEvent) {
-	os.contextMenu(getMenu(), e);
+function onContextmenu(ev: MouseEvent) {
+	os.contextMenu(getMenu(), ev);
 }
 
-function onDragstart(e: DragEvent) {
-	if (e.dataTransfer) {
-		e.dataTransfer.effectAllowed = 'move';
-		e.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file));
+function onDragstart(ev: DragEvent) {
+	if (ev.dataTransfer) {
+		ev.dataTransfer.effectAllowed = 'move';
+		ev.dataTransfer.setData(_DATA_TRANSFER_DRIVE_FILE_, JSON.stringify(props.file));
 	}
 	isDragging.value = true;
 
@@ -133,11 +133,11 @@ function rename() {
 }
 
 function describe() {
-	os.popup(import('@/components/media-caption.vue'), {
+	os.popup(defineAsyncComponent(() => import('@/components/media-caption.vue')), {
 		title: i18n.ts.describeFile,
 		input: {
 			placeholder: i18n.ts.inputNewDescription,
-			default: props.file.comment !== null ? props.file.comment : '',
+			default: props.file.comment != null ? props.file.comment : '',
 		},
 		image: props.file
 	}, {
@@ -146,7 +146,7 @@ function describe() {
 			let comment = result.result;
 			os.api('drive/files/update', {
 				fileId: props.file.id,
-				comment: comment.length == 0 ? null : comment
+				comment: comment.length === 0 ? null : comment
 			});
 		}
 	}, 'closed');
diff --git a/packages/client/src/components/drive.folder.vue b/packages/client/src/components/drive.folder.vue
index 57621bf097..3ccb5d6219 100644
--- a/packages/client/src/components/drive.folder.vue
+++ b/packages/client/src/components/drive.folder.vue
@@ -27,7 +27,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue';
+import { computed, defineAsyncComponent, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
@@ -71,7 +71,7 @@ function onMouseover() {
 }
 
 function onMouseout() {
-	hover.value = false
+	hover.value = false;
 }
 
 function onDragover(ev: DragEvent) {
@@ -84,12 +84,12 @@ function onDragover(ev: DragEvent) {
 		return;
 	}
 
-	const isFile = ev.dataTransfer.items[0].kind == 'file';
-	const isDriveFile = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
-	const isDriveFolder = ev.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_;
+	const isFile = ev.dataTransfer.items[0].kind === 'file';
+	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
+	const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
 
 	if (isFile || isDriveFile || isDriveFolder) {
-		ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+		ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
 	} else {
 		ev.dataTransfer.dropEffect = 'none';
 	}
@@ -118,7 +118,7 @@ function onDrop(ev: DragEvent) {
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
 	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-	if (driveFile != null && driveFile != '') {
+	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
 		emit('removeFile', file.id);
 		os.api('drive/files/update', {
@@ -130,11 +130,11 @@ function onDrop(ev: DragEvent) {
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚ฉใƒซใƒ€
 	const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
-	if (driveFolder != null && driveFolder != '') {
+	if (driveFolder != null && driveFolder !== '') {
 		const folder = JSON.parse(driveFolder);
 
 		// ็งปๅ‹•ๅ…ˆใŒ่‡ชๅˆ†่‡ช่บซใชใ‚‰reject
-		if (folder.id == props.folder.id) return;
+		if (folder.id === props.folder.id) return;
 
 		emit('removeFolder', folder.id);
 		os.api('drive/folders/update', {
@@ -204,7 +204,7 @@ function deleteFolder() {
 			defaultStore.set('uploadFolder', null);
 		}
 	}).catch(err => {
-		switch(err.id) {
+		switch (err.id) {
 			case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
 				os.alert({
 					type: 'error',
@@ -230,7 +230,7 @@ function onContextmenu(ev: MouseEvent) {
 		text: i18n.ts.openInWindow,
 		icon: 'fas fa-window-restore',
 		action: () => {
-			os.popup(import('./drive-window.vue'), {
+			os.popup(defineAsyncComponent(() => import('./drive-window.vue')), {
 				initialFolder: props.folder
 			}, {
 			}, 'closed');
diff --git a/packages/client/src/components/drive.nav-folder.vue b/packages/client/src/components/drive.nav-folder.vue
index 67223267c1..5482703317 100644
--- a/packages/client/src/components/drive.nav-folder.vue
+++ b/packages/client/src/components/drive.nav-folder.vue
@@ -24,10 +24,10 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'move', v?: Misskey.entities.DriveFolder): void;
-	(e: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void;
-	(e: 'removeFile', v: Misskey.entities.DriveFile['id']): void;
-	(e: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void;
+	(ev: 'move', v?: Misskey.entities.DriveFolder): void;
+	(ev: 'upload', file: File, folder?: Misskey.entities.DriveFolder | null): void;
+	(ev: 'removeFile', v: Misskey.entities.DriveFile['id']): void;
+	(ev: 'removeFolder', v: Misskey.entities.DriveFolder['id']): void;
 }>();
 
 const hover = ref(false);
@@ -45,22 +45,22 @@ function onMouseout() {
 	hover.value = false;
 }
 
-function onDragover(e: DragEvent) {
-	if (!e.dataTransfer) return;
+function onDragover(ev: DragEvent) {
+	if (!ev.dataTransfer) return;
 
 	// ใ“ใฎใƒ•ใ‚ฉใƒซใƒ€ใŒใƒซใƒผใƒˆใ‹ใคใ‚ซใƒฌใƒณใƒˆใƒ‡ใ‚ฃใƒฌใ‚ฏใƒˆใƒชใชใ‚‰ใƒ‰ใƒญใƒƒใƒ—็ฆๆญข
 	if (props.folder == null && props.parentFolder == null) {
-		e.dataTransfer.dropEffect = 'none';
+		ev.dataTransfer.dropEffect = 'none';
 	}
 
-	const isFile = e.dataTransfer.items[0].kind == 'file';
-	const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
-	const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_;
+	const isFile = ev.dataTransfer.items[0].kind === 'file';
+	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
+	const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
 
 	if (isFile || isDriveFile || isDriveFolder) {
-		e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+		ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
 	} else {
-		e.dataTransfer.dropEffect = 'none';
+		ev.dataTransfer.dropEffect = 'none';
 	}
 
 	return false;
@@ -74,22 +74,22 @@ function onDragleave() {
 	if (props.folder || props.parentFolder) draghover.value = false;
 }
 
-function onDrop(e: DragEvent) {
+function onDrop(ev: DragEvent) {
 	draghover.value = false;
 
-	if (!e.dataTransfer) return;
+	if (!ev.dataTransfer) return;
 
 	// ใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
-	if (e.dataTransfer.files.length > 0) {
-		for (const file of Array.from(e.dataTransfer.files)) {
+	if (ev.dataTransfer.files.length > 0) {
+		for (const file of Array.from(ev.dataTransfer.files)) {
 			emit('upload', file, props.folder);
 		}
 		return;
 	}
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
-	const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-	if (driveFile != null && driveFile != '') {
+	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
 		emit('removeFile', file.id);
 		os.api('drive/files/update', {
@@ -100,11 +100,11 @@ function onDrop(e: DragEvent) {
 	//#endregion
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚ฉใƒซใƒ€
-	const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
-	if (driveFolder != null && driveFolder != '') {
+	const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
+	if (driveFolder != null && driveFolder !== '') {
 		const folder = JSON.parse(driveFolder);
 		// ็งปๅ‹•ๅ…ˆใŒ่‡ชๅˆ†่‡ช่บซใชใ‚‰reject
-		if (props.folder && folder.id == props.folder.id) return;
+		if (props.folder && folder.id === props.folder.id) return;
 		emit('removeFolder', folder.id);
 		os.api('drive/folders/update', {
 			folderId: folder.id,
diff --git a/packages/client/src/components/drive.vue b/packages/client/src/components/drive.vue
index e044c67523..6c2c8acad0 100644
--- a/packages/client/src/components/drive.vue
+++ b/packages/client/src/components/drive.vue
@@ -97,6 +97,7 @@ import * as os from '@/os';
 import { stream } from '@/stream';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
+import { uploadFile, uploads } from '@/scripts/upload';
 
 const props = withDefaults(defineProps<{
 	initialFolder?: Misskey.entities.DriveFolder;
@@ -109,11 +110,11 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void;
-	(e: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
-	(e: 'move-root'): void;
-	(e: 'cd', v: Misskey.entities.DriveFolder | null): void;
-	(e: 'open-folder', v: Misskey.entities.DriveFolder): void;
+	(ev: 'selected', v: Misskey.entities.DriveFile | Misskey.entities.DriveFolder): void;
+	(ev: 'change-selection', v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
+	(ev: 'move-root'): void;
+	(ev: 'cd', v: Misskey.entities.DriveFolder | null): void;
+	(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
 }>();
 
 const loadMoreFiles = ref<InstanceType<typeof MkButton>>();
@@ -127,8 +128,9 @@ const moreFolders = ref(false);
 const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]);
 const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
 const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
-const uploadings = os.uploads;
+const uploadings = uploads;
 const connection = stream.useChannel('drive');
+const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // ๅค–้ƒจๆธกใ—ใŒๅคšใ„ใฎใง$refใฏไฝฟใ‚ใชใ„ใปใ†ใŒใ‚ˆใ„
 
 // ใƒ‰ใƒญใƒƒใƒ—ใ•ใ‚Œใ‚ˆใ†ใจใ—ใฆใ„ใ‚‹ใ‹
 const draghover = ref(false);
@@ -141,7 +143,7 @@ const fetching = ref(true);
 
 const ilFilesObserver = new IntersectionObserver(
 	(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles()
-)
+);
 
 watch(folder, () => emit('cd', folder.value));
 
@@ -151,7 +153,7 @@ function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) {
 
 function onStreamDriveFileUpdated(file: Misskey.entities.DriveFile) {
 	const current = folder.value ? folder.value.id : null;
-	if (current != file.folderId) {
+	if (current !== file.folderId) {
 		removeFile(file);
 	} else {
 		addFile(file, true);
@@ -168,7 +170,7 @@ function onStreamDriveFolderCreated(createdFolder: Misskey.entities.DriveFolder)
 
 function onStreamDriveFolderUpdated(updatedFolder: Misskey.entities.DriveFolder) {
 	const current = folder.value ? folder.value.id : null;
-	if (current != updatedFolder.parentId) {
+	if (current !== updatedFolder.parentId) {
 		removeFolder(updatedFolder);
 	} else {
 		addFolder(updatedFolder, true);
@@ -179,23 +181,23 @@ function onStreamDriveFolderDeleted(folderId: string) {
 	removeFolder(folderId);
 }
 
-function onDragover(e: DragEvent): any {
-	if (!e.dataTransfer) return;
+function onDragover(ev: DragEvent): any {
+	if (!ev.dataTransfer) return;
 
 	// ใƒ‰ใƒฉใƒƒใ‚ฐๅ…ƒใŒ่‡ชๅˆ†่‡ช่บซใฎๆ‰€ๆœ‰ใ™ใ‚‹ใ‚ขใ‚คใƒ†ใƒ ใ ใฃใŸใ‚‰
 	if (isDragSource.value) {
 		// ่‡ชๅˆ†่‡ช่บซใซใฏใƒ‰ใƒญใƒƒใƒ—ใ•ใ›ใชใ„
-		e.dataTransfer.dropEffect = 'none';
+		ev.dataTransfer.dropEffect = 'none';
 		return;
 	}
 
-	const isFile = e.dataTransfer.items[0].kind == 'file';
-	const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
-	const isDriveFolder = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FOLDER_;
+	const isFile = ev.dataTransfer.items[0].kind === 'file';
+	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
+	const isDriveFolder = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FOLDER_;
 	if (isFile || isDriveFile || isDriveFolder) {
-		e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+		ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
 	} else {
-		e.dataTransfer.dropEffect = 'none';
+		ev.dataTransfer.dropEffect = 'none';
 	}
 
 	return false;
@@ -209,24 +211,24 @@ function onDragleave() {
 	draghover.value = false;
 }
 
-function onDrop(e: DragEvent): any {
+function onDrop(ev: DragEvent): any {
 	draghover.value = false;
 
-	if (!e.dataTransfer) return;
+	if (!ev.dataTransfer) return;
 
 	// ใƒ‰ใƒญใƒƒใƒ—ใ•ใ‚ŒใฆใใŸใ‚‚ใฎใŒใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
-	if (e.dataTransfer.files.length > 0) {
-		for (const file of Array.from(e.dataTransfer.files)) {
+	if (ev.dataTransfer.files.length > 0) {
+		for (const file of Array.from(ev.dataTransfer.files)) {
 			upload(file, folder.value);
 		}
 		return;
 	}
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
-	const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-	if (driveFile != null && driveFile != '') {
+	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
-		if (files.value.some(f => f.id == file.id)) return;
+		if (files.value.some(f => f.id === file.id)) return;
 		removeFile(file.id);
 		os.api('drive/files/update', {
 			fileId: file.id,
@@ -236,13 +238,13 @@ function onDrop(e: DragEvent): any {
 	//#endregion
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚ฉใƒซใƒ€
-	const driveFolder = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
-	if (driveFolder != null && driveFolder != '') {
+	const driveFolder = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FOLDER_);
+	if (driveFolder != null && driveFolder !== '') {
 		const droppedFolder = JSON.parse(driveFolder);
 
 		// ็งปๅ‹•ๅ…ˆใŒ่‡ชๅˆ†่‡ช่บซใชใ‚‰reject
-		if (folder.value && droppedFolder.id == folder.value.id) return false;
-		if (folders.value.some(f => f.id == droppedFolder.id)) return false;
+		if (folder.value && droppedFolder.id === folder.value.id) return false;
+		if (folders.value.some(f => f.id === droppedFolder.id)) return false;
 		removeFolder(droppedFolder.id);
 		os.api('drive/folders/update', {
 			folderId: droppedFolder.id,
@@ -330,7 +332,7 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
 		// ๅ‰Š้™คๆ™‚ใซ่ฆชใƒ•ใ‚ฉใƒซใƒ€ใซ็งปๅ‹•
 		move(folderToDelete.parentId);
 	}).catch(err => {
-		switch(err.id) {
+		switch (err.id) {
 			case 'b0fc8a17-963c-405d-bfbc-859a487295e1':
 				os.alert({
 					type: 'error',
@@ -355,16 +357,16 @@ function onChangeFileInput() {
 }
 
 function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) {
-	os.upload(file, (folderToUpload && typeof folderToUpload == 'object') ? folderToUpload.id : null).then(res => {
+	uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => {
 		addFile(res, true);
 	});
 }
 
 function chooseFile(file: Misskey.entities.DriveFile) {
-	const isAlreadySelected = selectedFiles.value.some(f => f.id == file.id);
+	const isAlreadySelected = selectedFiles.value.some(f => f.id === file.id);
 	if (props.multiple) {
 		if (isAlreadySelected) {
-			selectedFiles.value = selectedFiles.value.filter(f => f.id != file.id);
+			selectedFiles.value = selectedFiles.value.filter(f => f.id !== file.id);
 		} else {
 			selectedFiles.value.push(file);
 		}
@@ -380,10 +382,10 @@ function chooseFile(file: Misskey.entities.DriveFile) {
 }
 
 function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
-	const isAlreadySelected = selectedFolders.value.some(f => f.id == folderToChoose.id);
+	const isAlreadySelected = selectedFolders.value.some(f => f.id === folderToChoose.id);
 	if (props.multiple) {
 		if (isAlreadySelected) {
-			selectedFolders.value = selectedFolders.value.filter(f => f.id != folderToChoose.id);
+			selectedFolders.value = selectedFolders.value.filter(f => f.id !== folderToChoose.id);
 		} else {
 			selectedFolders.value.push(folderToChoose);
 		}
@@ -402,7 +404,7 @@ function move(target?: Misskey.entities.DriveFolder) {
 	if (!target) {
 		goRoot();
 		return;
-	} else if (typeof target == 'object') {
+	} else if (typeof target === 'object') {
 		target = target.id;
 	}
 
@@ -428,9 +430,9 @@ function move(target?: Misskey.entities.DriveFolder) {
 
 function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) {
 	const current = folder.value ? folder.value.id : null;
-	if (current != folderToAdd.parentId) return;
+	if (current !== folderToAdd.parentId) return;
 
-	if (folders.value.some(f => f.id == folderToAdd.id)) {
+	if (folders.value.some(f => f.id === folderToAdd.id)) {
 		const exist = folders.value.map(f => f.id).indexOf(folderToAdd.id);
 		folders.value[exist] = folderToAdd;
 		return;
@@ -445,9 +447,9 @@ function addFolder(folderToAdd: Misskey.entities.DriveFolder, unshift = false) {
 
 function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) {
 	const current = folder.value ? folder.value.id : null;
-	if (current != fileToAdd.folderId) return;
+	if (current !== fileToAdd.folderId) return;
 
-	if (files.value.some(f => f.id == fileToAdd.id)) {
+	if (files.value.some(f => f.id === fileToAdd.id)) {
 		const exist = files.value.map(f => f.id).indexOf(fileToAdd.id);
 		files.value[exist] = fileToAdd;
 		return;
@@ -462,12 +464,12 @@ function addFile(fileToAdd: Misskey.entities.DriveFile, unshift = false) {
 
 function removeFolder(folderToRemove: Misskey.entities.DriveFolder | string) {
 	const folderIdToRemove = typeof folderToRemove === 'object' ? folderToRemove.id : folderToRemove;
-	folders.value = folders.value.filter(f => f.id != folderIdToRemove);
+	folders.value = folders.value.filter(f => f.id !== folderIdToRemove);
 }
 
 function removeFile(file: Misskey.entities.DriveFile | string) {
 	const fileId = typeof file === 'object' ? file.id : file;
-	files.value = files.value.filter(f => f.id != fileId);
+	files.value = files.value.filter(f => f.id !== fileId);
 }
 
 function appendFile(file: Misskey.entities.DriveFile) {
@@ -510,7 +512,7 @@ async function fetch() {
 		folderId: folder.value ? folder.value.id : null,
 		limit: foldersMax + 1
 	}).then(fetchedFolders => {
-		if (fetchedFolders.length == foldersMax + 1) {
+		if (fetchedFolders.length === foldersMax + 1) {
 			moreFolders.value = true;
 			fetchedFolders.pop();
 		}
@@ -522,7 +524,7 @@ async function fetch() {
 		type: props.type,
 		limit: filesMax + 1
 	}).then(fetchedFiles => {
-		if (fetchedFiles.length == filesMax + 1) {
+		if (fetchedFiles.length === filesMax + 1) {
 			moreFiles.value = true;
 			fetchedFiles.pop();
 		}
@@ -549,7 +551,7 @@ function fetchMoreFiles() {
 		untilId: files.value[files.value.length - 1].id,
 		limit: max + 1
 	}).then(files => {
-		if (files.length == max + 1) {
+		if (files.length === max + 1) {
 			moreFiles.value = true;
 			files.pop();
 		} else {
@@ -562,6 +564,10 @@ function fetchMoreFiles() {
 
 function getMenu() {
 	return [{
+		type: 'switch',
+		text: i18n.ts.keepOriginalUploading,
+		ref: keepOriginal,
+	}, null, {
 		text: i18n.ts.addFile,
 		type: 'label'
 	}, {
@@ -601,7 +607,7 @@ function onContextmenu(ev: MouseEvent) {
 onMounted(() => {
 	if (defaultStore.state.enableInfiniteScroll && loadMoreFiles.value) {
 		nextTick(() => {
-			ilFilesObserver.observe(loadMoreFiles.value?.$el)
+			ilFilesObserver.observe(loadMoreFiles.value?.$el);
 		});
 	}
 
@@ -622,7 +628,7 @@ onMounted(() => {
 onActivated(() => {
 	if (defaultStore.state.enableInfiniteScroll) {
 		nextTick(() => {
-			ilFilesObserver.observe(loadMoreFiles.value?.$el)
+			ilFilesObserver.observe(loadMoreFiles.value?.$el);
 		});
 	}
 });
diff --git a/packages/client/src/components/emoji-picker-window.vue b/packages/client/src/components/emoji-picker-window.vue
index 4d27fb48ba..610690d701 100644
--- a/packages/client/src/components/emoji-picker-window.vue
+++ b/packages/client/src/components/emoji-picker-window.vue
@@ -25,8 +25,8 @@ withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'chosen', v: any): void;
-	(e: 'closed'): void;
+	(ev: 'chosen', v: any): void;
+	(ev: 'closed'): void;
 }>();
 
 function chosen(emoji: any) {
diff --git a/packages/client/src/components/emoji-picker.section.vue b/packages/client/src/components/emoji-picker.section.vue
index 1026e894d1..52f7047487 100644
--- a/packages/client/src/components/emoji-picker.section.vue
+++ b/packages/client/src/components/emoji-picker.section.vue
@@ -24,7 +24,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'chosen', v: string, ev: MouseEvent): void;
+	(ev: 'chosen', v: string, event: MouseEvent): void;
 }>();
 
 const shown = ref(!!props.initialShown);
diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue
index 8601ea121c..64732e7033 100644
--- a/packages/client/src/components/emoji-picker.vue
+++ b/packages/client/src/components/emoji-picker.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
-	<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" @paste.stop="paste" @keyup.enter="done()">
+	<input ref="search" v-model.trim="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @paste.stop="paste" @keyup.enter="done()">
 	<div ref="emojis" class="emojis">
 		<section class="result">
 			<div v-if="searchResultCustom.length > 0">
@@ -61,7 +61,7 @@
 		</div>
 		<div>
 			<header class="_acrylic">{{ i18n.ts.emoji }}</header>
-			<XSection v-for="category in categories" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
+			<XSection v-for="category in categories" :key="category" :emojis="emojilist.filter(e => e.category === category).map(e => e.char)" @chosen="chosen">{{ category }}</XSection>
 		</div>
 	</div>
 	<div class="tabs">
@@ -97,7 +97,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'chosen', v: string): void;
+	(ev: 'chosen', v: string): void;
 }>();
 
 const search = ref<HTMLInputElement>();
@@ -138,7 +138,7 @@ watch(q, () => {
 		const emojis = customEmojis;
 		const matches = new Set<Misskey.entities.CustomEmoji>();
 
-		const exactMatch = emojis.find(e => e.name === newQ);
+		const exactMatch = emojis.find(emoji => emoji.name === newQ);
 		if (exactMatch) matches.add(exactMatch);
 
 		if (newQ.includes(' ')) { // ANDๆคœ็ดข
@@ -201,7 +201,7 @@ watch(q, () => {
 		const emojis = emojilist;
 		const matches = new Set<UnicodeEmojiDef>();
 
-		const exactMatch = emojis.find(e => e.name === newQ);
+		const exactMatch = emojis.find(emoji => emoji.name === newQ);
 		if (exactMatch) matches.add(exactMatch);
 
 		if (newQ.includes(' ')) { // ANDๆคœ็ดข
@@ -295,7 +295,7 @@ function chosen(emoji: any, ev?: MouseEvent) {
 	// ๆœ€่ฟ‘ไฝฟใฃใŸ็ตตๆ–‡ๅญ—ๆ›ดๆ–ฐ
 	if (!pinned.value.includes(key)) {
 		let recents = defaultStore.state.recentlyUsedEmojis;
-		recents = recents.filter((e: any) => e !== key);
+		recents = recents.filter((emoji: any) => emoji !== key);
 		recents.unshift(key);
 		defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
 	}
@@ -313,12 +313,12 @@ function done(query?: any): boolean | void {
 	if (query == null || typeof query !== 'string') return;
 
 	const q2 = query.replace(/:/g, '');
-	const exactMatchCustom = customEmojis.find(e => e.name === q2);
+	const exactMatchCustom = customEmojis.find(emoji => emoji.name === q2);
 	if (exactMatchCustom) {
 		chosen(exactMatchCustom);
 		return true;
 	}
-	const exactMatchUnicode = emojilist.find(e => e.char === q2 || e.name === q2);
+	const exactMatchUnicode = emojilist.find(emoji => emoji.char === q2 || emoji.name === q2);
 	if (exactMatchUnicode) {
 		chosen(exactMatchUnicode);
 		return true;
diff --git a/packages/client/src/components/follow-button.vue b/packages/client/src/components/follow-button.vue
index 93c9e891c1..efee795e43 100644
--- a/packages/client/src/components/follow-button.vue
+++ b/packages/client/src/components/follow-button.vue
@@ -28,7 +28,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onBeforeUnmount, onMounted, ref } from 'vue';
+import { onBeforeUnmount, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os';
 import { stream } from '@/stream';
@@ -43,32 +43,30 @@ const props = withDefaults(defineProps<{
 	large: false,
 });
 
-const isFollowing = ref(props.user.isFollowing);
-const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou);
-const wait = ref(false);
+let isFollowing = $ref(props.user.isFollowing);
+let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
+let wait = $ref(false);
 const connection = stream.useChannel('main');
 
 if (props.user.isFollowing == null) {
 	os.api('users/show', {
 		userId: props.user.id
-	}).then(u => {
-		isFollowing.value = u.isFollowing;
-		hasPendingFollowRequestFromYou.value = u.hasPendingFollowRequestFromYou;
-	});
+	})
+	.then(onFollowChange);
 }
 
 function onFollowChange(user: Misskey.entities.UserDetailed) {
-	if (user.id == props.user.id) {
-		isFollowing.value = user.isFollowing;
-		hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
+	if (user.id === props.user.id) {
+		isFollowing = user.isFollowing;
+		hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
 	}
 }
 
 async function onClick() {
-	wait.value = true;
+	wait = true;
 
 	try {
-		if (isFollowing.value) {
+		if (isFollowing) {
 			const { canceled } = await os.confirm({
 				type: 'warning',
 				text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }),
@@ -80,26 +78,22 @@ async function onClick() {
 				userId: props.user.id
 			});
 		} else {
-			if (hasPendingFollowRequestFromYou.value) {
+			if (hasPendingFollowRequestFromYou) {
 				await os.api('following/requests/cancel', {
 					userId: props.user.id
 				});
-			} else if (props.user.isLocked) {
-				await os.api('following/create', {
-					userId: props.user.id
-				});
-				hasPendingFollowRequestFromYou.value = true;
+				hasPendingFollowRequestFromYou = false;
 			} else {
 				await os.api('following/create', {
 					userId: props.user.id
 				});
-				hasPendingFollowRequestFromYou.value = true;
+				hasPendingFollowRequestFromYou = true;
 			}
 		}
-	} catch (e) {
-		console.error(e);
+	} catch (err) {
+		console.error(err);
 	} finally {
-		wait.value = false;
+		wait = false;
 	}
 }
 
diff --git a/packages/client/src/components/forgot-password.vue b/packages/client/src/components/forgot-password.vue
index 46cbf6bd70..19c1f23c85 100644
--- a/packages/client/src/components/forgot-password.vue
+++ b/packages/client/src/components/forgot-password.vue
@@ -41,8 +41,8 @@ import { instance } from '@/instance';
 import { i18n } from '@/i18n';
 
 const emit = defineEmits<{
-	(e: 'done'): void;
-	(e: 'closed'): void;
+	(ev: 'done'): void;
+	(ev: 'closed'): void;
 }>();
 
 let dialog: InstanceType<typeof XModalWindow> = $ref();
diff --git a/packages/client/src/components/form-dialog.vue b/packages/client/src/components/form-dialog.vue
index efd0da443d..11459f5937 100644
--- a/packages/client/src/components/form-dialog.vue
+++ b/packages/client/src/components/form-dialog.vue
@@ -44,7 +44,7 @@
 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
 				</FormRange>
-				<MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)" class="_formBlock">
+				<MkButton v-else-if="form[item].type === 'button'" class="_formBlock" @click="form[item].action($event, values)">
 					<span v-text="form[item].content || item"></span>
 				</MkButton>
 			</template>
diff --git a/packages/client/src/components/form/folder.vue b/packages/client/src/components/form/folder.vue
index 571afe50c0..1b960657d7 100644
--- a/packages/client/src/components/form/folder.vue
+++ b/packages/client/src/components/form/folder.vue
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{
 	defaultOpen: boolean;
 }>(), {
   defaultOpen: false,
-})
+});
 
 let opened = $ref(props.defaultOpen);
 let openedAtLeastOnce = $ref(props.defaultOpen);
diff --git a/packages/client/src/components/form/radios.vue b/packages/client/src/components/form/radios.vue
index ff5d51f9c7..a52acae9e1 100644
--- a/packages/client/src/components/form/radios.vue
+++ b/packages/client/src/components/form/radios.vue
@@ -14,7 +14,7 @@ export default defineComponent({
 	data() {
 		return {
 			value: this.modelValue,
-		}
+		};
 	},
 	watch: {
 		value() {
diff --git a/packages/client/src/components/form/range.vue b/packages/client/src/components/form/range.vue
index a82348d317..07f2c23124 100644
--- a/packages/client/src/components/form/range.vue
+++ b/packages/client/src/components/form/range.vue
@@ -16,7 +16,7 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, defineComponent, onMounted, onUnmounted, ref, watch } from 'vue';
 import * as os from '@/os';
 
 export default defineComponent({
@@ -112,7 +112,7 @@ export default defineComponent({
 			ev.preventDefault();
 
 			const tooltipShowing = ref(true);
-			os.popup(import('@/components/ui/tooltip.vue'), {
+			os.popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
 				showing: tooltipShowing,
 				text: computed(() => {
 					return props.textConverter(finalValue.value);
diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue
index b5a30d635c..fadb770aee 100644
--- a/packages/client/src/components/form/switch.vue
+++ b/packages/client/src/components/form/switch.vue
@@ -31,7 +31,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'update:modelValue', v: boolean): void;
+	(ev: 'update:modelValue', v: boolean): void;
 }>();
 
 let button = $ref<HTMLElement>();
diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue
index 52fef50f9b..5287d59b3e 100644
--- a/packages/client/src/components/global/a.vue
+++ b/packages/client/src/components/global/a.vue
@@ -5,14 +5,13 @@
 </template>
 
 <script lang="ts" setup>
-import { inject } from 'vue';
 import * as os from '@/os';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { router } from '@/router';
 import { url } from '@/config';
 import { popout as popout_ } from '@/scripts/popout';
 import { i18n } from '@/i18n';
-import { defaultStore } from '@/store';
+import { MisskeyNavigator } from '@/scripts/navigate';
 
 const props = withDefaults(defineProps<{
 	to: string;
@@ -23,9 +22,7 @@ const props = withDefaults(defineProps<{
 	behavior: null,
 });
 
-type Navigate = (path: string, record?: boolean) => void;
-const navHook = inject<null | Navigate>('navHook', null);
-const sideViewHook = inject<null | Navigate>('sideViewHook', null);
+const mkNav = new MisskeyNavigator();
 
 const active = $computed(() => {
 	if (props.activeClass == null) return false;
@@ -48,11 +45,11 @@ function onContextmenu(ev) {
 		action: () => {
 			os.pageWindow(props.to);
 		}
-	}, sideViewHook ? {
+	}, mkNav.sideViewHook ? {
 		icon: 'fas fa-columns',
 		text: i18n.ts.openInSideView,
 		action: () => {
-			sideViewHook(props.to);
+			if (mkNav.sideViewHook) mkNav.sideViewHook(props.to);
 		}
 	} : undefined, {
 		icon: 'fas fa-expand-alt',
@@ -101,18 +98,6 @@ function nav() {
 		}
 	}
 
-	if (navHook) {
-		navHook(props.to);
-	} else {
-		if (defaultStore.state.defaultSideView && sideViewHook && props.to !== '/') {
-			return sideViewHook(props.to);
-		}
-
-		if (router.currentRoute.value.path === props.to) {
-			window.scroll({ top: 0, behavior: 'smooth' });
-		} else {
-			router.push(props.to);
-		}
-	}
+	mkNav.push(props.to);
 }
 </script>
diff --git a/packages/client/src/components/global/avatar.vue b/packages/client/src/components/global/avatar.vue
index 27cfb6e4d4..4868896c99 100644
--- a/packages/client/src/components/global/avatar.vue
+++ b/packages/client/src/components/global/avatar.vue
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'click', ev: MouseEvent): void;
+	(ev: 'click', v: MouseEvent): void;
 }>();
 
 const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
diff --git a/packages/client/src/components/global/emoji.vue b/packages/client/src/components/global/emoji.vue
index 92edb1caf9..0075e0867d 100644
--- a/packages/client/src/components/global/emoji.vue
+++ b/packages/client/src/components/global/emoji.vue
@@ -46,7 +46,7 @@ export default defineComponent({
 		const url = computed(() => {
 			if (char.value) {
 				let codes = Array.from(char.value).map(x => x.codePointAt(0).toString(16));
-				if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
+				if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
 				codes = codes.filter(x => x && x.length);
 				return `${twemojiSvgBase}/${codes.join('-')}.svg`;
 			} else {
diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue
index e558614c12..63db19a520 100644
--- a/packages/client/src/components/global/header.vue
+++ b/packages/client/src/components/global/header.vue
@@ -38,7 +38,7 @@
 
 <script lang="ts">
 import { computed, defineComponent, onMounted, onUnmounted, PropType, ref, inject } from 'vue';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 import { popupMenu } from '@/os';
 import { url } from '@/config';
 import { scrollToTop } from '@/scripts/scroll';
diff --git a/packages/client/src/components/global/loading.vue b/packages/client/src/components/global/loading.vue
index 43ea1395ed..5a7e362fcf 100644
--- a/packages/client/src/components/global/loading.vue
+++ b/packages/client/src/components/global/loading.vue
@@ -1,11 +1,24 @@
 <template>
-<div class="yxspomdl" :class="{ inline, colored, mini }">
-	<div class="ring"></div>
+<div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini }]">
+	<div :class="$style.container">
+		<svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
+			<g transform="matrix(1.125,0,0,1.125,12,12)">
+				<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
+			</g>
+		</svg>
+		<svg :class="[$style.spinner, $style.fg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
+			<g transform="matrix(1.125,0,0,1.125,12,12)">
+				<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
+			</g>
+		</svg>
+	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { useCssModule } from 'vue';
+
+useCssModule();
 
 const props = withDefaults(defineProps<{
 	inline?: boolean;
@@ -18,8 +31,8 @@ const props = withDefaults(defineProps<{
 });
 </script>
 
-<style lang="scss" scoped>
-@keyframes ring {
+<style lang="scss" module>
+@keyframes spinner {
 	0% {
 		transform: rotate(0deg);
 	}
@@ -28,12 +41,12 @@ const props = withDefaults(defineProps<{
 	}
 }
 
-.yxspomdl {
+.root {
 	padding: 32px;
 	text-align: center;
 	cursor: wait;
 
-	--size: 48px;
+	--size: 40px;
 
 	&.colored {
 		color: var(--accent);
@@ -49,34 +62,33 @@ const props = withDefaults(defineProps<{
 		padding: 16px;
 		--size: 32px;
 	}
+}
 
-	> .ring {
-		position: relative;
-		display: inline-block;
-		vertical-align: middle;
+.container {
+	position: relative;
+	width: var(--size);
+	height: var(--size);
+	margin: 0 auto;
+}
 
-		&:before,
-		&:after {
-			content: " ";
-			display: block;
-			box-sizing: border-box;
-			width: var(--size);
-			height: var(--size);
-			border-radius: 50%;
-			border: solid 4px;
-		}
+.spinner {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: var(--size);
+	height: var(--size);
+	fill-rule: evenodd;
+	clip-rule: evenodd;
+	stroke-linecap: round;
+	stroke-linejoin: round;
+	stroke-miterlimit: 1.5;
+}
 
-		&:before {
-			border-color: currentColor;
-			opacity: 0.3;
-		}
+.bg {
+	opacity: 0.275;
+}
 
-		&:after {
-			position: absolute;
-			top: 0;
-			border-color: currentColor transparent transparent transparent;
-			animation: ring 0.5s linear infinite;
-		}
-	}
+.fg {
+	animation: spinner 0.5s linear infinite;
 }
 </style>
diff --git a/packages/client/src/components/global/misskey-flavored-markdown.vue b/packages/client/src/components/global/misskey-flavored-markdown.vue
index 243d8614ba..70d0108e9f 100644
--- a/packages/client/src/components/global/misskey-flavored-markdown.vue
+++ b/packages/client/src/components/global/misskey-flavored-markdown.vue
@@ -31,6 +31,32 @@ const props = withDefaults(defineProps<{
 	}
 }
 
+.mfm-x2 {
+	--mfm-zoom-size: 200%;
+}
+
+.mfm-x3 {
+	--mfm-zoom-size: 400%;
+}
+
+.mfm-x4 {
+	--mfm-zoom-size: 600%;
+}
+
+.mfm-x2, .mfm-x3, .mfm-x4 {
+	font-size: var(--mfm-zoom-size);
+
+	.mfm-x2, .mfm-x3, .mfm-x4 {
+		/* only half effective */
+		font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
+
+		.mfm-x2, .mfm-x3, .mfm-x4 {
+			/* disabled */
+			font-size: 100%;
+		}
+	}
+}
+
 @keyframes mfm-spin {
 	0% { transform: rotate(0deg); }
 	100% { transform: rotate(360deg); }
diff --git a/packages/client/src/components/global/time.vue b/packages/client/src/components/global/time.vue
index 5748d9de61..a7f142f961 100644
--- a/packages/client/src/components/global/time.vue
+++ b/packages/client/src/components/global/time.vue
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{
 	mode: 'relative',
 });
 
-const _time = typeof props.time == 'string' ? new Date(props.time) : props.time;
+const _time = typeof props.time === 'string' ? new Date(props.time) : props.time;
 const absolute = _time.toLocaleString();
 
 let now = $ref(new Date());
@@ -32,8 +32,7 @@ const relative = $computed(() => {
 		ago >= 60       ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) :
 		ago >= 10       ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) :
 		ago >= -1       ? i18n.ts._ago.justNow :
-		ago <  -1       ? i18n.ts._ago.future :
-		i18n.ts._ago.unknown);
+		i18n.ts._ago.future);
 });
 
 function tick() {
diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue
index 55f6c5d5f9..34ba9024cc 100644
--- a/packages/client/src/components/global/url.vue
+++ b/packages/client/src/components/global/url.vue
@@ -18,7 +18,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent, ref } from 'vue';
+import { defineAsyncComponent, defineComponent, ref } from 'vue';
 import { toUnicode as decodePunycode } from 'punycode/';
 import { url as local } from '@/config';
 import * as os from '@/os';
@@ -50,7 +50,7 @@ export default defineComponent({
 		const el = ref();
 		
 		useTooltip(el, (showing) => {
-			os.popup(import('@/components/url-preview-popup.vue'), {
+			os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), {
 				showing,
 				url: props.url,
 				source: el.value,
diff --git a/packages/client/src/components/image-viewer.vue b/packages/client/src/components/image-viewer.vue
index c39076df16..7bc88399ef 100644
--- a/packages/client/src/components/image-viewer.vue
+++ b/packages/client/src/components/image-viewer.vue
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'closed'): void;
+	(ev: 'closed'): void;
 }>();
 
 const modal = $ref<InstanceType<typeof MkModal>>();
diff --git a/packages/client/src/components/instance-ticker.vue b/packages/client/src/components/instance-ticker.vue
index 9b0a18ec90..c32409ecf4 100644
--- a/packages/client/src/components/instance-ticker.vue
+++ b/packages/client/src/components/instance-ticker.vue
@@ -39,6 +39,19 @@ const bg = {
 	border-radius: 4px 0 0 4px;
 	overflow: hidden;
 	color: #fff;
+	text-shadow: /* .866 โ‰ˆ sin(60deg) */
+		1px 0 1px #000,
+		.866px .5px 1px #000,
+		.5px .866px 1px #000,
+		0 1px 1px #000,
+		-.5px .866px 1px #000,
+		-.866px .5px 1px #000,
+		-1px 0 1px #000,
+		-.866px -.5px 1px #000,
+		-.5px -.866px 1px #000,
+		0 -1px 1px #000,
+		.5px -.866px 1px #000,
+		.866px -.5px 1px #000;
 
 	> .icon {
 		height: 100%;
diff --git a/packages/client/src/components/link.vue b/packages/client/src/components/link.vue
index 317c931cec..846a9a3a76 100644
--- a/packages/client/src/components/link.vue
+++ b/packages/client/src/components/link.vue
@@ -8,7 +8,7 @@
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { defineAsyncComponent } from 'vue';
 import { url as local } from '@/config';
 import { useTooltip } from '@/scripts/use-tooltip';
 import * as os from '@/os';
@@ -26,7 +26,7 @@ const target = self ? null : '_blank';
 const el = $ref();
 
 useTooltip($$(el), (showing) => {
-	os.popup(import('@/components/url-preview-popup.vue'), {
+	os.popup(defineAsyncComponent(() => import('@/components/url-preview-popup.vue')), {
 		showing,
 		url: props.url,
 		source: el,
diff --git a/packages/client/src/components/media-caption.vue b/packages/client/src/components/media-caption.vue
index ef546f3f70..feed3854f9 100644
--- a/packages/client/src/components/media-caption.vue
+++ b/packages/client/src/components/media-caption.vue
@@ -77,7 +77,7 @@ export default defineComponent({
 
 	computed: {
 		remainingLength(): number {
-			if (typeof this.inputValue != "string") return 512;
+			if (typeof this.inputValue !== "string") return 512;
 			return 512 - length(this.inputValue);
 		}
 	},
@@ -116,17 +116,17 @@ export default defineComponent({
 			}
 		},
 
-		onKeydown(e) {
-			if (e.which === 27) { // ESC
+		onKeydown(evt) {
+			if (evt.which === 27) { // ESC
 				this.cancel();
 			}
 		},
 
-		onInputKeydown(e) {
-			if (e.which === 13) { // Enter
-				if (e.ctrlKey) {
-					e.preventDefault();
-					e.stopPropagation();
+		onInputKeydown(evt) {
+			if (evt.which === 13) { // Enter
+				if (evt.ctrlKey) {
+					evt.preventDefault();
+					evt.stopPropagation();
 					this.ok();
 				}
 			}
diff --git a/packages/client/src/components/media-video.vue b/packages/client/src/components/media-video.vue
index 680eb27e64..5c38691e69 100644
--- a/packages/client/src/components/media-video.vue
+++ b/packages/client/src/components/media-video.vue
@@ -8,7 +8,8 @@
 <div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu">
 	<video
 		:poster="video.thumbnailUrl"
-		:title="video.name"
+		:title="video.comment"
+		:alt="video.comment"
 		preload="none"
 		controls
 		@contextmenu.stop
diff --git a/packages/client/src/components/mention.vue b/packages/client/src/components/mention.vue
index 479acfbc8f..70c2f49afa 100644
--- a/packages/client/src/components/mention.vue
+++ b/packages/client/src/components/mention.vue
@@ -1,22 +1,22 @@
 <template>
-<MkA v-if="url.startsWith('/')" v-user-preview="canonical" class="ldlomzub" :class="{ isMe }" :to="url" :style="{ background: bg }">
-	<img class="icon" :src="`/avatar/@${username}@${host}`" alt="">
+<MkA v-if="url.startsWith('/')" v-user-preview="canonical" :class="[$style.root, { isMe }]" :to="url" :style="{ background: bg }">
+	<img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt="">
 	<span class="main">
 		<span class="username">@{{ username }}</span>
-		<span v-if="(host != localHost) || $store.state.showFullAcct" class="host">@{{ toUnicode(host) }}</span>
+		<span v-if="(host != localHost) || $store.state.showFullAcct" :class="$style.mainHost">@{{ toUnicode(host) }}</span>
 	</span>
 </MkA>
-<a v-else class="ldlomzub" :href="url" target="_blank" rel="noopener" :style="{ background: bg }">
+<a v-else :class="$style.root" :href="url" target="_blank" rel="noopener" :style="{ background: bg }">
 	<span class="main">
 		<span class="username">@{{ username }}</span>
-		<span class="host">@{{ toUnicode(host) }}</span>
+		<span :class="$style.mainHost">@{{ toUnicode(host) }}</span>
 	</span>
 </a>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
-import * as tinycolor from 'tinycolor2';
+import { defineComponent, useCssModule } from 'vue';
+import tinycolor from 'tinycolor2';
 import { toUnicode } from 'punycode';
 import { host as localHost } from '@/config';
 import { $i } from '@/account';
@@ -45,6 +45,8 @@ export default defineComponent({
 		const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention'));
 		bg.setAlpha(0.1);
 
+		useCssModule();
+
 		return {
 			localHost,
 			isMe,
@@ -57,8 +59,8 @@ export default defineComponent({
 });
 </script>
 
-<style lang="scss" scoped>
-.ldlomzub {
+<style lang="scss" module>
+.root {
 	display: inline-block;
 	padding: 4px 8px 4px 4px;
 	border-radius: 999px;
@@ -67,18 +69,18 @@ export default defineComponent({
 	&.isMe {
 		color: var(--mentionMe);
 	}
+}
 
-	> .icon {
-		width: 1.5em;
-		margin: 0 0.2em 0 0;
-		vertical-align: bottom;
-		border-radius: 100%;
-	}
+.icon {
+	width: 1.5em;
+	height: 1.5em;
+	object-fit: cover;
+	margin: 0 0.2em 0 0;
+	vertical-align: bottom;
+	border-radius: 100%;
+}
 
-	> .main {
-		> .host {
-			opacity: 0.5;
-		}
-	}
+.mainHost {
+	opacity: 0.5;
 }
 </style>
diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index 37076652fd..4556a82d55 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -91,7 +91,8 @@ export default defineComponent({
 					let style;
 					switch (token.props.name) {
 						case 'tada': {
-							style = `font-size: 150%;` + (this.$store.state.animatedMfm ? 'animation: tada 1s linear infinite both;' : '');
+							const speed = validTime(token.props.args.speed) || '1s';
+							style = 'font-size: 150%;' + (this.$store.state.animatedMfm ? `animation: tada ${speed} linear infinite both;` : '');
 							break;
 						}
 						case 'jelly': {
@@ -123,11 +124,13 @@ export default defineComponent({
 							break;
 						}
 						case 'jump': {
-							style = this.$store.state.animatedMfm ? 'animation: mfm-jump 0.75s linear infinite;' : '';
+							const speed = validTime(token.props.args.speed) || '0.75s';
+							style = this.$store.state.animatedMfm ? `animation: mfm-jump ${speed} linear infinite;` : '';
 							break;
 						}
 						case 'bounce': {
-							style = this.$store.state.animatedMfm ? 'animation: mfm-bounce 0.75s linear infinite; transform-origin: center bottom;' : '';
+							const speed = validTime(token.props.args.speed) || '0.75s';
+							style = this.$store.state.animatedMfm ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : '';
 							break;
 						}
 						case 'flip': {
@@ -139,16 +142,19 @@ export default defineComponent({
 							break;
 						}
 						case 'x2': {
-							style = `font-size: 200%;`;
-							break;
+							return h('span', {
+								class: 'mfm-x2',
+							}, genEl(token.children));
 						}
 						case 'x3': {
-							style = `font-size: 400%;`;
-							break;
+							return h('span', {
+								class: 'mfm-x3',
+							}, genEl(token.children));
 						}
 						case 'x4': {
-							style = `font-size: 600%;`;
-							break;
+							return h('span', {
+								class: 'mfm-x4',
+							}, genEl(token.children));
 						}
 						case 'font': {
 							const family =
@@ -168,7 +174,8 @@ export default defineComponent({
 							}, genEl(token.children));
 						}
 						case 'rainbow': {
-							style = this.$store.state.animatedMfm ? 'animation: mfm-rainbow 1s linear infinite;' : '';
+							const speed = validTime(token.props.args.speed) || '1s';
+							style = this.$store.state.animatedMfm ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
 							break;
 						}
 						case 'sparkle': {
diff --git a/packages/client/src/components/modal-page-window.vue b/packages/client/src/components/modal-page-window.vue
index 2e17d5d030..21bdb657b7 100644
--- a/packages/client/src/components/modal-page-window.vue
+++ b/packages/client/src/components/modal-page-window.vue
@@ -39,8 +39,8 @@ export default defineComponent({
 
 	inject: {
 		sideViewHook: {
-			default: null
-		}
+			default: null,
+		},
 	},
 
 	provide() {
@@ -94,31 +94,31 @@ export default defineComponent({
 			}, {
 				icon: 'fas fa-expand-alt',
 				text: this.$ts.showInPage,
-				action: this.expand
+				action: this.expand,
 			}, this.sideViewHook ? {
 				icon: 'fas fa-columns',
 				text: this.$ts.openInSideView,
 				action: () => {
 					this.sideViewHook(this.path);
 					this.$refs.window.close();
-				}
+				},
 			} : undefined, {
 				icon: 'fas fa-external-link-alt',
 				text: this.$ts.popout,
-				action: this.popout
+				action: this.popout,
 			}, null, {
 				icon: 'fas fa-external-link-alt',
 				text: this.$ts.openInNewTab,
 				action: () => {
 					window.open(this.url, '_blank');
 					this.$refs.window.close();
-				}
+				},
 			}, {
 				icon: 'fas fa-link',
 				text: this.$ts.copyLink,
 				action: () => {
 					copyToClipboard(this.url);
-				}
+				},
 			}];
 		},
 	},
@@ -155,7 +155,7 @@ export default defineComponent({
 
 		onContextmenu(ev: MouseEvent) {
 			os.contextMenu(this.contextmenu, ev);
-		}
+		},
 	},
 });
 </script>
diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue
index d30284ca5f..6234b710d2 100644
--- a/packages/client/src/components/note-detailed.vue
+++ b/packages/client/src/components/note-detailed.vue
@@ -2,9 +2,9 @@
 <div
 	v-if="!muted"
 	v-show="!isDeleted"
+	ref="el"
 	v-hotkey="keymap"
 	v-size="{ max: [500, 450, 350, 300] }"
-	ref="el"
 	class="lxwezrsl _block"
 	:tabindex="!isDeleted ? '-1' : null"
 	:class="{ renote: isRenote }"
@@ -197,7 +197,7 @@ const keymap = {
 	'q': () => renoteButton.value.renote(true),
 	'esc': blur,
 	'm|o': () => menu(true),
-	's': () => showContent.value != showContent.value,
+	's': () => showContent.value !== showContent.value,
 };
 
 useNoteCapture({
@@ -222,7 +222,7 @@ function react(viaKeyboard = false): void {
 	reactionPicker.show(reactButton.value, reaction => {
 		os.api('notes/reactions/create', {
 			noteId: appearNote.id,
-			reaction: reaction
+			reaction: reaction,
 		});
 	}, () => {
 		focus();
@@ -233,7 +233,7 @@ function undoReact(note): void {
 	const oldReaction = note.myReaction;
 	if (!oldReaction) return;
 	os.api('notes/reactions/delete', {
-		noteId: note.id
+		noteId: note.id,
 	});
 }
 
@@ -257,7 +257,7 @@ function onContextmenu(ev: MouseEvent): void {
 
 function menu(viaKeyboard = false): void {
 	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
-		viaKeyboard
+		viaKeyboard,
 	}).then(focus);
 }
 
@@ -269,12 +269,12 @@ function showRenoteMenu(viaKeyboard = false): void {
 		danger: true,
 		action: () => {
 			os.api('notes/delete', {
-				noteId: note.id
+				noteId: note.id,
 			});
 			isDeleted.value = true;
-		}
+		},
 	}], renoteTime.value, {
-		viaKeyboard: viaKeyboard
+		viaKeyboard: viaKeyboard,
 	});
 }
 
@@ -288,14 +288,14 @@ function blur() {
 
 os.api('notes/children', {
 	noteId: appearNote.id,
-	limit: 30
+	limit: 30,
 }).then(res => {
 	replies.value = res;
 });
 
 if (appearNote.replyId) {
 	os.api('notes/conversation', {
-		noteId: appearNote.replyId
+		noteId: appearNote.replyId,
 	}).then(res => {
 		conversation.value = res.reverse();
 	});
diff --git a/packages/client/src/components/note-simple.vue b/packages/client/src/components/note-simple.vue
index c6907787b5..b813b9a2b9 100644
--- a/packages/client/src/components/note-simple.vue
+++ b/packages/client/src/components/note-simple.vue
@@ -5,7 +5,7 @@
 		<XNoteHeader class="header" :note="note" :mini="true"/>
 		<div class="body">
 			<p v-if="note.cw != null" class="cw">
-				<span v-if="note.cw != ''" class="text">{{ note.cw }}</span>
+				<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis"/>
 				<XCwButton v-model="showContent" :note="note"/>
 			</p>
 			<div v-show="note.cw == null || showContent" class="content">
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index 3cd7a819d4..e5744d1ce9 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -185,7 +185,7 @@ const keymap = {
 	'down|j|tab': focusAfter,
 	'esc': blur,
 	'm|o': () => menu(true),
-	's': () => showContent.value != showContent.value,
+	's': () => showContent.value !== showContent.value,
 };
 
 useNoteCapture({
@@ -210,7 +210,7 @@ function react(viaKeyboard = false): void {
 	reactionPicker.show(reactButton.value, reaction => {
 		os.api('notes/reactions/create', {
 			noteId: appearNote.id,
-			reaction: reaction
+			reaction: reaction,
 		});
 	}, () => {
 		focus();
@@ -221,7 +221,7 @@ function undoReact(note): void {
 	const oldReaction = note.myReaction;
 	if (!oldReaction) return;
 	os.api('notes/reactions/delete', {
-		noteId: note.id
+		noteId: note.id,
 	});
 }
 
@@ -245,7 +245,7 @@ function onContextmenu(ev: MouseEvent): void {
 
 function menu(viaKeyboard = false): void {
 	os.popupMenu(getNoteMenu({ note: note, translating, translation, menuButton }), menuButton.value, {
-		viaKeyboard
+		viaKeyboard,
 	}).then(focus);
 }
 
@@ -257,12 +257,12 @@ function showRenoteMenu(viaKeyboard = false): void {
 		danger: true,
 		action: () => {
 			os.api('notes/delete', {
-				noteId: note.id
+				noteId: note.id,
 			});
 			isDeleted.value = true;
-		}
+		},
 	}], renoteTime.value, {
-		viaKeyboard: viaKeyboard
+		viaKeyboard: viaKeyboard,
 	});
 }
 
@@ -284,7 +284,7 @@ function focusAfter() {
 
 function readPromo() {
 	os.api('promo/read', {
-		noteId: appearNote.id
+		noteId: appearNote.id,
 	});
 	isDeleted.value = true;
 }
diff --git a/packages/client/src/components/notification-setting-window.vue b/packages/client/src/components/notification-setting-window.vue
index ec1efec261..64d828394b 100644
--- a/packages/client/src/components/notification-setting-window.vue
+++ b/packages/client/src/components/notification-setting-window.vue
@@ -1,5 +1,6 @@
 <template>
-<XModalWindow ref="dialog"
+<XModalWindow
+	ref="dialog"
 	:width="400"
 	:height="450"
 	:with-ok-button="true"
@@ -28,18 +29,18 @@
 
 <script lang="ts">
 import { defineComponent, PropType } from 'vue';
-import XModalWindow from '@/components/ui/modal-window.vue';
+import { notificationTypes } from 'misskey-js';
 import MkSwitch from './form/switch.vue';
 import MkInfo from './ui/info.vue';
 import MkButton from './ui/button.vue';
-import { notificationTypes } from 'misskey-js';
+import XModalWindow from '@/components/ui/modal-window.vue';
 
 export default defineComponent({
 	components: {
 		XModalWindow,
 		MkSwitch,
 		MkInfo,
-		MkButton
+		MkButton,
 	},
 
 	props: {
@@ -53,7 +54,7 @@ export default defineComponent({
 			type: Boolean,
 			required: false,
 			default: true,
-		}
+		},
 	},
 
 	emits: ['done', 'closed'],
@@ -93,7 +94,7 @@ export default defineComponent({
 			for (const type in this.typesMap) {
 				this.typesMap[type as typeof notificationTypes[number]] = true;
 			}
-		}
-	}
+		},
+	},
 });
 </script>
diff --git a/packages/client/src/components/notification.vue b/packages/client/src/components/notification.vue
index 1a360f9905..cbfd809f37 100644
--- a/packages/client/src/components/notification.vue
+++ b/packages/client/src/components/notification.vue
@@ -16,7 +16,8 @@
 			<i v-else-if="notification.type === 'pollVote'" class="fas fa-poll-h"></i>
 			<i v-else-if="notification.type === 'pollEnded'" class="fas fa-poll-h"></i>
 			<!-- notification.reaction ใŒ null ใซใชใ‚‹ใ“ใจใฏใพใšใชใ„ใŒใ€ใ“ใ“ใงoptional chainingไฝฟใ†ใจไธ€้ƒจใƒ–ใƒฉใ‚ฆใ‚ถใงๅˆบใ•ใ‚‹ใฎใงๅฟตใฎ็‚บ -->
-			<XReactionIcon v-else-if="notification.type === 'reaction'"
+			<XReactionIcon
+				v-else-if="notification.type === 'reaction'"
 				ref="reactionRef"
 				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
 				:custom-emojis="notification.note.emojis"
@@ -72,12 +73,12 @@
 </template>
 
 <script lang="ts">
-import { defineComponent, ref, onMounted, onUnmounted } from 'vue';
+import { defineComponent, ref, onMounted, onUnmounted, watch } from 'vue';
 import * as misskey from 'misskey-js';
-import { getNoteSummary } from '@/scripts/get-note-summary';
 import XReactionIcon from './reaction-icon.vue';
 import MkFollowButton from './follow-button.vue';
 import XReactionTooltip from './reaction-tooltip.vue';
+import { getNoteSummary } from '@/scripts/get-note-summary';
 import { notePage } from '@/filters/note';
 import { userPage } from '@/filters/user';
 import { i18n } from '@/i18n';
@@ -87,7 +88,7 @@ import { useTooltip } from '@/scripts/use-tooltip';
 
 export default defineComponent({
 	components: {
-		XReactionIcon, MkFollowButton
+		XReactionIcon, MkFollowButton,
 	},
 
 	props: {
@@ -116,7 +117,7 @@ export default defineComponent({
 				const readObserver = new IntersectionObserver((entries, observer) => {
 					if (!entries.some(entry => entry.isIntersecting)) return;
 					stream.send('readNotification', {
-						id: props.notification.id
+						id: props.notification.id,
 					});
 					observer.disconnect();
 				});
@@ -126,6 +127,10 @@ export default defineComponent({
 				const connection = stream.useChannel('main');
 				connection.on('readAllNotifications', () => readObserver.disconnect());
 
+				watch(props.notification.isRead, () => {
+					readObserver.disconnect();
+				});
+
 				onUnmounted(() => {
 					readObserver.disconnect();
 					connection.dispose();
diff --git a/packages/client/src/components/notifications.vue b/packages/client/src/components/notifications.vue
index d522503a14..8eb569c369 100644
--- a/packages/client/src/components/notifications.vue
+++ b/packages/client/src/components/notifications.vue
@@ -19,8 +19,7 @@
 <script lang="ts" setup>
 import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue';
 import { notificationTypes } from 'misskey-js';
-import MkPagination from '@/components/ui/pagination.vue';
-import { Paging } from '@/components/ui/pagination.vue';
+import MkPagination, { Paging } from '@/components/ui/pagination.vue';
 import XNotification from '@/components/notification.vue';
 import XList from '@/components/date-separated-list.vue';
 import XNote from '@/components/note.vue';
@@ -49,14 +48,14 @@ const onNotification = (notification) => {
 	const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type);
 	if (isMuted || document.visibilityState === 'visible') {
 		stream.send('readNotification', {
-			id: notification.id
+			id: notification.id,
 		});
 	}
 
 	if (!isMuted) {
 		pagingComponent.value.prepend({
 			...notification,
-			isRead: document.visibilityState === 'visible'
+			isRead: document.visibilityState === 'visible',
 		});
 	}
 };
@@ -64,6 +63,31 @@ const onNotification = (notification) => {
 onMounted(() => {
 	const connection = stream.useChannel('main');
 	connection.on('notification', onNotification);
+	connection.on('readAllNotifications', () => {
+		if (pagingComponent.value) {
+			for (const item of pagingComponent.value.queue) {
+				item.isRead = true;
+			}
+			for (const item of pagingComponent.value.items) {
+				item.isRead = true;
+			}
+		}
+	});
+	connection.on('readNotifications', notificationIds => {
+		if (pagingComponent.value) {
+			for (let i = 0; i < pagingComponent.value.queue.length; i++) {
+				if (notificationIds.includes(pagingComponent.value.queue[i].id)) {
+					pagingComponent.value.queue[i].isRead = true;
+				}
+			}
+			for (let i = 0; i < (pagingComponent.value.items || []).length; i++) {
+				if (notificationIds.includes(pagingComponent.value.items[i].id)) {
+					pagingComponent.value.items[i].isRead = true;
+				}
+			}
+		}
+	});
+
 	onUnmounted(() => {
 		connection.dispose();
 	});
diff --git a/packages/client/src/components/number-diff.vue b/packages/client/src/components/number-diff.vue
index 9889c97ec3..e7d4a5472a 100644
--- a/packages/client/src/components/number-diff.vue
+++ b/packages/client/src/components/number-diff.vue
@@ -12,7 +12,7 @@ export default defineComponent({
 	props: {
 		value: {
 			type: Number,
-			required: true
+			required: true,
 		},
 	},
 
@@ -26,7 +26,7 @@ export default defineComponent({
 			isZero,
 			number,
 		};
-	}
+	},
 });
 </script>
 
diff --git a/packages/client/src/components/page/page.image.vue b/packages/client/src/components/page/page.image.vue
index 04ce74bd7c..6e38a9f424 100644
--- a/packages/client/src/components/page/page.image.vue
+++ b/packages/client/src/components/page/page.image.vue
@@ -1,34 +1,22 @@
 <template>
 <div class="lzyxtsnt">
-	<img v-if="image" :src="image.url"/>
+	<ImgWithBlurhash v-if="image" :hash="image.blurhash" :src="image.url" :alt="image.comment" :title="image.comment" :cover="false"/>
 </div>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
 import { defineComponent, PropType } from 'vue';
+import ImgWithBlurhash from '@/components/img-with-blurhash.vue';
 import * as os from '@/os';
 import { ImageBlock } from '@/scripts/hpml/block';
 import { Hpml } from '@/scripts/hpml/evaluator';
 
-export default defineComponent({
-	props: {
-		block: {
-			type: Object as PropType<ImageBlock>,
-			required: true
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true
-		}
-	},
-	setup(props, ctx) {
-		const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
+const props = defineProps<{
+	block: PropType<ImageBlock>,
+	hpml: PropType<Hpml>,
+}>();
 
-		return {
-			image
-		};
-	}
-});
+const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/components/page/page.post.vue b/packages/client/src/components/page/page.post.vue
index 847da37c51..3401f945bd 100644
--- a/packages/client/src/components/page/page.post.vue
+++ b/packages/client/src/components/page/page.post.vue
@@ -52,21 +52,21 @@ export default defineComponent({
 			const promise = new Promise((ok) => {
 				const canvas = this.hpml.canvases[this.block.canvasId];
 				canvas.toBlob(blob => {
-					const data = new FormData();
-					data.append('file', blob);
-					data.append('i', this.$i.token);
+					const formData = new FormData();
+					formData.append('file', blob);
+					formData.append('i', this.$i.token);
 					if (this.$store.state.uploadFolder) {
-						data.append('folderId', this.$store.state.uploadFolder);
+						formData.append('folderId', this.$store.state.uploadFolder);
 					}
 
 					fetch(apiUrl + '/drive/files/create', {
 						method: 'POST',
-						body: data
+						body: formData,
 					})
 					.then(response => response.json())
 					.then(f => {
 						ok(f);
-					})
+					});
 				});
 			});
 			os.promiseDialog(promise);
diff --git a/packages/client/src/components/page/page.vue b/packages/client/src/components/page/page.vue
index e54147bbd0..a067762372 100644
--- a/packages/client/src/components/page/page.vue
+++ b/packages/client/src/components/page/page.vue
@@ -38,8 +38,8 @@ export default defineComponent({
 					let ast;
 					try {
 						ast = parse(props.page.script);
-					} catch (e) {
-						console.error(e);
+					} catch (err) {
+						console.error(err);
 						/*os.alert({
 							type: 'error',
 							text: 'Syntax error :('
@@ -48,11 +48,11 @@ export default defineComponent({
 					}
 					hpml.aiscript.exec(ast).then(() => {
 						hpml.eval();
-					}).catch(e => {
-						console.error(e);
+					}).catch(err => {
+						console.error(err);
 						/*os.alert({
 							type: 'error',
-							text: e
+							text: err
 						});*/
 					});
 				} else {
diff --git a/packages/client/src/components/poll-editor.vue b/packages/client/src/components/poll-editor.vue
index 6f3f23a2d3..9aa5510c7f 100644
--- a/packages/client/src/components/poll-editor.vue
+++ b/packages/client/src/components/poll-editor.vue
@@ -104,7 +104,7 @@ function add() {
 }
 
 function remove(i) {
-	choices.value = choices.value.filter((_, _i) => _i != i);
+	choices.value = choices.value.filter((_, _i) => _i !== i);
 }
 
 function get() {
diff --git a/packages/client/src/components/post-form-attaches.vue b/packages/client/src/components/post-form-attaches.vue
index 9dd69a0ee5..6b9827407b 100644
--- a/packages/client/src/components/post-form-attaches.vue
+++ b/packages/client/src/components/post-form-attaches.vue
@@ -16,7 +16,7 @@
 
 <script lang="ts">
 import { defineComponent, defineAsyncComponent } from 'vue';
-import MkDriveFileThumbnail from './drive-file-thumbnail.vue'
+import MkDriveFileThumbnail from './drive-file-thumbnail.vue';
 import * as os from '@/os';
 
 export default defineComponent({
@@ -88,7 +88,7 @@ export default defineComponent({
 		},
 
 		async describe(file) {
-			os.popup(import("@/components/media-caption.vue"), {
+			os.popup(defineAsyncComponent(() => import("@/components/media-caption.vue")), {
 				title: this.$ts.describeFile,
 				input: {
 					placeholder: this.$ts.inputNewDescription,
@@ -98,7 +98,7 @@ export default defineComponent({
 			}, {
 				done: result => {
 					if (!result || result.canceled) return;
-					let comment = result.result.length == 0 ? null : result.result;
+					let comment = result.result.length === 0 ? null : result.result;
 					os.api('drive/files/update', {
 						fileId: file.id,
 						comment: comment,
@@ -114,19 +114,19 @@ export default defineComponent({
 			this.menu = os.popupMenu([{
 				text: this.$ts.renameFile,
 				icon: 'fas fa-i-cursor',
-				action: () => { this.rename(file) }
+				action: () => { this.rename(file); }
 			}, {
 				text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
 				icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye',
-				action: () => { this.toggleSensitive(file) }
+				action: () => { this.toggleSensitive(file); }
 			}, {
 				text: this.$ts.describeFile,
 				icon: 'fas fa-i-cursor',
-				action: () => { this.describe(file) }
+				action: () => { this.describe(file); }
 			}, {
 				text: this.$ts.attachCancel,
 				icon: 'fas fa-times-circle',
-				action: () => { this.detachMedia(file.id) }
+				action: () => { this.detachMedia(file.id); }
 			}], ev.currentTarget ?? ev.target).then(() => this.menu = null);
 		}
 	}
diff --git a/packages/client/src/components/post-form.vue b/packages/client/src/components/post-form.vue
index 656689ddcb..0197313e0e 100644
--- a/packages/client/src/components/post-form.vue
+++ b/packages/client/src/components/post-form.vue
@@ -62,7 +62,7 @@
 </template>
 
 <script lang="ts" setup>
-import { inject, watch, nextTick, onMounted } from 'vue';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent } from 'vue';
 import * as mfm from 'mfm-js';
 import * as misskey from 'misskey-js';
 import insertTextAtCursor from 'insert-text-at-cursor';
@@ -87,6 +87,7 @@ import MkInfo from '@/components/ui/info.vue';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
 import { $i, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account';
+import { uploadFile } from '@/scripts/upload';
 
 const modal = inject('modal');
 
@@ -106,7 +107,7 @@ const props = withDefaults(defineProps<{
 	fixed?: boolean;
 	autofocus?: boolean;
 }>(), {
-	initialVisibleUsers: [],
+	initialVisibleUsers: () => [],
 	autofocus: true,
 });
 
@@ -227,7 +228,7 @@ if (props.mention) {
 	text += ' ';
 }
 
-if (props.reply && (props.reply.user.username != $i.username || (props.reply.user.host != null && props.reply.user.host != host))) {
+if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) {
 	text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
 }
 
@@ -238,16 +239,15 @@ if (props.reply && props.reply.text != null) {
 	for (const x of extractMentions(ast)) {
 		const mention = x.host ?
 											`@${x.username}@${toASCII(x.host)}` :
-											(otherHost == null || otherHost == host) ?
+											(otherHost == null || otherHost === host) ?
 												`@${x.username}` :
 												`@${x.username}@${toASCII(otherHost)}`;
 
 		// ่‡ชๅˆ†ใฏ้™คๅค–
-		if ($i.username == x.username && x.host == null) continue;
-		if ($i.username == x.username && x.host == host) continue;
+		if ($i.username === x.username && (x.host == null || x.host === host)) continue;
 
 		// ้‡่ค‡ใฏ้™คๅค–
-		if (text.indexOf(`${mention} `) != -1) continue;
+		if (text.includes(`${mention} `)) continue;
 
 		text += `${mention} `;
 	}
@@ -302,7 +302,7 @@ function checkMissingMention() {
 		const ast = mfm.parse(text);
 
 		for (const x of extractMentions(ast)) {
-			if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) {
+			if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
 				hasNotSpecifiedMentions = true;
 				return;
 			}
@@ -315,7 +315,7 @@ function addMissingMention() {
 	const ast = mfm.parse(text);
 
 	for (const x of extractMentions(ast)) {
-		if (!visibleUsers.some(u => (u.username === x.username) && (u.host == x.host))) {
+		if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
 			os.api('users/show', { username: x.username, host: x.host }).then(user => {
 				visibleUsers.push(user);
 			});
@@ -356,7 +356,7 @@ function chooseFileFrom(ev) {
 }
 
 function detachFile(id) {
-	files = files.filter(x => x.id != id);
+	files = files.filter(x => x.id !== id);
 }
 
 function updateFiles(_files) {
@@ -372,7 +372,7 @@ function updateFileName(file, name) {
 }
 
 function upload(file: File, name?: string) {
-	os.upload(file, defaultStore.state.uploadFolder, name).then(res => {
+	uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
 		files.push(res);
 	});
 }
@@ -383,7 +383,7 @@ function setVisibility() {
 		return;
 	}
 
-	os.popup(import('./visibility-picker.vue'), {
+	os.popup(defineAsyncComponent(() => import('./visibility-picker.vue')), {
 		currentVisibility: visibility,
 		currentLocalOnly: localOnly,
 		src: visibilityButton,
@@ -426,24 +426,24 @@ function clear() {
 	quoteId = null;
 }
 
-function onKeydown(e: KeyboardEvent) {
-	if ((e.which === 10 || e.which === 13) && (e.ctrlKey || e.metaKey) && canPost) post();
-	if (e.which === 27) emit('esc');
+function onKeydown(ev: KeyboardEvent) {
+	if ((ev.which === 10 || ev.which === 13) && (ev.ctrlKey || ev.metaKey) && canPost) post();
+	if (ev.which === 27) emit('esc');
 	typing();
 }
 
-function onCompositionUpdate(e: CompositionEvent) {
-	imeText = e.data;
+function onCompositionUpdate(ev: CompositionEvent) {
+	imeText = ev.data;
 	typing();
 }
 
-function onCompositionEnd(e: CompositionEvent) {
+function onCompositionEnd(ev: CompositionEvent) {
 	imeText = '';
 }
 
-async function onPaste(e: ClipboardEvent) {
-	for (const { item, i } of Array.from(e.clipboardData.items).map((item, i) => ({item, i}))) {
-		if (item.kind == 'file') {
+async function onPaste(ev: ClipboardEvent) {
+	for (const { item, i } of Array.from(ev.clipboardData.items).map((item, i) => ({ item, i }))) {
+		if (item.kind === 'file') {
 			const file = item.getAsFile();
 			const lio = file.name.lastIndexOf('.');
 			const ext = lio >= 0 ? file.name.slice(lio) : '';
@@ -452,10 +452,10 @@ async function onPaste(e: ClipboardEvent) {
 		}
 	}
 
-	const paste = e.clipboardData.getData('text');
+	const paste = ev.clipboardData.getData('text');
 
 	if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) {
-		e.preventDefault();
+		ev.preventDefault();
 
 		os.confirm({
 			type: 'info',
@@ -471,49 +471,49 @@ async function onPaste(e: ClipboardEvent) {
 	}
 }
 
-function onDragover(e) {
-	if (!e.dataTransfer.items[0]) return;
-	const isFile = e.dataTransfer.items[0].kind == 'file';
-	const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
+function onDragover(ev) {
+	if (!ev.dataTransfer.items[0]) return;
+	const isFile = ev.dataTransfer.items[0].kind === 'file';
+	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
 	if (isFile || isDriveFile) {
-		e.preventDefault();
+		ev.preventDefault();
 		draghover = true;
-		e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+		ev.dataTransfer.dropEffect = ev.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
 	}
 }
 
-function onDragenter(e) {
+function onDragenter(ev) {
 	draghover = true;
 }
 
-function onDragleave(e) {
+function onDragleave(ev) {
 	draghover = false;
 }
 
-function onDrop(e): void {
+function onDrop(ev): void {
 	draghover = false;
 
 	// ใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
-	if (e.dataTransfer.files.length > 0) {
-		e.preventDefault();
-		for (const x of Array.from(e.dataTransfer.files)) upload(x);
+	if (ev.dataTransfer.files.length > 0) {
+		ev.preventDefault();
+		for (const x of Array.from(ev.dataTransfer.files)) upload(x);
 		return;
 	}
 
 	//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
-	const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-	if (driveFile != null && driveFile != '') {
+	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
 		files.push(file);
-		e.preventDefault();
+		ev.preventDefault();
 	}
 	//#endregion
 }
 
 function saveDraft() {
-	const data = JSON.parse(localStorage.getItem('drafts') || '{}');
+	const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
 
-	data[draftKey] = {
+	draftData[draftKey] = {
 		updatedAt: new Date(),
 		data: {
 			text: text,
@@ -526,20 +526,20 @@ function saveDraft() {
 		}
 	};
 
-	localStorage.setItem('drafts', JSON.stringify(data));
+	localStorage.setItem('drafts', JSON.stringify(draftData));
 }
 
 function deleteDraft() {
-	const data = JSON.parse(localStorage.getItem('drafts') || '{}');
+	const draftData = JSON.parse(localStorage.getItem('drafts') || '{}');
 
-	delete data[draftKey];
+	delete draftData[draftKey];
 
-	localStorage.setItem('drafts', JSON.stringify(data));
+	localStorage.setItem('drafts', JSON.stringify(draftData));
 }
 
 async function post() {
-	let data = {
-		text: text == '' ? undefined : text,
+	let postData = {
+		text: text === '' ? undefined : text,
 		fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
 		replyId: props.reply ? props.reply.id : undefined,
 		renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
@@ -548,18 +548,18 @@ async function post() {
 		cw: useCw ? cw || '' : undefined,
 		localOnly: localOnly,
 		visibility: visibility,
-		visibleUserIds: visibility == 'specified' ? visibleUsers.map(u => u.id) : undefined,
+		visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
 	};
 
 	if (withHashtags && hashtags && hashtags.trim() !== '') {
 		const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
-		data.text = data.text ? `${data.text} ${hashtags_}` : hashtags_;
+		postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
 	}
 
 	// plugin
 	if (notePostInterruptors.length > 0) {
 		for (const interruptor of notePostInterruptors) {
-			data = await interruptor.handler(JSON.parse(JSON.stringify(data)));
+			postData = await interruptor.handler(JSON.parse(JSON.stringify(postData)));
 		}
 	}
 
@@ -571,13 +571,13 @@ async function post() {
 	}
 
 	posting = true;
-	os.api('notes/create', data, token).then(() => {
+	os.api('notes/create', postData, token).then(() => {
 		clear();
 		nextTick(() => {
 			deleteDraft();
 			emit('posted');
-			if (data.text && data.text != '') {
-				const hashtags_ = mfm.parse(data.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
+			if (postData.text && postData.text !== '') {
+				const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
 				const history = JSON.parse(localStorage.getItem('hashtags') || '[]') as string[];
 				localStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
 			}
@@ -661,7 +661,7 @@ onMounted(() => {
 				cw = draft.data.cw;
 				visibility = draft.data.visibility;
 				localOnly = draft.data.localOnly;
-				files = (draft.data.files || []).filter(e => e);
+				files = (draft.data.files || []).filter(draftFile => draftFile);
 				if (draft.data.poll) {
 					poll = draft.data.poll;
 				}
diff --git a/packages/client/src/components/queue-chart.vue b/packages/client/src/components/queue-chart.vue
index 7e0ed58cbd..7bb548cf06 100644
--- a/packages/client/src/components/queue-chart.vue
+++ b/packages/client/src/components/queue-chart.vue
@@ -222,7 +222,7 @@ export default defineComponent({
 
 		return {
 			chartEl,
-		}
+		};
 	},
 });
 </script>
diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue
index 7dc079fde6..91a90a6996 100644
--- a/packages/client/src/components/reactions-viewer.reaction.vue
+++ b/packages/client/src/components/reactions-viewer.reaction.vue
@@ -7,8 +7,8 @@
 	:class="{ reacted: note.myReaction == reaction, canToggle }"
 	@click="toggleReaction()"
 >
-	<XReactionIcon :reaction="reaction" :custom-emojis="note.emojis"/>
-	<span>{{ count }}</span>
+	<XReactionIcon class="icon" :reaction="reaction" :custom-emojis="note.emojis"/>
+	<span class="count">{{ count }}</span>
 </button>
 </template>
 
@@ -141,12 +141,16 @@ export default defineComponent({
 			background: var(--accent);
 		}
 
-		> span {
+		> .count {
 			color: var(--fgOnAccent);
 		}
+
+		> .icon {
+			filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
+		}
 	}
 
-	> span {
+	> .count {
 		font-size: 0.9em;
 		line-height: 32px;
 		margin: 0 0 0 4px;
diff --git a/packages/client/src/components/sample.vue b/packages/client/src/components/sample.vue
index 65249ff7e9..f80b9c96b7 100644
--- a/packages/client/src/components/sample.vue
+++ b/packages/client/src/components/sample.vue
@@ -52,7 +52,7 @@ export default defineComponent({
 			flag: true,
 			radio: 'misskey',
 			mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`
-		}
+		};
 	},
 
 	methods: {
diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue
index 5c2048e7b0..848b11fada 100644
--- a/packages/client/src/components/signin-dialog.vue
+++ b/packages/client/src/components/signin-dialog.vue
@@ -2,12 +2,12 @@
 <XModalWindow ref="dialog"
 	:width="370"
 	:height="400"
-	@close="dialog.close()"
+	@close="onClose"
 	@closed="emit('closed')"
 >
 	<template #header>{{ $ts.login }}</template>
 
-	<MkSignin :auto-set="autoSet" @login="onLogin"/>
+	<MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/>
 </XModalWindow>
 </template>
 
@@ -18,17 +18,25 @@ import MkSignin from './signin.vue';
 
 const props = withDefaults(defineProps<{
 	autoSet?: boolean;
+	message?: string,
 }>(), {
 	autoSet: false,
+	message: ''
 });
 
 const emit = defineEmits<{
-	(e: 'done'): void;
-	(e: 'closed'): void;
+	(ev: 'done'): void;
+	(ev: 'closed'): void;
+	(ev: 'cancelled'): void;
 }>();
 
 const dialog = $ref<InstanceType<typeof XModalWindow>>();
 
+function onClose() {
+	emit('cancelled');
+	dialog.close();
+}
+
 function onLogin(res) {
 	emit('done', res);
 	dialog.close();
diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index f640e948ad..b772d1479b 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -1,39 +1,42 @@
 <template>
 <form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
 	<div class="auth _section _formRoot">
-		<div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }"></div>
+		<div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
+		<MkInfo v-if="message">
+			{{ message }}
+		</MkInfo>
 		<div v-if="!totpLogin" class="normal-signin">
-			<MkInput v-model="username" class="_formBlock" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
+			<MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
 				<template #prefix>@</template>
 				<template #suffix>@{{ host }}</template>
 			</MkInput>
-			<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="$ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
+			<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
 				<template #prefix><i class="fas fa-lock"></i></template>
-				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
+				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
 			</MkInput>
-			<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
+			<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 		</div>
 		<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
 			<div v-if="user && user.securityKeys" class="twofa-group tap-group">
-				<p>{{ $ts.tapSecurityKey }}</p>
+				<p>{{ i18n.ts.tapSecurityKey }}</p>
 				<MkButton v-if="!queryingKey" @click="queryKey">
-					{{ $ts.retry }}
+					{{ i18n.ts.retry }}
 				</MkButton>
 			</div>
 			<div v-if="user && user.securityKeys" class="or-hr">
-				<p class="or-msg">{{ $ts.or }}</p>
+				<p class="or-msg">{{ i18n.ts.or }}</p>
 			</div>
 			<div class="twofa-group totp-group">
-				<p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p>
+				<p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p>
 				<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required>
-					<template #label>{{ $ts.password }}</template>
+					<template #label>{{ i18n.ts.password }}</template>
 					<template #prefix><i class="fas fa-lock"></i></template>
 				</MkInput>
 				<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
-					<template #label>{{ $ts.token }}</template>
+					<template #label>{{ i18n.ts.token }}</template>
 					<template #prefix><i class="fas fa-gavel"></i></template>
 				</MkInput>
-				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
+				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 			</div>
 		</div>
 	</div>
@@ -45,190 +48,198 @@
 </form>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
 import { toUnicode } from 'punycode/';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
-import { apiUrl, host } from '@/config';
+import MkInfo from '@/components/ui/info.vue';
+import { apiUrl, host as configHost } from '@/config';
 import { byteify, hexify } from '@/scripts/2fa';
 import * as os from '@/os';
 import { login } from '@/account';
 import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		MkInput,
+let signing = $ref(false);
+let user = $ref(null);
+let username = $ref('');
+let password = $ref('');
+let token = $ref('');
+let host = $ref(toUnicode(configHost));
+let totpLogin = $ref(false);
+let credential = $ref(null);
+let challengeData = $ref(null);
+let queryingKey = $ref(false);
+let hCaptchaResponse = $ref(null);
+let reCaptchaResponse = $ref(null);
+
+const meta = $computed(() => instance);
+
+const emit = defineEmits<{
+	(ev: 'login', v: any): void;
+}>();
+
+const props = defineProps({
+	withAvatar: {
+		type: Boolean,
+		required: false,
+		default: true
 	},
-
-	props: {
-		withAvatar: {
-			type: Boolean,
-			required: false,
-			default: true
-		},
-		autoSet: {
-			type: Boolean,
-			required: false,
-			default: false,
-		}
+	autoSet: {
+		type: Boolean,
+		required: false,
+		default: false,
 	},
-
-	emits: ['login'],
-
-	data() {
-		return {
-			signing: false,
-			user: null,
-			username: '',
-			password: '',
-			token: '',
-			apiUrl,
-			host: toUnicode(host),
-			totpLogin: false,
-			credential: null,
-			challengeData: null,
-			queryingKey: false,
-		};
-	},
-
-	computed: {
-		meta() {
-			return this.$instance;
-		},
-	},
-
-	methods: {
-		onUsernameChange() {
-			os.api('users/show', {
-				username: this.username
-			}).then(user => {
-				this.user = user;
-			}, () => {
-				this.user = null;
-			});
-		},
-
-		onLogin(res) {
-			if (this.autoSet) {
-				return login(res.i);
-			} else {
-				return;
-			}
-		},
-
-		queryKey() {
-			this.queryingKey = true;
-			return navigator.credentials.get({
-				publicKey: {
-					challenge: byteify(this.challengeData.challenge, 'base64'),
-					allowCredentials: this.challengeData.securityKeys.map(key => ({
-						id: byteify(key.id, 'hex'),
-						type: 'public-key',
-						transports: ['usb', 'nfc', 'ble', 'internal']
-					})),
-					timeout: 60 * 1000
-				}
-			}).catch(() => {
-				this.queryingKey = false;
-				return Promise.reject(null);
-			}).then(credential => {
-				this.queryingKey = false;
-				this.signing = true;
-				return os.api('signin', {
-					username: this.username,
-					password: this.password,
-					signature: hexify(credential.response.signature),
-					authenticatorData: hexify(credential.response.authenticatorData),
-					clientDataJSON: hexify(credential.response.clientDataJSON),
-					credentialId: credential.id,
-					challengeId: this.challengeData.challengeId
-				});
-			}).then(res => {
-				this.$emit('login', res);
-				return this.onLogin(res);
-			}).catch(err => {
-				if (err === null) return;
-				os.alert({
-					type: 'error',
-					text: this.$ts.signinFailed
-				});
-				this.signing = false;
-			});
-		},
-
-		onSubmit() {
-			this.signing = true;
-			if (!this.totpLogin && this.user && this.user.twoFactorEnabled) {
-				if (window.PublicKeyCredential && this.user.securityKeys) {
-					os.api('signin', {
-						username: this.username,
-						password: this.password
-					}).then(res => {
-						this.totpLogin = true;
-						this.signing = false;
-						this.challengeData = res;
-						return this.queryKey();
-					}).catch(this.loginFailed);
-				} else {
-					this.totpLogin = true;
-					this.signing = false;
-				}
-			} else {
-				os.api('signin', {
-					username: this.username,
-					password: this.password,
-					token: this.user && this.user.twoFactorEnabled ? this.token : undefined
-				}).then(res => {
-					this.$emit('login', res);
-					this.onLogin(res);
-				}).catch(this.loginFailed);
-			}
-		},
-
-		loginFailed(err) {
-			switch (err.id) {
-				case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
-					os.alert({
-						type: 'error',
-						title: this.$ts.loginFailed,
-						text: this.$ts.noSuchUser
-					});
-					break;
-				}
-				case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
-					os.alert({
-						type: 'error',
-						title: this.$ts.loginFailed,
-						text: this.$ts.incorrectPassword,
-					});
-					break;
-				}
-				case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
-					showSuspendedDialog();
-					break;
-				}
-				default: {
-					os.alert({
-						type: 'error',
-						title: this.$ts.loginFailed,
-						text: JSON.stringify(err)
-					});
-				}
-			}
-
-			this.challengeData = null;
-			this.totpLogin = false;
-			this.signing = false;
-		},
-
-		resetPassword() {
-			os.popup(import('@/components/forgot-password.vue'), {}, {
-			}, 'closed');
-		}
+	message: {
+		type: String,
+		required: false,
+		default: ''
 	}
 });
+
+function onUsernameChange() {
+	os.api('users/show', {
+		username: username
+	}).then(userResponse => {
+		user = userResponse;
+	}, () => {
+		user = null;
+	});
+}
+
+function onLogin(res) {
+	if (props.autoSet) {
+		return login(res.i);
+	}
+}
+
+function queryKey() {
+	queryingKey = true;
+	return navigator.credentials.get({
+		publicKey: {
+			challenge: byteify(challengeData.challenge, 'base64'),
+			allowCredentials: challengeData.securityKeys.map(key => ({
+				id: byteify(key.id, 'hex'),
+				type: 'public-key',
+				transports: ['usb', 'nfc', 'ble', 'internal']
+			})),
+			timeout: 60 * 1000
+		}
+	}).catch(() => {
+		queryingKey = false;
+		return Promise.reject(null);
+	}).then(credential => {
+		queryingKey = false;
+		signing = true;
+		return os.api('signin', {
+			username,
+			password,
+			signature: hexify(credential.response.signature),
+			authenticatorData: hexify(credential.response.authenticatorData),
+			clientDataJSON: hexify(credential.response.clientDataJSON),
+			credentialId: credential.id,
+			challengeId: challengeData.challengeId,
+      'hcaptcha-response': hCaptchaResponse,
+			'g-recaptcha-response': reCaptchaResponse,
+		});
+	}).then(res => {
+		emit('login', res);
+		return onLogin(res);
+	}).catch(err => {
+		if (err === null) return;
+		os.alert({
+			type: 'error',
+			text: i18n.ts.signinFailed
+		});
+		signing = false;
+	});
+}
+
+function onSubmit() {
+	signing = true;
+	console.log('submit');
+	if (!totpLogin && user && user.twoFactorEnabled) {
+		if (window.PublicKeyCredential && user.securityKeys) {
+			os.api('signin', {
+				username,
+				password,
+        'hcaptcha-response': hCaptchaResponse,
+        'g-recaptcha-response': reCaptchaResponse,
+			}).then(res => {
+				totpLogin = true;
+				signing = false;
+				challengeData = res;
+				return queryKey();
+			}).catch(loginFailed);
+		} else {
+			totpLogin = true;
+			signing = false;
+		}
+	} else {
+		os.api('signin', {
+			username,
+			password,
+      'hcaptcha-response': hCaptchaResponse,
+			'g-recaptcha-response': reCaptchaResponse,
+			token: user && user.twoFactorEnabled ? token : undefined
+		}).then(res => {
+			emit('login', res);
+			onLogin(res);
+		}).catch(loginFailed);
+	}
+}
+
+function loginFailed(err) {
+	switch (err.id) {
+		case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.noSuchUser
+			});
+			break;
+		}
+		case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.incorrectPassword,
+			});
+			break;
+		}
+		case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
+			showSuspendedDialog();
+			break;
+		}
+		case '22d05606-fbcf-421a-a2db-b32610dcfd1b': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.rateLimitExceeded,
+			});
+			break;
+		}
+		default: {
+			console.log(err);
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: JSON.stringify(err)
+			});
+		}
+	}
+
+	challengeData = null;
+	totpLogin = false;
+	signing = false;
+}
+
+function resetPassword() {
+	os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
+	}, 'closed');
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/components/signup-dialog.vue b/packages/client/src/components/signup-dialog.vue
index bda2495ba7..6dad9257a4 100644
--- a/packages/client/src/components/signup-dialog.vue
+++ b/packages/client/src/components/signup-dialog.vue
@@ -27,8 +27,8 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'done'): void;
-	(e: 'closed'): void;
+	(ev: 'done'): void;
+	(ev: 'closed'): void;
 }>();
 
 const dialog = $ref<InstanceType<typeof XModalWindow>>();
diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index 38a9fd55f1..3f2af306e5 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -1,11 +1,11 @@
 <template>
-<form class="qlvuhzng _formRoot" :autocomplete="Math.random()" @submit.prevent="onSubmit">
+<form class="qlvuhzng _formRoot" autocomplete="new-password" @submit.prevent="onSubmit">
 	<template v-if="meta">
-		<MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" :autocomplete="Math.random()" spellcheck="false" required>
+		<MkInput v-if="meta.disableRegistration" v-model="invitationCode" class="_formBlock" type="text" spellcheck="false" required>
 			<template #label>{{ $ts.invitationCode }}</template>
 			<template #prefix><i class="fas fa-key"></i></template>
 		</MkInput>
-		<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
+		<MkInput v-model="username" class="_formBlock" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" spellcheck="false" required data-cy-signup-username @update:modelValue="onChangeUsername">
 			<template #label>{{ $ts.username }} <div v-tooltip:dialog="$ts.usernameInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
 			<template #prefix>@</template>
 			<template #suffix>@{{ host }}</template>
@@ -19,7 +19,7 @@
 				<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
 			</template>
 		</MkInput>
-		<MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" :autocomplete="Math.random()" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
+		<MkInput v-if="meta.emailRequiredForSignup" v-model="email" class="_formBlock" :debounce="true" type="email" spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
 			<template #label>{{ $ts.emailAddress }} <div v-tooltip:dialog="$ts._signup.emailAddressInfo" class="_button _help"><i class="far fa-question-circle"></i></div></template>
 			<template #prefix><i class="fas fa-envelope"></i></template>
 			<template #caption>
@@ -34,7 +34,7 @@
 				<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
 			</template>
 		</MkInput>
-		<MkInput v-model="password" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password @update:modelValue="onChangePassword">
+		<MkInput v-model="password" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
 			<template #label>{{ $ts.password }}</template>
 			<template #prefix><i class="fas fa-lock"></i></template>
 			<template #caption>
@@ -43,7 +43,7 @@
 				<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span>
 			</template>
 		</MkInput>
-		<MkInput v-model="retypedPassword" class="_formBlock" type="password" :autocomplete="Math.random()" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
+		<MkInput v-model="retypedPassword" class="_formBlock" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
 			<template #label>{{ $ts.password }} ({{ $ts.retype }})</template>
 			<template #prefix><i class="fas fa-lock"></i></template>
 			<template #caption>
@@ -58,8 +58,8 @@
 				</template>
 			</I18n>
 		</MkSwitch>
-		<captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
-		<captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
+		<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
+		<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
 		<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton>
 	</template>
 </form>
@@ -67,7 +67,7 @@
 
 <script lang="ts">
 import { defineComponent, defineAsyncComponent } from 'vue';
-const getPasswordStrength = require('syuilo-password-strength');
+const getPasswordStrength = await import('syuilo-password-strength');
 import { toUnicode } from 'punycode/';
 import { host, url } from '@/config';
 import MkButton from './ui/button.vue';
@@ -81,7 +81,7 @@ export default defineComponent({
 		MkButton,
 		MkInput,
 		MkSwitch,
-		captcha: defineAsyncComponent(() => import('./captcha.vue')),
+		MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
 	},
 
 	props: {
@@ -111,7 +111,7 @@ export default defineComponent({
 			ToSAgreement: false,
 			hCaptchaResponse: null,
 			reCaptchaResponse: null,
-		}
+		};
 	},
 
 	computed: {
@@ -124,20 +124,20 @@ export default defineComponent({
 				this.meta.tosUrl && !this.ToSAgreement ||
 				this.meta.enableHcaptcha && !this.hCaptchaResponse ||
 				this.meta.enableRecaptcha && !this.reCaptchaResponse ||
-				this.passwordRetypeState == 'not-match';
+				this.passwordRetypeState === 'not-match';
 		},
 
 		shouldShowProfileUrl(): boolean {
-			return (this.username != '' &&
-				this.usernameState != 'invalid-format' &&
-				this.usernameState != 'min-range' &&
-				this.usernameState != 'max-range');
+			return (this.username !== '' &&
+				this.usernameState !== 'invalid-format' &&
+				this.usernameState !== 'min-range' &&
+				this.usernameState !== 'max-range');
 		}
 	},
 
 	methods: {
 		onChangeUsername() {
-			if (this.username == '') {
+			if (this.username === '') {
 				this.usernameState = null;
 				return;
 			}
@@ -165,7 +165,7 @@ export default defineComponent({
 		},
 
 		onChangeEmail() {
-			if (this.email == '') {
+			if (this.email === '') {
 				this.emailState = null;
 				return;
 			}
@@ -188,7 +188,7 @@ export default defineComponent({
 		},
 
 		onChangePassword() {
-			if (this.password == '') {
+			if (this.password === '') {
 				this.passwordStrength = '';
 				return;
 			}
@@ -198,12 +198,12 @@ export default defineComponent({
 		},
 
 		onChangePasswordRetype() {
-			if (this.retypedPassword == '') {
+			if (this.retypedPassword === '') {
 				this.passwordRetypeState = null;
 				return;
 			}
 
-			this.passwordRetypeState = this.password == this.retypedPassword ? 'match' : 'not-match';
+			this.passwordRetypeState = this.password === this.retypedPassword ? 'match' : 'not-match';
 		},
 
 		onSubmit() {
diff --git a/packages/client/src/components/timeline.vue b/packages/client/src/components/timeline.vue
index 59956b9526..a3fa27ab78 100644
--- a/packages/client/src/components/timeline.vue
+++ b/packages/client/src/components/timeline.vue
@@ -19,8 +19,8 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'note'): void;
-	(e: 'queue', count: number): void;
+	(ev: 'note'): void;
+	(ev: 'queue', count: number): void;
 }>();
 
 provide('inChannel', computed(() => props.src === 'channel'));
@@ -95,7 +95,7 @@ if (props.src === 'antenna') {
 		visibility: 'specified'
 	};
 	const onNote = note => {
-		if (note.visibility == 'specified') {
+		if (note.visibility === 'specified') {
 			prepend(note);
 		}
 	};
diff --git a/packages/client/src/components/toast.vue b/packages/client/src/components/toast.vue
index 99933f3846..c9fad64eb6 100644
--- a/packages/client/src/components/toast.vue
+++ b/packages/client/src/components/toast.vue
@@ -19,7 +19,7 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'closed'): void;
+	(ev: 'closed'): void;
 }>();
 
 const zIndex = os.claimZIndex('high');
diff --git a/packages/client/src/components/ui/button.vue b/packages/client/src/components/ui/button.vue
index c7b6c8ba96..e6b20d9881 100644
--- a/packages/client/src/components/ui/button.vue
+++ b/packages/client/src/components/ui/button.vue
@@ -90,32 +90,32 @@ export default defineComponent({
 		}
 	},
 	methods: {
-		onMousedown(e: MouseEvent) {
+		onMousedown(evt: MouseEvent) {
 			function distance(p, q) {
 				return Math.hypot(p.x - q.x, p.y - q.y);
 			}
 
 			function calcCircleScale(boxW, boxH, circleCenterX, circleCenterY) {
-				const origin = {x: circleCenterX, y: circleCenterY};
-				const dist1 = distance({x: 0, y: 0}, origin);
-				const dist2 = distance({x: boxW, y: 0}, origin);
-				const dist3 = distance({x: 0, y: boxH}, origin);
-				const dist4 = distance({x: boxW, y: boxH }, origin);
+				const origin = { x: circleCenterX, y: circleCenterY };
+				const dist1 = distance({ x: 0, y: 0 }, origin);
+				const dist2 = distance({ x: boxW, y: 0 }, origin);
+				const dist3 = distance({ x: 0, y: boxH }, origin);
+				const dist4 = distance({ x: boxW, y: boxH }, origin);
 				return Math.max(dist1, dist2, dist3, dist4) * 2;
 			}
 
-			const rect = e.target.getBoundingClientRect();
+			const rect = evt.target.getBoundingClientRect();
 
 			const ripple = document.createElement('div');
-			ripple.style.top = (e.clientY - rect.top - 1).toString() + 'px';
-			ripple.style.left = (e.clientX - rect.left - 1).toString() + 'px';
+			ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
+			ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
 
 			this.$refs.ripples.appendChild(ripple);
 
-			const circleCenterX = e.clientX - rect.left;
-			const circleCenterY = e.clientY - rect.top;
+			const circleCenterX = evt.clientX - rect.left;
+			const circleCenterY = evt.clientY - rect.top;
 
-			const scale = calcCircleScale(e.target.clientWidth, e.target.clientHeight, circleCenterX, circleCenterY);
+			const scale = calcCircleScale(evt.target.clientWidth, evt.target.clientHeight, circleCenterX, circleCenterY);
 
 			window.setTimeout(() => {
 				ripple.style.transform = 'scale(' + (scale / 2) + ')';
diff --git a/packages/client/src/components/ui/context-menu.vue b/packages/client/src/components/ui/context-menu.vue
index f491b43b46..e637d361cf 100644
--- a/packages/client/src/components/ui/context-menu.vue
+++ b/packages/client/src/components/ui/context-menu.vue
@@ -19,7 +19,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'closed'): void;
+	(ev: 'closed'): void;
 }>();
 
 let rootEl = $ref<HTMLDivElement>();
@@ -63,8 +63,8 @@ onBeforeUnmount(() => {
 	}
 });
 
-function onMousedown(e: Event) {
-	if (!contains(rootEl, e.target) && (rootEl != e.target)) emit('closed');
+function onMousedown(evt: Event) {
+	if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
 }
 </script>
 
diff --git a/packages/client/src/components/ui/folder.vue b/packages/client/src/components/ui/folder.vue
index fe1602b2bb..7daa82cbd3 100644
--- a/packages/client/src/components/ui/folder.vue
+++ b/packages/client/src/components/ui/folder.vue
@@ -23,7 +23,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 
 const localStoragePrefix = 'ui:folder:';
 
diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
index a93cc8cda8..dad5dfa8b0 100644
--- a/packages/client/src/components/ui/menu.vue
+++ b/packages/client/src/components/ui/menu.vue
@@ -1,5 +1,6 @@
 <template>
-<div ref="itemsEl" v-hotkey="keymap"
+<div
+	ref="itemsEl" v-hotkey="keymap"
 	class="rrevdjwt"
 	:class="{ center: align === 'center', asDrawer }"
 	:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
@@ -60,7 +61,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'close'): void;
+	(ev: 'close'): void;
 }>();
 
 let itemsEl = $ref<HTMLDivElement>();
@@ -162,6 +163,15 @@ function focusDown() {
 			position: relative;
 		}
 
+		&:not(:disabled):hover {
+			color: var(--accent);
+			text-decoration: none;
+
+			&:before {
+				background: var(--accentedBg);
+			}
+		}
+
 		&.danger {
 			color: #ff2a2a;
 
@@ -191,15 +201,6 @@ function focusDown() {
 			}
 		}
 
-		&:not(:disabled):hover {
-			color: var(--accent);
-			text-decoration: none;
-
-			&:before {
-				background: var(--accentedBg);
-			}
-		}
-
 		&:not(:active):focus-visible {
 			box-shadow: 0 0 0 2px var(--focus) inset;
 		}
diff --git a/packages/client/src/components/ui/modal-window.vue b/packages/client/src/components/ui/modal-window.vue
index b4b8c2b965..d2b2ccff7a 100644
--- a/packages/client/src/components/ui/modal-window.vue
+++ b/packages/client/src/components/ui/modal-window.vue
@@ -1,7 +1,7 @@
 <template>
-<MkModal ref="modal" :prefer-type="'dialog'" @click="$emit('click')" @closed="$emit('closed')">
-	<div class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
-		<div class="header">
+<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
+	<div ref="rootEl" class="ebkgoccj _window _narrow_" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
+		<div ref="headerEl" class="header">
 			<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="fas fa-times"></i></button>
 			<span class="title">
 				<slot name="header"></slot>
@@ -11,82 +11,82 @@
 		</div>
 		<div v-if="padding" class="body">
 			<div class="_section">
-				<slot></slot>
+				<slot :width="bodyWidth" :height="bodyHeight"></slot>
 			</div>
 		</div>
 		<div v-else class="body">
-			<slot></slot>
+			<slot :width="bodyWidth" :height="bodyHeight"></slot>
 		</div>
 	</div>
 </MkModal>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onUnmounted } from 'vue';
 import MkModal from './modal.vue';
 
-export default defineComponent({
-	components: {
-		MkModal
-	},
-	props: {
-		withOkButton: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		okButtonDisabled: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		padding: {
-			type: Boolean,
-			required: false,
-			default: false
-		},
-		width: {
-			type: Number,
-			required: false,
-			default: 400
-		},
-		height: {
-			type: Number,
-			required: false,
-			default: null
-		},
-		canClose: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		scroll: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-	},
+const props = withDefaults(defineProps<{
+	withOkButton: boolean;
+	okButtonDisabled: boolean;
+	padding: boolean;
+	width: number;
+	height: number | null;
+	scroll: boolean;
+}>(), {
+	withOkButton: false,
+	okButtonDisabled: false,
+	padding: false,
+	width: 400,
+	height: null,
+	scroll: true,
+});
 
-	emits: ['click', 'close', 'closed', 'ok'],
+const emit = defineEmits<{
+	(event: 'click'): void;
+	(event: 'close'): void;
+	(event: 'closed'): void;
+	(event: 'ok'): void;
+}>();
 
-	data() {
-		return {
-		};
-	},
+let modal = $ref<InstanceType<typeof MkModal>>();
+let rootEl = $ref<HTMLElement>();
+let headerEl = $ref<HTMLElement>();
+let bodyWidth = $ref(0);
+let bodyHeight = $ref(0);
 
-	methods: {
-		close() {
-			this.$refs.modal.close();
-		},
+const close = () => {
+	modal.close();
+};
 
-		onKeydown(e) {
-			if (e.which === 27) { // Esc
-				e.preventDefault();
-				e.stopPropagation();
-				this.close();
-			}
-		},
+const onBgClick = () => {
+	emit('click');
+};
+
+const onKeydown = (evt) => {
+	if (evt.which === 27) { // Esc
+		evt.preventDefault();
+		evt.stopPropagation();
+		close();
 	}
+};
+
+const ro = new ResizeObserver((entries, observer) => {
+	bodyWidth = rootEl.offsetWidth;
+	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
+});
+
+onMounted(() => {
+	bodyWidth = rootEl.offsetWidth;
+	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
+	ro.observe(rootEl);
+});
+
+onUnmounted(() => {
+	ro.disconnect();
+});
+
+defineExpose({
+	close,
 });
 </script>
 
diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue
index 1e4159055e..d6a29ec4b7 100644
--- a/packages/client/src/components/ui/modal.vue
+++ b/packages/client/src/components/ui/modal.vue
@@ -1,5 +1,5 @@
 <template>
-<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="childRendered">
+<transition :name="$store.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : ''" :duration="$store.state.animation ? 200 : 0" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
 	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
 		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
 		<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
@@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{
 
 const emit = defineEmits<{
 	(ev: 'opening'): void;
+	(ev: 'opened'): void;
 	(ev: 'click'): void;
 	(ev: 'esc'): void;
 	(ev: 'close'): void;
@@ -212,7 +213,9 @@ const align = () => {
 	popover.style.top = top + 'px';
 };
 
-const childRendered = () => {
+const onOpened = () => {
+	emit('opened');
+
 	// ใƒขใƒผใƒ€ใƒซใ‚ณใƒณใƒ†ใƒณใƒ„ใซใƒžใ‚ฆใ‚นใƒœใ‚ฟใƒณใŒๆŠผใ•ใ‚Œใ€ใ‚ณใƒณใƒ†ใƒณใƒ„ๅค–ใงใƒžใ‚ฆใ‚นใƒœใ‚ฟใƒณใŒ้›ขใ•ใ‚ŒใŸใจใใซใƒขใƒผใƒ€ใƒซใƒใƒƒใ‚ฏใ‚ฐใƒฉใ‚ฆใƒณใƒ‰ใ‚ฏใƒชใƒƒใ‚ฏใจๅˆคๅฎšใ•ใ›ใชใ„ใŸใ‚ใซใƒžใ‚ฆใ‚นใ‚คใƒ™ใƒณใƒˆใ‚’็›ฃ่ฆ–ใ—ใƒ•ใƒฉใ‚ฐ็ฎก็†ใ™ใ‚‹
 	const el = content.value!.children[0];
 	el.addEventListener('mousedown', ev => {
@@ -234,10 +237,10 @@ onMounted(() => {
 		}
 		fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
 
-		await nextTick()
+		await nextTick();
 		
 		align();
-	}, { immediate: true, });
+	}, { immediate: true });
 
 	nextTick(() => {
 		const popover = content.value;
diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index 13f3215671..c081e06acd 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -14,8 +14,14 @@
 	</div>
 
 	<div v-else ref="rootEl">
+		<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
+			<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
+				{{ $ts.loadMore }}
+			</MkButton>
+			<MkLoading v-else class="loading"/>
+		</div>
 		<slot :items="items"></slot>
-		<div v-show="more" key="_more_" class="cxiknjgy _gap">
+		<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
 			<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
 				{{ $ts.loadMore }}
 			</MkButton>
@@ -62,7 +68,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'queue', count: number): void;
+	(ev: 'queue', count: number): void;
 }>();
 
 type Item = { id: string; [another: string]: unknown; };
@@ -106,7 +112,7 @@ const init = async (): Promise<void> => {
 		offset.value = res.length;
 		error.value = false;
 		fetching.value = false;
-	}, e => {
+	}, err => {
 		error.value = true;
 		fetching.value = false;
 	});
@@ -149,7 +155,7 @@ const fetchMore = async (): Promise<void> => {
 		}
 		offset.value += res.length;
 		moreFetching.value = false;
-	}, e => {
+	}, err => {
 		moreFetching.value = false;
 	});
 };
@@ -177,7 +183,7 @@ const fetchMoreAhead = async (): Promise<void> => {
 		}
 		offset.value += res.length;
 		moreFetching.value = false;
-	}, e => {
+	}, err => {
 		moreFetching.value = false;
 	});
 };
@@ -244,6 +250,11 @@ const append = (item: Item): void => {
 	items.value.push(item);
 };
 
+const removeItem = (finder: (item: Item) => boolean) => {
+	const i = items.value.findIndex(finder);
+	items.value.splice(i, 1);
+};
+
 const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
 	const i = items.value.findIndex(item => item.id === id);
 	items.value[i] = replacer(items.value[i]);
@@ -270,11 +281,12 @@ onDeactivated(() => {
 
 defineExpose({
 	items,
+	queue,
 	backed,
 	reload,
-	fetchMoreAhead,
 	prepend,
 	append,
+	removeItem,
 	updateItem,
 });
 </script>
diff --git a/packages/client/src/components/ui/popup-menu.vue b/packages/client/src/components/ui/popup-menu.vue
index 8d6c1b5695..2bc7030d77 100644
--- a/packages/client/src/components/ui/popup-menu.vue
+++ b/packages/client/src/components/ui/popup-menu.vue
@@ -19,7 +19,7 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'closed'): void;
+	(ev: 'closed'): void;
 }>();
 
 let modal = $ref<InstanceType<typeof MkModal>>();
diff --git a/packages/client/src/components/ui/tooltip.vue b/packages/client/src/components/ui/tooltip.vue
index ee1909554e..571d11ba3b 100644
--- a/packages/client/src/components/ui/tooltip.vue
+++ b/packages/client/src/components/ui/tooltip.vue
@@ -63,7 +63,7 @@ const setPosition = () => {
 		}
 
 		return [left, top];
-	}
+	};
 
 	const calcPosWhenBottom = () => {
 		let left: number;
@@ -84,7 +84,7 @@ const setPosition = () => {
 		}
 
 		return [left, top];
-	}
+	};
 
 	const calcPosWhenLeft = () => {
 		let left: number;
@@ -105,7 +105,7 @@ const setPosition = () => {
 		}
 
 		return [left, top];
-	}
+	};
 
 	const calcPosWhenRight = () => {
 		let left: number;
@@ -126,7 +126,7 @@ const setPosition = () => {
 		}
 
 		return [left, top];
-	}
+	};
 
 	const calc = (): {
 		left: number;
@@ -172,7 +172,7 @@ const setPosition = () => {
 		}
 
 		return null as never;
-	}
+	};
 
 	const { left, top, transformOrigin } = calc();
 	el.value.style.transformOrigin = transformOrigin;
diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue
index fa32ecfdef..2066cf579d 100644
--- a/packages/client/src/components/ui/window.vue
+++ b/packages/client/src/components/ui/window.vue
@@ -139,10 +139,10 @@ export default defineComponent({
 			this.showing = false;
 		},
 
-		onKeydown(e) {
-			if (e.which === 27) { // Esc
-				e.preventDefault();
-				e.stopPropagation();
+		onKeydown(evt) {
+			if (evt.which === 27) { // Esc
+				evt.preventDefault();
+				evt.stopPropagation();
 				this.close();
 			}
 		},
@@ -162,15 +162,15 @@ export default defineComponent({
 			this.top();
 		},
 
-		onHeaderMousedown(e) {
+		onHeaderMousedown(evt) {
 			const main = this.$el as any;
 
 			if (!contains(main, document.activeElement)) main.focus();
 
 			const position = main.getBoundingClientRect();
 
-			const clickX = e.touches && e.touches.length > 0 ? e.touches[0].clientX : e.clientX;
-			const clickY = e.touches && e.touches.length > 0 ? e.touches[0].clientY : e.clientY;
+			const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX;
+			const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY;
 			const moveBaseX = clickX - position.left;
 			const moveBaseY = clickY - position.top;
 			const browserWidth = window.innerWidth;
@@ -204,10 +204,10 @@ export default defineComponent({
 		},
 
 		// ไธŠใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onTopHandleMousedown(e) {
+		onTopHandleMousedown(evt) {
 			const main = this.$el as any;
 
-			const base = e.clientY;
+			const base = evt.clientY;
 			const height = parseInt(getComputedStyle(main, '').height, 10);
 			const top = parseInt(getComputedStyle(main, '').top, 10);
 
@@ -230,10 +230,10 @@ export default defineComponent({
 		},
 
 		// ๅณใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onRightHandleMousedown(e) {
+		onRightHandleMousedown(evt) {
 			const main = this.$el as any;
 
-			const base = e.clientX;
+			const base = evt.clientX;
 			const width = parseInt(getComputedStyle(main, '').width, 10);
 			const left = parseInt(getComputedStyle(main, '').left, 10);
 			const browserWidth = window.innerWidth;
@@ -254,10 +254,10 @@ export default defineComponent({
 		},
 
 		// ไธ‹ใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onBottomHandleMousedown(e) {
+		onBottomHandleMousedown(evt) {
 			const main = this.$el as any;
 
-			const base = e.clientY;
+			const base = evt.clientY;
 			const height = parseInt(getComputedStyle(main, '').height, 10);
 			const top = parseInt(getComputedStyle(main, '').top, 10);
 			const browserHeight = window.innerHeight;
@@ -278,10 +278,10 @@ export default defineComponent({
 		},
 
 		// ๅทฆใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onLeftHandleMousedown(e) {
+		onLeftHandleMousedown(evt) {
 			const main = this.$el as any;
 
-			const base = e.clientX;
+			const base = evt.clientX;
 			const width = parseInt(getComputedStyle(main, '').width, 10);
 			const left = parseInt(getComputedStyle(main, '').left, 10);
 
@@ -304,27 +304,27 @@ export default defineComponent({
 		},
 
 		// ๅทฆไธŠใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onTopLeftHandleMousedown(e) {
-			this.onTopHandleMousedown(e);
-			this.onLeftHandleMousedown(e);
+		onTopLeftHandleMousedown(evt) {
+			this.onTopHandleMousedown(evt);
+			this.onLeftHandleMousedown(evt);
 		},
 
 		// ๅณไธŠใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onTopRightHandleMousedown(e) {
-			this.onTopHandleMousedown(e);
-			this.onRightHandleMousedown(e);
+		onTopRightHandleMousedown(evt) {
+			this.onTopHandleMousedown(evt);
+			this.onRightHandleMousedown(evt);
 		},
 
 		// ๅณไธ‹ใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onBottomRightHandleMousedown(e) {
-			this.onBottomHandleMousedown(e);
-			this.onRightHandleMousedown(e);
+		onBottomRightHandleMousedown(evt) {
+			this.onBottomHandleMousedown(evt);
+			this.onRightHandleMousedown(evt);
 		},
 
 		// ๅทฆไธ‹ใƒใƒณใƒ‰ใƒซๆŽดใฟๆ™‚
-		onBottomLeftHandleMousedown(e) {
-			this.onBottomHandleMousedown(e);
-			this.onLeftHandleMousedown(e);
+		onBottomLeftHandleMousedown(evt) {
+			this.onBottomHandleMousedown(evt);
+			this.onLeftHandleMousedown(evt);
 		},
 
 		// ้ซ˜ใ•ใ‚’้ฉ็”จ
diff --git a/packages/client/src/components/url-preview.vue b/packages/client/src/components/url-preview.vue
index c7bbd1fbd1..6c593c7b41 100644
--- a/packages/client/src/components/url-preview.vue
+++ b/packages/client/src/components/url-preview.vue
@@ -90,7 +90,7 @@ fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${requestLang}`).the
 		sitename = info.sitename;
 		fetching = false;
 		player = info.player;
-	})
+	});
 });
 
 function adjustTweetHeight(message: any) {
diff --git a/packages/client/src/components/user-preview.vue b/packages/client/src/components/user-preview.vue
index 51c5330564..f80947f75a 100644
--- a/packages/client/src/components/user-preview.vue
+++ b/packages/client/src/components/user-preview.vue
@@ -70,7 +70,7 @@ export default defineComponent({
 	},
 
 	mounted() {
-		if (typeof this.q == 'object') {
+		if (typeof this.q === 'object') {
 			this.user = this.q;
 			this.fetched = true;
 		} else {
diff --git a/packages/client/src/components/user-select-dialog.vue b/packages/client/src/components/user-select-dialog.vue
index dbef34d547..b34d21af07 100644
--- a/packages/client/src/components/user-select-dialog.vue
+++ b/packages/client/src/components/user-select-dialog.vue
@@ -60,9 +60,9 @@ import * as os from '@/os';
 import { defaultStore } from '@/store';
 
 const emit = defineEmits<{
-  (e: 'ok', selected: misskey.entities.UserDetailed): void;
-  (e: 'cancel'): void;
-	(e: 'closed'): void;
+	(ev: 'ok', selected: misskey.entities.UserDetailed): void;
+	(ev: 'cancel'): void;
+	(ev: 'closed'): void;
 }>();
 
 let username = $ref('');
diff --git a/packages/client/src/components/visibility-picker.vue b/packages/client/src/components/visibility-picker.vue
index 4b20063a51..c717c3a461 100644
--- a/packages/client/src/components/visibility-picker.vue
+++ b/packages/client/src/components/visibility-picker.vue
@@ -57,9 +57,9 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void;
-	(e: 'changeLocalOnly', v: boolean): void;
-	(e: 'closed'): void;
+	(ev: 'changeVisibility', v: typeof misskey.noteVisibilities[number]): void;
+	(ev: 'changeLocalOnly', v: boolean): void;
+	(ev: 'closed'): void;
 }>();
 
 let v = $ref(props.currentVisibility);
diff --git a/packages/client/src/components/waiting-dialog.vue b/packages/client/src/components/waiting-dialog.vue
index 7dfcc55695..9e631b55b1 100644
--- a/packages/client/src/components/waiting-dialog.vue
+++ b/packages/client/src/components/waiting-dialog.vue
@@ -21,8 +21,8 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'done');
-	(e: 'closed');
+	(ev: 'done');
+	(ev: 'closed');
 }>();
 
 function done() {
diff --git a/packages/client/src/components/widgets.vue b/packages/client/src/components/widgets.vue
index da9d935281..74dd79f733 100644
--- a/packages/client/src/components/widgets.vue
+++ b/packages/client/src/components/widgets.vue
@@ -2,11 +2,11 @@
 <div class="vjoppmmu">
 	<template v-if="edit">
 		<header>
-			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)">
+			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select">
 				<template #label>{{ $ts.selectWidget }}</template>
 				<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ $t(`_widgets.${widget}`) }}</option>
 			</MkSelect>
-			<MkButton inline primary @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
+			<MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
 			<MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton>
 		</header>
 		<XDraggable
@@ -19,7 +19,7 @@
 				<div class="customize-container">
 					<button class="config _button" @click.prevent.stop="configWidget(element.id)"><i class="fas fa-cog"></i></button>
 					<button class="remove _button" @click.prevent.stop="removeWidget(element)"><i class="fas fa-times"></i></button>
-					<component class="handle" :ref="el => widgetRefs[element.id] = el" :is="`mkw-${element.name}`" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
+					<component :is="`mkw-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="handle" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
 				</div>
 			</template>
 		</XDraggable>
@@ -37,7 +37,7 @@ import { widgets as widgetDefs } from '@/widgets';
 
 export default defineComponent({
 	components: {
-		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
+		XDraggable: defineAsyncComponent(() => import('vuedraggable')),
 		MkSelect,
 		MkButton,
 	},
diff --git a/packages/client/src/directives/adaptive-border.ts b/packages/client/src/directives/adaptive-border.ts
index fc426ca2cc..619c9f0b6d 100644
--- a/packages/client/src/directives/adaptive-border.ts
+++ b/packages/client/src/directives/adaptive-border.ts
@@ -9,7 +9,7 @@ export default {
 			} else {
 				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
 			}
-		}
+		};
 	
 		const parentBg = getBgColor(src.parentElement);
 
diff --git a/packages/client/src/directives/get-size.ts b/packages/client/src/directives/get-size.ts
index 1fcd0718dc..2c4e9c188d 100644
--- a/packages/client/src/directives/get-size.ts
+++ b/packages/client/src/directives/get-size.ts
@@ -25,12 +25,12 @@ function calc(src: Element) {
 		return;
 	}
 	if (info.intersection) {
-		info.intersection.disconnect()
+		info.intersection.disconnect();
 		delete info.intersection;
-	};
+	}
 
 	info.fn(width, height);
-};
+}
 
 export default {
 	mounted(src, binding, vn) {
diff --git a/packages/client/src/directives/panel.ts b/packages/client/src/directives/panel.ts
index 5f9158db2e..d31dc41ed4 100644
--- a/packages/client/src/directives/panel.ts
+++ b/packages/client/src/directives/panel.ts
@@ -9,7 +9,7 @@ export default {
 			} else {
 				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
 			}
-		}
+		};
 	
 		const parentBg = getBgColor(src.parentElement);
 
diff --git a/packages/client/src/directives/size.ts b/packages/client/src/directives/size.ts
index 36f649f180..51855e0de5 100644
--- a/packages/client/src/directives/size.ts
+++ b/packages/client/src/directives/size.ts
@@ -60,9 +60,9 @@ function calc(el: Element) {
 		return;
 	}
 	if (info.intersection) {
-		info.intersection.disconnect()
+		info.intersection.disconnect();
 		delete info.intersection;
-	};
+	}
 
 	mountings.set(el, Object.assign(info, { previousWidth: width }));
 
diff --git a/packages/client/src/directives/tooltip.ts b/packages/client/src/directives/tooltip.ts
index dd715227a4..0e69da954e 100644
--- a/packages/client/src/directives/tooltip.ts
+++ b/packages/client/src/directives/tooltip.ts
@@ -1,7 +1,7 @@
 // TODO: useTooltip้–ขๆ•ฐไฝฟใ†ใ‚ˆใ†ใซใ—ใŸใ„
 // ใŸใ ใƒ‡ใ‚ฃใƒฌใ‚ฏใƒ†ใ‚ฃใƒ–ๅ†…ใงonUnmountedใชใฉใฎcomposition apiไฝฟใˆใ‚‹ใฎใ‹ไธๆ˜Ž
 
-import { Directive, ref } from 'vue';
+import { defineAsyncComponent, Directive, ref } from 'vue';
 import { isTouchUsing } from '@/scripts/touch';
 import { popup, alert } from '@/os';
 
@@ -45,7 +45,7 @@ export default {
 			if (self.text == null) return;
 
 			const showing = ref(true);
-			popup(import('@/components/ui/tooltip.vue'), {
+			popup(defineAsyncComponent(() => import('@/components/ui/tooltip.vue')), {
 				showing,
 				text: self.text,
 				targetElement: el,
diff --git a/packages/client/src/directives/user-preview.ts b/packages/client/src/directives/user-preview.ts
index cdd2afa194..9d18a69877 100644
--- a/packages/client/src/directives/user-preview.ts
+++ b/packages/client/src/directives/user-preview.ts
@@ -1,4 +1,4 @@
-import { Directive, ref } from 'vue';
+import { defineAsyncComponent, Directive, ref } from 'vue';
 import autobind from 'autobind-decorator';
 import { popup } from '@/os';
 
@@ -24,7 +24,7 @@ export class UserPreview {
 
 		const showing = ref(true);
 
-		popup(import('@/components/user-preview.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/user-preview.vue')), {
 			showing,
 			q: this.user,
 			source: this.el
diff --git a/packages/client/src/emojilist.json b/packages/client/src/emojilist.json
index 75c424ab4b..402e82e33b 100644
--- a/packages/client/src/emojilist.json
+++ b/packages/client/src/emojilist.json
@@ -96,6 +96,13 @@
 	{ "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] },
 	{ "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] },
 	{ "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] },
+	{ "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] },
+	{ "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] },
+	{ "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] },
+	{ "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] },
+	{ "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] },
+	{ "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] },
+	{ "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] },
 	{ "category": "face", "char": "๐Ÿ’ฉ", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] },
 	{ "category": "face", "char": "๐Ÿ˜ˆ", "name": "smiling_imp", "keywords": ["devil", "horns"] },
 	{ "category": "face", "char": "๐Ÿ‘ฟ", "name": "imp", "keywords": ["devil", "angry", "horns"] },
@@ -149,11 +156,19 @@
 	{ "category": "people", "char": "๐Ÿคž", "name": "crossed_fingers", "keywords": ["good", "lucky"] },
 	{ "category": "people", "char": "๐Ÿ––", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] },
 	{ "category": "people", "char": "โœ", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] },
+	{ "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] },
 	{ "category": "people", "char": "๐Ÿค", "name": "pinching_hand", "keywords": ["hand", "fingers"] },
 	{ "category": "people", "char": "๐ŸคŒ", "name": "pinched_fingers", "keywords": ["hand", "fingers"] },
 	{ "category": "people", "char": "๐Ÿคณ", "name": "selfie", "keywords": ["camera", "phone"] },
 	{ "category": "people", "char": "๐Ÿ’…", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] },
 	{ "category": "people", "char": "๐Ÿ‘„", "name": "lips", "keywords": ["mouth", "kiss"] },
+	{ "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] },
 	{ "category": "people", "char": "๐Ÿฆท", "name": "tooth", "keywords": ["teeth", "dentist"] },
 	{ "category": "people", "char": "๐Ÿ‘…", "name": "tongue", "keywords": ["mouth", "playful"] },
 	{ "category": "people", "char": "๐Ÿ‘‚", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] },
@@ -275,7 +290,11 @@
 	{ "category": "people", "char": "๐Ÿงšโ€โ™€๏ธ", "name": "woman_fairy", "keywords": ["woman", "female"] },
 	{ "category": "people", "char": "๐Ÿงšโ€โ™‚๏ธ", "name": "man_fairy", "keywords": ["man", "male"] },
 	{ "category": "people", "char": "๐Ÿ‘ผ", "name": "angel", "keywords": ["heaven", "wings", "halo"] },
+	{ "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] },
 	{ "category": "people", "char": "๐Ÿคฐ", "name": "pregnant_woman", "keywords": ["baby"] },
+	{ "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] },
+	{ "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] },
 	{ "category": "people", "char": "๐Ÿคฑ", "name": "breastfeeding", "keywords": ["nursing", "baby"] },
 	{ "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] },
 	{ "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] },
@@ -459,7 +478,7 @@
 	{ "category": "animals_and_nature", "char": "๐Ÿ›", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] },
 	{ "category": "animals_and_nature", "char": "๐Ÿฆ‹", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] },
 	{ "category": "animals_and_nature", "char": "๐ŸŒ", "name": "snail", "keywords": ["slow", "animal", "shell"] },
-	{ "category": "animals_and_nature", "char": "๐Ÿž", "name": "beetle", "keywords": ["animal", "insect", "nature", "ladybug"] },
+	{ "category": "animals_and_nature", "char": "๐Ÿž", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] },
 	{ "category": "animals_and_nature", "char": "๐Ÿœ", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] },
 	{ "category": "animals_and_nature", "char": "๐Ÿฆ—", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] },
 	{ "category": "animals_and_nature", "char": "๐Ÿ•ท", "name": "spider", "keywords": ["animal", "arachnid"] },
@@ -615,6 +634,10 @@
 	{ "category": "animals_and_nature", "char": "๐Ÿ’ง", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] },
 	{ "category": "animals_and_nature", "char": "๐Ÿ’ฆ", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] },
 	{ "category": "animals_and_nature", "char": "๐ŸŒŠ", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] },
+	{ "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] },
+	{ "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] },
+	{ "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] },
+	{ "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] },
 	{ "category": "food_and_drink", "char": "๐Ÿ", "name": "green_apple", "keywords": ["fruit", "nature"] },
 	{ "category": "food_and_drink", "char": "๐ŸŽ", "name": "apple", "keywords": ["fruit", "mac", "school"] },
 	{ "category": "food_and_drink", "char": "๐Ÿ", "name": "pear", "keywords": ["fruit", "nature", "food"] },
@@ -737,6 +760,9 @@
 	{ "category": "food_and_drink", "char": "๐Ÿฅฃ", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] },
 	{ "category": "food_and_drink", "char": "๐Ÿฅก", "name": "takeout_box", "keywords": ["food", "leftovers"] },
 	{ "category": "food_and_drink", "char": "๐Ÿฅข", "name": "chopsticks", "keywords": ["food"] },
+	{ "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] },
+	{ "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] },
+	{ "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] },
 	{ "category": "activity", "char": "โšฝ", "name": "soccer", "keywords": ["sports", "football"] },
 	{ "category": "activity", "char": "๐Ÿ€", "name": "basketball", "keywords": ["sports", "balls", "NBA"] },
 	{ "category": "activity", "char": "๐Ÿˆ", "name": "football", "keywords": ["sports", "balls", "NFL"] },
@@ -844,6 +870,8 @@
 	{ "category": "activity", "char": "๐Ÿช„", "name": "magic_wand", "keywords": [] },
 	{ "category": "activity", "char": "๐Ÿช…", "name": "pinata", "keywords": [] },
 	{ "category": "activity", "char": "๐Ÿช†", "name": "nesting_dolls", "keywords": [] },
+	{ "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] },
+	{ "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] },
 	{ "category": "travel_and_places", "char": "๐Ÿš—", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] },
 	{ "category": "travel_and_places", "char": "๐Ÿš•", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] },
 	{ "category": "travel_and_places", "char": "๐Ÿš™", "name": "blue_car", "keywords": ["transportation", "vehicle"] },
@@ -971,11 +999,12 @@
 	{ "category": "travel_and_places", "char": "๐Ÿ•‹", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] },
 	{ "category": "travel_and_places", "char": "โ›ฉ", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] },
 	{ "category": "travel_and_places", "char": "๐Ÿ›•", "name": "hindu_temple", "keywords": ["temple"] },
-
 	{ "category": "travel_and_places", "char": "๐Ÿชจ", "name": "rock", "keywords": [] },
 	{ "category": "travel_and_places", "char": "๐Ÿชต", "name": "wood", "keywords": [] },
 	{ "category": "travel_and_places", "char": "๐Ÿ›–", "name": "hut", "keywords": [] },
-
+	{ "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] },
+	{ "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] },
+	{ "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] },
 	{ "category": "objects", "char": "โŒš", "name": "watch", "keywords": ["time", "accessories"] },
 	{ "category": "objects", "char": "๐Ÿ“ฑ", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] },
 	{ "category": "objects", "char": "๐Ÿ“ฒ", "name": "calling", "keywords": ["iphone", "incoming"] },
@@ -1016,6 +1045,7 @@
 	{ "category": "objects", "char": "โŒ›", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] },
 	{ "category": "objects", "char": "๐Ÿ“ก", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] },
 	{ "category": "objects", "char": "๐Ÿ”‹", "name": "battery", "keywords": ["power", "energy", "sustain"] },
+	{ "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] },
 	{ "category": "objects", "char": "๐Ÿ”Œ", "name": "electric_plug", "keywords": ["charger", "power"] },
 	{ "category": "objects", "char": "๐Ÿ’ก", "name": "bulb", "keywords": ["light", "electricity", "idea"] },
 	{ "category": "objects", "char": "๐Ÿ”ฆ", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] },
@@ -1031,6 +1061,7 @@
 	{ "category": "objects", "char": "๐Ÿ’ฐ", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] },
 	{ "category": "objects", "char": "๐Ÿช™", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] },
 	{ "category": "objects", "char": "๐Ÿ’ณ", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] },
+	{ "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] },
 	{ "category": "objects", "char": "๐Ÿ’Ž", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] },
 	{ "category": "objects", "char": "โš–", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] },
 	{ "category": "objects", "char": "๐Ÿงฐ", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] },
@@ -1077,6 +1108,8 @@
 	{ "category": "objects", "char": "๐Ÿฉน", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
 	{ "category": "objects", "char": "๐Ÿฉบ", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
 	{ "category": "objects", "char": "๐Ÿช’", "name": "razor", "keywords": ["health"] },
+	{ "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] },
+	{ "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] },
 	{ "category": "objects", "char": "๐Ÿงฌ", "name": "dna", "keywords": ["biologist", "genetics", "life"] },
 	{ "category": "objects", "char": "๐Ÿงซ", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] },
 	{ "category": "objects", "char": "๐Ÿงช", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] },
@@ -1111,6 +1144,7 @@
 	{ "category": "objects", "char": "๐Ÿชค", "name": "mouse_trap", "keywords": ["household"] },
 	{ "category": "objects", "char": "๐Ÿชฃ", "name": "bucket", "keywords": ["household"] },
 	{ "category": "objects", "char": "๐Ÿชฅ", "name": "toothbrush", "keywords": ["household"] },
+	{ "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] },
 	{ "category": "objects", "char": "โ›ฑ", "name": "parasol_on_ground", "keywords": ["weather", "summer"] },
 	{ "category": "objects", "char": "๐Ÿ—ฟ", "name": "moyai", "keywords": ["rock", "easter island", "moai"] },
 	{ "category": "objects", "char": "๐Ÿ›", "name": "shopping", "keywords": ["mall", "buy", "purchase"] },
@@ -1404,6 +1438,7 @@
 	{ "category": "symbols", "char": "โž–", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] },
 	{ "category": "symbols", "char": "โž—", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] },
 	{ "category": "symbols", "char": "โœ–๏ธ", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] },
+	{ "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] },
 	{ "category": "symbols", "char": "โ™พ", "name": "infinity", "keywords": ["forever"] },
 	{ "category": "symbols", "char": "๐Ÿ’ฒ", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] },
 	{ "category": "symbols", "char": "๐Ÿ’ฑ", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] },
@@ -1747,3 +1782,4 @@
 	{ "category": "flags", "char": "๐Ÿ‡บ๐Ÿ‡ณ", "name": "united_nations", "keywords": ["un", "flag", "banner"] },
 	{ "category": "flags", "char": "๐Ÿดโ€โ˜ ๏ธ", "name": "pirate_flag", "keywords": ["skull", "crossbones", "flag", "banner"] }
 ]
+
diff --git a/packages/client/src/filters/bytes.ts b/packages/client/src/filters/bytes.ts
index 50e63534b6..c80f2f0ed2 100644
--- a/packages/client/src/filters/bytes.ts
+++ b/packages/client/src/filters/bytes.ts
@@ -1,7 +1,7 @@
 export default (v, digits = 0) => {
 	if (v == null) return '?';
 	const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
-	if (v == 0) return '0';
+	if (v === 0) return '0';
 	const isMinus = v < 0;
 	if (isMinus) v = -v;
 	const i = Math.floor(Math.log(v) / Math.log(1024));
diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts
index ab3299d22b..bb6176e409 100644
--- a/packages/client/src/init.ts
+++ b/packages/client/src/init.ts
@@ -13,9 +13,9 @@ if (localStorage.getItem('accounts') != null) {
 }
 //#endregion
 
-import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue';
+import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
 import compareVersions from 'compare-versions';
-import * as JSON5 from 'json5';
+import JSON5 from 'json5';
 
 import widgets from '@/widgets';
 import directives from '@/directives';
@@ -146,8 +146,7 @@ if ($i && $i.token) {
 		try {
 			document.body.innerHTML = '<div>Please wait...</div>';
 			await login(i);
-			location.reload();
-		} catch (e) {
+		} catch (err) {
 			// Render the error screen
 			// TODO: ใกใ‚ƒใ‚“ใจใ—ใŸใ‚ณใƒณใƒใƒผใƒใƒณใƒˆใ‚’ใƒฌใƒณใƒ€ใƒชใƒณใ‚ฐใ™ใ‚‹(v10ใจใ‹ใฎใƒˆใƒฉใƒ–ใƒซใ‚ทใƒฅใƒผใƒ†ใ‚ฃใƒณใ‚ฐใ‚ฒใƒผใƒ ไป˜ใใฎใ‚„ใคใฟใŸใ„ใช)
 			document.body.innerHTML = '<div id="err">Oops!</div>';
@@ -169,14 +168,14 @@ fetchInstanceMetaPromise.then(() => {
 	initializeSw();
 });
 
-const app = createApp(await (
-	window.location.search === '?zen' ? import('@/ui/zen.vue') :
-	!$i                               ? import('@/ui/visitor.vue') :
-	ui === 'deck'                     ? import('@/ui/deck.vue') :
-	ui === 'desktop'                  ? import('@/ui/desktop.vue') :
-	ui === 'classic'                  ? import('@/ui/classic.vue') :
-	import('@/ui/universal.vue')
-).then(x => x.default));
+const app = createApp(
+	window.location.search === '?zen' ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
+	!$i                               ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
+	ui === 'deck'                     ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
+	ui === 'desktop'                  ? defineAsyncComponent(() => import('@/ui/desktop.vue')) :
+	ui === 'classic'                  ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
+	defineAsyncComponent(() => import('@/ui/universal.vue'))
+);
 
 if (_DEV_) {
 	app.config.performance = true;
@@ -204,8 +203,24 @@ if (splash) splash.addEventListener('transitionend', () => {
 	splash.remove();
 });
 
-const rootEl = document.createElement('div');
-document.body.appendChild(rootEl);
+// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
+// ใชใœใ‹init.tsใฎๅ†…ๅฎนใŒ2ๅ›žๅฎŸ่กŒใ•ใ‚Œใ‚‹ใ“ใจใŒใ‚ใ‚‹ใŸใ‚ใ€mountใ™ใ‚‹divใ‚’1ใคใซๅˆถ้™ใ™ใ‚‹
+const rootEl = (() => {
+	const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
+
+	const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID);
+
+	if (currentEl) {
+		console.warn('multiple import detected');
+		return currentEl;
+	}
+
+	const rootEl = document.createElement('div');
+	rootEl.id = MISSKEY_MOUNT_DIV_ID;
+	document.body.appendChild(rootEl);
+	return rootEl;
+})();
+
 app.mount(rootEl);
 
 // boot.jsใฎใ‚„ใคใ‚’่งฃ้™ค
@@ -231,10 +246,10 @@ if (lastVersion !== version) {
 		if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
 			// ใƒญใ‚ฐใ‚คใƒณใ—ใฆใ‚‹ๅ ดๅˆใ ใ‘
 			if ($i) {
-				popup(import('@/components/updated.vue'), {}, {}, 'closed');
+				popup(defineAsyncComponent(() => import('@/components/updated.vue')), {}, {}, 'closed');
 			}
 		}
-	} catch (e) {
+	} catch (err) {
 	}
 }
 
@@ -319,7 +334,7 @@ stream.on('_disconnected_', async () => {
 	}
 });
 
-stream.on('emojiAdded', data => {
+stream.on('emojiAdded', emojiData => {
 	// TODO
 	//store.commit('instance/set', );
 });
diff --git a/packages/client/src/instance.ts b/packages/client/src/instance.ts
index 6e912aa2e5..d24eb2419a 100644
--- a/packages/client/src/instance.ts
+++ b/packages/client/src/instance.ts
@@ -4,11 +4,11 @@ import { api } from './os';
 
 // TODO: ไป–ใฎใ‚ฟใƒ–ใจๆฐธ็ถšๅŒ–ใ•ใ‚ŒใŸstateใ‚’ๅŒๆœŸ
 
-const data = localStorage.getItem('instance');
+const instanceData = localStorage.getItem('instance');
 
 // TODO: instanceใ‚’ใƒชใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใซใ™ใ‚‹ใ‹ใฏๅ†่€ƒใฎไฝ™ๅœฐใ‚ใ‚Š
 
-export const instance: Misskey.entities.InstanceMetadata = reactive(data ? JSON.parse(data) : {
+export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : {
 	// TODO: set default values
 });
 
diff --git a/packages/client/src/os.ts b/packages/client/src/os.ts
index 43c110555f..14860465fa 100644
--- a/packages/client/src/os.ts
+++ b/packages/client/src/os.ts
@@ -1,6 +1,6 @@
 // TODO: ใชใ‚“ใงใ‚‚ใ‹ใ‚“ใงใ‚‚os.tsใซ็ชใฃ่พผใ‚€ใฎใ‚„ใ‚ใŸใ„ใฎใงใ‚ˆใ—ใชใซๅˆ†ๅ‰ฒใ™ใ‚‹
 
-import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue';
+import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
 import { EventEmitter } from 'eventemitter3';
 import insertTextAtCursor from 'insert-text-at-cursor';
 import * as Misskey from 'misskey-js';
@@ -10,7 +10,6 @@ import MkWaitingDialog from '@/components/waiting-dialog.vue';
 import { MenuItem } from '@/types/menu';
 import { resolve } from '@/router';
 import { $i } from '@/account';
-import { defaultStore } from '@/store';
 
 export const pendingApiRequestsCount = ref(0);
 
@@ -35,7 +34,7 @@ export const api = ((endpoint: string, data: Record<string, any> = {}, token?: s
 			method: 'POST',
 			body: JSON.stringify(data),
 			credentials: 'omit',
-			cache: 'no-cache'
+			cache: 'no-cache',
 		}).then(async (res) => {
 			const body = res.status === 204 ? null : await res.json();
 
@@ -60,10 +59,10 @@ export const apiWithDialog = ((
 	token?: string | null | undefined,
 ) => {
 	const promise = api(endpoint, data, token);
-	promiseDialog(promise, null, (e) => {
+	promiseDialog(promise, null, (err) => {
 		alert({
 			type: 'error',
-			text: e.message + '\n' + (e as any).id,
+			text: err.message + '\n' + (err as any).id,
 		});
 	});
 
@@ -73,7 +72,7 @@ export const apiWithDialog = ((
 export function promiseDialog<T extends Promise<any>>(
 	promise: T,
 	onSuccess?: ((res: any) => void) | null,
-	onFailure?: ((e: Error) => void) | null,
+	onFailure?: ((err: Error) => void) | null,
 	text?: string,
 ): T {
 	const showing = ref(true);
@@ -89,14 +88,14 @@ export function promiseDialog<T extends Promise<any>>(
 				showing.value = false;
 			}, 1000);
 		}
-	}).catch(e => {
+	}).catch(err => {
 		showing.value = false;
 		if (onFailure) {
-			onFailure(e);
+			onFailure(err);
 		} else {
 			alert({
 				type: 'error',
-				text: e
+				text: err,
 			});
 		}
 	});
@@ -111,10 +110,6 @@ export function promiseDialog<T extends Promise<any>>(
 	return promise;
 }
 
-function isModule(x: any): x is typeof import('*.vue') {
-	return x.default != null;
-}
-
 let popupIdCount = 0;
 export const popups = ref([]) as Ref<{
 	id: any;
@@ -132,10 +127,7 @@ export function claimZIndex(priority: 'low' | 'middle' | 'high' = 'low'): number
 	return zIndexes[priority];
 }
 
-export async function popup(component: Component | typeof import('*.vue') | Promise<Component | typeof import('*.vue')>, props: Record<string, any>, events = {}, disposeEvent?: string) {
-	if (component.then) component = await component;
-
-	if (isModule(component)) component = component.default;
+export async function popup(component: Component, props: Record<string, any>, events = {}, disposeEvent?: string) {
 	markRaw(component);
 
 	const id = ++popupIdCount;
@@ -150,7 +142,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom
 		props,
 		events: disposeEvent ? {
 			...events,
-			[disposeEvent]: dispose
+			[disposeEvent]: dispose,
 		} : events,
 		id,
 	};
@@ -164,7 +156,7 @@ export async function popup(component: Component | typeof import('*.vue') | Prom
 
 export function pageWindow(path: string) {
 	const { component, props } = resolve(path);
-	popup(import('@/components/page-window.vue'), {
+	popup(defineAsyncComponent(() => import('@/components/page-window.vue')), {
 		initialPath: path,
 		initialComponent: markRaw(component),
 		initialProps: props,
@@ -173,7 +165,7 @@ export function pageWindow(path: string) {
 
 export function modalPageWindow(path: string) {
 	const { component, props } = resolve(path);
-	popup(import('@/components/modal-page-window.vue'), {
+	popup(defineAsyncComponent(() => import('@/components/modal-page-window.vue')), {
 		initialPath: path,
 		initialComponent: markRaw(component),
 		initialProps: props,
@@ -181,8 +173,8 @@ export function modalPageWindow(path: string) {
 }
 
 export function toast(message: string) {
-	popup(import('@/components/toast.vue'), {
-		message
+	popup(defineAsyncComponent(() => import('@/components/toast.vue')), {
+		message,
 	}, {}, 'closed');
 }
 
@@ -192,7 +184,7 @@ export function alert(props: {
 	text?: string | null;
 }): Promise<void> {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/dialog.vue'), props, {
+		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), props, {
 			done: result => {
 				resolve();
 			},
@@ -206,7 +198,7 @@ export function confirm(props: {
 	text?: string | null;
 }): Promise<{ canceled: boolean }> {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
 			...props,
 			showCancelButton: true,
 		}, {
@@ -227,14 +219,14 @@ export function inputText(props: {
 	canceled: false; result: string;
 }> {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
 			title: props.title,
 			text: props.text,
 			input: {
 				type: props.type,
 				placeholder: props.placeholder,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? result : { canceled: true });
@@ -252,14 +244,14 @@ export function inputNumber(props: {
 	canceled: false; result: number;
 }> {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
 			title: props.title,
 			text: props.text,
 			input: {
 				type: 'number',
 				placeholder: props.placeholder,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? result : { canceled: true });
@@ -277,14 +269,14 @@ export function inputDate(props: {
 	canceled: false; result: Date;
 }> {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
 			title: props.title,
 			text: props.text,
 			input: {
 				type: 'date',
 				placeholder: props.placeholder,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true });
@@ -293,7 +285,7 @@ export function inputDate(props: {
 	});
 }
 
-export function select<C extends any = any>(props: {
+export function select<C = any>(props: {
 	title?: string | null;
 	text?: string | null;
 	default?: string | null;
@@ -314,14 +306,14 @@ export function select<C extends any = any>(props: {
 	canceled: false; result: C;
 }> {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/dialog.vue')), {
 			title: props.title,
 			text: props.text,
 			select: {
 				items: props.items,
 				groupedItems: props.groupedItems,
 				default: props.default,
-			}
+			},
 		}, {
 			done: result => {
 				resolve(result ? result : { canceled: true });
@@ -336,9 +328,9 @@ export function success() {
 		window.setTimeout(() => {
 			showing.value = false;
 		}, 1000);
-		popup(import('@/components/waiting-dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
 			success: true,
-			showing: showing
+			showing: showing,
 		}, {
 			done: () => resolve(),
 		}, 'closed');
@@ -348,9 +340,9 @@ export function success() {
 export function waiting() {
 	return new Promise((resolve, reject) => {
 		const showing = ref(true);
-		popup(import('@/components/waiting-dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/waiting-dialog.vue')), {
 			success: false,
-			showing: showing
+			showing: showing,
 		}, {
 			done: () => resolve(),
 		}, 'closed');
@@ -359,7 +351,7 @@ export function waiting() {
 
 export function form(title, form) {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/form-dialog.vue'), { title, form }, {
+		popup(defineAsyncComponent(() => import('@/components/form-dialog.vue')), { title, form }, {
 			done: result => {
 				resolve(result);
 			},
@@ -369,7 +361,7 @@ export function form(title, form) {
 
 export async function selectUser() {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/user-select-dialog.vue'), {}, {
+		popup(defineAsyncComponent(() => import('@/components/user-select-dialog.vue')), {}, {
 			ok: user => {
 				resolve(user);
 			},
@@ -379,9 +371,9 @@ export async function selectUser() {
 
 export async function selectDriveFile(multiple: boolean) {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/drive-select-dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
 			type: 'file',
-			multiple
+			multiple,
 		}, {
 			done: files => {
 				if (files) {
@@ -394,9 +386,9 @@ export async function selectDriveFile(multiple: boolean) {
 
 export async function selectDriveFolder(multiple: boolean) {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/drive-select-dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/drive-select-dialog.vue')), {
 			type: 'folder',
-			multiple
+			multiple,
 		}, {
 			done: folders => {
 				if (folders) {
@@ -409,9 +401,9 @@ export async function selectDriveFolder(multiple: boolean) {
 
 export async function pickEmoji(src: HTMLElement | null, opts) {
 	return new Promise((resolve, reject) => {
-		popup(import('@/components/emoji-picker-dialog.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
 			src,
-			...opts
+			...opts,
 		}, {
 			done: emoji => {
 				resolve(emoji);
@@ -420,6 +412,21 @@ export async function pickEmoji(src: HTMLElement | null, opts) {
 	});
 }
 
+export async function cropImage(image: Misskey.entities.DriveFile, options: {
+	aspectRatio: number;
+}): Promise<Misskey.entities.DriveFile> {
+	return new Promise((resolve, reject) => {
+		popup(defineAsyncComponent(() => import('@/components/cropper-dialog.vue')), {
+			file: image,
+			aspectRatio: options.aspectRatio,
+		}, {
+			ok: x => {
+				resolve(x);
+			},
+		}, 'closed');
+	});
+}
+
 type AwaitType<T> =
 	T extends Promise<infer U> ? U :
 	T extends (...args: any[]) => Promise<infer V> ? V :
@@ -459,9 +466,9 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
 		characterData: false,
 	});
 
-	openingEmojiPicker = await popup(import('@/components/emoji-picker-window.vue'), {
+	openingEmojiPicker = await popup(defineAsyncComponent(() => import('@/components/emoji-picker-window.vue')), {
 		src,
-		...opts
+		...opts,
 	}, {
 		chosen: emoji => {
 			insertTextAtCursor(activeTextarea, emoji);
@@ -470,7 +477,7 @@ export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea:
 			openingEmojiPicker!.dispose();
 			openingEmojiPicker = null;
 			observer.disconnect();
-		}
+		},
 	});
 }
 
@@ -481,12 +488,12 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
 }) {
 	return new Promise((resolve, reject) => {
 		let dispose;
-		popup(import('@/components/ui/popup-menu.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/ui/popup-menu.vue')), {
 			items,
 			src,
 			width: options?.width,
 			align: options?.align,
-			viaKeyboard: options?.viaKeyboard
+			viaKeyboard: options?.viaKeyboard,
 		}, {
 			closed: () => {
 				resolve();
@@ -502,7 +509,7 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent)
 	ev.preventDefault();
 	return new Promise((resolve, reject) => {
 		let dispose;
-		popup(import('@/components/ui/context-menu.vue'), {
+		popup(defineAsyncComponent(() => import('@/components/ui/context-menu.vue')), {
 			items,
 			ev,
 		}, {
@@ -537,78 +544,6 @@ export function post(props: Record<string, any> = {}) {
 
 export const deckGlobalEvents = new EventEmitter();
 
-export const uploads = ref<{
-	id: string;
-	name: string;
-	progressMax: number | undefined;
-	progressValue: number | undefined;
-	img: string;
-}[]>([]);
-
-export function upload(file: File, folder?: any, name?: string, keepOriginal: boolean = defaultStore.state.keepOriginalUploading): Promise<Misskey.entities.DriveFile> {
-	if (folder && typeof folder === 'object') folder = folder.id;
-
-	return new Promise((resolve, reject) => {
-		const id = Math.random().toString();
-
-		const reader = new FileReader();
-		reader.onload = (e) => {
-			const ctx = reactive({
-				id: id,
-				name: name || file.name || 'untitled',
-				progressMax: undefined,
-				progressValue: undefined,
-				img: window.URL.createObjectURL(file)
-			});
-
-			uploads.value.push(ctx);
-
-			console.log(keepOriginal);
-
-			const data = new FormData();
-			data.append('i', $i.token);
-			data.append('force', 'true');
-			data.append('file', file);
-
-			if (folder) data.append('folderId', folder);
-			if (name) data.append('name', name);
-
-			const xhr = new XMLHttpRequest();
-			xhr.open('POST', apiUrl + '/drive/files/create', true);
-			xhr.onload = (ev) => {
-				if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
-					// TODO: ๆถˆใ™ใฎใงใฏใชใใฆๅ†้€ใงใใ‚‹ใ‚ˆใ†ใซใ—ใŸใ„
-					uploads.value = uploads.value.filter(x => x.id != id);
-
-					alert({
-						type: 'error',
-						text: 'upload failed'
-					});
-
-					reject();
-					return;
-				}
-
-				const driveFile = JSON.parse(ev.target.response);
-
-				resolve(driveFile);
-
-				uploads.value = uploads.value.filter(x => x.id != id);
-			};
-
-			xhr.upload.onprogress = e => {
-				if (e.lengthComputable) {
-					ctx.progressMax = e.total;
-					ctx.progressValue = e.loaded;
-				}
-			};
-
-			xhr.send(data);
-		};
-		reader.readAsArrayBuffer(file);
-	});
-}
-
 /*
 export function checkExistence(fileData: ArrayBuffer): Promise<any> {
 	return new Promise((resolve, reject) => {
diff --git a/packages/client/src/pages/about-misskey.vue b/packages/client/src/pages/about-misskey.vue
index ff04ed84f2..691bc4f07b 100644
--- a/packages/client/src/pages/about-misskey.vue
+++ b/packages/client/src/pages/about-misskey.vue
@@ -150,6 +150,7 @@ const patrons = [
 	'Weeble',
 	'่‰ๆšฎใ›ใ›ใ›',
 	'ThatOneCalculator',
+	'pixeldesu',
 ];
 
 let easterEggReady = false;
diff --git a/packages/client/src/pages/admin/abuses.vue b/packages/client/src/pages/admin/abuses.vue
index 92f93797ce..e1d0361c0b 100644
--- a/packages/client/src/pages/admin/abuses.vue
+++ b/packages/client/src/pages/admin/abuses.vue
@@ -24,10 +24,10 @@
 			</div>
 			<!-- TODO
 			<div class="inputs" style="display: flex; padding-top: 1.2em;">
-				<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()">
+				<MkInput v-model="searchUsername" style="margin: 0; flex: 1;" type="text" spellcheck="false">
 					<span>{{ $ts.username }}</span>
 				</MkInput>
-				<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" @update:modelValue="$refs.reports.reload()" :disabled="pagination.params().origin === 'local'">
+				<MkInput v-model="searchHost" style="margin: 0; flex: 1;" type="text" spellcheck="false" :disabled="pagination.params().origin === 'local'">
 					<span>{{ $ts.host }}</span>
 				</MkInput>
 			</div>
@@ -41,8 +41,8 @@
 </div>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
 
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
@@ -50,45 +50,35 @@ import MkPagination from '@/components/ui/pagination.vue';
 import XAbuseReport from '@/components/abuse-report.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkInput,
-		MkSelect,
-		MkPagination,
-		XAbuseReport,
-	},
+let reports = $ref<InstanceType<typeof MkPagination>>();
 
-	emits: ['info'],
+let state = $ref('unresolved');
+let reporterOrigin = $ref('combined');
+let targetUserOrigin = $ref('combined');
+let searchUsername = $ref('');
+let searchHost = $ref('');
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.abuseReports,
-				icon: 'fas fa-exclamation-circle',
-				bg: 'var(--bg)',
-			},
-			searchUsername: '',
-			searchHost: '',
-			state: 'unresolved',
-			reporterOrigin: 'combined',
-			targetUserOrigin: 'combined',
-			pagination: {
-				endpoint: 'admin/abuse-user-reports' as const,
-				limit: 10,
-				params: computed(() => ({
-					state: this.state,
-					reporterOrigin: this.reporterOrigin,
-					targetUserOrigin: this.targetUserOrigin,
-				})),
-			},
-		}
-	},
+const pagination = {
+	endpoint: 'admin/abuse-user-reports' as const,
+	limit: 10,
+	params: computed(() => ({
+		state,
+		reporterOrigin,
+		targetUserOrigin,
+	})),
+};
 
-	methods: {
-		resolved(reportId) {
-			this.$refs.reports.removeItem(item => item.id === reportId);
-		},
+function resolved(reportId) {
+	reports.removeItem(item => item.id === reportId);
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.abuseReports,
+		icon: 'fas fa-exclamation-circle',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/ads.vue b/packages/client/src/pages/admin/ads.vue
index 8f164caa99..b18e08db96 100644
--- a/packages/client/src/pages/admin/ads.vue
+++ b/packages/client/src/pages/admin/ads.vue
@@ -7,7 +7,7 @@
 				<template #label>URL</template>
 			</MkInput>
 			<MkInput v-model="ad.imageUrl" class="_formBlock">
-				<template #label>{{ $ts.imageUrl }}</template>
+				<template #label>{{ i18n.ts.imageUrl }}</template>
 			</MkInput>
 			<FormRadios v-model="ad.place" class="_formBlock">
 				<template #label>Form</template>
@@ -17,34 +17,34 @@
 			</FormRadios>
 			<!--
 			<div style="margin: 32px 0;">
-				{{ $ts.priority }}
-				<MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio>
-				<MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio>
-				<MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio>
+				{{ i18n.ts.priority }}
+				<MkRadio v-model="ad.priority" value="high">{{ i18n.ts.high }}</MkRadio>
+				<MkRadio v-model="ad.priority" value="middle">{{ i18n.ts.middle }}</MkRadio>
+				<MkRadio v-model="ad.priority" value="low">{{ i18n.ts.low }}</MkRadio>
 			</div>
 			-->
 			<FormSplit>
 				<MkInput v-model="ad.ratio" type="number">
-					<template #label>{{ $ts.ratio }}</template>
+					<template #label>{{ i18n.ts.ratio }}</template>
 				</MkInput>
 				<MkInput v-model="ad.expiresAt" type="date">
-					<template #label>{{ $ts.expiration }}</template>
+					<template #label>{{ i18n.ts.expiration }}</template>
 				</MkInput>
 			</FormSplit>
 			<MkTextarea v-model="ad.memo" class="_formBlock">
-				<template #label>{{ $ts.memo }}</template>
+				<template #label>{{ i18n.ts.memo }}</template>
 			</MkTextarea>
 			<div class="buttons _formBlock">
-				<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
-				<MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
+				<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
+				<MkButton class="button" inline danger @click="remove(ad)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
 			</div>
 		</div>
 	</div>
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
@@ -52,81 +52,65 @@ import FormRadios from '@/components/form/radios.vue';
 import FormSplit from '@/components/form/split.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		MkInput,
-		MkTextarea,
-		FormRadios,
-		FormSplit,
-	},
+let ads: any[] = $ref([]);
 
-	emits: ['info'],
+os.api('admin/ad/list').then(adsResponse => {
+	ads = adsResponse;
+});
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.ads,
-				icon: 'fas fa-audio-description',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-plus',
-					text: this.$ts.add,
-					handler: this.add,
-				}],
-			},
-			ads: [],
-		}
-	},
+function add() {
+	ads.unshift({
+		id: null,
+		memo: '',
+		place: 'square',
+		priority: 'middle',
+		ratio: 1,
+		url: '',
+		imageUrl: null,
+		expiresAt: null,
+	});
+}
 
-	created() {
-		os.api('admin/ad/list').then(ads => {
-			this.ads = ads;
+function remove(ad) {
+	os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: ad.url }),
+	}).then(({ canceled }) => {
+		if (canceled) return;
+		ads = ads.filter(x => x !== ad);
+		os.apiWithDialog('admin/ad/delete', {
+			id: ad.id
 		});
-	},
+	});
+}
 
-	methods: {
-		add() {
-			this.ads.unshift({
-				id: null,
-				memo: '',
-				place: 'square',
-				priority: 'middle',
-				ratio: 1,
-				url: '',
-				imageUrl: null,
-				expiresAt: null,
-			});
-		},
+function save(ad) {
+	if (ad.id == null) {
+		os.apiWithDialog('admin/ad/create', {
+			...ad,
+			expiresAt: new Date(ad.expiresAt).getTime()
+		});
+	} else {
+		os.apiWithDialog('admin/ad/update', {
+			...ad,
+			expiresAt: new Date(ad.expiresAt).getTime()
+		});
+	}
+}
 
-		remove(ad) {
-			os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: ad.url }),
-			}).then(({ canceled }) => {
-				if (canceled) return;
-				this.ads = this.ads.filter(x => x != ad);
-				os.apiWithDialog('admin/ad/delete', {
-					id: ad.id
-				});
-			});
-		},
-
-		save(ad) {
-			if (ad.id == null) {
-				os.apiWithDialog('admin/ad/create', {
-					...ad,
-					expiresAt: new Date(ad.expiresAt).getTime()
-				});
-			} else {
-				os.apiWithDialog('admin/ad/update', {
-					...ad,
-					expiresAt: new Date(ad.expiresAt).getTime()
-				});
-			}
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.ads,
+		icon: 'fas fa-audio-description',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-plus',
+			text: i18n.ts.add,
+			handler: add,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/announcements.vue b/packages/client/src/pages/admin/announcements.vue
index a0d720bb29..97774975de 100644
--- a/packages/client/src/pages/admin/announcements.vue
+++ b/packages/client/src/pages/admin/announcements.vue
@@ -3,112 +3,98 @@
 	<section v-for="announcement in announcements" class="_card _gap announcements">
 		<div class="_content announcement">
 			<MkInput v-model="announcement.title">
-				<template #label>{{ $ts.title }}</template>
+				<template #label>{{ i18n.ts.title }}</template>
 			</MkInput>
 			<MkTextarea v-model="announcement.text">
-				<template #label>{{ $ts.text }}</template>
+				<template #label>{{ i18n.ts.text }}</template>
 			</MkTextarea>
 			<MkInput v-model="announcement.imageUrl">
-				<template #label>{{ $ts.imageUrl }}</template>
+				<template #label>{{ i18n.ts.imageUrl }}</template>
 			</MkInput>
-			<p v-if="announcement.reads">{{ $t('nUsersRead', { n: announcement.reads }) }}</p>
+			<p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p>
 			<div class="buttons">
-				<MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
-				<MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
+				<MkButton class="button" inline primary @click="save(announcement)"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
+				<MkButton class="button" inline @click="remove(announcement)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
 			</div>
 		</div>
 	</section>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		MkInput,
-		MkTextarea,
-	},
+let announcements: any[] = $ref([]);
 
-	emits: ['info'],
+os.api('admin/announcements/list').then(announcementResponse => {
+	announcements = announcementResponse;
+});
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.announcements,
-				icon: 'fas fa-broadcast-tower',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-plus',
-					text: this.$ts.add,
-					handler: this.add,
-				}],
-			},
-			announcements: [],
-		}
-	},
+function add() {
+	announcements.unshift({
+		id: null,
+		title: '',
+		text: '',
+		imageUrl: null
+	});
+}
 
-	created() {
-		os.api('admin/announcements/list').then(announcements => {
-			this.announcements = announcements;
+function remove(announcement) {
+	os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: announcement.title }),
+	}).then(({ canceled }) => {
+		if (canceled) return;
+		announcements = announcements.filter(x => x !== announcement);
+		os.api('admin/announcements/delete', announcement);
+	});
+}
+
+function save(announcement) {
+	if (announcement.id == null) {
+		os.api('admin/announcements/create', announcement).then(() => {
+			os.alert({
+				type: 'success',
+				text: i18n.ts.saved
+			});
+		}).catch(err => {
+			os.alert({
+				type: 'error',
+				text: err
+			});
 		});
-	},
-
-	methods: {
-		add() {
-			this.announcements.unshift({
-				id: null,
-				title: '',
-				text: '',
-				imageUrl: null
+	} else {
+		os.api('admin/announcements/update', announcement).then(() => {
+			os.alert({
+				type: 'success',
+				text: i18n.ts.saved
 			});
-		},
-
-		remove(announcement) {
-			os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: announcement.title }),
-			}).then(({ canceled }) => {
-				if (canceled) return;
-				this.announcements = this.announcements.filter(x => x != announcement);
-				os.api('admin/announcements/delete', announcement);
+		}).catch(err => {
+			os.alert({
+				type: 'error',
+				text: err
 			});
-		},
+		});
+	}
+}
 
-		save(announcement) {
-			if (announcement.id == null) {
-				os.api('admin/announcements/create', announcement).then(() => {
-					os.alert({
-						type: 'success',
-						text: this.$ts.saved
-					});
-				}).catch(e => {
-					os.alert({
-						type: 'error',
-						text: e
-					});
-				});
-			} else {
-				os.api('admin/announcements/update', announcement).then(() => {
-					os.alert({
-						type: 'success',
-						text: this.$ts.saved
-					});
-				}).catch(e => {
-					os.alert({
-						type: 'error',
-						text: e
-					});
-				});
-			}
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.announcements,
+		icon: 'fas fa-broadcast-tower',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-plus',
+			text: i18n.ts.add,
+			handler: add,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/bot-protection.vue b/packages/client/src/pages/admin/bot-protection.vue
index 5e0cdd96a5..30fee5015a 100644
--- a/packages/client/src/pages/admin/bot-protection.vue
+++ b/packages/client/src/pages/admin/bot-protection.vue
@@ -43,8 +43,8 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
 import FormRadios from '@/components/form/radios.vue';
 import FormInput from '@/components/form/input.vue';
 import FormButton from '@/components/ui/button.vue';
@@ -54,64 +54,39 @@ import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
 
-export default defineComponent({
-	components: {
-		FormRadios,
-		FormInput,
-		FormButton,
-		FormSuspense,
-		FormSlot,
-		MkCaptcha: defineAsyncComponent(() => import('@/components/captcha.vue')),
-	},
+const MkCaptcha = defineAsyncComponent(() => import('@/components/captcha.vue'));
 
-	emits: ['info'],
+let provider = $ref(null);
+let hcaptchaSiteKey: string | null = $ref(null);
+let hcaptchaSecretKey: string | null = $ref(null);
+let recaptchaSiteKey: string | null = $ref(null);
+let recaptchaSecretKey: string | null = $ref(null);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.botProtection,
-				icon: 'fas fa-shield-alt'
-			},
-			provider: null,
-			enableHcaptcha: false,
-			hcaptchaSiteKey: null,
-			hcaptchaSecretKey: null,
-			enableRecaptcha: false,
-			recaptchaSiteKey: null,
-			recaptchaSecretKey: null,
-		}
-	},
+const enableHcaptcha = $computed(() => provider === 'hcaptcha');
+const enableRecaptcha = $computed(() => provider === 'recaptcha');
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.enableHcaptcha = meta.enableHcaptcha;
-			this.hcaptchaSiteKey = meta.hcaptchaSiteKey;
-			this.hcaptchaSecretKey = meta.hcaptchaSecretKey;
-			this.enableRecaptcha = meta.enableRecaptcha;
-			this.recaptchaSiteKey = meta.recaptchaSiteKey;
-			this.recaptchaSecretKey = meta.recaptchaSecretKey;
+async function init() {
+	const meta = await os.api('admin/meta');
+	enableHcaptcha = meta.enableHcaptcha;
+	hcaptchaSiteKey = meta.hcaptchaSiteKey;
+	hcaptchaSecretKey = meta.hcaptchaSecretKey;
+	enableRecaptcha = meta.enableRecaptcha;
+	recaptchaSiteKey = meta.recaptchaSiteKey;
+	recaptchaSecretKey = meta.recaptchaSecretKey;
 
-			this.provider = this.enableHcaptcha ? 'hcaptcha' : this.enableRecaptcha ? 'recaptcha' : null;
+	provider = enableHcaptcha ? 'hcaptcha' : enableRecaptcha ? 'recaptcha' : null;
+}
 
-			this.$watch(() => this.provider, () => {
-				this.enableHcaptcha = this.provider === 'hcaptcha';
-				this.enableRecaptcha = this.provider === 'recaptcha';
-			});
-		},
-	
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				enableHcaptcha: this.enableHcaptcha,
-				hcaptchaSiteKey: this.hcaptchaSiteKey,
-				hcaptchaSecretKey: this.hcaptchaSecretKey,
-				enableRecaptcha: this.enableRecaptcha,
-				recaptchaSiteKey: this.recaptchaSiteKey,
-				recaptchaSecretKey: this.recaptchaSecretKey,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
-	}
-});
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		enableHcaptcha,
+		hcaptchaSiteKey,
+		hcaptchaSecretKey,
+		enableRecaptcha,
+		recaptchaSiteKey,
+		recaptchaSecretKey,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 </script>
diff --git a/packages/client/src/pages/admin/database.vue b/packages/client/src/pages/admin/database.vue
index 3a835eeafa..d3519922b1 100644
--- a/packages/client/src/pages/admin/database.vue
+++ b/packages/client/src/pages/admin/database.vue
@@ -9,36 +9,23 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkKeyValue from '@/components/key-value.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import bytes from '@/filters/bytes';
 import number from '@/filters/number';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSuspense,
-		MkKeyValue,
-	},
+const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
 
-	emits: ['info'],
-
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.database,
-				icon: 'fas fa-database',
-				bg: 'var(--bg)',
-			},
-			databasePromiseFactory: () => os.api('admin/get-table-stats', {}).then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size)),
-		}
-	},
-
-	methods: {
-		bytes, number,
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.database,
+		icon: 'fas fa-database',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/email-settings.vue b/packages/client/src/pages/admin/email-settings.vue
index 7df0b6db1c..aa13043193 100644
--- a/packages/client/src/pages/admin/email-settings.vue
+++ b/packages/client/src/pages/admin/email-settings.vue
@@ -3,37 +3,37 @@
 	<FormSuspense :p="init">
 		<div class="_formRoot">
 			<FormSwitch v-model="enableEmail" class="_formBlock">
-				<template #label>{{ $ts.enableEmail }}</template>
-				<template #caption>{{ $ts.emailConfigInfo }}</template>
+				<template #label>{{ i18n.ts.enableEmail }}</template>
+				<template #caption>{{ i18n.ts.emailConfigInfo }}</template>
 			</FormSwitch>
 
 			<template v-if="enableEmail">
 				<FormInput v-model="email" type="email" class="_formBlock">
-					<template #label>{{ $ts.emailAddress }}</template>
+					<template #label>{{ i18n.ts.emailAddress }}</template>
 				</FormInput>
 
 				<FormSection>
-					<template #label>{{ $ts.smtpConfig }}</template>
+					<template #label>{{ i18n.ts.smtpConfig }}</template>
 					<FormSplit :min-width="280">
 						<FormInput v-model="smtpHost" class="_formBlock">
-							<template #label>{{ $ts.smtpHost }}</template>
+							<template #label>{{ i18n.ts.smtpHost }}</template>
 						</FormInput>
 						<FormInput v-model="smtpPort" type="number" class="_formBlock">
-							<template #label>{{ $ts.smtpPort }}</template>
+							<template #label>{{ i18n.ts.smtpPort }}</template>
 						</FormInput>
 					</FormSplit>
 					<FormSplit :min-width="280">
 						<FormInput v-model="smtpUser" class="_formBlock">
-							<template #label>{{ $ts.smtpUser }}</template>
+							<template #label>{{ i18n.ts.smtpUser }}</template>
 						</FormInput>
 						<FormInput v-model="smtpPass" type="password" class="_formBlock">
-							<template #label>{{ $ts.smtpPass }}</template>
+							<template #label>{{ i18n.ts.smtpPass }}</template>
 						</FormInput>
 					</FormSplit>
-					<FormInfo class="_formBlock">{{ $ts.emptyToDisableSmtpAuth }}</FormInfo>
+					<FormInfo class="_formBlock">{{ i18n.ts.emptyToDisableSmtpAuth }}</FormInfo>
 					<FormSwitch v-model="smtpSecure" class="_formBlock">
-						<template #label>{{ $ts.smtpSecure }}</template>
-						<template #caption>{{ $ts.smtpSecureInfo }}</template>
+						<template #label>{{ i18n.ts.smtpSecure }}</template>
+						<template #caption>{{ i18n.ts.smtpSecureInfo }}</template>
 					</FormSwitch>
 				</FormSection>
 			</template>
@@ -42,8 +42,8 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
 import FormInfo from '@/components/ui/info.vue';
@@ -52,86 +52,71 @@ import FormSplit from '@/components/form/split.vue';
 import FormSection from '@/components/form/section.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
-import { fetchInstance } from '@/instance';
+import { fetchInstance, instance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormSplit,
-		FormSection,
-		FormInfo,
-		FormSuspense,
-	},
+let enableEmail: boolean = $ref(false);
+let email: any = $ref(null);
+let smtpSecure: boolean = $ref(false);
+let smtpHost: string = $ref('');
+let smtpPort: number = $ref(0);
+let smtpUser: string = $ref('');
+let smtpPass: string = $ref('');
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	enableEmail = meta.enableEmail;
+	email = meta.email;
+	smtpSecure = meta.smtpSecure;
+	smtpHost = meta.smtpHost;
+	smtpPort = meta.smtpPort;
+	smtpUser = meta.smtpUser;
+	smtpPass = meta.smtpPass;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.emailServer,
-				icon: 'fas fa-envelope',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					text: this.$ts.testEmail,
-					handler: this.testEmail,
-				}, {
-					asFullButton: true,
-					icon: 'fas fa-check',
-					text: this.$ts.save,
-					handler: this.save,
-				}],
-			},
-			enableEmail: false,
-			email: null,
-			smtpSecure: false,
-			smtpHost: '',
-			smtpPort: 0,
-			smtpUser: '',
-			smtpPass: '',
-		}
-	},
+async function testEmail() {
+	const { canceled, result: destination } = await os.inputText({
+		title: i18n.ts.destination,
+		type: 'email',
+		placeholder: instance.maintainerEmail
+	});
+	if (canceled) return;
+	os.apiWithDialog('admin/send-email', {
+		to: destination,
+		subject: 'Test email',
+		text: 'Yo'
+	});
+}
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.enableEmail = meta.enableEmail;
-			this.email = meta.email;
-			this.smtpSecure = meta.smtpSecure;
-			this.smtpHost = meta.smtpHost;
-			this.smtpPort = meta.smtpPort;
-			this.smtpUser = meta.smtpUser;
-			this.smtpPass = meta.smtpPass;
-		},
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		enableEmail,
+		email,
+		smtpSecure,
+		smtpHost,
+		smtpPort,
+		smtpUser,
+		smtpPass,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-		async testEmail() {
-			const { canceled, result: destination } = await os.inputText({
-				title: this.$ts.destination,
-				type: 'email',
-				placeholder: this.$instance.maintainerEmail
-			});
-			if (canceled) return;
-			os.apiWithDialog('admin/send-email', {
-				to: destination,
-				subject: 'Test email',
-				text: 'Yo'
-			});
-		},
-
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				enableEmail: this.enableEmail,
-				email: this.email,
-				smtpSecure: this.smtpSecure,
-				smtpHost: this.smtpHost,
-				smtpPort: this.smtpPort,
-				smtpUser: this.smtpUser,
-				smtpPass: this.smtpPass,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.emailServer,
+		icon: 'fas fa-envelope',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			text: i18n.ts.testEmail,
+			handler: testEmail,
+		}, {
+			asFullButton: true,
+			icon: 'fas fa-check',
+			text: i18n.ts.save,
+			handler: save,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/emoji-edit-dialog.vue b/packages/client/src/pages/admin/emoji-edit-dialog.vue
index 2e3903426e..d482fa49e6 100644
--- a/packages/client/src/pages/admin/emoji-edit-dialog.vue
+++ b/packages/client/src/pages/admin/emoji-edit-dialog.vue
@@ -27,85 +27,71 @@
 </XModalWindow>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import XModalWindow from '@/components/ui/modal-window.vue';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import * as os from '@/os';
 import { unique } from '@/scripts/array';
+import { i18n } from '@/i18n';
+import { emojiCategories } from '@/instance';
 
-export default defineComponent({
-	components: {
-		XModalWindow,
-		MkButton,
-		MkInput,
-	},
+const props = defineProps<{
+	emoji: any,
+}>();
 
-	props: {
-		emoji: {
-			required: true,
+let dialog = $ref(null);
+let name: string = $ref(props.emoji.name);
+let category: string = $ref(props.emoji.category);
+let aliases: string = $ref(props.emoji.aliases.join(' '));
+let categories: string[] = $ref(emojiCategories);
+
+const emit = defineEmits<{
+	(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
+	(ev: 'closed'): void
+}>();
+
+function ok() {
+	update();
+}
+
+async function update() {
+	await os.apiWithDialog('admin/emoji/update', {
+		id: props.emoji.id,
+		name,
+		category,
+		aliases: aliases.split(' '),
+	});
+
+	emit('done', {
+		updated: {
+			id: props.emoji.id,
+			name,
+			category,
+			aliases: aliases.split(' '),
 		}
-	},
+	});
 
-	emits: ['done', 'closed'],
+	dialog.close();
+}
 
-	data() {
-		return {
-			name: this.emoji.name,
-			category: this.emoji.category,
-			aliases: this.emoji.aliases?.join(' '),
-			categories: [],
-		}
-	},
+async function del() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: name }),
+	});
+	if (canceled) return;
 
-	created() {
-		os.api('meta', { detail: false }).then(({ emojis }) => {
-			this.categories = unique(emojis.map((x: any) => x.category || '').filter((x: string) => x !== ''));
+	os.api('admin/emoji/delete', {
+		id: props.emoji.id
+	}).then(() => {
+		emit('done', {
+			deleted: true
 		});
-	},
-
-	methods: {
-		ok() {
-			this.update();
-		},
-
-		async update() {
-			await os.apiWithDialog('admin/emoji/update', {
-				id: this.emoji.id,
-				name: this.name,
-				category: this.category,
-				aliases: this.aliases.split(' '),
-			});
-
-			this.$emit('done', {
-				updated: {
-					name: this.name,
-					category: this.category,
-					aliases: this.aliases.split(' '),
-				}
-			});
-			this.$refs.dialog.close();
-		},
-
-		async del() {
-			const { canceled } = await os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: this.emoji.name }),
-			});
-			if (canceled) return;
-
-			os.api('admin/emoji/delete', {
-				id: this.emoji.id
-			}).then(() => {
-				this.$emit('done', {
-					deleted: true
-				});
-				this.$refs.dialog.close();
-			});
-		},
-	}
-});
+		dialog.close();
+	});
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/pages/admin/emojis.vue b/packages/client/src/pages/admin/emojis.vue
index a080ee9c23..8ca5b3d65c 100644
--- a/packages/client/src/pages/admin/emojis.vue
+++ b/packages/client/src/pages/admin/emojis.vue
@@ -63,7 +63,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, defineComponent, ref, toRef } from 'vue';
+import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import MkPagination from '@/components/ui/pagination.vue';
@@ -130,17 +130,17 @@ const add = async (ev: MouseEvent) => {
 };
 
 const edit = (emoji) => {
-	os.popup(import('./emoji-edit-dialog.vue'), {
+	os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), {
 		emoji: emoji
 	}, {
 		done: result => {
 			if (result.updated) {
-				emojisPaginationComponent.value.replaceItem(item => item.id === emoji.id, {
-					...emoji,
+				emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({
+					...oldEmoji,
 					...result.updated
-				});
+				}));
 			} else if (result.deleted) {
-				emojisPaginationComponent.value.removeItem(item => item.id === emoji.id);
+				emojisPaginationComponent.value.removeItem((item) => item.id === emoji.id);
 			}
 		},
 	}, 'closed');
@@ -159,7 +159,7 @@ const remoteMenu = (emoji, ev: MouseEvent) => {
 	}, {
 		text: i18n.ts.import,
 		icon: 'fas fa-plus',
-		action: () => { im(emoji) }
+		action: () => { im(emoji); }
 	}], ev.currentTarget ?? ev.target);
 };
 
@@ -175,10 +175,10 @@ const menu = (ev: MouseEvent) => {
 					type: 'info',
 					text: i18n.ts.exportRequested,
 				});
-			}).catch((e) => {
+			}).catch((err) => {
 				os.alert({
 					type: 'error',
-					text: e.message,
+					text: err.message,
 				});
 			});
 		}
@@ -195,10 +195,10 @@ const menu = (ev: MouseEvent) => {
 					type: 'info',
 					text: i18n.ts.importRequested,
 				});
-			}).catch((e) => {
+			}).catch((err) => {
 				os.alert({
 					type: 'error',
-					text: e.message,
+					text: err.message,
 				});
 			});
 		}
diff --git a/packages/client/src/pages/admin/file-dialog.vue b/packages/client/src/pages/admin/file-dialog.vue
index 4c33f62399..0765548aab 100644
--- a/packages/client/src/pages/admin/file-dialog.vue
+++ b/packages/client/src/pages/admin/file-dialog.vue
@@ -34,74 +34,52 @@
 </XModalWindow>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import XModalWindow from '@/components/ui/modal-window.vue';
 import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue';
 import bytes from '@/filters/bytes';
 import * as os from '@/os';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		MkSwitch,
-		XModalWindow,
-		MkDriveFileThumbnail,
-	},
+let file: any = $ref(null);
+let info: any = $ref(null);
+let isSensitive: boolean = $ref(false);
 
-	props: {
-		fileId: {
-			required: true,
-		}
-	},
+const props = defineProps<{
+	fileId: string,
+}>();
 
-	emits: ['closed'],
+async function fetch() {
+	file = await os.api('drive/files/show', { fileId: props.fileId });
+	info = await os.api('admin/drive/show-file', { fileId: props.fileId });
+	isSensitive = file.isSensitive;
+}
 
-	data() {
-		return {
-			file: null,
-			info: null,
-			isSensitive: false,
-		};
-	},
+fetch();
 
-	created() {
-		this.fetch();
-	},
+function showUser() {
+	os.pageWindow(`/user-info/${file.userId}`);
+}
 
-	methods: {
-		async fetch() {
-			this.file = await os.api('drive/files/show', { fileId: this.fileId });
-			this.info = await os.api('admin/drive/show-file', { fileId: this.fileId });
-			this.isSensitive = this.file.isSensitive;
-		},
+async function del() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: file.name }),
+	});
+	if (canceled) return;
 
-		showUser() {
-			os.pageWindow(`/user-info/${this.file.userId}`);
-		},
+	os.apiWithDialog('drive/files/delete', {
+		fileId: file.id
+	});
+}
 
-		async del() {
-			const { canceled } = await os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: this.file.name }),
-			});
-			if (canceled) return;
-
-			os.apiWithDialog('drive/files/delete', {
-				fileId: this.file.id
-			});
-		},
-
-		async toggleIsSensitive(v) {
-			await os.api('drive/files/update', { fileId: this.fileId, isSensitive: v });
-			this.isSensitive = v;
-		},
-
-		bytes
-	}
-});
+async function toggleIsSensitive(v) {
+	await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
+	isSensitive = v;
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/pages/admin/files.vue b/packages/client/src/pages/admin/files.vue
index c62f053092..3cda688698 100644
--- a/packages/client/src/pages/admin/files.vue
+++ b/packages/client/src/pages/admin/files.vue
@@ -55,7 +55,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, defineAsyncComponent } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSelect from '@/components/form/select.vue';
@@ -93,7 +93,7 @@ function clear() {
 }
 
 function show(file) {
-	os.popup(import('./file-dialog.vue'), {
+	os.popup(defineAsyncComponent(() => import('./file-dialog.vue')), {
 		fileId: file.id
 	}, {}, 'closed');
 }
diff --git a/packages/client/src/pages/admin/index.vue b/packages/client/src/pages/admin/index.vue
index 6b11650f48..9b7fa5678e 100644
--- a/packages/client/src/pages/admin/index.vue
+++ b/packages/client/src/pages/admin/index.vue
@@ -1,6 +1,6 @@
 <template>
 <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
-	<div v-if="!narrow || page == null" class="nav">
+	<div v-if="!narrow || initialPage == null" class="nav">
 		<MkHeader :info="header"></MkHeader>
 	
 		<MkSpacer :content-max="700" :margin-min="16">
@@ -12,21 +12,21 @@
 				<MkInfo v-if="noMaintainerInformation" warn class="info">{{ $ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ $ts.configure }}</MkA></MkInfo>
 				<MkInfo v-if="noBotProtection" warn class="info">{{ $ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ $ts.configure }}</MkA></MkInfo>
 
-				<MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
+				<MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu>
 			</div>
 		</MkSpacer>
 	</div>
-	<div class="main">
+	<div v-if="!(narrow && initialPage == null)" class="main">
 		<MkStickyContainer>
 			<template #header><MkHeader v-if="childInfo && !childInfo.hideHeader" :info="childInfo"/></template>
-			<component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
+			<component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/>
 		</MkStickyContainer>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { computed, defineAsyncComponent, defineComponent, isRef, nextTick, onMounted, reactive, ref, watch } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent, nextTick, onMounted, onUnmounted, provide, watch } from 'vue';
 import { i18n } from '@/i18n';
 import MkSuperMenu from '@/components/ui/super-menu.vue';
 import MkInfo from '@/components/ui/info.vue';
@@ -35,292 +35,277 @@ import { instance } from '@/instance';
 import * as symbols from '@/symbols';
 import * as os from '@/os';
 import { lookupUser } from '@/scripts/lookup-user';
+import { MisskeyNavigator } from '@/scripts/navigate';
 
-export default defineComponent({
-	components: {
-		MkSuperMenu,
-		MkInfo,
-	},
+const isEmpty = (x: string | null) => x == null || x === '';
 
-	provide: {
-		shouldOmitHeaderTitle: false,
-	},
+const nav = new MisskeyNavigator();
 
-	props: {
-		initialPage: {
-			type: String,
-			required: false
+const indexInfo = {
+	title: i18n.ts.controlPanel,
+	icon: 'fas fa-cog',
+	bg: 'var(--bg)',
+	hideHeader: true,
+};
+
+const props = defineProps<{
+	initialPage?: string,
+}>();
+
+provide('shouldOmitHeaderTitle', false);
+
+let INFO = $ref(indexInfo);
+let childInfo = $ref(null);
+let page = $ref(props.initialPage);
+let narrow = $ref(false);
+let view = $ref(null);
+let el = $ref(null);
+let pageProps = $ref({});
+let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
+let noBotProtection = !instance.enableHcaptcha && !instance.enableRecaptcha;
+
+const NARROW_THRESHOLD = 600;
+const ro = new ResizeObserver((entries, observer) => {
+	if (entries.length === 0) return;
+	narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
+});
+
+const menuDef = $computed(() => [{
+	title: i18n.ts.quickAction,
+	items: [{
+		type: 'button',
+		icon: 'fas fa-search',
+		text: i18n.ts.lookup,
+		action: lookup,
+	}, ...(instance.disableRegistration ? [{
+		type: 'button',
+		icon: 'fas fa-user',
+		text: i18n.ts.invite,
+		action: invite,
+	}] : [])],
+}, {
+	title: i18n.ts.administration,
+	items: [{
+		icon: 'fas fa-tachometer-alt',
+		text: i18n.ts.dashboard,
+		to: '/admin/overview',
+		active: props.initialPage === 'overview',
+	}, {
+		icon: 'fas fa-users',
+		text: i18n.ts.users,
+		to: '/admin/users',
+		active: props.initialPage === 'users',
+	}, {
+		icon: 'fas fa-laugh',
+		text: i18n.ts.customEmojis,
+		to: '/admin/emojis',
+		active: props.initialPage === 'emojis',
+	}, {
+		icon: 'fas fa-globe',
+		text: i18n.ts.federation,
+		to: '/admin/federation',
+		active: props.initialPage === 'federation',
+	}, {
+		icon: 'fas fa-clipboard-list',
+		text: i18n.ts.jobQueue,
+		to: '/admin/queue',
+		active: props.initialPage === 'queue',
+	}, {
+		icon: 'fas fa-cloud',
+		text: i18n.ts.files,
+		to: '/admin/files',
+		active: props.initialPage === 'files',
+	}, {
+		icon: 'fas fa-broadcast-tower',
+		text: i18n.ts.announcements,
+		to: '/admin/announcements',
+		active: props.initialPage === 'announcements',
+	}, {
+		icon: 'fas fa-audio-description',
+		text: i18n.ts.ads,
+		to: '/admin/ads',
+		active: props.initialPage === 'ads',
+	}, {
+		icon: 'fas fa-exclamation-circle',
+		text: i18n.ts.abuseReports,
+		to: '/admin/abuses',
+		active: props.initialPage === 'abuses',
+	}],
+}, {
+	title: i18n.ts.settings,
+	items: [{
+		icon: 'fas fa-cog',
+		text: i18n.ts.general,
+		to: '/admin/settings',
+		active: props.initialPage === 'settings',
+	}, {
+		icon: 'fas fa-envelope',
+		text: i18n.ts.emailServer,
+		to: '/admin/email-settings',
+		active: props.initialPage === 'email-settings',
+	}, {
+		icon: 'fas fa-cloud',
+		text: i18n.ts.objectStorage,
+		to: '/admin/object-storage',
+		active: props.initialPage === 'object-storage',
+	}, {
+		icon: 'fas fa-lock',
+		text: i18n.ts.security,
+		to: '/admin/security',
+		active: props.initialPage === 'security',
+	}, {
+		icon: 'fas fa-globe',
+		text: i18n.ts.relays,
+		to: '/admin/relays',
+		active: props.initialPage === 'relays',
+	}, {
+		icon: 'fas fa-share-alt',
+		text: i18n.ts.integration,
+		to: '/admin/integrations',
+		active: props.initialPage === 'integrations',
+	}, {
+		icon: 'fas fa-ban',
+		text: i18n.ts.instanceBlocking,
+		to: '/admin/instance-block',
+		active: props.initialPage === 'instance-block',
+	}, {
+		icon: 'fas fa-ghost',
+		text: i18n.ts.proxyAccount,
+		to: '/admin/proxy-account',
+		active: props.initialPage === 'proxy-account',
+	}, {
+		icon: 'fas fa-cogs',
+		text: i18n.ts.other,
+		to: '/admin/other-settings',
+		active: props.initialPage === 'other-settings',
+	}],
+}, {
+	title: i18n.ts.info,
+	items: [{
+		icon: 'fas fa-database',
+		text: i18n.ts.database,
+		to: '/admin/database',
+		active: props.initialPage === 'database',
+	}],
+}]);
+
+const component = $computed(() => {
+	if (props.initialPage == null) return null;
+	switch (props.initialPage) {
+		case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
+		case 'users': return defineAsyncComponent(() => import('./users.vue'));
+		case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
+		case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
+		case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
+		case 'files': return defineAsyncComponent(() => import('./files.vue'));
+		case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
+		case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
+		case 'database': return defineAsyncComponent(() => import('./database.vue'));
+		case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
+		case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
+		case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
+		case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
+		case 'security': return defineAsyncComponent(() => import('./security.vue'));
+		case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
+		case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
+		case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
+		case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
+		case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
+	}
+});
+
+watch(component, () => {
+	pageProps = {};
+
+	nextTick(() => {
+		scroll(el, { top: 0 });
+	});
+}, { immediate: true });
+
+watch(() => props.initialPage, () => {
+	if (props.initialPage == null && !narrow) {
+		nav.push('/admin/overview');
+	} else {
+		if (props.initialPage == null) {
+			INFO = indexInfo;
 		}
-	},
+	}
+});
 
-	setup(props, context) {
-		const indexInfo = {
-			title: i18n.ts.controlPanel,
-			icon: 'fas fa-cog',
-			bg: 'var(--bg)',
-			hideHeader: true,
-		};
-		const INFO = ref(indexInfo);
-		const childInfo = ref(null);
-		const page = ref(props.initialPage);
-		const narrow = ref(false);
-		const view = ref(null);
-		const el = ref(null);
-		const pageChanged = (page) => {
-			if (page == null) return;
-			const viewInfo = page[symbols.PAGE_INFO];
-			if (isRef(viewInfo)) {
-				watch(viewInfo, () => {
-					childInfo.value = viewInfo.value;
-				}, { immediate: true });
-			} else {
-				childInfo.value = viewInfo;
-			}
-		};
-		const pageProps = ref({});
+watch(narrow, () => {
+	if (props.initialPage == null && !narrow) {
+		nav.push('/admin/overview');
+	}
+});
 
-		const isEmpty = (x: any) => x == null || x == '';
+onMounted(() => {
+	ro.observe(el);
 
-		const noMaintainerInformation = ref(false);
-		const noBotProtection = ref(false);
+	narrow = el.offsetWidth < NARROW_THRESHOLD;
+	if (props.initialPage == null && !narrow) {
+		nav.push('/admin/overview');
+	}
+});
 
-		os.api('meta', { detail: true }).then(meta => {
-			// TODO: ่จญๅฎšใŒๅฎŒไบ†ใ—ใฆใ‚‚ๆฎ‹ใฃใŸใพใพใซใชใ‚‹ใฎใงใ€ใ‚นใƒˆใƒชใƒผใƒŸใƒณใ‚ฐใงmetaๆ›ดๆ–ฐใ‚คใƒ™ใƒณใƒˆใ‚’ๅ—ใ‘ๅ–ใฃใฆใ‚ˆใ—ใชใซๆ›ดๆ–ฐใ™ใ‚‹
-			noMaintainerInformation.value = isEmpty(meta.maintainerName) || isEmpty(meta.maintainerEmail);
-			noBotProtection.value = !meta.enableHcaptcha && !meta.enableRecaptcha;
+onUnmounted(() => {
+	ro.disconnect();
+});
+
+const pageChanged = (page) => {
+	if (page == null) {
+		childInfo = null;
+	} else {
+		childInfo = page[symbols.PAGE_INFO];
+	}
+};
+
+const invite = () => {
+	os.api('admin/invite').then(x => {
+		os.alert({
+			type: 'info',
+			text: x.code
 		});
-
-		const menuDef = computed(() => [{
-			title: i18n.ts.quickAction,
-			items: [{
-				type: 'button',
-				icon: 'fas fa-search',
-				text: i18n.ts.lookup,
-				action: lookup,
-			}, ...(instance.disableRegistration ? [{
-				type: 'button',
-				icon: 'fas fa-user',
-				text: i18n.ts.invite,
-				action: invite,
-			}] : [])],
-		}, {
-			title: i18n.ts.administration,
-			items: [{
-				icon: 'fas fa-tachometer-alt',
-				text: i18n.ts.dashboard,
-				to: '/admin/overview',
-				active: page.value === 'overview',
-			}, {
-				icon: 'fas fa-users',
-				text: i18n.ts.users,
-				to: '/admin/users',
-				active: page.value === 'users',
-			}, {
-				icon: 'fas fa-laugh',
-				text: i18n.ts.customEmojis,
-				to: '/admin/emojis',
-				active: page.value === 'emojis',
-			}, {
-				icon: 'fas fa-globe',
-				text: i18n.ts.federation,
-				to: '/admin/federation',
-				active: page.value === 'federation',
-			}, {
-				icon: 'fas fa-clipboard-list',
-				text: i18n.ts.jobQueue,
-				to: '/admin/queue',
-				active: page.value === 'queue',
-			}, {
-				icon: 'fas fa-cloud',
-				text: i18n.ts.files,
-				to: '/admin/files',
-				active: page.value === 'files',
-			}, {
-				icon: 'fas fa-broadcast-tower',
-				text: i18n.ts.announcements,
-				to: '/admin/announcements',
-				active: page.value === 'announcements',
-			}, {
-				icon: 'fas fa-audio-description',
-				text: i18n.ts.ads,
-				to: '/admin/ads',
-				active: page.value === 'ads',
-			}, {
-				icon: 'fas fa-exclamation-circle',
-				text: i18n.ts.abuseReports,
-				to: '/admin/abuses',
-				active: page.value === 'abuses',
-			}],
-		}, {
-			title: i18n.ts.settings,
-			items: [{
-				icon: 'fas fa-cog',
-				text: i18n.ts.general,
-				to: '/admin/settings',
-				active: page.value === 'settings',
-			}, {
-				icon: 'fas fa-envelope',
-				text: i18n.ts.emailServer,
-				to: '/admin/email-settings',
-				active: page.value === 'email-settings',
-			}, {
-				icon: 'fas fa-cloud',
-				text: i18n.ts.objectStorage,
-				to: '/admin/object-storage',
-				active: page.value === 'object-storage',
-			}, {
-				icon: 'fas fa-lock',
-				text: i18n.ts.security,
-				to: '/admin/security',
-				active: page.value === 'security',
-			}, {
-				icon: 'fas fa-globe',
-				text: i18n.ts.relays,
-				to: '/admin/relays',
-				active: page.value === 'relays',
-			}, {
-				icon: 'fas fa-share-alt',
-				text: i18n.ts.integration,
-				to: '/admin/integrations',
-				active: page.value === 'integrations',
-			}, {
-				icon: 'fas fa-ban',
-				text: i18n.ts.instanceBlocking,
-				to: '/admin/instance-block',
-				active: page.value === 'instance-block',
-			}, {
-				icon: 'fas fa-ghost',
-				text: i18n.ts.proxyAccount,
-				to: '/admin/proxy-account',
-				active: page.value === 'proxy-account',
-			}, {
-				icon: 'fas fa-cogs',
-				text: i18n.ts.other,
-				to: '/admin/other-settings',
-				active: page.value === 'other-settings',
-			}],
-		}, {
-			title: i18n.ts.info,
-			items: [{
-				icon: 'fas fa-database',
-				text: i18n.ts.database,
-				to: '/admin/database',
-				active: page.value === 'database',
-			}],
-		}]);
-		const component = computed(() => {
-			if (page.value == null) return null;
-			switch (page.value) {
-				case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
-				case 'users': return defineAsyncComponent(() => import('./users.vue'));
-				case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
-				case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
-				case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
-				case 'files': return defineAsyncComponent(() => import('./files.vue'));
-				case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));
-				case 'ads': return defineAsyncComponent(() => import('./ads.vue'));
-				case 'database': return defineAsyncComponent(() => import('./database.vue'));
-				case 'abuses': return defineAsyncComponent(() => import('./abuses.vue'));
-				case 'settings': return defineAsyncComponent(() => import('./settings.vue'));
-				case 'email-settings': return defineAsyncComponent(() => import('./email-settings.vue'));
-				case 'object-storage': return defineAsyncComponent(() => import('./object-storage.vue'));
-				case 'security': return defineAsyncComponent(() => import('./security.vue'));
-				case 'relays': return defineAsyncComponent(() => import('./relays.vue'));
-				case 'integrations': return defineAsyncComponent(() => import('./integrations.vue'));
-				case 'instance-block': return defineAsyncComponent(() => import('./instance-block.vue'));
-				case 'proxy-account': return defineAsyncComponent(() => import('./proxy-account.vue'));
-				case 'other-settings': return defineAsyncComponent(() => import('./other-settings.vue'));
-			}
+	}).catch(err => {
+		os.alert({
+			type: 'error',
+			text: err,
 		});
+	});
+};
 
-		watch(component, () => {
-			pageProps.value = {};
+const lookup = (ev) => {
+	os.popupMenu([{
+		text: i18n.ts.user,
+		icon: 'fas fa-user',
+		action: () => {
+			lookupUser();
+		}
+	}, {
+		text: i18n.ts.note,
+		icon: 'fas fa-pencil-alt',
+		action: () => {
+			alert('TODO');
+		}
+	}, {
+		text: i18n.ts.file,
+		icon: 'fas fa-cloud',
+		action: () => {
+			alert('TODO');
+		}
+	}, {
+		text: i18n.ts.instance,
+		icon: 'fas fa-globe',
+		action: () => {
+			alert('TODO');
+		}
+	}], ev.currentTarget ?? ev.target);
+};
 
-			nextTick(() => {
-				scroll(el.value, { top: 0 });
-			});
-		}, { immediate: true });
-
-		watch(() => props.initialPage, () => {
-			if (props.initialPage == null && !narrow.value) {
-				page.value = 'overview';
-			} else {
-				page.value = props.initialPage;
-				if (props.initialPage == null) {
-					INFO.value = indexInfo;
-				}
-			}
-		});
-
-		onMounted(() => {
-			narrow.value = el.value.offsetWidth < 800;
-			if (!narrow.value) {
-				page.value = 'overview';
-			}
-		});
-
-		const invite = () => {
-			os.api('admin/invite').then(x => {
-				os.alert({
-					type: 'info',
-					text: x.code
-				});
-			}).catch(e => {
-				os.alert({
-					type: 'error',
-					text: e
-				});
-			});
-		};
-
-		const lookup = (ev) => {
-			os.popupMenu([{
-				text: i18n.ts.user,
-				icon: 'fas fa-user',
-				action: () => {
-					lookupUser();
-				}
-			}, {
-				text: i18n.ts.note,
-				icon: 'fas fa-pencil-alt',
-				action: () => {
-					alert('TODO');
-				}
-			}, {
-				text: i18n.ts.file,
-				icon: 'fas fa-cloud',
-				action: () => {
-					alert('TODO');
-				}
-			}, {
-				text: i18n.ts.instance,
-				icon: 'fas fa-globe',
-				action: () => {
-					alert('TODO');
-				}
-			}], ev.currentTarget ?? ev.target);
-		};
-
-		return {
-			[symbols.PAGE_INFO]: INFO,
-			menuDef,
-			header: {
-				title: i18n.ts.controlPanel,
-			},
-			noMaintainerInformation,
-			noBotProtection,
-			page,
-			narrow,
-			view,
-			el,
-			pageChanged,
-			childInfo,
-			pageProps,
-			component,
-			invite,
-			lookup,
-		};
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: INFO,
+	header: {
+		title: i18n.ts.controlPanel,
+	}
 });
 </script>
 
diff --git a/packages/client/src/pages/admin/instance-block.vue b/packages/client/src/pages/admin/instance-block.vue
index 4cb8dc604e..3347846a80 100644
--- a/packages/client/src/pages/admin/instance-block.vue
+++ b/packages/client/src/pages/admin/instance-block.vue
@@ -2,57 +2,45 @@
 <MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
 	<FormSuspense :p="init">
 		<FormTextarea v-model="blockedHosts" class="_formBlock">
-			<span>{{ $ts.blockedInstances }}</span>
-			<template #caption>{{ $ts.blockedInstancesDescription }}</template>
+			<span>{{ i18n.ts.blockedInstances }}</span>
+			<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
 		</FormTextarea>
 
-		<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+		<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
 	</FormSuspense>
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormButton from '@/components/ui/button.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormButton,
-		FormTextarea,
-		FormSuspense,
-	},
+let blockedHosts: string = $ref('');
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	blockedHosts = meta.blockedHosts.join('\n');
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.instanceBlocking,
-				icon: 'fas fa-ban',
-				bg: 'var(--bg)',
-			},
-			blockedHosts: '',
-		}
-	},
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		blockedHosts: blockedHosts.split('\n') || [],
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.blockedHosts = meta.blockedHosts.join('\n');
-		},
-
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				blockedHosts: this.blockedHosts.split('\n') || [],
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.instanceBlocking,
+		icon: 'fas fa-ban',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/integrations.discord.vue b/packages/client/src/pages/admin/integrations.discord.vue
index 6b50f1b0a9..9fdc51a6ca 100644
--- a/packages/client/src/pages/admin/integrations.discord.vue
+++ b/packages/client/src/pages/admin/integrations.discord.vue
@@ -24,57 +24,36 @@
 </FormSuspense>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
 import FormButton from '@/components/ui/button.vue';
 import FormInfo from '@/components/ui/info.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
-import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormInfo,
-		FormButton,
-		FormSuspense,
-	},
+let uri: string = $ref('');
+let enableDiscordIntegration: boolean = $ref(false);
+let discordClientId: string | null = $ref(null);
+let discordClientSecret: string | null = $ref(null);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	uri = meta.uri;
+	enableDiscordIntegration = meta.enableDiscordIntegration;
+	discordClientId = meta.discordClientId;
+	discordClientSecret = meta.discordClientSecret;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: 'Discord',
-				icon: 'fab fa-discord'
-			},
-			enableDiscordIntegration: false,
-			discordClientId: null,
-			discordClientSecret: null,
-		}
-	},
-
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.uri = meta.uri;
-			this.enableDiscordIntegration = meta.enableDiscordIntegration;
-			this.discordClientId = meta.discordClientId;
-			this.discordClientSecret = meta.discordClientSecret;
-		},
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				enableDiscordIntegration: this.enableDiscordIntegration,
-				discordClientId: this.discordClientId,
-				discordClientSecret: this.discordClientSecret,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
-	}
-});
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		enableDiscordIntegration,
+		discordClientId,
+		discordClientSecret,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 </script>
diff --git a/packages/client/src/pages/admin/integrations.github.vue b/packages/client/src/pages/admin/integrations.github.vue
index 67f299e1bc..b10ccb8394 100644
--- a/packages/client/src/pages/admin/integrations.github.vue
+++ b/packages/client/src/pages/admin/integrations.github.vue
@@ -24,57 +24,36 @@
 </FormSuspense>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
 import FormButton from '@/components/ui/button.vue';
 import FormInfo from '@/components/ui/info.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
-import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormInfo,
-		FormButton,
-		FormSuspense,
-	},
+let uri: string = $ref('');
+let enableGithubIntegration: boolean = $ref(false);
+let githubClientId: string | null = $ref(null);
+let githubClientSecret: string | null = $ref(null);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	uri = meta.uri;
+	enableGithubIntegration = meta.enableGithubIntegration;
+	githubClientId = meta.githubClientId;
+	githubClientSecret = meta.githubClientSecret;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: 'GitHub',
-				icon: 'fab fa-github'
-			},
-			enableGithubIntegration: false,
-			githubClientId: null,
-			githubClientSecret: null,
-		}
-	},
-
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.uri = meta.uri;
-			this.enableGithubIntegration = meta.enableGithubIntegration;
-			this.githubClientId = meta.githubClientId;
-			this.githubClientSecret = meta.githubClientSecret;
-		},
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				enableGithubIntegration: this.enableGithubIntegration,
-				githubClientId: this.githubClientId,
-				githubClientSecret: this.githubClientSecret,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
-	}
-});
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		enableGithubIntegration,
+		githubClientId,
+		githubClientSecret,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 </script>
diff --git a/packages/client/src/pages/admin/integrations.twitter.vue b/packages/client/src/pages/admin/integrations.twitter.vue
index a389c71506..11b5fd86b2 100644
--- a/packages/client/src/pages/admin/integrations.twitter.vue
+++ b/packages/client/src/pages/admin/integrations.twitter.vue
@@ -24,7 +24,7 @@
 </FormSuspense>
 </template>
 
-<script lang="ts">
+<script lang="ts" setup>
 import { defineComponent } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
@@ -32,49 +32,28 @@ import FormButton from '@/components/ui/button.vue';
 import FormInfo from '@/components/ui/info.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
-import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormInfo,
-		FormButton,
-		FormSuspense,
-	},
+let uri: string = $ref('');
+let enableTwitterIntegration: boolean = $ref(false);
+let twitterConsumerKey: string | null = $ref(null);
+let twitterConsumerSecret: string | null = $ref(null);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	uri = meta.uri;
+	enableTwitterIntegration = meta.enableTwitterIntegration;
+	twitterConsumerKey = meta.twitterConsumerKey;
+	twitterConsumerSecret = meta.twitterConsumerSecret;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: 'Twitter',
-				icon: 'fab fa-twitter'
-			},
-			enableTwitterIntegration: false,
-			twitterConsumerKey: null,
-			twitterConsumerSecret: null,
-		}
-	},
-
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.uri = meta.uri;
-			this.enableTwitterIntegration = meta.enableTwitterIntegration;
-			this.twitterConsumerKey = meta.twitterConsumerKey;
-			this.twitterConsumerSecret = meta.twitterConsumerSecret;
-		},
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				enableTwitterIntegration: this.enableTwitterIntegration,
-				twitterConsumerKey: this.twitterConsumerKey,
-				twitterConsumerSecret: this.twitterConsumerSecret,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
-	}
-});
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		enableTwitterIntegration,
+		twitterConsumerKey,
+		twitterConsumerSecret,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 </script>
diff --git a/packages/client/src/pages/admin/integrations.vue b/packages/client/src/pages/admin/integrations.vue
index 4db8a9e0a9..d6061d0e51 100644
--- a/packages/client/src/pages/admin/integrations.vue
+++ b/packages/client/src/pages/admin/integrations.vue
@@ -4,69 +4,52 @@
 		<FormFolder class="_formBlock">
 			<template #icon><i class="fab fa-twitter"></i></template>
 			<template #label>Twitter</template>
-			<template #suffix>{{ enableTwitterIntegration ? $ts.enabled : $ts.disabled }}</template>
+			<template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
 			<XTwitter/>
 		</FormFolder>
-		<FormFolder to="/admin/integrations/github" class="_formBlock">
+		<FormFolder class="_formBlock">
 			<template #icon><i class="fab fa-github"></i></template>
 			<template #label>GitHub</template>
-			<template #suffix>{{ enableGithubIntegration ? $ts.enabled : $ts.disabled }}</template>
+			<template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
 			<XGithub/>
 		</FormFolder>
-		<FormFolder to="/admin/integrations/discord" class="_formBlock">
+		<FormFolder class="_formBlock">
 			<template #icon><i class="fab fa-discord"></i></template>
 			<template #label>Discord</template>
-			<template #suffix>{{ enableDiscordIntegration ? $ts.enabled : $ts.disabled }}</template>
+			<template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
 			<XDiscord/>
 		</FormFolder>
 	</FormSuspense>
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormFolder from '@/components/form/folder.vue';
-import FormSecion from '@/components/form/section.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import XTwitter from './integrations.twitter.vue';
 import XGithub from './integrations.github.vue';
 import XDiscord from './integrations.discord.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
-import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormFolder,
-		FormSecion,
-		FormSuspense,
-		XTwitter,
-		XGithub,
-		XDiscord,
-	},
+let enableTwitterIntegration: boolean = $ref(false);
+let enableGithubIntegration: boolean = $ref(false);
+let enableDiscordIntegration: boolean = $ref(false);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	enableTwitterIntegration = meta.enableTwitterIntegration;
+	enableGithubIntegration = meta.enableGithubIntegration;
+	enableDiscordIntegration = meta.enableDiscordIntegration;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.integration,
-				icon: 'fas fa-share-alt',
-				bg: 'var(--bg)',
-			},
-			enableTwitterIntegration: false,
-			enableGithubIntegration: false,
-			enableDiscordIntegration: false,
-		}
-	},
-
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.enableTwitterIntegration = meta.enableTwitterIntegration;
-			this.enableGithubIntegration = meta.enableGithubIntegration;
-			this.enableDiscordIntegration = meta.enableDiscordIntegration;
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.integration,
+		icon: 'fas fa-share-alt',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/metrics.vue b/packages/client/src/pages/admin/metrics.vue
index 1de297fd93..7e5f5bb094 100644
--- a/packages/client/src/pages/admin/metrics.vue
+++ b/packages/client/src/pages/admin/metrics.vue
@@ -132,7 +132,7 @@ export default defineComponent({
 			overviewHeight: '1fr',
 			queueHeight: '1fr',
 			paused: false,
-		}
+		};
 	},
 
 	computed: {
diff --git a/packages/client/src/pages/admin/object-storage.vue b/packages/client/src/pages/admin/object-storage.vue
index a1ee0761c8..d109db9c38 100644
--- a/packages/client/src/pages/admin/object-storage.vue
+++ b/packages/client/src/pages/admin/object-storage.vue
@@ -2,32 +2,32 @@
 <MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
 	<FormSuspense :p="init">
 		<div class="_formRoot">
-			<FormSwitch v-model="useObjectStorage" class="_formBlock">{{ $ts.useObjectStorage }}</FormSwitch>
+			<FormSwitch v-model="useObjectStorage" class="_formBlock">{{ i18n.ts.useObjectStorage }}</FormSwitch>
 
 			<template v-if="useObjectStorage">
 				<FormInput v-model="objectStorageBaseUrl" class="_formBlock">
-					<template #label>{{ $ts.objectStorageBaseUrl }}</template>
-					<template #caption>{{ $ts.objectStorageBaseUrlDesc }}</template>
+					<template #label>{{ i18n.ts.objectStorageBaseUrl }}</template>
+					<template #caption>{{ i18n.ts.objectStorageBaseUrlDesc }}</template>
 				</FormInput>
 
 				<FormInput v-model="objectStorageBucket" class="_formBlock">
-					<template #label>{{ $ts.objectStorageBucket }}</template>
-					<template #caption>{{ $ts.objectStorageBucketDesc }}</template>
+					<template #label>{{ i18n.ts.objectStorageBucket }}</template>
+					<template #caption>{{ i18n.ts.objectStorageBucketDesc }}</template>
 				</FormInput>
 
 				<FormInput v-model="objectStoragePrefix" class="_formBlock">
-					<template #label>{{ $ts.objectStoragePrefix }}</template>
-					<template #caption>{{ $ts.objectStoragePrefixDesc }}</template>
+					<template #label>{{ i18n.ts.objectStoragePrefix }}</template>
+					<template #caption>{{ i18n.ts.objectStoragePrefixDesc }}</template>
 				</FormInput>
 
 				<FormInput v-model="objectStorageEndpoint" class="_formBlock">
-					<template #label>{{ $ts.objectStorageEndpoint }}</template>
-					<template #caption>{{ $ts.objectStorageEndpointDesc }}</template>
+					<template #label>{{ i18n.ts.objectStorageEndpoint }}</template>
+					<template #caption>{{ i18n.ts.objectStorageEndpointDesc }}</template>
 				</FormInput>
 
 				<FormInput v-model="objectStorageRegion" class="_formBlock">
-					<template #label>{{ $ts.objectStorageRegion }}</template>
-					<template #caption>{{ $ts.objectStorageRegionDesc }}</template>
+					<template #label>{{ i18n.ts.objectStorageRegion }}</template>
+					<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
 				</FormInput>
 
 				<FormSplit :min-width="280">
@@ -43,17 +43,17 @@
 				</FormSplit>
 
 				<FormSwitch v-model="objectStorageUseSSL" class="_formBlock">
-					<template #label>{{ $ts.objectStorageUseSSL }}</template>
-					<template #caption>{{ $ts.objectStorageUseSSLDesc }}</template>
+					<template #label>{{ i18n.ts.objectStorageUseSSL }}</template>
+					<template #caption>{{ i18n.ts.objectStorageUseSSLDesc }}</template>
 				</FormSwitch>
 
 				<FormSwitch v-model="objectStorageUseProxy" class="_formBlock">
-					<template #label>{{ $ts.objectStorageUseProxy }}</template>
-					<template #caption>{{ $ts.objectStorageUseProxyDesc }}</template>
+					<template #label>{{ i18n.ts.objectStorageUseProxy }}</template>
+					<template #caption>{{ i18n.ts.objectStorageUseProxyDesc }}</template>
 				</FormSwitch>
 
 				<FormSwitch v-model="objectStorageSetPublicRead" class="_formBlock">
-					<template #label>{{ $ts.objectStorageSetPublicRead }}</template>
+					<template #label>{{ i18n.ts.objectStorageSetPublicRead }}</template>
 				</FormSwitch>
 
 				<FormSwitch v-model="objectStorageS3ForcePathStyle" class="_formBlock">
@@ -65,8 +65,8 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
 import FormGroup from '@/components/form/group.vue';
@@ -76,84 +76,70 @@ import FormSection from '@/components/form/section.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormGroup,
-		FormSuspense,
-		FormSplit,
-		FormSection,
-	},
+let useObjectStorage: boolean = $ref(false);
+let objectStorageBaseUrl: string | null = $ref(null);
+let objectStorageBucket: string | null = $ref(null);
+let objectStoragePrefix: string | null = $ref(null);
+let objectStorageEndpoint: string | null = $ref(null);
+let objectStorageRegion: string | null = $ref(null);
+let objectStoragePort: number | null = $ref(null);
+let objectStorageAccessKey: string | null = $ref(null);
+let objectStorageSecretKey: string | null = $ref(null);
+let objectStorageUseSSL: boolean = $ref(false);
+let objectStorageUseProxy: boolean = $ref(false);
+let objectStorageSetPublicRead: boolean = $ref(false);
+let objectStorageS3ForcePathStyle: boolean = $ref(true);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	useObjectStorage = meta.useObjectStorage;
+	objectStorageBaseUrl = meta.objectStorageBaseUrl;
+	objectStorageBucket = meta.objectStorageBucket;
+	objectStoragePrefix = meta.objectStoragePrefix;
+	objectStorageEndpoint = meta.objectStorageEndpoint;
+	objectStorageRegion = meta.objectStorageRegion;
+	objectStoragePort = meta.objectStoragePort;
+	objectStorageAccessKey = meta.objectStorageAccessKey;
+	objectStorageSecretKey = meta.objectStorageSecretKey;
+	objectStorageUseSSL = meta.objectStorageUseSSL;
+	objectStorageUseProxy = meta.objectStorageUseProxy;
+	objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
+	objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.objectStorage,
-				icon: 'fas fa-cloud',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-check',
-					text: this.$ts.save,
-					handler: this.save,
-				}],
-			},
-			useObjectStorage: false,
-			objectStorageBaseUrl: null,
-			objectStorageBucket: null,
-			objectStoragePrefix: null,
-			objectStorageEndpoint: null,
-			objectStorageRegion: null,
-			objectStoragePort: null,
-			objectStorageAccessKey: null,
-			objectStorageSecretKey: null,
-			objectStorageUseSSL: false,
-			objectStorageUseProxy: false,
-			objectStorageSetPublicRead: false,
-			objectStorageS3ForcePathStyle: true,
-		}
-	},
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		useObjectStorage,
+		objectStorageBaseUrl,
+		objectStorageBucket,
+		objectStoragePrefix,
+		objectStorageEndpoint,
+		objectStorageRegion,
+		objectStoragePort,
+		objectStorageAccessKey,
+		objectStorageSecretKey,
+		objectStorageUseSSL,
+		objectStorageUseProxy,
+		objectStorageSetPublicRead,
+		objectStorageS3ForcePathStyle,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.useObjectStorage = meta.useObjectStorage;
-			this.objectStorageBaseUrl = meta.objectStorageBaseUrl;
-			this.objectStorageBucket = meta.objectStorageBucket;
-			this.objectStoragePrefix = meta.objectStoragePrefix;
-			this.objectStorageEndpoint = meta.objectStorageEndpoint;
-			this.objectStorageRegion = meta.objectStorageRegion;
-			this.objectStoragePort = meta.objectStoragePort;
-			this.objectStorageAccessKey = meta.objectStorageAccessKey;
-			this.objectStorageSecretKey = meta.objectStorageSecretKey;
-			this.objectStorageUseSSL = meta.objectStorageUseSSL;
-			this.objectStorageUseProxy = meta.objectStorageUseProxy;
-			this.objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
-			this.objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
-		},
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				useObjectStorage: this.useObjectStorage,
-				objectStorageBaseUrl: this.objectStorageBaseUrl ? this.objectStorageBaseUrl : null,
-				objectStorageBucket: this.objectStorageBucket ? this.objectStorageBucket : null,
-				objectStoragePrefix: this.objectStoragePrefix ? this.objectStoragePrefix : null,
-				objectStorageEndpoint: this.objectStorageEndpoint ? this.objectStorageEndpoint : null,
-				objectStorageRegion: this.objectStorageRegion ? this.objectStorageRegion : null,
-				objectStoragePort: this.objectStoragePort ? this.objectStoragePort : null,
-				objectStorageAccessKey: this.objectStorageAccessKey ? this.objectStorageAccessKey : null,
-				objectStorageSecretKey: this.objectStorageSecretKey ? this.objectStorageSecretKey : null,
-				objectStorageUseSSL: this.objectStorageUseSSL,
-				objectStorageUseProxy: this.objectStorageUseProxy,
-				objectStorageSetPublicRead: this.objectStorageSetPublicRead,
-				objectStorageS3ForcePathStyle: this.objectStorageS3ForcePathStyle,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+  [symbols.PAGE_INFO]: {
+		title: i18n.ts.objectStorage,
+		icon: 'fas fa-cloud',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-check',
+			text: i18n.ts.save,
+			handler: save,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/other-settings.vue b/packages/client/src/pages/admin/other-settings.vue
index 99ea6a5f32..552b05f347 100644
--- a/packages/client/src/pages/admin/other-settings.vue
+++ b/packages/client/src/pages/admin/other-settings.vue
@@ -6,52 +6,35 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import FormSwitch from '@/components/form/switch.vue';
-import FormInput from '@/components/form/input.vue';
-import FormSection from '@/components/form/section.vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormSection,
-		FormSuspense,
-	},
+async function init() {
+	await os.api('admin/meta');
+}
 
-	emits: ['info'],
+function save() {
+	os.apiWithDialog('admin/update-meta').then(() => {
+		fetchInstance();
+	});
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.other,
-				icon: 'fas fa-cogs',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-check',
-					text: this.$ts.save,
-					handler: this.save,
-				}],
-			},
-		}
-	},
-
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-		},
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+  [symbols.PAGE_INFO]: {
+		title: i18n.ts.other,
+		icon: 'fas fa-cogs',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-check',
+			text: i18n.ts.save,
+			handler: save,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/overview.vue b/packages/client/src/pages/admin/overview.vue
index b8ae8ad9e1..cc69424c3b 100644
--- a/packages/client/src/pages/admin/overview.vue
+++ b/packages/client/src/pages/admin/overview.vue
@@ -5,20 +5,20 @@
 			<div class="label">Users</div>
 			<div class="value _monospace">
 				{{ number(stats.originalUsersCount) }}
-				<MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
+				<MkNumberDiff v-if="usersComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="usersComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
 			</div>
 		</div>
 		<div class="number _panel">
 			<div class="label">Notes</div>
 			<div class="value _monospace">
 				{{ number(stats.originalNotesCount) }}
-				<MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="$ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
+				<MkNumberDiff v-if="notesComparedToThePrevDay != null" v-tooltip="i18n.ts.dayOverDayChanges" class="diff" :value="notesComparedToThePrevDay"><template #before>(</template><template #after>)</template></MkNumberDiff>
 			</div>
 		</div>
 	</div>
 
 	<MkContainer :foldable="true" class="charts">
-		<template #header><i class="fas fa-chart-bar"></i>{{ $ts.charts }}</template>
+		<template #header><i class="fas fa-chart-bar"></i>{{ i18n.ts.charts }}</template>
 		<div style="padding: 12px;">
 			<MkInstanceStats :chart-limit="500" :detailed="true"/>
 		</div>
@@ -38,7 +38,7 @@
 		<!--<XMetrics/>-->
 
 	<MkFolder style="margin: var(--margin)">
-		<template #header><i class="fas fa-info-circle"></i> {{ $ts.info }}</template>
+		<template #header><i class="fas fa-info-circle"></i> {{ i18n.ts.info }}</template>
 		<div class="cfcdecdf">
 			<div class="number _panel">
 				<div class="label">Misskey</div>
@@ -65,103 +65,61 @@
 </div>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent, markRaw, version as vueVersion } from 'vue';
+<script lang="ts" setup>
+import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
 import MkInstanceStats from '@/components/instance-stats.vue';
-import MkButton from '@/components/ui/button.vue';
-import MkSelect from '@/components/form/select.vue';
 import MkNumberDiff from '@/components/number-diff.vue';
 import MkContainer from '@/components/ui/container.vue';
 import MkFolder from '@/components/ui/folder.vue';
 import MkQueueChart from '@/components/queue-chart.vue';
 import { version, url } from '@/config';
-import bytes from '@/filters/bytes';
 import number from '@/filters/number';
 import XMetrics from './metrics.vue';
 import * as os from '@/os';
 import { stream } from '@/stream';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkNumberDiff,
-		MkInstanceStats,
-		MkContainer,
-		MkFolder,
-		MkQueueChart,
-		XMetrics,
-	},
+let stats: any = $ref(null);
+let serverInfo: any = $ref(null);
+let usersComparedToThePrevDay: any = $ref(null);
+let notesComparedToThePrevDay: any = $ref(null);
+const queueStatsConnection = markRaw(stream.useChannel('queueStats'));
 
-	emits: ['info'],
+onMounted(async () => {	
+	os.api('stats', {}).then(statsResponse => {
+		stats = statsResponse;
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.dashboard,
-				icon: 'fas fa-tachometer-alt',
-				bg: 'var(--bg)',
-			},
-			version,
-			vueVersion,
-			url,
-			stats: null,
-			meta: null,
-			serverInfo: null,
-			usersComparedToThePrevDay: null,
-			notesComparedToThePrevDay: null,
-			fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),
-			fetchModLogs: () => os.api('admin/show-moderation-logs', {}),
-			queueStatsConnection: markRaw(stream.useChannel('queueStats')),
-		}
-	},
-
-	async mounted() {
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-		
-		os.api('stats', {}).then(stats => {
-			this.stats = stats;
-
-			os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
-				this.usersComparedToThePrevDay = this.stats.originalUsersCount - chart.local.total[1];
-			});
-
-			os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
-				this.notesComparedToThePrevDay = this.stats.originalNotesCount - chart.local.total[1];
-			});
+		os.api('charts/users', { limit: 2, span: 'day' }).then(chart => {
+			usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1];
 		});
 
-		os.api('admin/server-info', {}).then(serverInfo => {
-			this.serverInfo = serverInfo;
+		os.api('charts/notes', { limit: 2, span: 'day' }).then(chart => {
+			notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1];
 		});
+	});
 
-		this.$nextTick(() => {
-			this.queueStatsConnection.send('requestLog', {
-				id: Math.random().toString().substr(2, 8),
-				length: 200
-			});
+	os.api('admin/server-info').then(serverInfoResponse => {
+		serverInfo = serverInfoResponse;
+	});
+
+	nextTick(() => {
+		queueStatsConnection.send('requestLog', {
+			id: Math.random().toString().substr(2, 8),
+			length: 200
 		});
-	},
+	});
+});
 
-	beforeUnmount() {
-		this.queueStatsConnection.dispose();
-	},
+onBeforeUnmount(() => {
+	queueStatsConnection.dispose();
+});
 
-	methods: {
-		async showInstanceInfo(q) {
-			let instance = q;
-			if (typeof q === 'string') {
-				instance = await os.api('federation/show-instance', {
-					host: q
-				});
-			}
-			// TODO
-		},
-
-		bytes,
-
-		number,
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.dashboard,
+		icon: 'fas fa-tachometer-alt',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/proxy-account.vue b/packages/client/src/pages/admin/proxy-account.vue
index 00f14a176f..727e20e7e5 100644
--- a/packages/client/src/pages/admin/proxy-account.vue
+++ b/packages/client/src/pages/admin/proxy-account.vue
@@ -1,19 +1,19 @@
 <template>
 <MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
 	<FormSuspense :p="init">
-		<MkInfo class="_formBlock">{{ $ts.proxyAccountDescription }}</MkInfo>
+		<MkInfo class="_formBlock">{{ i18n.ts.proxyAccountDescription }}</MkInfo>
 		<MkKeyValue class="_formBlock">
-			<template #key>{{ $ts.proxyAccount }}</template>
-			<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : $ts.none }}</template>
+			<template #key>{{ i18n.ts.proxyAccount }}</template>
+			<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
 		</MkKeyValue>
 
-		<FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ $ts.selectAccount }}</FormButton>
+		<FormButton primary class="_formBlock" @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</FormButton>
 	</FormSuspense>
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import MkKeyValue from '@/components/key-value.vue';
 import FormButton from '@/components/ui/button.vue';
 import MkInfo from '@/components/ui/info.vue';
@@ -21,53 +21,40 @@ import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkKeyValue,
-		FormButton,
-		MkInfo,
-		FormSuspense,
-	},
+let proxyAccount: any = $ref(null);
+let proxyAccountId: any = $ref(null);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	proxyAccountId = meta.proxyAccountId;
+	if (proxyAccountId) {
+		proxyAccount = await os.api('users/show', { userId: proxyAccountId });
+	}
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.proxyAccount,
-				icon: 'fas fa-ghost',
-				bg: 'var(--bg)',
-			},
-			proxyAccount: null,
-			proxyAccountId: null,
-		}
-	},
+function chooseProxyAccount() {
+	os.selectUser().then(user => {
+		proxyAccount = user;
+		proxyAccountId = user.id;
+		save();
+	});
+}
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.proxyAccountId = meta.proxyAccountId;
-			if (this.proxyAccountId) {
-				this.proxyAccount = await os.api('users/show', { userId: this.proxyAccountId });
-			}
-		},
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		proxyAccountId: proxyAccountId,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-		chooseProxyAccount() {
-			os.selectUser().then(user => {
-				this.proxyAccount = user;
-				this.proxyAccountId = user.id;
-				this.save();
-			});
-		},
-
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				proxyAccountId: this.proxyAccountId,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.proxyAccount,
+		icon: 'fas fa-ghost',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/queue.chart.vue b/packages/client/src/pages/admin/queue.chart.vue
index 136fb63bb6..be63830bdd 100644
--- a/packages/client/src/pages/admin/queue.chart.vue
+++ b/packages/client/src/pages/admin/queue.chart.vue
@@ -26,62 +26,40 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, markRaw, onMounted, onUnmounted, ref } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref } from 'vue';
 import number from '@/filters/number';
 import MkQueueChart from '@/components/queue-chart.vue';
 import * as os from '@/os';
 
-export default defineComponent({
-	components: {
-		MkQueueChart
-	},
+const activeSincePrevTick = ref(0);
+const active = ref(0);
+const waiting = ref(0);
+const delayed = ref(0);
+const jobs = ref([]);
 
-	props: {
-		domain: {
-			type: String,
-			required: true,
-		},
-		connection: {
-			required: true,
-		},
-	},
+const props = defineProps<{
+	domain: string,
+	connection: any,
+}>();
 
-	setup(props) {
-		const activeSincePrevTick = ref(0);
-		const active = ref(0);
-		const waiting = ref(0);
-		const delayed = ref(0);
-		const jobs = ref([]);
+onMounted(() => {
+	os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => {
+		jobs.value = result;
+	});
 
-		onMounted(() => {
-			os.api(props.domain === 'inbox' ? 'admin/queue/inbox-delayed' : props.domain === 'deliver' ? 'admin/queue/deliver-delayed' : null, {}).then(result => {
-				jobs.value = result;
-			});
+	const onStats = (stats) => {
+		activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
+		active.value = stats[props.domain].active;
+		waiting.value = stats[props.domain].waiting;
+		delayed.value = stats[props.domain].delayed;
+	};
 
-			const onStats = (stats) => {
-				activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
-				active.value = stats[props.domain].active;
-				waiting.value = stats[props.domain].waiting;
-				delayed.value = stats[props.domain].delayed;
-			};
+	props.connection.on('stats', onStats);
 
-			props.connection.on('stats', onStats);
-
-			onUnmounted(() => {
-				props.connection.off('stats', onStats);
-			});
-		});
-
-		return {
-			jobs,
-			activeSincePrevTick,
-			active,
-			waiting,
-			delayed,
-			number,
-		};
-	},
+	onUnmounted(() => {
+		props.connection.off('stats', onStats);
+	});
 });
 </script>
 
diff --git a/packages/client/src/pages/admin/queue.vue b/packages/client/src/pages/admin/queue.vue
index 35fd618c82..656b18199f 100644
--- a/packages/client/src/pages/admin/queue.vue
+++ b/packages/client/src/pages/admin/queue.vue
@@ -6,71 +6,60 @@
 	<XQueue :connection="connection" domain="deliver">
 		<template #title>Out</template>
 	</XQueue>
-	<MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ $ts.clearQueue }}</MkButton>
+	<MkButton danger @click="clear()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.clearQueue }}</MkButton>
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
+<script lang="ts" setup>
+import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import XQueue from './queue.chart.vue';
 import * as os from '@/os';
 import { stream } from '@/stream';
 import * as symbols from '@/symbols';
 import * as config from '@/config';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		XQueue,
-	},
+const connection = markRaw(stream.useChannel('queueStats'));
 
-	emits: ['info'],
+function clear() {
+	os.confirm({
+		type: 'warning',
+		title: i18n.ts.clearQueueConfirmTitle,
+		text: i18n.ts.clearQueueConfirmText,
+	}).then(({ canceled }) => {
+		if (canceled) return;
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.jobQueue,
-				icon: 'fas fa-clipboard-list',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-up-right-from-square',
-					text: this.$ts.dashboard,
-					handler: () => {
-						window.open(config.url + '/queue', '_blank');
-					},
-				}],
-			},
-			connection: markRaw(stream.useChannel('queueStats')),
-		}
-	},
+		os.apiWithDialog('admin/queue/clear');
+	});
+}
 
-	mounted() {
-		this.$nextTick(() => {
-			this.connection.send('requestLog', {
-				id: Math.random().toString().substr(2, 8),
-				length: 200
-			});
+onMounted(() => {
+	nextTick(() => {
+		connection.send('requestLog', {
+			id: Math.random().toString().substr(2, 8),
+			length: 200
 		});
-	},
+	});
+});
 
-	beforeUnmount() {
-		this.connection.dispose();
-	},
+onBeforeUnmount(() => {
+	connection.dispose();
+});
 
-	methods: {
-		clear() {
-			os.confirm({
-				type: 'warning',
-				title: this.$ts.clearQueueConfirmTitle,
-				text: this.$ts.clearQueueConfirmText,
-			}).then(({ canceled }) => {
-				if (canceled) return;
-
-				os.apiWithDialog('admin/queue/clear', {});
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.jobQueue,
+		icon: 'fas fa-clipboard-list',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-up-right-from-square',
+			text: i18n.ts.dashboard,
+			handler: () => {
+				window.open(config.url + '/queue', '_blank');
+			},
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/relays.vue b/packages/client/src/pages/admin/relays.vue
index bb840db0a2..1a36bb4753 100644
--- a/packages/client/src/pages/admin/relays.vue
+++ b/packages/client/src/pages/admin/relays.vue
@@ -8,84 +8,71 @@
 			<i v-else class="fas fa-clock icon requesting"></i>
 			<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
 		</div>
-		<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ $ts.remove }}</MkButton>
+		<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.remove }}</MkButton>
 	</div>
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-	},
+let relays: any[] = $ref([]);
 
-	emits: ['info'],
+async function addRelay() {
+	const { canceled, result: inbox } = await os.inputText({
+		title: i18n.ts.addRelay,
+		type: 'url',
+		placeholder: i18n.ts.inboxUrl
+	});
+	if (canceled) return;
+	os.api('admin/relays/add', {
+		inbox
+	}).then((relay: any) => {
+		refresh();
+	}).catch((err: any) => {
+		os.alert({
+			type: 'error',
+			text: err.message || err
+		});
+	});
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.relays,
-				icon: 'fas fa-globe',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-plus',
-					text: this.$ts.addRelay,
-					handler: this.addRelay,
-				}],
-			},
-			relays: [],
-			inbox: '',
-		}
-	},
+function remove(inbox: string) {
+	os.api('admin/relays/remove', {
+		inbox
+	}).then(() => {
+		refresh();
+	}).catch((err: any) => {
+		os.alert({
+			type: 'error',
+			text: err.message || err
+		});
+	});
+}
 
-	created() {
-		this.refresh();
-	},
+function refresh() {
+	os.api('admin/relays/list').then((relayList: any) => {
+		relays = relayList;
+	});
+}
 
-	methods: {
-		async addRelay() {
-			const { canceled, result: inbox } = await os.inputText({
-				title: this.$ts.addRelay,
-				type: 'url',
-				placeholder: this.$ts.inboxUrl
-			});
-			if (canceled) return;
-			os.api('admin/relays/add', {
-				inbox
-			}).then((relay: any) => {
-				this.refresh();
-			}).catch((e: any) => {
-				os.alert({
-					type: 'error',
-					text: e.message || e
-				});
-			});
-		},
+refresh();
 
-		remove(inbox: string) {
-			os.api('admin/relays/remove', {
-				inbox
-			}).then(() => {
-				this.refresh();
-			}).catch((e: any) => {
-				os.alert({
-					type: 'error',
-					text: e.message || e
-				});
-			});
-		},
-
-		refresh() {
-			os.api('admin/relays/list').then((relays: any) => {
-				this.relays = relays;
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.relays,
+		icon: 'fas fa-globe',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-plus',
+			text: i18n.ts.addRelay,
+			handler: addRelay,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/security.vue b/packages/client/src/pages/admin/security.vue
index d1c979b3e0..6b8f70cca5 100644
--- a/packages/client/src/pages/admin/security.vue
+++ b/packages/client/src/pages/admin/security.vue
@@ -4,10 +4,10 @@
 		<div class="_formRoot">
 			<FormFolder class="_formBlock">
 				<template #icon><i class="fas fa-shield-alt"></i></template>
-				<template #label>{{ $ts.botProtection }}</template>
+				<template #label>{{ i18n.ts.botProtection }}</template>
 				<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
 				<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
-				<template v-else #suffix>{{ $ts.none }} ({{ $ts.notRecommended }})</template>
+				<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
 
 				<XBotProtection/>
 			</FormFolder>
@@ -21,7 +21,7 @@
 						<template #label>Summaly Proxy URL</template>
 					</FormInput>
 
-					<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
+					<FormButton primary class="_formBlock" @click="save"><i class="fas fa-save"></i> {{ i18n.ts.save }}</FormButton>
 				</div>
 			</FormFolder>
 		</div>
@@ -29,8 +29,8 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormFolder from '@/components/form/folder.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInfo from '@/components/ui/info.vue';
@@ -42,49 +42,32 @@ import XBotProtection from './bot-protection.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormFolder,
-		FormSwitch,
-		FormInfo,
-		FormSection,
-		FormSuspense,
-		FormButton,
-		FormInput,
-		XBotProtection,
-	},
+let summalyProxy: string = $ref('');
+let enableHcaptcha: boolean = $ref(false);
+let enableRecaptcha: boolean = $ref(false);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	summalyProxy = meta.summalyProxy;
+	enableHcaptcha = meta.enableHcaptcha;
+	enableRecaptcha = meta.enableRecaptcha;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.security,
-				icon: 'fas fa-lock',
-				bg: 'var(--bg)',
-			},
-			summalyProxy: '',
-			enableHcaptcha: false,
-			enableRecaptcha: false,
-		}
-	},
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		summalyProxy,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.summalyProxy = meta.summalyProxy;
-			this.enableHcaptcha = meta.enableHcaptcha;
-			this.enableRecaptcha = meta.enableRecaptcha;
-		},
-
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				summalyProxy: this.summalyProxy,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.security,
+		icon: 'fas fa-lock',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/admin/settings.vue b/packages/client/src/pages/admin/settings.vue
index f2970d0459..6dc30fe50b 100644
--- a/packages/client/src/pages/admin/settings.vue
+++ b/packages/client/src/pages/admin/settings.vue
@@ -3,104 +3,104 @@
 	<FormSuspense :p="init">
 		<div class="_formRoot">
 			<FormInput v-model="name" class="_formBlock">
-				<template #label>{{ $ts.instanceName }}</template>
+				<template #label>{{ i18n.ts.instanceName }}</template>
 			</FormInput>
 
 			<FormTextarea v-model="description" class="_formBlock">
-				<template #label>{{ $ts.instanceDescription }}</template>
+				<template #label>{{ i18n.ts.instanceDescription }}</template>
 			</FormTextarea>
 
 			<FormInput v-model="tosUrl" class="_formBlock">
 				<template #prefix><i class="fas fa-link"></i></template>
-				<template #label>{{ $ts.tosUrl }}</template>
+				<template #label>{{ i18n.ts.tosUrl }}</template>
 			</FormInput>
 
 			<FormSplit :min-width="300">
 				<FormInput v-model="maintainerName" class="_formBlock">
-					<template #label>{{ $ts.maintainerName }}</template>
+					<template #label>{{ i18n.ts.maintainerName }}</template>
 				</FormInput>
 
 				<FormInput v-model="maintainerEmail" type="email" class="_formBlock">
 					<template #prefix><i class="fas fa-envelope"></i></template>
-					<template #label>{{ $ts.maintainerEmail }}</template>
+					<template #label>{{ i18n.ts.maintainerEmail }}</template>
 				</FormInput>
 			</FormSplit>
 
 			<FormTextarea v-model="pinnedUsers" class="_formBlock">
-				<template #label>{{ $ts.pinnedUsers }}</template>
-				<template #caption>{{ $ts.pinnedUsersDescription }}</template>
+				<template #label>{{ i18n.ts.pinnedUsers }}</template>
+				<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
 			</FormTextarea>
 
 			<FormSection>
 				<FormSwitch v-model="enableRegistration" class="_formBlock">
-					<template #label>{{ $ts.enableRegistration }}</template>
+					<template #label>{{ i18n.ts.enableRegistration }}</template>
 				</FormSwitch>
 
 				<FormSwitch v-model="emailRequiredForSignup" class="_formBlock">
-					<template #label>{{ $ts.emailRequiredForSignup }}</template>
+					<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
 				</FormSwitch>
 			</FormSection>
 
 			<FormSection>
-				<FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ $ts.enableLocalTimeline }}</FormSwitch>
-				<FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ $ts.enableGlobalTimeline }}</FormSwitch>
-				<FormInfo class="_formBlock">{{ $ts.disablingTimelinesInfo }}</FormInfo>
+				<FormSwitch v-model="enableLocalTimeline" class="_formBlock">{{ i18n.ts.enableLocalTimeline }}</FormSwitch>
+				<FormSwitch v-model="enableGlobalTimeline" class="_formBlock">{{ i18n.ts.enableGlobalTimeline }}</FormSwitch>
+				<FormInfo class="_formBlock">{{ i18n.ts.disablingTimelinesInfo }}</FormInfo>
 			</FormSection>
 
 			<FormSection>
-				<template #label>{{ $ts.theme }}</template>
+				<template #label>{{ i18n.ts.theme }}</template>
 
 				<FormInput v-model="iconUrl" class="_formBlock">
 					<template #prefix><i class="fas fa-link"></i></template>
-					<template #label>{{ $ts.iconUrl }}</template>
+					<template #label>{{ i18n.ts.iconUrl }}</template>
 				</FormInput>
 
 				<FormInput v-model="bannerUrl" class="_formBlock">
 					<template #prefix><i class="fas fa-link"></i></template>
-					<template #label>{{ $ts.bannerUrl }}</template>
+					<template #label>{{ i18n.ts.bannerUrl }}</template>
 				</FormInput>
 
 				<FormInput v-model="backgroundImageUrl" class="_formBlock">
 					<template #prefix><i class="fas fa-link"></i></template>
-					<template #label>{{ $ts.backgroundImageUrl }}</template>
+					<template #label>{{ i18n.ts.backgroundImageUrl }}</template>
 				</FormInput>
 
 				<FormInput v-model="themeColor" class="_formBlock">
 					<template #prefix><i class="fas fa-palette"></i></template>
-					<template #label>{{ $ts.themeColor }}</template>
+					<template #label>{{ i18n.ts.themeColor }}</template>
 					<template #caption>#RRGGBB</template>
 				</FormInput>
 
 				<FormTextarea v-model="defaultLightTheme" class="_formBlock">
-					<template #label>{{ $ts.instanceDefaultLightTheme }}</template>
-					<template #caption>{{ $ts.instanceDefaultThemeDescription }}</template>
+					<template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
+					<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
 				</FormTextarea>
 
 				<FormTextarea v-model="defaultDarkTheme" class="_formBlock">
-					<template #label>{{ $ts.instanceDefaultDarkTheme }}</template>
-					<template #caption>{{ $ts.instanceDefaultThemeDescription }}</template>
+					<template #label>{{ i18n.ts.instanceDefaultDarkTheme }}</template>
+					<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
 				</FormTextarea>
 			</FormSection>
 
 			<FormSection>
-				<template #label>{{ $ts.files }}</template>
+				<template #label>{{ i18n.ts.files }}</template>
 
 				<FormSwitch v-model="cacheRemoteFiles" class="_formBlock">
-					<template #label>{{ $ts.cacheRemoteFiles }}</template>
-					<template #caption>{{ $ts.cacheRemoteFilesDescription }}</template>
+					<template #label>{{ i18n.ts.cacheRemoteFiles }}</template>
+					<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}</template>
 				</FormSwitch>
 
 				<FormSplit :min-width="280">
 					<FormInput v-model="localDriveCapacityMb" type="number" class="_formBlock">
-						<template #label>{{ $ts.driveCapacityPerLocalAccount }}</template>
+						<template #label>{{ i18n.ts.driveCapacityPerLocalAccount }}</template>
 						<template #suffix>MB</template>
-						<template #caption>{{ $ts.inMb }}</template>
+						<template #caption>{{ i18n.ts.inMb }}</template>
 					</FormInput>
 
 					<FormInput v-model="remoteDriveCapacityMb" type="number" :disabled="!cacheRemoteFiles" class="_formBlock">
-						<template #label>{{ $ts.driveCapacityPerRemoteAccount }}</template>
+						<template #label>{{ i18n.ts.driveCapacityPerRemoteAccount }}</template>
 						<template #suffix>MB</template>
-						<template #caption>{{ $ts.inMb }}</template>
+						<template #caption>{{ i18n.ts.inMb }}</template>
 					</FormInput>
 				</FormSplit>
 			</FormSection>
@@ -109,8 +109,8 @@
 				<template #label>ServiceWorker</template>
 
 				<FormSwitch v-model="enableServiceWorker" class="_formBlock">
-					<template #label>{{ $ts.enableServiceworker }}</template>
-					<template #caption>{{ $ts.serviceworkerInfo }}</template>
+					<template #label>{{ i18n.ts.enableServiceworker }}</template>
+					<template #caption>{{ i18n.ts.serviceworkerInfo }}</template>
 				</FormSwitch>
 
 				<template v-if="enableServiceWorker">
@@ -142,8 +142,8 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormInput from '@/components/form/input.vue';
 import FormTextarea from '@/components/form/textarea.vue';
@@ -154,119 +154,103 @@ import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { fetchInstance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormInput,
-		FormSuspense,
-		FormTextarea,
-		FormInfo,
-		FormSection,
-		FormSplit,
-	},
+let name: string | null = $ref(null);
+let description: string | null = $ref(null);
+let tosUrl: string | null = $ref(null);
+let maintainerName: string | null = $ref(null);
+let maintainerEmail: string | null = $ref(null);
+let iconUrl: string | null = $ref(null);
+let bannerUrl: string | null = $ref(null);
+let backgroundImageUrl: string | null = $ref(null);
+let themeColor: any = $ref(null);
+let defaultLightTheme: any = $ref(null);
+let defaultDarkTheme: any = $ref(null);
+let enableLocalTimeline: boolean = $ref(false);
+let enableGlobalTimeline: boolean = $ref(false);
+let pinnedUsers: string = $ref('');
+let cacheRemoteFiles: boolean = $ref(false);
+let localDriveCapacityMb: any = $ref(0);
+let remoteDriveCapacityMb: any = $ref(0);
+let enableRegistration: boolean = $ref(false);
+let emailRequiredForSignup: boolean = $ref(false);
+let enableServiceWorker: boolean = $ref(false);
+let swPublicKey: any = $ref(null);
+let swPrivateKey: any = $ref(null);
+let deeplAuthKey: string = $ref('');
+let deeplIsPro: boolean = $ref(false);
 
-	emits: ['info'],
+async function init() {
+	const meta = await os.api('admin/meta');
+	name = meta.name;
+	description = meta.description;
+	tosUrl = meta.tosUrl;
+	iconUrl = meta.iconUrl;
+	bannerUrl = meta.bannerUrl;
+	backgroundImageUrl = meta.backgroundImageUrl;
+	themeColor = meta.themeColor;
+	defaultLightTheme = meta.defaultLightTheme;
+	defaultDarkTheme = meta.defaultDarkTheme;
+	maintainerName = meta.maintainerName;
+	maintainerEmail = meta.maintainerEmail;
+	enableLocalTimeline = !meta.disableLocalTimeline;
+	enableGlobalTimeline = !meta.disableGlobalTimeline;
+	pinnedUsers = meta.pinnedUsers.join('\n');
+	cacheRemoteFiles = meta.cacheRemoteFiles;
+	localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
+	remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
+	enableRegistration = !meta.disableRegistration;
+	emailRequiredForSignup = meta.emailRequiredForSignup;
+	enableServiceWorker = meta.enableServiceWorker;
+	swPublicKey = meta.swPublickey;
+	swPrivateKey = meta.swPrivateKey;
+	deeplAuthKey = meta.deeplAuthKey;
+	deeplIsPro = meta.deeplIsPro;
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.general,
-				icon: 'fas fa-cog',
-				bg: 'var(--bg)',
-				actions: [{
-					asFullButton: true,
-					icon: 'fas fa-check',
-					text: this.$ts.save,
-					handler: this.save,
-				}],
-			},
-			name: null,
-			description: null,
-			tosUrl: null as string | null,
-			maintainerName: null,
-			maintainerEmail: null,
-			iconUrl: null,
-			bannerUrl: null,
-			backgroundImageUrl: null,
-			themeColor: null,
-			defaultLightTheme: null,
-			defaultDarkTheme: null,
-			enableLocalTimeline: false,
-			enableGlobalTimeline: false,
-			pinnedUsers: '',
-			cacheRemoteFiles: false,
-			localDriveCapacityMb: 0,
-			remoteDriveCapacityMb: 0,
-			enableRegistration: false,
-			emailRequiredForSignup: false,
-			enableServiceWorker: false,
-			swPublicKey: null,
-			swPrivateKey: null,
-			deeplAuthKey: '',
-			deeplIsPro: false,
-		}
-	},
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		name,
+		description,
+		tosUrl,
+		iconUrl,
+		bannerUrl,
+		backgroundImageUrl,
+		themeColor: themeColor === '' ? null : themeColor,
+		defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme,
+		defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme,
+		maintainerName,
+		maintainerEmail,
+		disableLocalTimeline: !enableLocalTimeline,
+		disableGlobalTimeline: !enableGlobalTimeline,
+		pinnedUsers: pinnedUsers.split('\n'),
+		cacheRemoteFiles,
+		localDriveCapacityMb: parseInt(localDriveCapacityMb, 10),
+		remoteDriveCapacityMb: parseInt(remoteDriveCapacityMb, 10),
+		disableRegistration: !enableRegistration,
+		emailRequiredForSignup,
+		enableServiceWorker,
+		swPublicKey,
+		swPrivateKey,
+		deeplAuthKey,
+		deeplIsPro,
+	}).then(() => {
+		fetchInstance();
+	});
+}
 
-	methods: {
-		async init() {
-			const meta = await os.api('admin/meta');
-			this.name = meta.name;
-			this.description = meta.description;
-			this.tosUrl = meta.tosUrl;
-			this.iconUrl = meta.iconUrl;
-			this.bannerUrl = meta.bannerUrl;
-			this.backgroundImageUrl = meta.backgroundImageUrl;
-			this.themeColor = meta.themeColor;
-			this.defaultLightTheme = meta.defaultLightTheme;
-			this.defaultDarkTheme = meta.defaultDarkTheme;
-			this.maintainerName = meta.maintainerName;
-			this.maintainerEmail = meta.maintainerEmail;
-			this.enableLocalTimeline = !meta.disableLocalTimeline;
-			this.enableGlobalTimeline = !meta.disableGlobalTimeline;
-			this.pinnedUsers = meta.pinnedUsers.join('\n');
-			this.cacheRemoteFiles = meta.cacheRemoteFiles;
-			this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
-			this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
-			this.enableRegistration = !meta.disableRegistration;
-			this.emailRequiredForSignup = meta.emailRequiredForSignup;
-			this.enableServiceWorker = meta.enableServiceWorker;
-			this.swPublicKey = meta.swPublickey;
-			this.swPrivateKey = meta.swPrivateKey;
-			this.deeplAuthKey = meta.deeplAuthKey;
-			this.deeplIsPro = meta.deeplIsPro;
-		},
-
-		save() {
-			os.apiWithDialog('admin/update-meta', {
-				name: this.name,
-				description: this.description,
-				tosUrl: this.tosUrl,
-				iconUrl: this.iconUrl,
-				bannerUrl: this.bannerUrl,
-				backgroundImageUrl: this.backgroundImageUrl,
-				themeColor: this.themeColor === '' ? null : this.themeColor,
-				defaultLightTheme: this.defaultLightTheme === '' ? null : this.defaultLightTheme,
-				defaultDarkTheme: this.defaultDarkTheme === '' ? null : this.defaultDarkTheme,
-				maintainerName: this.maintainerName,
-				maintainerEmail: this.maintainerEmail,
-				disableLocalTimeline: !this.enableLocalTimeline,
-				disableGlobalTimeline: !this.enableGlobalTimeline,
-				pinnedUsers: this.pinnedUsers.split('\n'),
-				cacheRemoteFiles: this.cacheRemoteFiles,
-				localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
-				remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
-				disableRegistration: !this.enableRegistration,
-				emailRequiredForSignup: this.emailRequiredForSignup,
-				enableServiceWorker: this.enableServiceWorker,
-				swPublicKey: this.swPublicKey,
-				swPrivateKey: this.swPrivateKey,
-				deeplAuthKey: this.deeplAuthKey,
-				deeplIsPro: this.deeplIsPro,
-			}).then(() => {
-				fetchInstance();
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.general,
+		icon: 'fas fa-cog',
+		bg: 'var(--bg)',
+		actions: [{
+			asFullButton: true,
+			icon: 'fas fa-check',
+			text: i18n.ts.save,
+			handler: save,
+		}],
 	}
 });
 </script>
diff --git a/packages/client/src/pages/api-console.vue b/packages/client/src/pages/api-console.vue
index 142a3bee2e..88acbcd3a3 100644
--- a/packages/client/src/pages/api-console.vue
+++ b/packages/client/src/pages/api-console.vue
@@ -25,72 +25,60 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
+<script lang="ts" setup>
+import { ref } from 'vue';
+import JSON5 from 'json5';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { Endpoints } from 'misskey-js';
 
-export default defineComponent({
-	components: {
-		MkButton, MkInput, MkTextarea, MkSwitch,
-	},
+const body = ref('{}');
+const endpoint = ref('');
+const endpoints = ref<any[]>([]);
+const sending = ref(false);
+const res = ref('');
+const withCredential = ref(true);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: 'API console',
-				icon: 'fas fa-terminal'
-			},
+os.api('endpoints').then(endpointResponse => {
+	endpoints.value = endpointResponse;
+});
 
-			endpoint: '',
-			body: '{}',
-			res: null,
-			sending: false,
-			endpoints: [],
-			withCredential: true,
+function send() {
+	sending.value = true;
+	const requestBody = JSON5.parse(body.value);
+	os.api(endpoint.value as keyof Endpoints, requestBody, requestBody.i || (withCredential.value ? undefined : null)).then(resp => {
+		sending.value = false;
+		res.value = JSON5.stringify(resp, null, 2);
+	}, err => {
+		sending.value = false;
+		res.value = JSON5.stringify(err, null, 2);
+	});
+}
 
-		};
-	},
-
-	created() {
-		os.api('endpoints').then(endpoints => {
-			this.endpoints = endpoints;
-		});
-	},
-
-	methods: {
-		send() {
-			this.sending = true;
-			const body = JSON5.parse(this.body);
-			os.api(this.endpoint, body, body.i || (this.withCredential ? undefined : null)).then(res => {
-				this.sending = false;
-				this.res = JSON5.stringify(res, null, 2);
-			}, err => {
-				this.sending = false;
-				this.res = JSON5.stringify(err, null, 2);
-			});
-		},
-
-		onEndpointChange() {
-			os.api('endpoint', { endpoint: this.endpoint }, this.withCredential ? undefined : null).then(endpoint => {
-				const body = {};
-				for (const p of endpoint.params) {
-					body[p.name] =
-						p.type === 'String' ? '' :
-						p.type === 'Number' ? 0 :
-						p.type === 'Boolean' ? false :
-						p.type === 'Array' ? [] :
-						p.type === 'Object' ? {} :
-						null;
-				}
-				this.body = JSON5.stringify(body, null, 2);
-			});
+function onEndpointChange() {
+	os.api('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => {
+		const endpointBody = {};
+		for (const p of resp.params) {
+			endpointBody[p.name] =
+				p.type === 'String' ? '' :
+				p.type === 'Number' ? 0 :
+				p.type === 'Boolean' ? false :
+				p.type === 'Array' ? [] :
+				p.type === 'Object' ? {} :
+				null;
 		}
-	}
+		body.value = JSON5.stringify(endpointBody, null, 2);
+	});
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: 'API console',
+		icon: 'fas fa-terminal'
+	},
 });
 </script>
diff --git a/packages/client/src/pages/auth.form.vue b/packages/client/src/pages/auth.form.vue
index bc719aebd3..5feff0149a 100644
--- a/packages/client/src/pages/auth.form.vue
+++ b/packages/client/src/pages/auth.form.vue
@@ -32,7 +32,7 @@ export default defineComponent({
 	computed: {
 		name(): string {
 			const el = document.createElement('div');
-			el.textContent = this.app.name
+			el.textContent = this.app.name;
 			return el.innerHTML;
 		},
 		app(): any {
diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue
index 3818c7481a..ea3a5dab76 100644
--- a/packages/client/src/pages/channel-editor.vue
+++ b/packages/client/src/pages/channel-editor.vue
@@ -111,8 +111,8 @@ export default defineComponent({
 			}
 		},
 
-		setBannerImage(e) {
-			selectFile(e.currentTarget ?? e.target, null).then(file => {
+		setBannerImage(evt) {
+			selectFile(evt.currentTarget ?? evt.target, null).then(file => {
 				this.bannerId = file.id;
 			});
 		},
diff --git a/packages/client/src/pages/emojis.category.vue b/packages/client/src/pages/emojis.category.vue
index 9a317418be..c47870f4d4 100644
--- a/packages/client/src/pages/emojis.category.vue
+++ b/packages/client/src/pages/emojis.category.vue
@@ -58,7 +58,7 @@ export default defineComponent({
 			tags: emojiTags,
 			selectedTags: new Set(),
 			searchEmojis: null,
-		}
+		};
 	},
 
 	watch: {
@@ -79,9 +79,9 @@ export default defineComponent({
 			}
 
 			if (this.selectedTags.size === 0) {
-				this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
+				this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q));
 			} else {
-				this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t)));
+				this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t)));
 			}
 		},
 
diff --git a/packages/client/src/pages/emojis.vue b/packages/client/src/pages/emojis.vue
index 886b5f7119..f44b29df04 100644
--- a/packages/client/src/pages/emojis.vue
+++ b/packages/client/src/pages/emojis.vue
@@ -25,10 +25,10 @@ function menu(ev) {
 					type: 'info',
 					text: i18n.ts.exportRequested,
 				});
-			}).catch((e) => {
+			}).catch((err) => {
 				os.alert({
 					type: 'error',
-					text: e.message,
+					text: err.message,
 				});
 			});
 		}
diff --git a/packages/client/src/pages/federation.vue b/packages/client/src/pages/federation.vue
index 5add2b5324..447918905b 100644
--- a/packages/client/src/pages/federation.vue
+++ b/packages/client/src/pages/federation.vue
@@ -127,7 +127,7 @@ function getStatus(instance) {
 	if (instance.isSuspended) return 'suspended';
 	if (instance.isNotResponding) return 'error';
 	return 'alive';
-};
+}
 
 defineExpose({
 	[symbols.PAGE_INFO]: {
diff --git a/packages/client/src/pages/follow.vue b/packages/client/src/pages/follow.vue
index d8a6824dca..e69e0481e0 100644
--- a/packages/client/src/pages/follow.vue
+++ b/packages/client/src/pages/follow.vue
@@ -20,7 +20,7 @@ export default defineComponent({
 				uri: acct
 			});
 			promise.then(res => {
-				if (res.type == 'User') {
+				if (res.type === 'User') {
 					this.follow(res.object);
 				} else if (res.type === 'Note') {
 					this.$router.push(`/notes/${res.object.id}`);
diff --git a/packages/client/src/pages/gallery/edit.vue b/packages/client/src/pages/gallery/edit.vue
index 25ee513186..bc87160c44 100644
--- a/packages/client/src/pages/gallery/edit.vue
+++ b/packages/client/src/pages/gallery/edit.vue
@@ -71,7 +71,7 @@ export default defineComponent({
 			description: null,
 			title: null,
 			isSensitive: false,
-		}
+		};
 	},
 
 	watch: {
@@ -91,8 +91,8 @@ export default defineComponent({
 	},
 
 	methods: {
-		selectFile(e) {
-			selectFiles(e.currentTarget ?? e.target, null).then(files => {
+		selectFile(evt) {
+			selectFiles(evt.currentTarget ?? evt.target, null).then(files => {
 				this.files = this.files.concat(files);
 			});
 		},
diff --git a/packages/client/src/pages/gallery/post.vue b/packages/client/src/pages/gallery/post.vue
index 1755c23286..1ca3443e56 100644
--- a/packages/client/src/pages/gallery/post.vue
+++ b/packages/client/src/pages/gallery/post.vue
@@ -119,8 +119,8 @@ export default defineComponent({
 				postId: this.postId
 			}).then(post => {
 				this.post = post;
-			}).catch(e => {
-				this.error = e;
+			}).catch(err => {
+				this.error = err;
 			});
 		},
 
diff --git a/packages/client/src/pages/messaging/index.vue b/packages/client/src/pages/messaging/index.vue
index 88a1e07afc..7c1d3e3cbe 100644
--- a/packages/client/src/pages/messaging/index.vue
+++ b/packages/client/src/pages/messaging/index.vue
@@ -90,14 +90,14 @@ export default defineComponent({
 		getAcct: Acct.toString,
 
 		isMe(message) {
-			return message.userId == this.$i.id;
+			return message.userId === this.$i.id;
 		},
 
 		onMessage(message) {
 			if (message.recipientId) {
 				this.messages = this.messages.filter(m => !(
-					(m.recipientId == message.recipientId && m.userId == message.userId) ||
-					(m.recipientId == message.userId && m.userId == message.recipientId)));
+					(m.recipientId === message.recipientId && m.userId === message.userId) ||
+					(m.recipientId === message.userId && m.userId === message.recipientId)));
 
 				this.messages.unshift(message);
 			} else if (message.groupId) {
@@ -108,7 +108,7 @@ export default defineComponent({
 
 		onRead(ids) {
 			for (const id of ids) {
-				const found = this.messages.find(m => m.id == id);
+				const found = this.messages.find(m => m.id === id);
 				if (found) {
 					if (found.recipientId) {
 						found.isRead = true;
@@ -123,11 +123,11 @@ export default defineComponent({
 			os.popupMenu([{
 				text: this.$ts.messagingWithUser,
 				icon: 'fas fa-user',
-				action: () => { this.startUser() }
+				action: () => { this.startUser(); }
 			}, {
 				text: this.$ts.messagingWithGroup,
 				icon: 'fas fa-users',
-				action: () => { this.startGroup() }
+				action: () => { this.startGroup(); }
 			}], ev.currentTarget ?? ev.target);
 		},
 
diff --git a/packages/client/src/pages/messaging/messaging-room.form.vue b/packages/client/src/pages/messaging/messaging-room.form.vue
index 3863c8f82b..8e779c4f39 100644
--- a/packages/client/src/pages/messaging/messaging-room.form.vue
+++ b/packages/client/src/pages/messaging/messaging-room.form.vue
@@ -31,6 +31,7 @@ import * as os from '@/os';
 import { stream } from '@/stream';
 import { Autocomplete } from '@/scripts/autocomplete';
 import { throttle } from 'throttle-debounce';
+import { uploadFile } from '@/scripts/upload';
 
 export default defineComponent({
 	props: {
@@ -58,7 +59,7 @@ export default defineComponent({
 			return this.user ? 'user:' + this.user.id : 'group:' + this.group.id;
 		},
 		canSend(): boolean {
-			return (this.text != null && this.text != '') || this.file != null;
+			return (this.text != null && this.text !== '') || this.file != null;
 		},
 		room(): any {
 			return this.$parent;
@@ -87,12 +88,11 @@ export default defineComponent({
 		}
 	},
 	methods: {
-		async onPaste(e: ClipboardEvent) {
-			const data = e.clipboardData;
-			const items = data.items;
+		async onPaste(evt: ClipboardEvent) {
+			const items = evt.clipboardData.items;
 
-			if (items.length == 1) {
-				if (items[0].kind == 'file') {
+			if (items.length === 1) {
+				if (items[0].kind === 'file') {
 					const file = items[0].getAsFile();
 					const lio = file.name.lastIndexOf('.');
 					const ext = lio >= 0 ? file.name.slice(lio) : '';
@@ -100,7 +100,7 @@ export default defineComponent({
 					if (formatted) this.upload(file, formatted);
 				}
 			} else {
-				if (items[0].kind == 'file') {
+				if (items[0].kind === 'file') {
 					os.alert({
 						type: 'error',
 						text: this.$ts.onlyOneFileCanBeAttached
@@ -109,23 +109,23 @@ export default defineComponent({
 			}
 		},
 
-		onDragover(e) {
-			const isFile = e.dataTransfer.items[0].kind == 'file';
-			const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
+		onDragover(evt) {
+			const isFile = evt.dataTransfer.items[0].kind === 'file';
+			const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
 			if (isFile || isDriveFile) {
-				e.preventDefault();
-				e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+				evt.preventDefault();
+				evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
 			}
 		},
 
-		onDrop(e): void {
+		onDrop(evt): void {
 			// ใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
-			if (e.dataTransfer.files.length == 1) {
-				e.preventDefault();
-				this.upload(e.dataTransfer.files[0]);
+			if (evt.dataTransfer.files.length === 1) {
+				evt.preventDefault();
+				this.upload(evt.dataTransfer.files[0]);
 				return;
-			} else if (e.dataTransfer.files.length > 1) {
-				e.preventDefault();
+			} else if (evt.dataTransfer.files.length > 1) {
+				evt.preventDefault();
 				os.alert({
 					type: 'error',
 					text: this.$ts.onlyOneFileCanBeAttached
@@ -134,17 +134,17 @@ export default defineComponent({
 			}
 
 			//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
-			const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-			if (driveFile != null && driveFile != '') {
+			const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+			if (driveFile != null && driveFile !== '') {
 				this.file = JSON.parse(driveFile);
-				e.preventDefault();
+				evt.preventDefault();
 			}
 			//#endregion
 		},
 
-		onKeydown(e) {
+		onKeydown(evt) {
 			this.typing();
-			if ((e.which == 10 || e.which == 13) && (e.ctrlKey || e.metaKey) && this.canSend) {
+			if ((evt.which === 10 || evt.which === 13) && (evt.ctrlKey || evt.metaKey) && this.canSend) {
 				this.send();
 			}
 		},
@@ -153,8 +153,8 @@ export default defineComponent({
 			this.typing();
 		},
 
-		chooseFile(e) {
-			selectFile(e.currentTarget ?? e.target, this.$ts.selectFile).then(file => {
+		chooseFile(evt) {
+			selectFile(evt.currentTarget ?? evt.target, this.$ts.selectFile).then(file => {
 				this.file = file;
 			});
 		},
@@ -164,7 +164,7 @@ export default defineComponent({
 		},
 
 		upload(file: File, name?: string) {
-			os.upload(file, this.$store.state.uploadFolder, name).then(res => {
+			uploadFile(file, this.$store.state.uploadFolder, name).then(res => {
 				this.file = res;
 			});
 		},
@@ -192,25 +192,25 @@ export default defineComponent({
 		},
 
 		saveDraft() {
-			const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
+			const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
 
-			data[this.draftKey] = {
+			drafts[this.draftKey] = {
 				updatedAt: new Date(),
 				data: {
 					text: this.text,
 					file: this.file
 				}
-			}
+			};
 
-			localStorage.setItem('message_drafts', JSON.stringify(data));
+			localStorage.setItem('message_drafts', JSON.stringify(drafts));
 		},
 
 		deleteDraft() {
-			const data = JSON.parse(localStorage.getItem('message_drafts') || '{}');
+			const drafts = JSON.parse(localStorage.getItem('message_drafts') || '{}');
 
-			delete data[this.draftKey];
+			delete drafts[this.draftKey];
 
-			localStorage.setItem('message_drafts', JSON.stringify(data));
+			localStorage.setItem('message_drafts', JSON.stringify(drafts));
 		},
 
 		async insertEmoji(ev) {
diff --git a/packages/client/src/pages/messaging/messaging-room.vue b/packages/client/src/pages/messaging/messaging-room.vue
index 2ecc68eb54..fd1962218a 100644
--- a/packages/client/src/pages/messaging/messaging-room.vue
+++ b/packages/client/src/pages/messaging/messaging-room.vue
@@ -166,23 +166,23 @@ const Component = defineComponent({
 			});
 		},
 
-		onDragover(e) {
-			const isFile = e.dataTransfer.items[0].kind == 'file';
-			const isDriveFile = e.dataTransfer.types[0] == _DATA_TRANSFER_DRIVE_FILE_;
+		onDragover(evt) {
+			const isFile = evt.dataTransfer.items[0].kind === 'file';
+			const isDriveFile = evt.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
 
 			if (isFile || isDriveFile) {
-				e.dataTransfer.dropEffect = e.dataTransfer.effectAllowed == 'all' ? 'copy' : 'move';
+				evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'all' ? 'copy' : 'move';
 			} else {
-				e.dataTransfer.dropEffect = 'none';
+				evt.dataTransfer.dropEffect = 'none';
 			}
 		},
 
-		onDrop(e): void {
+		onDrop(evt): void {
 			// ใƒ•ใ‚กใ‚คใƒซใ ใฃใŸใ‚‰
-			if (e.dataTransfer.files.length == 1) {
-				this.form.upload(e.dataTransfer.files[0]);
+			if (evt.dataTransfer.files.length === 1) {
+				this.form.upload(evt.dataTransfer.files[0]);
 				return;
-			} else if (e.dataTransfer.files.length > 1) {
+			} else if (evt.dataTransfer.files.length > 1) {
 				os.alert({
 					type: 'error',
 					text: this.$ts.onlyOneFileCanBeAttached
@@ -191,8 +191,8 @@ const Component = defineComponent({
 			}
 
 			//#region ใƒ‰ใƒฉใ‚คใƒ–ใฎใƒ•ใ‚กใ‚คใƒซ
-			const driveFile = e.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
-			if (driveFile != null && driveFile != '') {
+			const driveFile = evt.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+			if (driveFile != null && driveFile !== '') {
 				const file = JSON.parse(driveFile);
 				this.form.file = file;
 			}
@@ -209,7 +209,7 @@ const Component = defineComponent({
 					limit: max + 1,
 					untilId: this.existMoreMessages ? this.messages[0].id : undefined
 				}).then(messages => {
-					if (messages.length == max + 1) {
+					if (messages.length === max + 1) {
 						this.existMoreMessages = true;
 						messages.pop();
 					} else {
@@ -235,7 +235,7 @@ const Component = defineComponent({
 			const _isBottom = isBottom(this.$el, 64);
 
 			this.messages.push(message);
-			if (message.userId != this.$i.id && !document.hidden) {
+			if (message.userId !== this.$i.id && !document.hidden) {
 				this.connection.send('read', {
 					id: message.id
 				});
@@ -246,7 +246,7 @@ const Component = defineComponent({
 				this.$nextTick(() => {
 					this.scrollToBottom();
 				});
-			} else if (message.userId != this.$i.id) {
+			} else if (message.userId !== this.$i.id) {
 				// Notify
 				this.notifyNewMessage();
 			}
@@ -256,7 +256,7 @@ const Component = defineComponent({
 			if (this.user) {
 				if (!Array.isArray(x)) x = [x];
 				for (const id of x) {
-					if (this.messages.some(x => x.id == id)) {
+					if (this.messages.some(x => x.id === id)) {
 						const exist = this.messages.map(x => x.id).indexOf(id);
 						this.messages[exist] = {
 							...this.messages[exist],
@@ -266,7 +266,7 @@ const Component = defineComponent({
 				}
 			} else if (this.group) {
 				for (const id of x.ids) {
-					if (this.messages.some(x => x.id == id)) {
+					if (this.messages.some(x => x.id === id)) {
 						const exist = this.messages.map(x => x.id).indexOf(id);
 						this.messages[exist] = {
 							...this.messages[exist],
diff --git a/packages/client/src/pages/mfm-cheat-sheet.vue b/packages/client/src/pages/mfm-cheat-sheet.vue
index 83ae5741c3..2c10494ede 100644
--- a/packages/client/src/pages/mfm-cheat-sheet.vue
+++ b/packages/client/src/pages/mfm-cheat-sheet.vue
@@ -325,23 +325,23 @@ export default defineComponent({
 			preview_inlineMath: '\\(x= \\frac{-b\' \\pm \\sqrt{(b\')^2-ac}}{a}\\)',
 			preview_quote: `> ${this.$ts._mfm.dummy}`,
 			preview_search: `${this.$ts._mfm.dummy} ๆคœ็ดข`,
-			preview_jelly: `$[jelly ๐Ÿฎ]`,
-			preview_tada: `$[tada ๐Ÿฎ]`,
-			preview_jump: `$[jump ๐Ÿฎ]`,
-			preview_bounce: `$[bounce ๐Ÿฎ]`,
-			preview_shake: `$[shake ๐Ÿฎ]`,
-			preview_twitch: `$[twitch ๐Ÿฎ]`,
-			preview_spin: `$[spin ๐Ÿฎ] $[spin.left ๐Ÿฎ] $[spin.alternate ๐Ÿฎ]\n$[spin.x ๐Ÿฎ] $[spin.x,left ๐Ÿฎ] $[spin.x,alternate ๐Ÿฎ]\n$[spin.y ๐Ÿฎ] $[spin.y,left ๐Ÿฎ] $[spin.y,alternate ๐Ÿฎ]`,
+			preview_jelly: `$[jelly ๐Ÿฎ] $[jelly.speed=5s ๐Ÿฎ]`,
+			preview_tada: `$[tada ๐Ÿฎ] $[tada.speed=5s ๐Ÿฎ]`,
+			preview_jump: `$[jump ๐Ÿฎ] $[jump.speed=5s ๐Ÿฎ]`,
+			preview_bounce: `$[bounce ๐Ÿฎ] $[bounce.speed=5s ๐Ÿฎ]`,
+			preview_shake: `$[shake ๐Ÿฎ] $[shake.speed=5s ๐Ÿฎ]`,
+			preview_twitch: `$[twitch ๐Ÿฎ] $[twitch.speed=5s ๐Ÿฎ]`,
+			preview_spin: `$[spin ๐Ÿฎ] $[spin.left ๐Ÿฎ] $[spin.alternate ๐Ÿฎ]\n$[spin.x ๐Ÿฎ] $[spin.x,left ๐Ÿฎ] $[spin.x,alternate ๐Ÿฎ]\n$[spin.y ๐Ÿฎ] $[spin.y,left ๐Ÿฎ] $[spin.y,alternate ๐Ÿฎ]\n\n$[spin.speed=5s ๐Ÿฎ]`,
 			preview_flip: `$[flip ${this.$ts._mfm.dummy}]\n$[flip.v ${this.$ts._mfm.dummy}]\n$[flip.h,v ${this.$ts._mfm.dummy}]`,
 			preview_font: `$[font.serif ${this.$ts._mfm.dummy}]\n$[font.monospace ${this.$ts._mfm.dummy}]\n$[font.cursive ${this.$ts._mfm.dummy}]\n$[font.fantasy ${this.$ts._mfm.dummy}]`,
 			preview_x2: `$[x2 ๐Ÿฎ]`,
 			preview_x3: `$[x3 ๐Ÿฎ]`,
 			preview_x4: `$[x4 ๐Ÿฎ]`,
 			preview_blur: `$[blur ${this.$ts._mfm.dummy}]`,
-			preview_rainbow: `$[rainbow ๐Ÿฎ]`,
+			preview_rainbow: `$[rainbow ๐Ÿฎ] $[rainbow.speed=5s ๐Ÿฎ]`,
 			preview_sparkle: `$[sparkle ๐Ÿฎ]`,
 			preview_rotate: `$[rotate ๐Ÿฎ]`,
-		}
+		};
 	},
 });
 </script>
diff --git a/packages/client/src/pages/miauth.vue b/packages/client/src/pages/miauth.vue
index 6e85b784ff..4032d7723e 100644
--- a/packages/client/src/pages/miauth.vue
+++ b/packages/client/src/pages/miauth.vue
@@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue';
 import MkButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import { login } from '@/account';
+import { appendQuery, query } from '@/scripts/url';
 
 export default defineComponent({
 	components: {
@@ -82,7 +83,9 @@ export default defineComponent({
 
 			this.state = 'accepted';
 			if (this.callback) {
-				location.href = `${this.callback}?session=${this.session}`;
+				location.href = appendQuery(this.callback, query({
+					session: this.session
+				}));
 			}
 		},
 		deny() {
diff --git a/packages/client/src/pages/my-antennas/edit.vue b/packages/client/src/pages/my-antennas/edit.vue
index 04928c81a3..38e56ce35d 100644
--- a/packages/client/src/pages/my-antennas/edit.vue
+++ b/packages/client/src/pages/my-antennas/edit.vue
@@ -4,49 +4,34 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@/components/ui/button.vue';
+<script lang="ts" setup>
+import { watch } from 'vue';
 import XAntenna from './editor.vue';
 import * as symbols from '@/symbols';
 import * as os from '@/os';
+import { MisskeyNavigator } from '@/scripts/navigate';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		XAntenna,
-	},
+const nav = new MisskeyNavigator();
 
-	props: {
-		antennaId: {
-			type: String,
-			required: true,
-		}
-	},
+let antenna: any = $ref(null);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.manageAntennas,
-				icon: 'fas fa-satellite',
-			},
-			antenna: null,
-		};
-	},
+const props = defineProps<{
+	antennaId: string
+}>();
 
-	watch: {
-		antennaId: {
-			async handler() {
-				this.antenna = await os.api('antennas/show', { antennaId: this.antennaId });
-			},
-			immediate: true,
-		}
-	},
+function onAntennaUpdated() {
+	nav.push('/my/antennas');
+}
 
-	methods: {
-		onAntennaUpdated() {
-			this.$router.push('/my/antennas');
-		},
+os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => {
+	antenna = antennaResponse;
+});
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.manageAntennas,
+		icon: 'fas fa-satellite',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/my-antennas/editor.vue b/packages/client/src/pages/my-antennas/editor.vue
index 8c1d6148fe..6f3c4afbfe 100644
--- a/packages/client/src/pages/my-antennas/editor.vue
+++ b/packages/client/src/pages/my-antennas/editor.vue
@@ -44,135 +44,100 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { watch } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import MkInput from '@/components/form/input.vue';
 import MkTextarea from '@/components/form/textarea.vue';
 import MkSelect from '@/components/form/select.vue';
 import MkSwitch from '@/components/form/switch.vue';
-import * as Acct from 'misskey-js/built/acct';
 import * as os from '@/os';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton, MkInput, MkTextarea, MkSelect, MkSwitch
-	},
+const props = defineProps<{
+	antenna: any
+}>();
 
-	props: {
-		antenna: {
-			type: Object,
-			required: true
-		}
-	},
+const emit = defineEmits<{
+	(ev: 'created'): void,
+	(ev: 'updated'): void,
+	(ev: 'deleted'): void,
+}>();
 
-	data() {
-		return {
-			name: '',
-			src: '',
-			userListId: null,
-			userGroupId: null,
-			users: '',
-			keywords: '',
-			excludeKeywords: '',
-			caseSensitive: false,
-			withReplies: false,
-			withFile: false,
-			notify: false,
-			userLists: null,
-			userGroups: null,
-		};
-	},
+let name: string = $ref(props.antenna.name);
+let src: string = $ref(props.antenna.src);
+let userListId: any = $ref(props.antenna.userListId);
+let userGroupId: any = $ref(props.antenna.userGroupId);
+let users: string = $ref(props.antenna.users.join('\n'));
+let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n'));
+let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
+let caseSensitive: boolean = $ref(props.antenna.caseSensitive);
+let withReplies: boolean = $ref(props.antenna.withReplies);
+let withFile: boolean = $ref(props.antenna.withFile);
+let notify: boolean = $ref(props.antenna.notify);
+let userLists: any = $ref(null);
+let userGroups: any = $ref(null);
 
-	watch: {
-		async src() {
-			if (this.src === 'list' && this.userLists === null) {
-				this.userLists = await os.api('users/lists/list');
-			}
+watch(() => src, async () => {
+	if (src === 'list' && userLists === null) {
+		userLists = await os.api('users/lists/list');
+	}
 
-			if (this.src === 'group' && this.userGroups === null) {
-				const groups1 = await os.api('users/groups/owned');
-				const groups2 = await os.api('users/groups/joined');
+	if (src === 'group' && userGroups === null) {
+		const groups1 = await os.api('users/groups/owned');
+		const groups2 = await os.api('users/groups/joined');
 
-				this.userGroups = [...groups1, ...groups2];
-			}
-		}
-	},
-
-	created() {
-		this.name = this.antenna.name;
-		this.src = this.antenna.src;
-		this.userListId = this.antenna.userListId;
-		this.userGroupId = this.antenna.userGroupId;
-		this.users = this.antenna.users.join('\n');
-		this.keywords = this.antenna.keywords.map(x => x.join(' ')).join('\n');
-		this.excludeKeywords = this.antenna.excludeKeywords.map(x => x.join(' ')).join('\n');
-		this.caseSensitive = this.antenna.caseSensitive;
-		this.withReplies = this.antenna.withReplies;
-		this.withFile = this.antenna.withFile;
-		this.notify = this.antenna.notify;
-	},
-
-	methods: {
-		async saveAntenna() {
-			if (this.antenna.id == null) {
-				await os.apiWithDialog('antennas/create', {
-					name: this.name,
-					src: this.src,
-					userListId: this.userListId,
-					userGroupId: this.userGroupId,
-					withReplies: this.withReplies,
-					withFile: this.withFile,
-					notify: this.notify,
-					caseSensitive: this.caseSensitive,
-					users: this.users.trim().split('\n').map(x => x.trim()),
-					keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
-					excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
-				});
-				this.$emit('created');
-			} else {
-				await os.apiWithDialog('antennas/update', {
-					antennaId: this.antenna.id,
-					name: this.name,
-					src: this.src,
-					userListId: this.userListId,
-					userGroupId: this.userGroupId,
-					withReplies: this.withReplies,
-					withFile: this.withFile,
-					notify: this.notify,
-					caseSensitive: this.caseSensitive,
-					users: this.users.trim().split('\n').map(x => x.trim()),
-					keywords: this.keywords.trim().split('\n').map(x => x.trim().split(' ')),
-					excludeKeywords: this.excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
-				});
-				this.$emit('updated');
-			}
-		},
-
-		async deleteAntenna() {
-			const { canceled } = await os.confirm({
-				type: 'warning',
-				text: this.$t('removeAreYouSure', { x: this.antenna.name }),
-			});
-			if (canceled) return;
-
-			await os.api('antennas/delete', {
-				antennaId: this.antenna.id,
-			});
-
-			os.success();
-			this.$emit('deleted');
-		},
-
-		addUser() {
-			os.selectUser().then(user => {
-				this.users = this.users.trim();
-				this.users += '\n@' + Acct.toString(user);
-				this.users = this.users.trim();
-			});
-		}
+		userGroups = [...groups1, ...groups2];
 	}
 });
+
+async function saveAntenna() {
+	const antennaData = {
+		name,
+		src,
+		userListId,
+		userGroupId,
+		withReplies,
+		withFile,
+		notify,
+		caseSensitive,
+		users: users.trim().split('\n').map(x => x.trim()),
+		keywords: keywords.trim().split('\n').map(x => x.trim().split(' ')),
+		excludeKeywords: excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
+	};
+
+	if (props.antenna.id == null) {
+		await os.apiWithDialog('antennas/create', antennaData);
+		emit('created');
+	} else {
+		antennaData['antennaId'] = props.antenna.id;
+		await os.apiWithDialog('antennas/update', antennaData);
+		emit('updated');
+	}
+}
+
+async function deleteAntenna() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('removeAreYouSure', { x: props.antenna.name }),
+	});
+	if (canceled) return;
+
+	await os.api('antennas/delete', {
+		antennaId: props.antenna.id,
+	});
+
+	os.success();
+	emit('deleted');
+}
+
+function addUser() {
+	os.selectUser().then(user => {
+		users = users.trim();
+		users += '\n@' + Acct.toString(user as any);
+		users = users.trim();
+	});
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/pages/my-antennas/index.vue b/packages/client/src/pages/my-antennas/index.vue
index 7138d269a9..a568f64c52 100644
--- a/packages/client/src/pages/my-antennas/index.vue
+++ b/packages/client/src/pages/my-antennas/index.vue
@@ -1,7 +1,7 @@
 <template>
 <MkSpacer :content-max="700">
 	<div class="ieepwinx">
-		<MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ $ts.add }}</MkButton>
+		<MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="fas fa-plus"></i> {{ i18n.ts.add }}</MkButton>
 
 		<div class="">
 			<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
@@ -14,35 +14,24 @@
 </MkSpacer>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 import MkPagination from '@/components/ui/pagination.vue';
 import MkButton from '@/components/ui/button.vue';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkPagination,
-		MkButton,
-	},
+const pagination = {
+	endpoint: 'antennas/list' as const,
+	limit: 10,
+};
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.manageAntennas,
-				icon: 'fas fa-satellite',
-				bg: 'var(--bg)',
-				action: {
-					icon: 'fas fa-plus',
-					handler: this.create
-				}
-			},
-			pagination: {
-				endpoint: 'antennas/list' as const,
-				limit: 10,
-			},
-		};
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.manageAntennas,
+		icon: 'fas fa-satellite',
+		bg: 'var(--bg)'
+	}
 });
 </script>
 
diff --git a/packages/client/src/pages/note.vue b/packages/client/src/pages/note.vue
index 29261ec484..f0a18ecc36 100644
--- a/packages/client/src/pages/note.vue
+++ b/packages/client/src/pages/note.vue
@@ -108,6 +108,10 @@ export default defineComponent({
 	},
 	methods: {
 		fetch() {
+			this.hasPrev = false;
+			this.hasNext = false;
+			this.showPrev = false;
+			this.showNext = false;
 			this.note = null;
 			os.api('notes/show', {
 				noteId: this.noteId
@@ -132,8 +136,8 @@ export default defineComponent({
 					this.hasPrev = prev.length !== 0;
 					this.hasNext = next.length !== 0;
 				});
-			}).catch(e => {
-				this.error = e;
+			}).catch(err => {
+				this.error = err;
 			});
 		}
 	}
diff --git a/packages/client/src/pages/page-editor/page-editor.vue b/packages/client/src/pages/page-editor/page-editor.vue
index f302ac4f90..9566592618 100644
--- a/packages/client/src/pages/page-editor/page-editor.vue
+++ b/packages/client/src/pages/page-editor/page-editor.vue
@@ -114,7 +114,7 @@ export default defineComponent({
 			readonly: this.readonly,
 			getScriptBlockList: this.getScriptBlockList,
 			getPageBlockList: this.getPageBlockList
-		}
+		};
 	},
 
 	props: {
diff --git a/packages/client/src/pages/page.vue b/packages/client/src/pages/page.vue
index b2c039a269..5bca971438 100644
--- a/packages/client/src/pages/page.vue
+++ b/packages/client/src/pages/page.vue
@@ -139,8 +139,8 @@ export default defineComponent({
 				username: this.username,
 			}).then(page => {
 				this.page = page;
-			}).catch(e => {
-				this.error = e;
+			}).catch(err => {
+				this.error = err;
 			});
 		},
 
diff --git a/packages/client/src/pages/reset-password.vue b/packages/client/src/pages/reset-password.vue
index 7d008ae75c..b3e2ca8d6f 100644
--- a/packages/client/src/pages/reset-password.vue
+++ b/packages/client/src/pages/reset-password.vue
@@ -12,7 +12,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { defineAsyncComponent, onMounted } from 'vue';
 import FormInput from '@/components/form/input.vue';
 import FormButton from '@/components/ui/button.vue';
 import * as os from '@/os';
@@ -36,7 +36,7 @@ async function save() {
 
 onMounted(() => {
 	if (props.token == null) {
-		os.popup(import('@/components/forgot-password.vue'), {}, {}, 'closed');
+		os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {}, 'closed');
 		router.push('/');
 	}
 });
diff --git a/packages/client/src/pages/scratchpad.vue b/packages/client/src/pages/scratchpad.vue
index f871dc48e8..34a41b81a5 100644
--- a/packages/client/src/pages/scratchpad.vue
+++ b/packages/client/src/pages/scratchpad.vue
@@ -6,20 +6,20 @@
 	</div>
 
 	<MkContainer :foldable="true" class="_gap">
-		<template #header>{{ $ts.output }}</template>
+		<template #header>{{ i18n.ts.output }}</template>
 		<div class="bepmlvbi">
 			<div v-for="log in logs" :key="log.id" class="log" :class="{ print: log.print }">{{ log.text }}</div>
 		</div>
 	</MkContainer>
 
 	<div class="_gap">
-		{{ $ts.scratchpadDescription }}
+		{{ i18n.ts.scratchpadDescription }}
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref, watch } from 'vue';
 import 'prismjs';
 import { highlight, languages } from 'prismjs/components/prism-core';
 import 'prismjs/components/prism-clike';
@@ -27,103 +27,90 @@ import 'prismjs/components/prism-javascript';
 import 'prismjs/themes/prism-okaidia.css';
 import { PrismEditor } from 'vue-prism-editor';
 import 'vue-prism-editor/dist/prismeditor.min.css';
-import { AiScript, parse, utils, values } from '@syuilo/aiscript';
+import { AiScript, parse, utils } from '@syuilo/aiscript';
 import MkContainer from '@/components/ui/container.vue';
 import MkButton from '@/components/ui/button.vue';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkContainer,
-		MkButton,
-		PrismEditor,
-	},
+const code = ref('');
+const logs = ref<any[]>([]);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.scratchpad,
-				icon: 'fas fa-terminal',
-			},
-			code: '',
-			logs: [],
-		}
-	},
+const saved = localStorage.getItem('scratchpad');
+if (saved) {
+	code.value = saved;
+}
 
-	watch: {
-		code() {
-			localStorage.setItem('scratchpad', this.code);
-		}
-	},
+watch(code, () => {
+	localStorage.setItem('scratchpad', code.value);
+});
 
-	created() {
-		const saved = localStorage.getItem('scratchpad');
-		if (saved) {
-			this.code = saved;
-		}
-	},
-
-	methods: {
-		async run() {
-			this.logs = [];
-			const aiscript = new AiScript(createAiScriptEnv({
-				storageKey: 'scratchpad',
-				token: this.$i?.token,
-			}), {
-				in: (q) => {
-					return new Promise(ok => {
-						os.inputText({
-							title: q,
-						}).then(({ canceled, result: a }) => {
-							ok(a);
-						});
-					});
-				},
-				out: (value) => {
-					this.logs.push({
-						id: Math.random(),
-						text: value.type === 'str' ? value.value : utils.valToString(value),
-						print: true
-					});
-				},
-				log: (type, params) => {
-					switch (type) {
-						case 'end': this.logs.push({
-							id: Math.random(),
-							text: utils.valToString(params.val, true),
-							print: false
-						}); break;
-						default: break;
-					}
-				}
+async function run() {
+	logs.value = [];
+	const aiscript = new AiScript(createAiScriptEnv({
+		storageKey: 'scratchpad',
+		token: $i?.token,
+	}), {
+		in: (q) => {
+			return new Promise(ok => {
+				os.inputText({
+					title: q,
+				}).then(({ canceled, result: a }) => {
+					ok(a);
+				});
 			});
-
-			let ast;
-			try {
-				ast = parse(this.code);
-			} catch (e) {
-				os.alert({
-					type: 'error',
-					text: 'Syntax error :('
-				});
-				return;
-			}
-			try {
-				await aiscript.exec(ast);
-			} catch (e) {
-				os.alert({
-					type: 'error',
-					text: e
-				});
-			}
 		},
-
-		highlighter(code) {
-			return highlight(code, languages.js, 'javascript');
+		out: (value) => {
+			logs.value.push({
+				id: Math.random(),
+				text: value.type === 'str' ? value.value : utils.valToString(value),
+				print: true
+			});
 		},
+		log: (type, params) => {
+			switch (type) {
+				case 'end': logs.value.push({
+					id: Math.random(),
+					text: utils.valToString(params.val, true),
+					print: false
+				}); break;
+				default: break;
+			}
+		}
+	});
+
+	let ast;
+	try {
+		ast = parse(code.value);
+	} catch (error) {
+		os.alert({
+			type: 'error',
+			text: 'Syntax error :('
+		});
+		return;
 	}
+	try {
+		await aiscript.exec(ast);
+	} catch (error: any) {
+		os.alert({
+			type: 'error',
+			text: error.message
+		});
+	}
+}
+
+function highlighter(code) {
+	return highlight(code, languages.js, 'javascript');
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.scratchpad,
+		icon: 'fas fa-terminal',
+	},
 });
 </script>
 
diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index 10599d99ff..fb3a7a17f3 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -1,49 +1,49 @@
 <template>
 <div>
-	<MkButton v-if="!data && !$i.twoFactorEnabled" @click="register">{{ $ts._2fa.registerDevice }}</MkButton>
+	<MkButton v-if="!twoFactorData && !$i.twoFactorEnabled" @click="register">{{ i18n.ts._2fa.registerDevice }}</MkButton>
 	<template v-if="$i.twoFactorEnabled">
-		<p>{{ $ts._2fa.alreadyRegistered }}</p>
-		<MkButton @click="unregister">{{ $ts.unregister }}</MkButton>
+		<p>{{ i18n.ts._2fa.alreadyRegistered }}</p>
+		<MkButton @click="unregister">{{ i18n.ts.unregister }}</MkButton>
 
 		<template v-if="supportsCredentials">
 			<hr class="totp-method-sep">
 
-			<h2 class="heading">{{ $ts.securityKey }}</h2>
-			<p>{{ $ts._2fa.securityKeyInfo }}</p>
+			<h2 class="heading">{{ i18n.ts.securityKey }}</h2>
+			<p>{{ i18n.ts._2fa.securityKeyInfo }}</p>
 			<div class="key-list">
 				<div v-for="key in $i.securityKeysList" class="key">
 					<h3>{{ key.name }}</h3>
-					<div class="last-used">{{ $ts.lastUsed }}<MkTime :time="key.lastUsed"/></div>
-					<MkButton @click="unregisterKey(key)">{{ $ts.unregister }}</MkButton>
+					<div class="last-used">{{ i18n.ts.lastUsed }}<MkTime :time="key.lastUsed"/></div>
+					<MkButton @click="unregisterKey(key)">{{ i18n.ts.unregister }}</MkButton>
 				</div>
 			</div>
 
-			<MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ $ts.passwordLessLogin }}</MkSwitch>
+			<MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ i18n.ts.passwordLessLogin }}</MkSwitch>
 
-			<MkInfo v-if="registration && registration.error" warn>{{ $ts.error }} {{ registration.error }}</MkInfo>
-			<MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ $ts._2fa.registerKey }}</MkButton>
+			<MkInfo v-if="registration && registration.error" warn>{{ i18n.ts.error }} {{ registration.error }}</MkInfo>
+			<MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ i18n.ts._2fa.registerKey }}</MkButton>
 
 			<ol v-if="registration && !registration.error">
 				<li v-if="registration.stage >= 0">
-					{{ $ts.tapSecurityKey }}
+					{{ i18n.ts.tapSecurityKey }}
 					<i v-if="registration.saving && registration.stage == 0" class="fas fa-spinner fa-pulse fa-fw"></i>
 				</li>
 				<li v-if="registration.stage >= 1">
 					<MkForm :disabled="registration.stage != 1 || registration.saving">
 						<MkInput v-model="keyName" :max="30">
-							<template #label>{{ $ts.securityKeyName }}</template>
+							<template #label>{{ i18n.ts.securityKeyName }}</template>
 						</MkInput>
-						<MkButton :disabled="keyName.length == 0" @click="registerKey">{{ $ts.registerSecurityKey }}</MkButton>
+						<MkButton :disabled="keyName.length == 0" @click="registerKey">{{ i18n.ts.registerSecurityKey }}</MkButton>
 						<i v-if="registration.saving && registration.stage == 1" class="fas fa-spinner fa-pulse fa-fw"></i>
 					</MkForm>
 				</li>
 			</ol>
 		</template>
 	</template>
-	<div v-if="data && !$i.twoFactorEnabled">
+	<div v-if="twoFactorData && !$i.twoFactorEnabled">
 		<ol style="margin: 0; padding: 0 0 0 1em;">
 			<li>
-				<I18n :src="$ts._2fa.step1" tag="span">
+				<I18n :src="i18n.ts._2fa.step1" tag="span">
 					<template #a>
 						<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
 					</template>
@@ -52,19 +52,20 @@
 					</template>
 				</I18n>
 			</li>
-			<li>{{ $ts._2fa.step2 }}<br><img :src="data.qr"></li>
-			<li>{{ $ts._2fa.step3 }}<br>
-				<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ $ts.token }}</template></MkInput>
-				<MkButton primary @click="submit">{{ $ts.done }}</MkButton>
+			<li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"><p>{{ $ts._2fa.step2Url }}<br>{{ twoFactorData.url }}</p></li>
+			<li>
+				{{ i18n.ts._2fa.step3 }}<br>
+				<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput>
+				<MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton>
 			</li>
 		</ol>
-		<MkInfo>{{ $ts._2fa.step4 }}</MkInfo>
+		<MkInfo>{{ i18n.ts._2fa.step4 }}</MkInfo>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 import { hostname } from '@/config';
 import { byteify, hexify, stringify } from '@/scripts/2fa';
 import MkButton from '@/components/ui/button.vue';
@@ -72,155 +73,144 @@ import MkInfo from '@/components/ui/info.vue';
 import MkInput from '@/components/form/input.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import * as os from '@/os';
-import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton, MkInfo, MkInput, MkSwitch
-	},
+const twoFactorData = ref<any>(null);
+const supportsCredentials = ref(!!navigator.credentials);
+const usePasswordLessLogin = ref($i!.usePasswordLessLogin);
+const registration = ref<any>(null);
+const keyName = ref('');
+const token = ref(null);
 
-	data() {
-		return {
-			data: null,
-			supportsCredentials: !!navigator.credentials,
-			usePasswordLessLogin: this.$i.usePasswordLessLogin,
-			registration: null,
-			keyName: '',
-			token: null,
-		};
-	},
+function register() {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		os.api('i/2fa/register', {
+			password: password
+		}).then(data => {
+			twoFactorData.value = data;
+		});
+	});
+}
 
-	methods: {
-		register() {
-			os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			}).then(({ canceled, result: password }) => {
-				if (canceled) return;
-				os.api('i/2fa/register', {
-					password: password
-				}).then(data => {
-					this.data = data;
-				});
+function unregister() {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		os.api('i/2fa/unregister', {
+			password: password
+		}).then(() => {
+			usePasswordLessLogin.value = false;
+			updatePasswordLessLogin();
+		}).then(() => {
+			os.success();
+			$i!.twoFactorEnabled = false;
+		});
+	});
+}
+
+function submit() {
+	os.api('i/2fa/done', {
+		token: token.value
+	}).then(() => {
+		os.success();
+		$i!.twoFactorEnabled = true;
+	}).catch(err => {
+		os.alert({
+			type: 'error',
+			text: err,
+		});
+	});
+}
+
+function registerKey() {
+	registration.value.saving = true;
+	os.api('i/2fa/key-done', {
+		password: registration.value.password,
+		name: keyName.value,
+		challengeId: registration.value.challengeId,
+		// we convert each 16 bits to a string to serialise
+		clientDataJSON: stringify(registration.value.credential.response.clientDataJSON),
+		attestationObject: hexify(registration.value.credential.response.attestationObject)
+	}).then(key => {
+		registration.value = null;
+		key.lastUsed = new Date();
+		os.success();
+	});
+}
+
+function unregisterKey(key) {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		return os.api('i/2fa/remove-key', {
+			password,
+			credentialId: key.id
+		}).then(() => {
+			usePasswordLessLogin.value = false;
+			updatePasswordLessLogin();
+		}).then(() => {
+			os.success();
+		});
+	});
+}
+
+function addSecurityKey() {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		os.api('i/2fa/register-key', {
+			password
+		}).then(reg => {
+			registration.value = {
+				password,
+				challengeId: reg!.challengeId,
+				stage: 0,
+				publicKeyOptions: {
+					challenge: byteify(reg!.challenge, 'base64'),
+					rp: {
+						id: hostname,
+						name: 'Misskey'
+					},
+					user: {
+						id: byteify($i!.id, 'ascii'),
+						name: $i!.username,
+						displayName: $i!.name,
+					},
+					pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
+					timeout: 60000,
+					attestation: 'direct'
+				},
+				saving: true
+			};
+			return navigator.credentials.create({
+				publicKey: registration.value.publicKeyOptions
 			});
-		},
+		}).then(credential => {
+			registration.value.credential = credential;
+			registration.value.saving = false;
+			registration.value.stage = 1;
+		}).catch(err => {
+			console.warn('Error while registering?', err);
+			registration.value.error = err.message;
+			registration.value.stage = -1;
+		});
+	});
+}
 
-		unregister() {
-			os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			}).then(({ canceled, result: password }) => {
-				if (canceled) return;
-				os.api('i/2fa/unregister', {
-					password: password
-				}).then(() => {
-					this.usePasswordLessLogin = false;
-					this.updatePasswordLessLogin();
-				}).then(() => {
-					os.success();
-					this.$i.twoFactorEnabled = false;
-				});
-			});
-		},
-
-		submit() {
-			os.api('i/2fa/done', {
-				token: this.token
-			}).then(() => {
-				os.success();
-				this.$i.twoFactorEnabled = true;
-			}).catch(e => {
-				os.alert({
-					type: 'error',
-					text: e
-				});
-			});
-		},
-
-		registerKey() {
-			this.registration.saving = true;
-			os.api('i/2fa/key-done', {
-				password: this.registration.password,
-				name: this.keyName,
-				challengeId: this.registration.challengeId,
-				// we convert each 16 bits to a string to serialise
-				clientDataJSON: stringify(this.registration.credential.response.clientDataJSON),
-				attestationObject: hexify(this.registration.credential.response.attestationObject)
-			}).then(key => {
-				this.registration = null;
-				key.lastUsed = new Date();
-				os.success();
-			})
-		},
-
-		unregisterKey(key) {
-			os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			}).then(({ canceled, result: password }) => {
-				if (canceled) return;
-				return os.api('i/2fa/remove-key', {
-					password,
-					credentialId: key.id
-				}).then(() => {
-					this.usePasswordLessLogin = false;
-					this.updatePasswordLessLogin();
-				}).then(() => {
-					os.success();
-				});
-			});
-		},
-
-		addSecurityKey() {
-			os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			}).then(({ canceled, result: password }) => {
-				if (canceled) return;
-				os.api('i/2fa/register-key', {
-					password
-				}).then(registration => {
-					this.registration = {
-						password,
-						challengeId: registration.challengeId,
-						stage: 0,
-						publicKeyOptions: {
-							challenge: byteify(registration.challenge, 'base64'),
-							rp: {
-								id: hostname,
-								name: 'Misskey'
-							},
-							user: {
-								id: byteify(this.$i.id, 'ascii'),
-								name: this.$i.username,
-								displayName: this.$i.name,
-							},
-							pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
-							timeout: 60000,
-							attestation: 'direct'
-						},
-						saving: true
-					};
-					return navigator.credentials.create({
-						publicKey: this.registration.publicKeyOptions
-					});
-				}).then(credential => {
-					this.registration.credential = credential;
-					this.registration.saving = false;
-					this.registration.stage = 1;
-				}).catch(err => {
-					console.warn('Error while registering?', err);
-					this.registration.error = err.message;
-					this.registration.stage = -1;
-				});
-			});
-		},
-
-		updatePasswordLessLogin() {
-			os.api('i/2fa/password-less', {
-				value: !!this.usePasswordLessLogin
-			});
-		}
-	}
-});
+async function updatePasswordLessLogin() {
+	await os.api('i/2fa/password-less', {
+		value: !!usePasswordLessLogin.value
+	});
+}
 </script>
diff --git a/packages/client/src/pages/settings/account-info.vue b/packages/client/src/pages/settings/account-info.vue
index c98ad056f6..12142b4dc1 100644
--- a/packages/client/src/pages/settings/account-info.vue
+++ b/packages/client/src/pages/settings/account-info.vue
@@ -7,163 +7,150 @@
 
 	<FormSection>
 		<MkKeyValue>
-			<template #key>{{ $ts.registeredDate }}</template>
+			<template #key>{{ i18n.ts.registeredDate }}</template>
 			<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
 		</MkKeyValue>
 	</FormSection>
 
 	<FormSection v-if="stats">
-		<template #label>{{ $ts.statistics }}</template>
+		<template #label>{{ i18n.ts.statistics }}</template>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.notesCount }}</template>
+			<template #key>{{ i18n.ts.notesCount }}</template>
 			<template #value>{{ number(stats.notesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.repliesCount }}</template>
+			<template #key>{{ i18n.ts.repliesCount }}</template>
 			<template #value>{{ number(stats.repliesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.renotesCount }}</template>
+			<template #key>{{ i18n.ts.renotesCount }}</template>
 			<template #value>{{ number(stats.renotesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.repliedCount }}</template>
+			<template #key>{{ i18n.ts.repliedCount }}</template>
 			<template #value>{{ number(stats.repliedCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.renotedCount }}</template>
+			<template #key>{{ i18n.ts.renotedCount }}</template>
 			<template #value>{{ number(stats.renotedCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.pollVotesCount }}</template>
+			<template #key>{{ i18n.ts.pollVotesCount }}</template>
 			<template #value>{{ number(stats.pollVotesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.pollVotedCount }}</template>
+			<template #key>{{ i18n.ts.pollVotedCount }}</template>
 			<template #value>{{ number(stats.pollVotedCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.sentReactionsCount }}</template>
+			<template #key>{{ i18n.ts.sentReactionsCount }}</template>
 			<template #value>{{ number(stats.sentReactionsCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.receivedReactionsCount }}</template>
+			<template #key>{{ i18n.ts.receivedReactionsCount }}</template>
 			<template #value>{{ number(stats.receivedReactionsCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.noteFavoritesCount }}</template>
+			<template #key>{{ i18n.ts.noteFavoritesCount }}</template>
 			<template #value>{{ number(stats.noteFavoritesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.followingCount }}</template>
+			<template #key>{{ i18n.ts.followingCount }}</template>
 			<template #value>{{ number(stats.followingCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.followingCount }} ({{ $ts.local }})</template>
+			<template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.local }})</template>
 			<template #value>{{ number(stats.localFollowingCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.followingCount }} ({{ $ts.remote }})</template>
+			<template #key>{{ i18n.ts.followingCount }} ({{ i18n.ts.remote }})</template>
 			<template #value>{{ number(stats.remoteFollowingCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.followersCount }}</template>
+			<template #key>{{ i18n.ts.followersCount }}</template>
 			<template #value>{{ number(stats.followersCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.followersCount }} ({{ $ts.local }})</template>
+			<template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.local }})</template>
 			<template #value>{{ number(stats.localFollowersCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.followersCount }} ({{ $ts.remote }})</template>
+			<template #key>{{ i18n.ts.followersCount }} ({{ i18n.ts.remote }})</template>
 			<template #value>{{ number(stats.remoteFollowersCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.pageLikesCount }}</template>
+			<template #key>{{ i18n.ts.pageLikesCount }}</template>
 			<template #value>{{ number(stats.pageLikesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.pageLikedCount }}</template>
+			<template #key>{{ i18n.ts.pageLikedCount }}</template>
 			<template #value>{{ number(stats.pageLikedCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.driveFilesCount }}</template>
+			<template #key>{{ i18n.ts.driveFilesCount }}</template>
 			<template #value>{{ number(stats.driveFilesCount) }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
-			<template #key>{{ $ts.driveUsage }}</template>
+			<template #key>{{ i18n.ts.driveUsage }}</template>
 			<template #value>{{ bytes(stats.driveUsage) }}</template>
 		</MkKeyValue>
 	</FormSection>
 
 	<FormSection>
-		<template #label>{{ $ts.other }}</template>
+		<template #label>{{ i18n.ts.other }}</template>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>emailVerified</template>
-			<template #value>{{ $i.emailVerified ? $ts.yes : $ts.no }}</template>
+			<template #value>{{ $i.emailVerified ? i18n.ts.yes : i18n.ts.no }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>twoFactorEnabled</template>
-			<template #value>{{ $i.twoFactorEnabled ? $ts.yes : $ts.no }}</template>
+			<template #value>{{ $i.twoFactorEnabled ? i18n.ts.yes : i18n.ts.no }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>securityKeys</template>
-			<template #value>{{ $i.securityKeys ? $ts.yes : $ts.no }}</template>
+			<template #value>{{ $i.securityKeys ? i18n.ts.yes : i18n.ts.no }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>usePasswordLessLogin</template>
-			<template #value>{{ $i.usePasswordLessLogin ? $ts.yes : $ts.no }}</template>
+			<template #value>{{ $i.usePasswordLessLogin ? i18n.ts.yes : i18n.ts.no }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>isModerator</template>
-			<template #value>{{ $i.isModerator ? $ts.yes : $ts.no }}</template>
+			<template #value>{{ $i.isModerator ? i18n.ts.yes : i18n.ts.no }}</template>
 		</MkKeyValue>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>isAdmin</template>
-			<template #value>{{ $i.isAdmin ? $ts.yes : $ts.no }}</template>
+			<template #value>{{ $i.isAdmin ? i18n.ts.yes : i18n.ts.no }}</template>
 		</MkKeyValue>
 	</FormSection>
 </div>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, onMounted, ref } from 'vue';
 import FormSection from '@/components/form/section.vue';
 import MkKeyValue from '@/components/key-value.vue';
 import * as os from '@/os';
 import number from '@/filters/number';
 import bytes from '@/filters/bytes';
 import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSection,
-		MkKeyValue,
-	},
+const stats = ref<any>({});
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.accountInfo,
-				icon: 'fas fa-info-circle'
-			},
-			stats: null
-		}
-	},
+onMounted(() => {
+	os.api('users/stats', {
+		userId: $i!.id
+	}).then(response => {
+		stats.value = response;
+	});
+});
 
-	mounted() {
-		os.api('users/stats', {
-			userId: this.$i.id
-		}).then(stats => {
-			this.stats = stats;
-		});
-	},
-
-	methods: {
-		number,
-		bytes,
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.accountInfo,
+		icon: 'fas fa-info-circle'
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/accounts.vue b/packages/client/src/pages/settings/accounts.vue
index a744a031d4..5e75639c55 100644
--- a/packages/client/src/pages/settings/accounts.vue
+++ b/packages/client/src/pages/settings/accounts.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="_formRoot">
 	<FormSuspense :p="init">
-		<FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ $ts.addAccount }}</FormButton>
+		<FormButton primary @click="addAccount"><i class="fas fa-plus"></i> {{ i18n.ts.addAccount }}</FormButton>
 
 		<div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
 			<div class="avatar">
@@ -20,90 +20,89 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent, defineExpose, ref } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
-import { getAccounts, addAccount, login } from '@/account';
+import { getAccounts, addAccount as addAccounts, login, $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSuspense,
-		FormButton,
-	},
+const storedAccounts = ref<any>(null);
+const accounts = ref<any>(null);
 
-	emits: ['info'],
+const init = async () => {
+	getAccounts().then(accounts => {
+		storedAccounts.value = accounts.filter(x => x.id !== $i!.id);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.accounts,
-				icon: 'fas fa-users',
-				bg: 'var(--bg)',
-			},
-			storedAccounts: getAccounts().then(accounts => accounts.filter(x => x.id !== this.$i.id)),
-			accounts: null,
-			init: async () => os.api('users/show', {
-				userIds: (await this.storedAccounts).map(x => x.id)
-			}).then(accounts => {
-				this.accounts = accounts;
-			}),
-		};
-	},
+		console.log(storedAccounts.value);
 
-	methods: {
-		menu(account, ev) {
-			os.popupMenu([{
-				text: this.$ts.switch,
-				icon: 'fas fa-exchange-alt',
-				action: () => this.switchAccount(account),
-			}, {
-				text: this.$ts.remove,
-				icon: 'fas fa-trash-alt',
-				danger: true,
-				action: () => this.removeAccount(account),
-			}], ev.currentTarget ?? ev.target);
+		return os.api('users/show', {
+			userIds: storedAccounts.value.map(x => x.id)
+		});
+	}).then(response => {
+		accounts.value = response;
+		console.log(accounts.value);
+	});
+};
+
+function menu(account, ev) {
+	os.popupMenu([{
+		text: i18n.ts.switch,
+		icon: 'fas fa-exchange-alt',
+		action: () => switchAccount(account),
+	}, {
+		text: i18n.ts.remove,
+		icon: 'fas fa-trash-alt',
+		danger: true,
+		action: () => removeAccount(account),
+	}], ev.currentTarget ?? ev.target);
+}
+
+function addAccount(ev) {
+	os.popupMenu([{
+		text: i18n.ts.existingAccount,
+		action: () => { addExistingAccount(); },
+	}, {
+		text: i18n.ts.createAccount,
+		action: () => { createAccount(); },
+	}], ev.currentTarget ?? ev.target);
+}
+
+function addExistingAccount() {
+	os.popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {}, {
+		done: res => {
+			addAccounts(res.id, res.i);
+			os.success();
 		},
+	}, 'closed');
+}
 
-		addAccount(ev) {
-			os.popupMenu([{
-				text: this.$ts.existingAccount,
-				action: () => { this.addExistingAccount(); },
-			}, {
-				text: this.$ts.createAccount,
-				action: () => { this.createAccount(); },
-			}], ev.currentTarget ?? ev.target);
+function createAccount() {
+	os.popup(defineAsyncComponent(() => import('@/components/signup-dialog.vue')), {}, {
+		done: res => {
+			addAccounts(res.id, res.i);
+			switchAccountWithToken(res.i);
 		},
+	}, 'closed');
+}
 
-		addExistingAccount() {
-			os.popup(import('@/components/signin-dialog.vue'), {}, {
-				done: res => {
-					addAccount(res.id, res.i);
-					os.success();
-				},
-			}, 'closed');
-		},
+async function switchAccount(account: any) {
+	const fetchedAccounts: any[] = await getAccounts();
+	const token = fetchedAccounts.find(x => x.id === account.id).token;
+	switchAccountWithToken(token);
+}
 
-		createAccount() {
-			os.popup(import('@/components/signup-dialog.vue'), {}, {
-				done: res => {
-					addAccount(res.id, res.i);
-					this.switchAccountWithToken(res.i);
-				},
-			}, 'closed');
-		},
+function switchAccountWithToken(token: string) {
+	login(token);
+}
 
-		async switchAccount(account: any) {
-			const storedAccounts = await getAccounts();
-			const token = storedAccounts.find(x => x.id === account.id).token;
-			this.switchAccountWithToken(token);
-		},
-
-		switchAccountWithToken(token: string) {
-			login(token);
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.accounts,
+		icon: 'fas fa-users',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/api.vue b/packages/client/src/pages/settings/api.vue
index 20ff2a8d96..e6375763f1 100644
--- a/packages/client/src/pages/settings/api.vue
+++ b/packages/client/src/pages/settings/api.vue
@@ -1,56 +1,45 @@
 <template>
 <div class="_formRoot">
-	<FormButton primary class="_formBlock" @click="generateToken">{{ $ts.generateAccessToken }}</FormButton>
-	<FormLink to="/settings/apps" class="_formBlock">{{ $ts.manageAccessTokens }}</FormLink>
+	<FormButton primary class="_formBlock" @click="generateToken">{{ i18n.ts.generateAccessToken }}</FormButton>
+	<FormLink to="/settings/apps" class="_formBlock">{{ i18n.ts.manageAccessTokens }}</FormLink>
 	<FormLink to="/api-console" :behavior="isDesktop ? 'window' : null" class="_formBlock">API console</FormLink>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent, defineExpose, ref } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import FormButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormButton,
-		FormLink,
-	},
+const isDesktop = ref(window.innerWidth >= 1100);
 
-	emits: ['info'],
+function generateToken() {
+	os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {}, {
+		done: async result => {
+			const { name, permissions } = result;
+			const { token } = await os.api('miauth/gen-token', {
+				session: null,
+				name: name,
+				permission: permissions,
+			});
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: 'API',
-				icon: 'fas fa-key',
-				bg: 'var(--bg)',
-			},
-			isDesktop: window.innerWidth >= 1100,
-		};
-	},
-
-	methods: {
-		generateToken() {
-			os.popup(import('@/components/token-generate-window.vue'), {}, {
-				done: async result => {
-					const { name, permissions } = result;
-					const { token } = await os.api('miauth/gen-token', {
-						session: null,
-						name: name,
-						permission: permissions,
-					});
-
-					os.alert({
-						type: 'success',
-						title: this.$ts.token,
-						text: token
-					});
-				},
-			}, 'closed');
+			os.alert({
+				type: 'success',
+				title: i18n.ts.token,
+				text: token
+			});
 		},
+	}, 'closed');
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: 'API',
+		icon: 'fas fa-key',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/apps.vue b/packages/client/src/pages/settings/apps.vue
index 9c0fa8a54d..7b0b5548d5 100644
--- a/packages/client/src/pages/settings/apps.vue
+++ b/packages/client/src/pages/settings/apps.vue
@@ -4,7 +4,7 @@
 		<template #empty>
 			<div class="_fullinfo">
 				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
-				<div>{{ $ts.nothing }}</div>
+				<div>{{ i18n.ts.nothing }}</div>
 			</div>
 		</template>
 		<template v-slot="{items}">
@@ -14,18 +14,18 @@
 					<div class="name">{{ token.name }}</div>
 					<div class="description">{{ token.description }}</div>
 					<div class="_keyValue">
-						<div>{{ $ts.installedDate }}:</div>
+						<div>{{ i18n.ts.installedDate }}:</div>
 						<div><MkTime :time="token.createdAt"/></div>
 					</div>
 					<div class="_keyValue">
-						<div>{{ $ts.lastUsedDate }}:</div>
+						<div>{{ i18n.ts.lastUsedDate }}:</div>
 						<div><MkTime :time="token.lastUsedAt"/></div>
 					</div>
 					<div class="actions">
 						<button class="_button" @click="revoke(token)"><i class="fas fa-trash-alt"></i></button>
 					</div>
 					<details>
-						<summary>{{ $ts.details }}</summary>
+						<summary>{{ i18n.ts.details }}</summary>
 						<ul>
 							<li v-for="p in token.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
 						</ul>
@@ -37,42 +37,34 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref } from 'vue';
 import FormPagination from '@/components/ui/pagination.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormPagination,
-	},
+const list = ref<any>(null);
 
-	emits: ['info'],
+const pagination = {
+	endpoint: 'i/apps' as const,
+	limit: 100,
+	params: {
+		sort: '+lastUsedAt'
+	}
+};
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.installedApps,
-				icon: 'fas fa-plug',
-				bg: 'var(--bg)',
-			},
-			pagination: {
-				endpoint: 'i/apps' as const,
-				limit: 100,
-				params: {
-					sort: '+lastUsedAt'
-				}
-			},
-		};
-	},
+function revoke(token) {
+	os.api('i/revoke-token', { tokenId: token.id }).then(() => {
+		list.value.reload();
+	});
+}
 
-	methods: {
-		revoke(token) {
-			os.api('i/revoke-token', { tokenId: token.id }).then(() => {
-				this.$refs.list.reload();
-			});
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.installedApps,
+		icon: 'fas fa-plug',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/custom-css.vue b/packages/client/src/pages/settings/custom-css.vue
index 556ee30c1d..20db077ceb 100644
--- a/packages/client/src/pages/settings/custom-css.vue
+++ b/packages/client/src/pages/settings/custom-css.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="_formRoot">
-	<FormInfo warn class="_formBlock">{{ $ts.customCssWarn }}</FormInfo>
+	<FormInfo warn class="_formBlock">{{ i18n.ts.customCssWarn }}</FormInfo>
 
 	<FormTextarea v-model="localCustomCss" manual-save tall class="_monospace _formBlock" style="tab-size: 2;">
 		<template #label>CSS</template>
@@ -8,50 +8,38 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormInfo from '@/components/ui/info.vue';
 import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import * as symbols from '@/symbols';
-import { defaultStore } from '@/store';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormTextarea,
-		FormInfo,
-	},
+const localCustomCss = ref(localStorage.getItem('customCss') ?? '');
 
-	emits: ['info'],
+async function apply() {
+	localStorage.setItem('customCss', localCustomCss.value);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.customCss,
-				icon: 'fas fa-code',
-				bg: 'var(--bg)',
-			},
-			localCustomCss: localStorage.getItem('customCss')
-		}
-	},
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
 
-	mounted() {
-		this.$watch('localCustomCss', this.apply);
-	},
+	unisonReload();
+}
 
-	methods: {
-		async apply() {
-			localStorage.setItem('customCss', this.localCustomCss);
+watch(localCustomCss, async () => {
+	await apply();
+});
 
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.customCss,
+		icon: 'fas fa-code',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/deck.vue b/packages/client/src/pages/settings/deck.vue
index 46b90d3d1a..2d868aa0a7 100644
--- a/packages/client/src/pages/settings/deck.vue
+++ b/packages/client/src/pages/settings/deck.vue
@@ -1,36 +1,36 @@
 <template>
 <div class="_formRoot">
 	<FormGroup>
-		<template #label>{{ $ts.defaultNavigationBehaviour }}</template>
-		<FormSwitch v-model="navWindow">{{ $ts.openInWindow }}</FormSwitch>
+		<template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template>
+		<FormSwitch v-model="navWindow">{{ i18n.ts.openInWindow }}</FormSwitch>
 	</FormGroup>
 
-	<FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ $ts._deck.alwaysShowMainColumn }}</FormSwitch>
+	<FormSwitch v-model="alwaysShowMainColumn" class="_formBlock">{{ i18n.ts._deck.alwaysShowMainColumn }}</FormSwitch>
 
 	<FormRadios v-model="columnAlign" class="_formBlock">
-		<template #label>{{ $ts._deck.columnAlign }}</template>
-		<option value="left">{{ $ts.left }}</option>
-		<option value="center">{{ $ts.center }}</option>
+		<template #label>{{ i18n.ts._deck.columnAlign }}</template>
+		<option value="left">{{ i18n.ts.left }}</option>
+		<option value="center">{{ i18n.ts.center }}</option>
 	</FormRadios>
 
 	<FormRadios v-model="columnHeaderHeight" class="_formBlock">
-		<template #label>{{ $ts._deck.columnHeaderHeight }}</template>
-		<option :value="42">{{ $ts.narrow }}</option>
-		<option :value="45">{{ $ts.medium }}</option>
-		<option :value="48">{{ $ts.wide }}</option>
+		<template #label>{{ i18n.ts._deck.columnHeaderHeight }}</template>
+		<option :value="42">{{ i18n.ts.narrow }}</option>
+		<option :value="45">{{ i18n.ts.medium }}</option>
+		<option :value="48">{{ i18n.ts.wide }}</option>
 	</FormRadios>
 
 	<FormInput v-model="columnMargin" type="number" class="_formBlock">
-		<template #label>{{ $ts._deck.columnMargin }}</template>
+		<template #label>{{ i18n.ts._deck.columnMargin }}</template>
 		<template #suffix>px</template>
 	</FormInput>
 
-	<FormLink class="_formBlock" @click="setProfile">{{ $ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
+	<FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed, defineExpose, watch } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormLink from '@/components/form/link.vue';
 import FormRadios from '@/components/form/radios.vue';
@@ -40,59 +40,41 @@ import { deckStore } from '@/ui/deck/deck-store';
 import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormLink,
-		FormInput,
-		FormRadios,
-		FormGroup,
-	},
+const navWindow = computed(deckStore.makeGetterSetter('navWindow'));
+const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn'));
+const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
+const columnMargin = computed(deckStore.makeGetterSetter('columnMargin'));
+const columnHeaderHeight = computed(deckStore.makeGetterSetter('columnHeaderHeight'));
+const profile = computed(deckStore.makeGetterSetter('profile'));
 
-	emits: ['info'],
+watch(navWindow, async () => {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.deck,
-				icon: 'fas fa-columns',
-				bg: 'var(--bg)',
-			},
-		}
-	},
+	unisonReload();
+});
 
-	computed: {
-		navWindow: deckStore.makeGetterSetter('navWindow'),
-		alwaysShowMainColumn: deckStore.makeGetterSetter('alwaysShowMainColumn'),
-		columnAlign: deckStore.makeGetterSetter('columnAlign'),
-		columnMargin: deckStore.makeGetterSetter('columnMargin'),
-		columnHeaderHeight: deckStore.makeGetterSetter('columnHeaderHeight'),
-		profile: deckStore.makeGetterSetter('profile'),
-	},
+async function setProfile() {
+	const { canceled, result: name } = await os.inputText({
+		title: i18n.ts._deck.profile,
+		allowEmpty: false
+	});
+	if (canceled) return;
+	
+	profile.value = name;
+	unisonReload();
+}
 
-	watch: {
-		async navWindow() {
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
-	},
-
-	methods: {
-		async setProfile() {
-			const { canceled, result: name } = await os.inputText({
-				title: this.$ts._deck.profile,
-				allowEmpty: false
-			});
-			if (canceled) return;
-			this.profile = name;
-			unisonReload();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.deck,
+		icon: 'fas fa-columns',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/delete-account.vue b/packages/client/src/pages/settings/delete-account.vue
index 7edc81a309..e9f19aaf0b 100644
--- a/packages/client/src/pages/settings/delete-account.vue
+++ b/packages/client/src/pages/settings/delete-account.vue
@@ -1,64 +1,52 @@
 <template>
 <div class="_formRoot">
-	<FormInfo warn class="_formBlock">{{ $ts._accountDelete.mayTakeTime }}</FormInfo>
-	<FormInfo class="_formBlock">{{ $ts._accountDelete.sendEmail }}</FormInfo>
-	<FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ $ts._accountDelete.requestAccountDelete }}</FormButton>
-	<FormButton v-else disabled>{{ $ts._accountDelete.inProgress }}</FormButton>
+	<FormInfo warn class="_formBlock">{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
+	<FormInfo class="_formBlock">{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
+	<FormButton v-if="!$i.isDeleted" danger class="_formBlock" @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</FormButton>
+	<FormButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</FormButton>
 </div>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose } from 'vue';
 import FormInfo from '@/components/ui/info.vue';
 import FormButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import { signout } from '@/account';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormButton,
-		FormInfo,
-	},
+async function deleteAccount() {
+	{
+		const { canceled } = await os.confirm({
+			type: 'warning',
+			text: i18n.ts.deleteAccountConfirm,
+		});
+		if (canceled) return;
+	}
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts._accountDelete.accountDelete,
-				icon: 'fas fa-exclamation-triangle',
-				bg: 'var(--bg)',
-			},
-		}
-	},
+	const { canceled, result: password } = await os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	});
+	if (canceled) return;
 
-	methods: {
-		async deleteAccount() {
-			{
-				const { canceled } = await os.confirm({
-					type: 'warning',
-					text: this.$ts.deleteAccountConfirm,
-				});
-				if (canceled) return;
-			}
+	await os.apiWithDialog('i/delete-account', {
+		password: password
+	});
 
-			const { canceled, result: password } = await os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			});
-			if (canceled) return;
+	await os.alert({
+		title: i18n.ts._accountDelete.started,
+	});
 
-			await os.apiWithDialog('i/delete-account', {
-				password: password
-			});
+	await signout();
+}
 
-			await os.alert({
-				title: this.$ts._accountDelete.started,
-			});
-
-			signout();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts._accountDelete.accountDelete,
+		icon: 'fas fa-exclamation-triangle',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/drive.vue b/packages/client/src/pages/settings/drive.vue
index 9309eb5ec7..09a2537ed5 100644
--- a/packages/client/src/pages/settings/drive.vue
+++ b/packages/client/src/pages/settings/drive.vue
@@ -1,41 +1,41 @@
 <template>
 <div class="_formRoot">
 	<FormSection v-if="!fetching">
-		<template #label>{{ $ts.usageAmount }}</template>
+		<template #label>{{ i18n.ts.usageAmount }}</template>
 		<div class="_formBlock uawsfosz">
 			<div class="meter"><div :style="meterStyle"></div></div>
 		</div>
 		<FormSplit>
 			<MkKeyValue class="_formBlock">
-				<template #key>{{ $ts.capacity }}</template>
+				<template #key>{{ i18n.ts.capacity }}</template>
 				<template #value>{{ bytes(capacity, 1) }}</template>
 			</MkKeyValue>
 			<MkKeyValue class="_formBlock">
-				<template #key>{{ $ts.inUse }}</template>
+				<template #key>{{ i18n.ts.inUse }}</template>
 				<template #value>{{ bytes(usage, 1) }}</template>
 			</MkKeyValue>
 		</FormSplit>
 	</FormSection>
 
 	<FormSection>
-		<template #label>{{ $ts.statistics }}</template>
+		<template #label>{{ i18n.ts.statistics }}</template>
 		<MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/>
 	</FormSection>
 
 	<FormSection>
 		<FormLink @click="chooseUploadFolder()">
-			{{ $ts.uploadFolder }}
+			{{ i18n.ts.uploadFolder }}
 			<template #suffix>{{ uploadFolder ? uploadFolder.name : '-' }}</template>
 			<template #suffixIcon><i class="fas fa-folder-open"></i></template>
 		</FormLink>
-		<FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ $ts.keepOriginalUploading }}<template #caption>{{ $ts.keepOriginalUploadingDescription }}</template></FormSwitch>
+		<FormSwitch v-model="keepOriginalUploading" class="_formBlock">{{ i18n.ts.keepOriginalUploading }}<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template></FormSwitch>
 	</FormSection>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as tinycolor from 'tinycolor2';
+<script lang="ts" setup>
+import { computed, defineExpose, ref } from 'vue';
+import tinycolor from 'tinycolor2';
 import FormLink from '@/components/form/link.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormSection from '@/components/form/section.vue';
@@ -46,80 +46,59 @@ import bytes from '@/filters/bytes';
 import * as symbols from '@/symbols';
 import { defaultStore } from '@/store';
 import MkChart from '@/components/chart.vue';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormLink,
-		FormSwitch,
-		FormSection,
-		MkKeyValue,
-		FormSplit,
-		MkChart,
-	},
+const fetching = ref(true);
+const usage = ref<any>(null);
+const capacity = ref<any>(null);
+const uploadFolder = ref<any>(null);
 
-	emits: ['info'],
+const meterStyle = computed(() => {
+	return {
+		width: `${usage.value / capacity.value * 100}%`,
+		background: tinycolor({
+			h: 180 - (usage.value / capacity.value * 180),
+			s: 0.7,
+			l: 0.5
+		})
+	};
+});
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.drive,
-				icon: 'fas fa-cloud',
-				bg: 'var(--bg)',
-			},
-			fetching: true,
-			usage: null,
-			capacity: null,
-			uploadFolder: null,
+const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading'));
+
+os.api('drive').then(info => {
+	capacity.value = info.capacity;
+	usage.value = info.usage;
+	fetching.value = false;
+});
+
+if (defaultStore.state.uploadFolder) {
+	os.api('drive/folders/show', {
+		folderId: defaultStore.state.uploadFolder
+	}).then(response => {
+		uploadFolder.value = response;
+	});
+}
+
+function chooseUploadFolder() {
+	os.selectDriveFolder(false).then(async folder => {
+		defaultStore.set('uploadFolder', folder ? folder.id : null);
+		os.success();
+		if (defaultStore.state.uploadFolder) {
+			uploadFolder.value = await os.api('drive/folders/show', {
+				folderId: defaultStore.state.uploadFolder
+			});
+		} else {
+			uploadFolder.value = null;
 		}
-	},
+	});
+}
 
-	computed: {
-		meterStyle(): any {
-			return {
-				width: `${this.usage / this.capacity * 100}%`,
-				background: tinycolor({
-					h: 180 - (this.usage / this.capacity * 180),
-					s: 0.7,
-					l: 0.5
-				})
-			};
-		},
-		keepOriginalUploading: defaultStore.makeGetterSetter('keepOriginalUploading'),
-	},
-
-	async created() {
-		os.api('drive').then(info => {
-			this.capacity = info.capacity;
-			this.usage = info.usage;
-			this.fetching = false;
-			this.$nextTick(() => {
-				this.renderChart();
-			});
-		});
-
-		if (this.$store.state.uploadFolder) {
-			this.uploadFolder = await os.api('drive/folders/show', {
-				folderId: this.$store.state.uploadFolder
-			});
-		}
-	},
-
-	methods: {
-		chooseUploadFolder() {
-			os.selectDriveFolder(false).then(async folder => {
-				this.$store.set('uploadFolder', folder ? folder.id : null);
-				os.success();
-				if (this.$store.state.uploadFolder) {
-					this.uploadFolder = await os.api('drive/folders/show', {
-						folderId: this.$store.state.uploadFolder
-					});
-				} else {
-					this.uploadFolder = null;
-				}
-			});
-		},
-
-		bytes
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.drive,
+		icon: 'fas fa-cloud',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/email.vue b/packages/client/src/pages/settings/email.vue
index 4697fec9b7..37f14068e2 100644
--- a/packages/client/src/pages/settings/email.vue
+++ b/packages/client/src/pages/settings/email.vue
@@ -39,8 +39,8 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, ref, watch } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, onMounted, ref, watch } from 'vue';
 import FormSection from '@/components/form/section.vue';
 import FormInput from '@/components/form/input.vue';
 import FormSwitch from '@/components/form/switch.vue';
@@ -49,79 +49,62 @@ import * as symbols from '@/symbols';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSection,
-		FormSwitch,
-		FormInput,
-	},
+const emailAddress = ref($i!.email);
 
-	emits: ['info'],
+const onChangeReceiveAnnouncementEmail = (v) => {
+	os.api('i/update', {
+		receiveAnnouncementEmail: v
+	});
+};
 
-	setup(props, context) {
-		const emailAddress = ref($i.email);
-
-		const INFO = {
-			title: i18n.ts.email,
-			icon: 'fas fa-envelope',
-			bg: 'var(--bg)',
-		};
-
-		const onChangeReceiveAnnouncementEmail = (v) => {
-			os.api('i/update', {
-				receiveAnnouncementEmail: v
-			});
-		};
-
-		const saveEmailAddress = () => {
-			os.inputText({
-				title: i18n.ts.password,
-				type: 'password'
-			}).then(({ canceled, result: password }) => {
-				if (canceled) return;
-				os.apiWithDialog('i/update-email', {
-					password: password,
-					email: emailAddress.value,
-				});
-			});
-		};
-
-		const emailNotification_mention = ref($i.emailNotificationTypes.includes('mention'));
-		const emailNotification_reply = ref($i.emailNotificationTypes.includes('reply'));
-		const emailNotification_quote = ref($i.emailNotificationTypes.includes('quote'));
-		const emailNotification_follow = ref($i.emailNotificationTypes.includes('follow'));
-		const emailNotification_receiveFollowRequest = ref($i.emailNotificationTypes.includes('receiveFollowRequest'));
-		const emailNotification_groupInvited = ref($i.emailNotificationTypes.includes('groupInvited'));
-
-		const saveNotificationSettings = () => {
-			os.api('i/update', {
-				emailNotificationTypes: [
-					...[emailNotification_mention.value ? 'mention' : null],
-					...[emailNotification_reply.value ? 'reply' : null],
-					...[emailNotification_quote.value ? 'quote' : null],
-					...[emailNotification_follow.value ? 'follow' : null],
-					...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null],
-					...[emailNotification_groupInvited.value ? 'groupInvited' : null],
-				].filter(x => x != null)
-			});
-		};
-
-		watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
-			saveNotificationSettings();
+const saveEmailAddress = () => {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		os.apiWithDialog('i/update-email', {
+			password: password,
+			email: emailAddress.value,
 		});
+	});
+};
 
-		onMounted(() => {
-			watch(emailAddress, () => {
-				saveEmailAddress();
-			});
-		});
+const emailNotification_mention = ref($i!.emailNotificationTypes.includes('mention'));
+const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply'));
+const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote'));
+const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow'));
+const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest'));
+const emailNotification_groupInvited = ref($i!.emailNotificationTypes.includes('groupInvited'));
 
-		return {
-			[symbols.PAGE_INFO]: INFO,
-			emailAddress,
-			onChangeReceiveAnnouncementEmail,
-			emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited,
-		};
-	},
+const saveNotificationSettings = () => {
+	os.api('i/update', {
+		emailNotificationTypes: [
+			...[emailNotification_mention.value ? 'mention' : null],
+			...[emailNotification_reply.value ? 'reply' : null],
+			...[emailNotification_quote.value ? 'quote' : null],
+			...[emailNotification_follow.value ? 'follow' : null],
+			...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null],
+			...[emailNotification_groupInvited.value ? 'groupInvited' : null],
+		].filter(x => x != null)
+	});
+};
+
+watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
+	saveNotificationSettings();
+});
+
+onMounted(() => {
+	watch(emailAddress, () => {
+		saveEmailAddress();
+	});
+});
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.email,
+		icon: 'fas fa-envelope',
+		bg: 'var(--bg)',
+	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index c8f6f58322..64b8cc3106 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -1,10 +1,10 @@
 <template>
 <div class="_formRoot">
 	<FormSelect v-model="lang" class="_formBlock">
-		<template #label>{{ $ts.uiLanguage }}</template>
+		<template #label>{{ i18n.ts.uiLanguage }}</template>
 		<option v-for="x in langs" :key="x[0]" :value="x[0]">{{ x[1] }}</option>
 		<template #caption>
-			<I18n :src="$ts.i18nInfo" tag="span">
+			<I18n :src="i18n.ts.i18nInfo" tag="span">
 				<template #link>
 					<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
 				</template>
@@ -13,48 +13,48 @@
 	</FormSelect>
 
 	<FormRadios v-model="overridedDeviceKind" class="_formBlock">
-		<template #label>{{ $ts.overridedDeviceKind }}</template>
-		<option :value="null">{{ $ts.auto }}</option>
-		<option value="smartphone"><i class="fas fa-mobile-alt"/> {{ $ts.smartphone }}</option>
-		<option value="tablet"><i class="fas fa-tablet-alt"/> {{ $ts.tablet }}</option>
-		<option value="desktop"><i class="fas fa-desktop"/> {{ $ts.desktop }}</option>
+		<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
+		<option :value="null">{{ i18n.ts.auto }}</option>
+		<option value="smartphone"><i class="fas fa-mobile-alt"/> {{ i18n.ts.smartphone }}</option>
+		<option value="tablet"><i class="fas fa-tablet-alt"/> {{ i18n.ts.tablet }}</option>
+		<option value="desktop"><i class="fas fa-desktop"/> {{ i18n.ts.desktop }}</option>
 	</FormRadios>
 
-	<FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ $ts.showFixedPostForm }}</FormSwitch>
+	<FormSwitch v-model="showFixedPostForm" class="_formBlock">{{ i18n.ts.showFixedPostForm }}</FormSwitch>
 
 	<FormSection>
-		<template #label>{{ $ts.behavior }}</template>
-		<FormSwitch v-model="imageNewTab" class="_formBlock">{{ $ts.openImageInNewTab }}</FormSwitch>
-		<FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ $ts.enableInfiniteScroll }}</FormSwitch>
-		<FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ $ts.useReactionPickerForContextMenu }}</FormSwitch>
-		<FormSwitch v-model="disablePagesScript" class="_formBlock">{{ $ts.disablePagesScript }}</FormSwitch>
+		<template #label>{{ i18n.ts.behavior }}</template>
+		<FormSwitch v-model="imageNewTab" class="_formBlock">{{ i18n.ts.openImageInNewTab }}</FormSwitch>
+		<FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ i18n.ts.enableInfiniteScroll }}</FormSwitch>
+		<FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch>
+		<FormSwitch v-model="disablePagesScript" class="_formBlock">{{ i18n.ts.disablePagesScript }}</FormSwitch>
 
 		<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
-			<template #label>{{ $ts.whenServerDisconnected }}</template>
-			<option value="reload">{{ $ts._serverDisconnectedBehavior.reload }}</option>
-			<option value="dialog">{{ $ts._serverDisconnectedBehavior.dialog }}</option>
-			<option value="quiet">{{ $ts._serverDisconnectedBehavior.quiet }}</option>
+			<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
+			<option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option>
+			<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
+			<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
 		</FormSelect>
 	</FormSection>
 
 	<FormSection>
-		<template #label>{{ $ts.appearance }}</template>
-		<FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ $ts.disableAnimatedMfm }}</FormSwitch>
-		<FormSwitch v-model="reduceAnimation" class="_formBlock">{{ $ts.reduceUiAnimation }}</FormSwitch>
-		<FormSwitch v-model="useBlurEffect" class="_formBlock">{{ $ts.useBlurEffect }}</FormSwitch>
-		<FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ $ts.useBlurEffectForModal }}</FormSwitch>
-		<FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ $ts.showGapBetweenNotesInTimeline }}</FormSwitch>
-		<FormSwitch v-model="loadRawImages" class="_formBlock">{{ $ts.loadRawImages }}</FormSwitch>
-		<FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ $ts.disableShowingAnimatedImages }}</FormSwitch>
-		<FormSwitch v-model="squareAvatars" class="_formBlock">{{ $ts.squareAvatars }}</FormSwitch>
-		<FormSwitch v-model="useSystemFont" class="_formBlock">{{ $ts.useSystemFont }}</FormSwitch>
-		<FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ $ts.useOsNativeEmojis }}
+		<template #label>{{ i18n.ts.appearance }}</template>
+		<FormSwitch v-model="disableAnimatedMfm" class="_formBlock">{{ i18n.ts.disableAnimatedMfm }}</FormSwitch>
+		<FormSwitch v-model="reduceAnimation" class="_formBlock">{{ i18n.ts.reduceUiAnimation }}</FormSwitch>
+		<FormSwitch v-model="useBlurEffect" class="_formBlock">{{ i18n.ts.useBlurEffect }}</FormSwitch>
+		<FormSwitch v-model="useBlurEffectForModal" class="_formBlock">{{ i18n.ts.useBlurEffectForModal }}</FormSwitch>
+		<FormSwitch v-model="showGapBetweenNotesInTimeline" class="_formBlock">{{ i18n.ts.showGapBetweenNotesInTimeline }}</FormSwitch>
+		<FormSwitch v-model="loadRawImages" class="_formBlock">{{ i18n.ts.loadRawImages }}</FormSwitch>
+		<FormSwitch v-model="disableShowingAnimatedImages" class="_formBlock">{{ i18n.ts.disableShowingAnimatedImages }}</FormSwitch>
+		<FormSwitch v-model="squareAvatars" class="_formBlock">{{ i18n.ts.squareAvatars }}</FormSwitch>
+		<FormSwitch v-model="useSystemFont" class="_formBlock">{{ i18n.ts.useSystemFont }}</FormSwitch>
+		<FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ i18n.ts.useOsNativeEmojis }}
 			<div><Mfm :key="useOsNativeEmojis" text="๐Ÿฎ๐Ÿฆ๐Ÿญ๐Ÿฉ๐Ÿฐ๐Ÿซ๐Ÿฌ๐Ÿฅž๐Ÿช"/></div>
 		</FormSwitch>
-		<FormSwitch v-model="disableDrawer" class="_formBlock">{{ $ts.disableDrawer }}</FormSwitch>
+		<FormSwitch v-model="disableDrawer" class="_formBlock">{{ i18n.ts.disableDrawer }}</FormSwitch>
 
 		<FormRadios v-model="fontSize" class="_formBlock">
-			<template #label>{{ $ts.fontSize }}</template>
+			<template #label>{{ i18n.ts.fontSize }}</template>
 			<option value="small"><span style="font-size: 14px;">Aa</span></option>
 			<option :value="null"><span style="font-size: 16px;">Aa</span></option>
 			<option value="large"><span style="font-size: 18px;">Aa</span></option>
@@ -63,36 +63,36 @@
 	</FormSection>
 
 	<FormSection>
-		<FormSwitch v-model="aiChanMode">{{ $ts.aiChanMode }}</FormSwitch>
+		<FormSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</FormSwitch>
 	</FormSection>
 
 	<FormSelect v-model="instanceTicker" class="_formBlock">
-		<template #label>{{ $ts.instanceTicker }}</template>
-		<option value="none">{{ $ts._instanceTicker.none }}</option>
-		<option value="remote">{{ $ts._instanceTicker.remote }}</option>
-		<option value="always">{{ $ts._instanceTicker.always }}</option>
+		<template #label>{{ i18n.ts.instanceTicker }}</template>
+		<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
+		<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
+		<option value="always">{{ i18n.ts._instanceTicker.always }}</option>
 	</FormSelect>
 
 	<FormSelect v-model="nsfw" class="_formBlock">
-		<template #label>{{ $ts.nsfw }}</template>
-		<option value="respect">{{ $ts._nsfw.respect }}</option>
-		<option value="ignore">{{ $ts._nsfw.ignore }}</option>
-		<option value="force">{{ $ts._nsfw.force }}</option>
+		<template #label>{{ i18n.ts.nsfw }}</template>
+		<option value="respect">{{ i18n.ts._nsfw.respect }}</option>
+		<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
+		<option value="force">{{ i18n.ts._nsfw.force }}</option>
 	</FormSelect>
 
 	<FormGroup>
-		<template #label>{{ $ts.defaultNavigationBehaviour }}</template>
-		<FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch>
+		<template #label>{{ i18n.ts.defaultNavigationBehaviour }}</template>
+		<FormSwitch v-model="defaultSideView">{{ i18n.ts.openInSideView }}</FormSwitch>
 	</FormGroup>
 
-	<FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink>
+	<FormLink to="/settings/deck" class="_formBlock">{{ i18n.ts.deck }}</FormLink>
 
-	<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
+	<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed, defineExpose, ref, watch } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormSelect from '@/components/form/select.vue';
 import FormRadios from '@/components/form/radios.vue';
@@ -102,122 +102,87 @@ import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/link.vue';
 import { langs } from '@/config';
 import { defaultStore } from '@/store';
-import { ColdDeviceStorage } from '@/store';
 import * as os from '@/os';
 import { unisonReload } from '@/scripts/unison-reload';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkLink,
-		FormSwitch,
-		FormSelect,
-		FormRadios,
-		FormGroup,
-		FormLink,
-		FormSection,
-	},
+const lang = ref(localStorage.getItem('lang'));
+const fontSize = ref(localStorage.getItem('fontSize'));
+const useSystemFont = ref(localStorage.getItem('useSystemFont') != null);
 
-	emits: ['info'],
+async function reloadAsk() {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.general,
-				icon: 'fas fa-cogs',
-				bg: 'var(--bg)'
-			},
-			langs,
-			lang: localStorage.getItem('lang'),
-			fontSize: localStorage.getItem('fontSize'),
-			useSystemFont: localStorage.getItem('useSystemFont') != null,
-		}
-	},
+	unisonReload();
+}
 
-	computed: {
-		overridedDeviceKind: defaultStore.makeGetterSetter('overridedDeviceKind'),
-		serverDisconnectedBehavior: defaultStore.makeGetterSetter('serverDisconnectedBehavior'),
-		reduceAnimation: defaultStore.makeGetterSetter('animation', v => !v, v => !v),
-		useBlurEffectForModal: defaultStore.makeGetterSetter('useBlurEffectForModal'),
-		useBlurEffect: defaultStore.makeGetterSetter('useBlurEffect'),
-		showGapBetweenNotesInTimeline: defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'),
-		disableAnimatedMfm: defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v),
-		useOsNativeEmojis: defaultStore.makeGetterSetter('useOsNativeEmojis'),
-		disableDrawer: defaultStore.makeGetterSetter('disableDrawer'),
-		disableShowingAnimatedImages: defaultStore.makeGetterSetter('disableShowingAnimatedImages'),
-		loadRawImages: defaultStore.makeGetterSetter('loadRawImages'),
-		imageNewTab: defaultStore.makeGetterSetter('imageNewTab'),
-		nsfw: defaultStore.makeGetterSetter('nsfw'),
-		disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'),
-		showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'),
-		defaultSideView: defaultStore.makeGetterSetter('defaultSideView'),
-		instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
-		enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
-		useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),
-		squareAvatars: defaultStore.makeGetterSetter('squareAvatars'),
-		aiChanMode: defaultStore.makeGetterSetter('aiChanMode'),
-	},
+const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
+const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
+const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
+const useBlurEffectForModal = computed(defaultStore.makeGetterSetter('useBlurEffectForModal'));
+const useBlurEffect = computed(defaultStore.makeGetterSetter('useBlurEffect'));
+const showGapBetweenNotesInTimeline = computed(defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'));
+const disableAnimatedMfm = computed(defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v));
+const useOsNativeEmojis = computed(defaultStore.makeGetterSetter('useOsNativeEmojis'));
+const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
+const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
+const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
+const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
+const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
+const disablePagesScript = computed(defaultStore.makeGetterSetter('disablePagesScript'));
+const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
+const defaultSideView = computed(defaultStore.makeGetterSetter('defaultSideView'));
+const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
+const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
+const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
+const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
+const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
 
-	watch: {
-		lang() {
-			localStorage.setItem('lang', this.lang);
-			localStorage.removeItem('locale');
-			this.reloadAsk();
-		},
+watch(lang, () => {
+	localStorage.setItem('lang', lang.value as string);
+	localStorage.removeItem('locale');
+});
 
-		fontSize() {
-			if (this.fontSize == null) {
-				localStorage.removeItem('fontSize');
-			} else {
-				localStorage.setItem('fontSize', this.fontSize);
-			}
-			this.reloadAsk();
-		},
+watch(fontSize, () => {
+	if (fontSize.value == null) {
+		localStorage.removeItem('fontSize');
+	} else {
+		localStorage.setItem('fontSize', fontSize.value);
+	}
+});
 
-		useSystemFont() {
-			if (this.useSystemFont) {
-				localStorage.setItem('useSystemFont', 't');
-			} else {
-				localStorage.removeItem('useSystemFont');
-			}
-			this.reloadAsk();
-		},
+watch(useSystemFont, () => {
+	if (useSystemFont.value) {
+		localStorage.setItem('useSystemFont', 't');
+	} else {
+		localStorage.removeItem('useSystemFont');
+	}
+});
 
-		enableInfiniteScroll() {
-			this.reloadAsk();
-		},
+watch([
+	lang,
+	fontSize,
+	useSystemFont,
+	enableInfiniteScroll,
+	squareAvatars,
+	aiChanMode,
+	showGapBetweenNotesInTimeline,
+	instanceTicker,
+	overridedDeviceKind
+], async () => {
+	await reloadAsk();
+});
 
-		squareAvatars() {
-			this.reloadAsk();
-		},
-
-		aiChanMode() {
-			this.reloadAsk();
-		},
-
-		showGapBetweenNotesInTimeline() {
-			this.reloadAsk();
-		},
-
-		instanceTicker() {
-			this.reloadAsk();
-		},
-
-		overridedDeviceKind() {
-			this.reloadAsk();
-		},
-	},
-
-	methods: {
-		async reloadAsk() {
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.general,
+		icon: 'fas fa-cogs',
+		bg: 'var(--bg)'
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/import-export.vue b/packages/client/src/pages/settings/import-export.vue
index c153b4d28c..127cbcd4c1 100644
--- a/packages/client/src/pages/settings/import-export.vue
+++ b/packages/client/src/pages/settings/import-export.vue
@@ -37,8 +37,8 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, ref } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import FormSection from '@/components/form/section.vue';
 import FormGroup from '@/components/form/group.vue';
@@ -48,108 +48,80 @@ import { selectFile } from '@/scripts/select-file';
 import * as symbols from '@/symbols';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSection,
-		FormGroup,
-		FormSwitch,
-		MkButton,
-	},
+const excludeMutingUsers = ref(false);
+const excludeInactiveUsers = ref(false);
 
-	emits: ['info'],
+const onExportSuccess = () => {
+	os.alert({
+		type: 'info',
+		text: i18n.ts.exportRequested,
+	});
+};
 
-	setup(props, context) {
-		const INFO = {
-			title: i18n.ts.importAndExport,
-			icon: 'fas fa-boxes',
-			bg: 'var(--bg)',
-		};
+const onImportSuccess = () => {
+	os.alert({
+		type: 'info',
+		text: i18n.ts.importRequested,
+	});
+};
 
-		const excludeMutingUsers = ref(false);
-		const excludeInactiveUsers = ref(false);
+const onError = (ev) => {
+	os.alert({
+		type: 'error',
+		text: ev.message,
+	});
+};
 
-		const onExportSuccess = () => {
-			os.alert({
-				type: 'info',
-				text: i18n.ts.exportRequested,
-			});
-		};
+const exportNotes = () => {
+	os.api('i/export-notes', {}).then(onExportSuccess).catch(onError);
+};
 
-		const onImportSuccess = () => {
-			os.alert({
-				type: 'info',
-				text: i18n.ts.importRequested,
-			});
-		};
+const exportFollowing = () => {
+	os.api('i/export-following', {
+		excludeMuting: excludeMutingUsers.value,
+		excludeInactive: excludeInactiveUsers.value,
+	})
+	.then(onExportSuccess).catch(onError);
+};
 
-		const onError = (e) => {
-			os.alert({
-				type: 'error',
-				text: e.message,
-			});
-		};
+const exportBlocking = () => {
+	os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError);
+};
 
-		const exportNotes = () => {
-			os.api('i/export-notes', {}).then(onExportSuccess).catch(onError);
-		};
+const exportUserLists = () => {
+	os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError);
+};
 
-		const exportFollowing = () => {
-			os.api('i/export-following', {
-				excludeMuting: excludeMutingUsers.value,
-				excludeInactive: excludeInactiveUsers.value,
-			})
-			.then(onExportSuccess).catch(onError);
-		};
+const exportMuting = () => {
+	os.api('i/export-mute', {}).then(onExportSuccess).catch(onError);
+};
 
-		const exportBlocking = () => {
-			os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError);
-		};
+const importFollowing = async (ev) => {
+	const file = await selectFile(ev.currentTarget ?? ev.target);
+	os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
 
-		const exportUserLists = () => {
-			os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError);
-		};
+const importUserLists = async (ev) => {
+	const file = await selectFile(ev.currentTarget ?? ev.target);
+	os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
 
-		const exportMuting = () => {
-			os.api('i/export-mute', {}).then(onExportSuccess).catch(onError);
-		};
+const importMuting = async (ev) => {
+	const file = await selectFile(ev.currentTarget ?? ev.target);
+	os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
 
-		const importFollowing = async (ev) => {
-			const file = await selectFile(ev.currentTarget ?? ev.target);
-			os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError);
-		};
+const importBlocking = async (ev) => {
+	const file = await selectFile(ev.currentTarget ?? ev.target);
+	os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
 
-		const importUserLists = async (ev) => {
-			const file = await selectFile(ev.currentTarget ?? ev.target);
-			os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError);
-		};
-
-		const importMuting = async (ev) => {
-			const file = await selectFile(ev.currentTarget ?? ev.target);
-			os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError);
-		};
-
-		const importBlocking = async (ev) => {
-			const file = await selectFile(ev.currentTarget ?? ev.target);
-			os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
-		};
-
-		return {
-			[symbols.PAGE_INFO]: INFO,
-			excludeMutingUsers,
-			excludeInactiveUsers,
-
-			exportNotes,
-			exportFollowing,
-			exportBlocking,
-			exportUserLists,
-			exportMuting,
-
-			importFollowing,
-			importUserLists,
-			importMuting,
-			importBlocking,
-		};
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.importAndExport,
+		icon: 'fas fa-boxes',
+		bg: 'var(--bg)',
+	}
 });
 </script>
 
diff --git a/packages/client/src/pages/settings/index.vue b/packages/client/src/pages/settings/index.vue
index 44c3be62fe..e6670ea930 100644
--- a/packages/client/src/pages/settings/index.vue
+++ b/packages/client/src/pages/settings/index.vue
@@ -2,19 +2,22 @@
 <MkSpacer :content-max="900" :margin-min="20" :margin-max="32">
 	<div ref="el" class="vvcocwet" :class="{ wide: !narrow }">
 		<div class="header">
-			<div class="title">{{ $ts.settings }}</div>
+			<div class="title">
+				<MkA v-if="narrow" to="/settings">{{ $ts.settings }}</MkA>
+				<template v-else>{{ $ts.settings }}</template>
+			</div>
 			<div v-if="childInfo" class="subtitle">{{ childInfo.title }}</div>
 		</div>
 		<div class="body">
-			<div v-if="!narrow || page == null" class="nav">
+			<div v-if="!narrow || initialPage == null" class="nav">
 				<div class="baaadecd">
 					<MkInfo v-if="emailNotConfigured" warn class="info">{{ $ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ $ts.configure }}</MkA></MkInfo>
-					<MkSuperMenu :def="menuDef" :grid="page == null"></MkSuperMenu>
+					<MkSuperMenu :def="menuDef" :grid="initialPage == null"></MkSuperMenu>
 				</div>
 			</div>
-			<div class="main">
+			<div v-if="!(narrow && initialPage == null)" class="main">
 				<div class="bkzroven">
-					<component :is="component" :ref="el => pageChanged(el)" :key="page" v-bind="pageProps"/>
+					<component :is="component" :ref="el => pageChanged(el)" :key="initialPage" v-bind="pageProps"/>
 				</div>
 			</div>
 		</div>
@@ -23,7 +26,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, defineAsyncComponent, nextTick, onMounted, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 import { i18n } from '@/i18n';
 import MkInfo from '@/components/ui/info.vue';
 import MkSuperMenu from '@/components/ui/super-menu.vue';
@@ -33,6 +36,7 @@ import { unisonReload } from '@/scripts/unison-reload';
 import * as symbols from '@/symbols';
 import { instance } from '@/instance';
 import { $i } from '@/account';
+import { MisskeyNavigator } from '@/scripts/navigate';
 
 const props = defineProps<{
   initialPage?: string
@@ -45,53 +49,61 @@ const indexInfo = {
 	hideHeader: true,
 };
 const INFO = ref(indexInfo);
-const page = ref(props.initialPage);
-const narrow = ref(false);
-const view = ref(null);
 const el = ref<HTMLElement | null>(null);
 const childInfo = ref(null);
+
+const nav = new MisskeyNavigator();
+
+const narrow = ref(false);
+const NARROW_THRESHOLD = 600;
+
+const ro = new ResizeObserver((entries, observer) => {
+	if (entries.length === 0) return;
+	narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
+});
+
 const menuDef = computed(() => [{
 	title: i18n.ts.basicSettings,
 	items: [{
 		icon: 'fas fa-user',
 		text: i18n.ts.profile,
 		to: '/settings/profile',
-		active: page.value === 'profile',
+		active: props.initialPage === 'profile',
 	}, {
 		icon: 'fas fa-lock-open',
 		text: i18n.ts.privacy,
 		to: '/settings/privacy',
-		active: page.value === 'privacy',
+		active: props.initialPage === 'privacy',
 	}, {
 		icon: 'fas fa-laugh',
 		text: i18n.ts.reaction,
 		to: '/settings/reaction',
-		active: page.value === 'reaction',
+		active: props.initialPage === 'reaction',
 	}, {
 		icon: 'fas fa-cloud',
 		text: i18n.ts.drive,
 		to: '/settings/drive',
-		active: page.value === 'drive',
+		active: props.initialPage === 'drive',
 	}, {
 		icon: 'fas fa-bell',
 		text: i18n.ts.notifications,
 		to: '/settings/notifications',
-		active: page.value === 'notifications',
+		active: props.initialPage === 'notifications',
 	}, {
 		icon: 'fas fa-envelope',
 		text: i18n.ts.email,
 		to: '/settings/email',
-		active: page.value === 'email',
+		active: props.initialPage === 'email',
 	}, {
 		icon: 'fas fa-share-alt',
 		text: i18n.ts.integration,
 		to: '/settings/integration',
-		active: page.value === 'integration',
+		active: props.initialPage === 'integration',
 	}, {
 		icon: 'fas fa-lock',
 		text: i18n.ts.security,
 		to: '/settings/security',
-		active: page.value === 'security',
+		active: props.initialPage === 'security',
 	}],
 }, {
 	title: i18n.ts.clientSettings,
@@ -99,27 +111,27 @@ const menuDef = computed(() => [{
 		icon: 'fas fa-cogs',
 		text: i18n.ts.general,
 		to: '/settings/general',
-		active: page.value === 'general',
+		active: props.initialPage === 'general',
 	}, {
 		icon: 'fas fa-palette',
 		text: i18n.ts.theme,
 		to: '/settings/theme',
-		active: page.value === 'theme',
+		active: props.initialPage === 'theme',
 	}, {
 		icon: 'fas fa-list-ul',
 		text: i18n.ts.menu,
 		to: '/settings/menu',
-		active: page.value === 'menu',
+		active: props.initialPage === 'menu',
 	}, {
 		icon: 'fas fa-music',
 		text: i18n.ts.sounds,
 		to: '/settings/sounds',
-		active: page.value === 'sounds',
+		active: props.initialPage === 'sounds',
 	}, {
 		icon: 'fas fa-plug',
 		text: i18n.ts.plugins,
 		to: '/settings/plugin',
-		active: page.value === 'plugin',
+		active: props.initialPage === 'plugin',
 	}],
 }, {
 	title: i18n.ts.otherSettings,
@@ -127,37 +139,37 @@ const menuDef = computed(() => [{
 		icon: 'fas fa-boxes',
 		text: i18n.ts.importAndExport,
 		to: '/settings/import-export',
-		active: page.value === 'import-export',
+		active: props.initialPage === 'import-export',
 	}, {
 		icon: 'fas fa-volume-mute',
 		text: i18n.ts.instanceMute,
 		to: '/settings/instance-mute',
-		active: page.value === 'instance-mute',
+		active: props.initialPage === 'instance-mute',
 	}, {
 		icon: 'fas fa-ban',
 		text: i18n.ts.muteAndBlock,
 		to: '/settings/mute-block',
-		active: page.value === 'mute-block',
+		active: props.initialPage === 'mute-block',
 	}, {
 		icon: 'fas fa-comment-slash',
 		text: i18n.ts.wordMute,
 		to: '/settings/word-mute',
-		active: page.value === 'word-mute',
+		active: props.initialPage === 'word-mute',
 	}, {
 		icon: 'fas fa-key',
 		text: 'API',
 		to: '/settings/api',
-		active: page.value === 'api',
+		active: props.initialPage === 'api',
 	}, {
 		icon: 'fas fa-bolt',
 		text: 'Webhook',
 		to: '/settings/webhook',
-		active: page.value === 'webhook',
+		active: props.initialPage === 'webhook',
 	}, {
 		icon: 'fas fa-ellipsis-h',
 		text: i18n.ts.other,
 		to: '/settings/other',
-		active: page.value === 'other',
+		active: props.initialPage === 'other',
 	}],
 }, {
 	items: [{
@@ -182,8 +194,8 @@ const menuDef = computed(() => [{
 
 const pageProps = ref({});
 const component = computed(() => {
-	if (page.value == null) return null;
-	switch (page.value) {
+	if (props.initialPage == null) return null;
+	switch (props.initialPage) {
 		case 'accounts': return defineAsyncComponent(() => import('./accounts.vue'));
 		case 'profile': return defineAsyncComponent(() => import('./profile.vue'));
 		case 'privacy': return defineAsyncComponent(() => import('./privacy.vue'));
@@ -230,27 +242,41 @@ watch(component, () => {
 
 watch(() => props.initialPage, () => {
 	if (props.initialPage == null && !narrow.value) {
-		page.value = 'profile';
+		nav.push('/settings/profile');
 	} else {
-		page.value = props.initialPage;
 		if (props.initialPage == null) {
 			INFO.value = indexInfo;
 		}
 	}
 });
 
-onMounted(() => {
-	narrow.value = el.value.offsetWidth < 800;
-	if (!narrow.value) {
-		page.value = 'profile';
+watch(narrow, () => {
+	if (props.initialPage == null && !narrow.value) {
+		nav.push('/settings/profile');
 	}
 });
 
+onMounted(() => {
+	ro.observe(el.value);
+
+	narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
+	if (props.initialPage == null && !narrow.value) {
+		nav.push('/settings/profile');
+	}
+});
+
+onUnmounted(() => {
+	ro.disconnect();
+});
+
 const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
 
 const pageChanged = (page) => {
-	if (page == null) return;
-	childInfo.value = page[symbols.PAGE_INFO];
+	if (page == null) {
+		childInfo.value = null;
+	} else {
+		childInfo.value = page[symbols.PAGE_INFO];
+	}
 };
 
 defineExpose({
@@ -267,6 +293,7 @@ defineExpose({
 		font-weight: bold;
 
 		> .title {
+			display: block;
 			width: 34%;
 		}
 
diff --git a/packages/client/src/pages/settings/instance-mute.vue b/packages/client/src/pages/settings/instance-mute.vue
index f84a209b60..bcc2ee85ad 100644
--- a/packages/client/src/pages/settings/instance-mute.vue
+++ b/packages/client/src/pages/settings/instance-mute.vue
@@ -1,67 +1,51 @@
 <template>
 <div class="_formRoot">
-	<MkInfo>{{ $ts._instanceMute.title }}</MkInfo>
+	<MkInfo>{{ i18n.ts._instanceMute.title }}</MkInfo>
 	<FormTextarea v-model="instanceMutes" class="_formBlock">
-		<template #label>{{ $ts._instanceMute.heading }}</template>
-		<template #caption>{{ $ts._instanceMute.instanceMuteDescription }}<br>{{ $ts._instanceMute.instanceMuteDescription2 }}</template>
+		<template #label>{{ i18n.ts._instanceMute.heading }}</template>
+		<template #caption>{{ i18n.ts._instanceMute.instanceMuteDescription }}<br>{{ i18n.ts._instanceMute.instanceMuteDescription2 }}</template>
 	</FormTextarea>
-	<MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
+	<MkButton primary :disabled="!changed" class="_formBlock" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
-<script>
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import MkInfo from '@/components/ui/info.vue';
 import MkButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		FormTextarea,
-		MkInfo,
-	},
+const instanceMutes = ref($i!.mutedInstances.join('\n'));
+const changed = ref(false);
 
-	emits: ['info'],
+async function save() {
+	let mutes = instanceMutes.value
+		.trim().split('\n')
+		.map(el => el.trim())
+		.filter(el => el);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.instanceMute,
-				icon: 'fas fa-volume-mute'
-			},
-			tab: 'soft',
-			instanceMutes: '',
-			changed: false,
-		}
-	},
+	await os.api('i/update', {
+		mutedInstances: mutes,
+	});
 
-	watch: {
-		instanceMutes: {
-			handler() {
-				this.changed = true;
-			},
-			deep: true
-		},
-	},
+	changed.value = false;
 
-	async created() {
-		this.instanceMutes = this.$i.mutedInstances.join('\n');
-	},
+	// Refresh filtered list to signal to the user how they've been saved
+	instanceMutes.value = mutes.join('\n');
+}
 
-	methods: {
-		async save() {
-			let mutes = this.instanceMutes.trim().split('\n').map(el => el.trim()).filter(el => el);
-			await os.api('i/update', {
-				mutedInstances: mutes,
-			});
-			this.changed = false;
+watch(instanceMutes, () => {
+	changed.value = true;
+});
 
-			// Refresh filtered list to signal to the user how they've been saved
-			this.instanceMutes = mutes.join('\n');
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.instanceMute,
+		icon: 'fas fa-volume-mute'
 	}
-})
+});
 </script>
diff --git a/packages/client/src/pages/settings/integration.vue b/packages/client/src/pages/settings/integration.vue
index ca36c91665..75c6200944 100644
--- a/packages/client/src/pages/settings/integration.vue
+++ b/packages/client/src/pages/settings/integration.vue
@@ -1,133 +1,98 @@
 <template>
 <div class="_formRoot">
-	<FormSection v-if="enableTwitterIntegration">
+	<FormSection v-if="instance.enableTwitterIntegration">
 		<template #label><i class="fab fa-twitter"></i> Twitter</template>
-		<p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
-		<MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ $ts.disconnectService }}</MkButton>
-		<MkButton v-else primary @click="connectTwitter">{{ $ts.connectService }}</MkButton>
+		<p v-if="integrations.twitter">{{ i18n.ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
+		<MkButton v-if="integrations.twitter" danger @click="disconnectTwitter">{{ i18n.ts.disconnectService }}</MkButton>
+		<MkButton v-else primary @click="connectTwitter">{{ i18n.ts.connectService }}</MkButton>
 	</FormSection>
 
-	<FormSection v-if="enableDiscordIntegration">
+	<FormSection v-if="instance.enableDiscordIntegration">
 		<template #label><i class="fab fa-discord"></i> Discord</template>
-		<p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
-		<MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ $ts.disconnectService }}</MkButton>
-		<MkButton v-else primary @click="connectDiscord">{{ $ts.connectService }}</MkButton>
+		<p v-if="integrations.discord">{{ i18n.ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
+		<MkButton v-if="integrations.discord" danger @click="disconnectDiscord">{{ i18n.ts.disconnectService }}</MkButton>
+		<MkButton v-else primary @click="connectDiscord">{{ i18n.ts.connectService }}</MkButton>
 	</FormSection>
 
-	<FormSection v-if="enableGithubIntegration">
+	<FormSection v-if="instance.enableGithubIntegration">
 		<template #label><i class="fab fa-github"></i> GitHub</template>
-		<p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
-		<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ $ts.disconnectService }}</MkButton>
-		<MkButton v-else primary @click="connectGithub">{{ $ts.connectService }}</MkButton>
+		<p v-if="integrations.github">{{ i18n.ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
+		<MkButton v-if="integrations.github" danger @click="disconnectGithub">{{ i18n.ts.disconnectService }}</MkButton>
+		<MkButton v-else primary @click="connectGithub">{{ i18n.ts.connectService }}</MkButton>
 	</FormSection>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed, defineExpose, onMounted, ref, watch } from 'vue';
 import { apiUrl } from '@/config';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/ui/button.vue';
-import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSection,
-		MkButton
-	},
+const twitterForm = ref<Window | null>(null);
+const discordForm = ref<Window | null>(null);
+const githubForm = ref<Window | null>(null);
 
-	emits: ['info'],
+const integrations = computed(() => $i!.integrations);
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.integration,
-				icon: 'fas fa-share-alt',
-				bg: 'var(--bg)',
-			},
-			apiUrl,
-			twitterForm: null,
-			discordForm: null,
-			githubForm: null,
-			enableTwitterIntegration: false,
-			enableDiscordIntegration: false,
-			enableGithubIntegration: false,
-		};
-	},
+function openWindow(service: string, type: string) {
+	return window.open(`${apiUrl}/${type}/${service}`,
+		`${service}_${type}_window`,
+		'height=570, width=520'
+	);
+}
 
-	computed: {
-		integrations() {
-			return this.$i.integrations;
-		},
-		
-		meta() {
-			return this.$instance;
-		},
-	},
+function connectTwitter() {
+	twitterForm.value = openWindow('twitter', 'connect');
+}
 
-	created() {
-		this.enableTwitterIntegration = this.meta.enableTwitterIntegration;
-		this.enableDiscordIntegration = this.meta.enableDiscordIntegration;
-		this.enableGithubIntegration = this.meta.enableGithubIntegration;
-	},
+function disconnectTwitter() {
+	openWindow('twitter', 'disconnect');
+}
 
-	mounted() {
-		document.cookie = `igi=${this.$i.token}; path=/;` +
-			` max-age=31536000;` +
-			(document.location.protocol.startsWith('https') ? ' secure' : '');
+function connectDiscord() {
+	discordForm.value = openWindow('discord', 'connect');
+}
 
-		this.$watch('integrations', () => {
-			if (this.integrations.twitter) {
-				if (this.twitterForm) this.twitterForm.close();
-			}
-			if (this.integrations.discord) {
-				if (this.discordForm) this.discordForm.close();
-			}
-			if (this.integrations.github) {
-				if (this.githubForm) this.githubForm.close();
-			}
-		}, {
-			deep: true
-		});
-	},
+function disconnectDiscord() {
+	openWindow('discord', 'disconnect');
+}
 
-	methods: {
-		connectTwitter() {
-			this.twitterForm = window.open(apiUrl + '/connect/twitter',
-				'twitter_connect_window',
-				'height=570, width=520');
-		},
+function connectGithub() {
+	githubForm.value = openWindow('github', 'connect');
+}
 
-		disconnectTwitter() {
-			window.open(apiUrl + '/disconnect/twitter',
-				'twitter_disconnect_window',
-				'height=570, width=520');
-		},
+function disconnectGithub() {
+	openWindow('github', 'disconnect');
+}
 
-		connectDiscord() {
-			this.discordForm = window.open(apiUrl + '/connect/discord',
-				'discord_connect_window',
-				'height=570, width=520');
-		},
+onMounted(() => {
+	document.cookie = `igi=${$i!.token}; path=/;` +
+		` max-age=31536000;` +
+		(document.location.protocol.startsWith('https') ? ' secure' : '');
 
-		disconnectDiscord() {
-			window.open(apiUrl + '/disconnect/discord',
-				'discord_disconnect_window',
-				'height=570, width=520');
-		},
+	watch(integrations, () => {
+		if (integrations.value.twitter) {
+			if (twitterForm.value) twitterForm.value.close();
+		}
+		if (integrations.value.discord) {
+			if (discordForm.value) discordForm.value.close();
+		}
+		if (integrations.value.github) {
+			if (githubForm.value) githubForm.value.close();
+		}
+	});
+});
 
-		connectGithub() {
-			this.githubForm = window.open(apiUrl + '/connect/github',
-				'github_connect_window',
-				'height=570, width=520');
-		},
-
-		disconnectGithub() {
-			window.open(apiUrl + '/disconnect/github',
-				'github_disconnect_window',
-				'height=570, width=520');
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.integration,
+		icon: 'fas fa-share-alt',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/menu.vue b/packages/client/src/pages/settings/menu.vue
index 6e38cd5dfe..2288c3f718 100644
--- a/packages/client/src/pages/settings/menu.vue
+++ b/packages/client/src/pages/settings/menu.vue
@@ -1,24 +1,24 @@
 <template>
 <div class="_formRoot">
 	<FormTextarea v-model="items" tall manual-save class="_formBlock">
-		<template #label>{{ $ts.menu }}</template>
-		<template #caption><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template>
+		<template #label>{{ i18n.ts.menu }}</template>
+		<template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template>
 	</FormTextarea>
 
 	<FormRadios v-model="menuDisplay" class="_formBlock">
-		<template #label>{{ $ts.display }}</template>
-		<option value="sideFull">{{ $ts._menuDisplay.sideFull }}</option>
-		<option value="sideIcon">{{ $ts._menuDisplay.sideIcon }}</option>
-		<option value="top">{{ $ts._menuDisplay.top }}</option>
-		<!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ $ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: ใ‚ตใ‚คใƒ‰ใƒใƒผใ‚’ๅฎŒๅ…จใซ้š ใ›ใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹ใจใ€ๅˆฅ้€”ใƒใƒณใƒใƒผใ‚ฌใƒผใƒœใ‚ฟใƒณใฎใ‚ˆใ†ใชใ‚‚ใฎใ‚’UIใซ่กจ็คบใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Š้ขๅ€’ -->
+		<template #label>{{ i18n.ts.display }}</template>
+		<option value="sideFull">{{ i18n.ts._menuDisplay.sideFull }}</option>
+		<option value="sideIcon">{{ i18n.ts._menuDisplay.sideIcon }}</option>
+		<option value="top">{{ i18n.ts._menuDisplay.top }}</option>
+		<!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: ใ‚ตใ‚คใƒ‰ใƒใƒผใ‚’ๅฎŒๅ…จใซ้š ใ›ใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹ใจใ€ๅˆฅ้€”ใƒใƒณใƒใƒผใ‚ฌใƒผใƒœใ‚ฟใƒณใฎใ‚ˆใ†ใชใ‚‚ใฎใ‚’UIใซ่กจ็คบใ™ใ‚‹ๅฟ…่ฆใŒใ‚ใ‚Š้ขๅ€’ -->
 	</FormRadios>
 
-	<FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton>
+	<FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed, defineExpose, ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormRadios from '@/components/form/radios.vue';
 import FormButton from '@/components/ui/button.vue';
@@ -27,81 +27,60 @@ import { menuDef } from '@/menu';
 import { defaultStore } from '@/store';
 import * as symbols from '@/symbols';
 import { unisonReload } from '@/scripts/unison-reload';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormButton,
-		FormTextarea,
-		FormRadios,
-	},
+const items = ref(defaultStore.state.menu.join('\n'));
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.menu,
-				icon: 'fas fa-list-ul',
-				bg: 'var(--bg)',
-			},
-			menuDef: menuDef,
-			items: defaultStore.state.menu.join('\n'),
-		}
-	},
+const split = computed(() => items.value.trim().split('\n').filter(x => x.trim() !== ''));
+const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
 
-	computed: {
-		splited(): string[] {
-			return this.items.trim().split('\n').filter(x => x.trim() !== '');
-		},
+async function reloadAsk() {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting
+	});
+	if (canceled) return;
 
-		menuDisplay: defaultStore.makeGetterSetter('menuDisplay')
-	},
+	unisonReload();
+}
 
-	watch: {
-		menuDisplay() {
-			this.reloadAsk();
-		},
+async function addItem() {
+	const menu = Object.keys(menuDef).filter(k => !defaultStore.state.menu.includes(k));
+	const { canceled, result: item } = await os.select({
+		title: i18n.ts.addItem,
+		items: [...menu.map(k => ({
+			value: k, text: i18n.ts[menuDef[k].title]
+		})), {
+			value: '-', text: i18n.ts.divider
+		}]
+	});
+	if (canceled) return;
+	items.value = [...split.value, item].join('\n');
+}
 
-		items() {
-			this.save();
-		},
-	},
+async function save() {
+	defaultStore.set('menu', split.value);
+	await reloadAsk();
+}
 
-	methods: {
-		async addItem() {
-			const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.menu.includes(k));
-			const { canceled, result: item } = await os.select({
-				title: this.$ts.addItem,
-				items: [...menu.map(k => ({
-					value: k, text: this.$ts[this.menuDef[k].title]
-				})), ...[{
-					value: '-', text: this.$ts.divider
-				}]]
-			});
-			if (canceled) return;
-			this.items = [...this.splited, item].join('\n');
-		},
+function reset() {
+	defaultStore.reset('menu');
+	items.value = defaultStore.state.menu.join('\n');
+}
 
-		save() {
-			this.$store.set('menu', this.splited);
-			this.reloadAsk();
-		},
+watch(items, async () => {
+	await save();
+});
 
-		reset() {
-			this.$store.reset('menu');
-			this.items = this.$store.state.menu.join('\n');
-		},
+watch(menuDisplay, async () => {
+	await reloadAsk();
+});
 
-		async reloadAsk() {
-			const { canceled } = await os.confirm({
-				type: 'info',
-				text: this.$ts.reloadToApplySetting,
-				showCancelButton: true
-			});
-			if (canceled) return;
-
-			unisonReload();
-		}
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.menu,
+		icon: 'fas fa-list-ul',
+		bg: 'var(--bg)',
+	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/notifications.vue b/packages/client/src/pages/settings/notifications.vue
index 12171530bb..b8fff95a8d 100644
--- a/packages/client/src/pages/settings/notifications.vue
+++ b/packages/client/src/pages/settings/notifications.vue
@@ -1,71 +1,59 @@
 <template>
 <div class="_formRoot">
-	<FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ $ts.notificationSetting }}</FormLink>
+	<FormLink class="_formBlock" @click="configure"><template #icon><i class="fas fa-cog"></i></template>{{ i18n.ts.notificationSetting }}</FormLink>
 	<FormSection>
-		<FormLink class="_formBlock" @click="readAllNotifications">{{ $ts.markAsReadAllNotifications }}</FormLink>
-		<FormLink class="_formBlock" @click="readAllUnreadNotes">{{ $ts.markAsReadAllUnreadNotes }}</FormLink>
-		<FormLink class="_formBlock" @click="readAllMessagingMessages">{{ $ts.markAsReadAllTalkMessages }}</FormLink>
+		<FormLink class="_formBlock" @click="readAllNotifications">{{ i18n.ts.markAsReadAllNotifications }}</FormLink>
+		<FormLink class="_formBlock" @click="readAllUnreadNotes">{{ i18n.ts.markAsReadAllUnreadNotes }}</FormLink>
+		<FormLink class="_formBlock" @click="readAllMessagingMessages">{{ i18n.ts.markAsReadAllTalkMessages }}</FormLink>
 	</FormSection>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent, defineExpose } from 'vue';
 import FormButton from '@/components/ui/button.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import { notificationTypes } from 'misskey-js';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormLink,
-		FormButton,
-		FormSection,
-	},
+async function readAllUnreadNotes() {
+	await os.api('i/read-all-unread-notes');
+}
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.notifications,
-				icon: 'fas fa-bell',
-				bg: 'var(--bg)',
-			},
+async function readAllMessagingMessages() {
+	await os.api('i/read-all-messaging-messages');
+}
+
+async function readAllNotifications() {
+	await os.api('notifications/mark-all-as-read');
+}
+
+function configure() {
+	const includingTypes = notificationTypes.filter(x => !$i!.mutingNotificationTypes.includes(x));
+	os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
+		includingTypes,
+		showGlobalToggle: false,
+	}, {
+		done: async (res) => {
+			const { includingTypes: value } = res;
+			await os.apiWithDialog('i/update', {
+				mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)),
+			}).then(i => {
+				$i!.mutingNotificationTypes = i.mutingNotificationTypes;
+			});
 		}
-	},
+	}, 'closed');
+}
 
-	methods: {
-		readAllUnreadNotes() {
-			os.api('i/read-all-unread-notes');
-		},
-
-		readAllMessagingMessages() {
-			os.api('i/read-all-messaging-messages');
-		},
-
-		readAllNotifications() {
-			os.api('notifications/mark-all-as-read');
-		},
-
-		configure() {
-			const includingTypes = notificationTypes.filter(x => !this.$i.mutingNotificationTypes.includes(x));
-			os.popup(import('@/components/notification-setting-window.vue'), {
-				includingTypes,
-				showGlobalToggle: false,
-			}, {
-				done: async (res) => {
-					const { includingTypes: value } = res;
-					await os.apiWithDialog('i/update', {
-						mutingNotificationTypes: notificationTypes.filter(x => !value.includes(x)),
-					}).then(i => {
-						this.$i.mutingNotificationTypes = i.mutingNotificationTypes;
-					});
-				}
-			}, 'closed');
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.notifications,
+		icon: 'fas fa-bell',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/other.vue b/packages/client/src/pages/settings/other.vue
index a9903acc7e..82e174a5b4 100644
--- a/packages/client/src/pages/settings/other.vue
+++ b/packages/client/src/pages/settings/other.vue
@@ -1,66 +1,44 @@
 <template>
 <div class="_formRoot">
-	<FormSwitch :value="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote">
-		{{ $ts.showFeaturedNotesInTimeline }}
+	<FormSwitch v-model="$i.injectFeaturedNote" class="_formBlock" @update:modelValue="onChangeInjectFeaturedNote">
+		{{ i18n.ts.showFeaturedNotesInTimeline }}
 	</FormSwitch>
 
 	<!--
-	<FormSwitch v-model="reportError" class="_formBlock">{{ $ts.sendErrorReports }}<template #caption>{{ $ts.sendErrorReportsDescription }}</template></FormSwitch>
+	<FormSwitch v-model="reportError" class="_formBlock">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></FormSwitch>
 	-->
 
-	<FormLink to="/settings/account-info" class="_formBlock">{{ $ts.accountInfo }}</FormLink>
+	<FormLink to="/settings/account-info" class="_formBlock">{{ i18n.ts.accountInfo }}</FormLink>
 
-	<FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ $ts.closeAccount }}</FormLink>
+	<FormLink to="/settings/delete-account" class="_formBlock"><template #icon><i class="fas fa-exclamation-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink>
 </div>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed, defineExpose } from 'vue';
 import FormSwitch from '@/components/form/switch.vue';
-import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import * as os from '@/os';
-import { debug } from '@/config';
 import { defaultStore } from '@/store';
-import { unisonReload } from '@/scripts/unison-reload';
 import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSection,
-		FormSwitch,
-		FormLink,
-	},
+const reportError = computed(defaultStore.makeGetterSetter('reportError'));
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.other,
-				icon: 'fas fa-ellipsis-h',
-				bg: 'var(--bg)',
-			},
-			debug,
-		}
-	},
+function onChangeInjectFeaturedNote(v) {
+	os.api('i/update', {
+		injectFeaturedNote: v
+	}).then((i) => {
+		$i!.injectFeaturedNote = i.injectFeaturedNote;
+	});
+}
 
-	computed: {
-		reportError: defaultStore.makeGetterSetter('reportError'),
-	},
-
-	methods: {
-		changeDebug(v) {
-			console.log(v);
-			localStorage.setItem('debug', v.toString());
-			unisonReload();
-		},
-
-		onChangeInjectFeaturedNote(v) {
-			os.api('i/update', {
-				injectFeaturedNote: v
-			});
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.other,
+		icon: 'fas fa-ellipsis-h',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/plugin.install.vue b/packages/client/src/pages/settings/plugin.install.vue
index d35d20d17a..96c0abfd99 100644
--- a/packages/client/src/pages/settings/plugin.install.vue
+++ b/packages/client/src/pages/settings/plugin.install.vue
@@ -1,19 +1,19 @@
 <template>
 <div class="_formRoot">
-	<FormInfo warn class="_formBlock">{{ $ts._plugin.installWarn }}</FormInfo>
+	<FormInfo warn class="_formBlock">{{ i18n.ts._plugin.installWarn }}</FormInfo>
 
 	<FormTextarea v-model="code" tall class="_formBlock">
-		<template #label>{{ $ts.code }}</template>
+		<template #label>{{ i18n.ts.code }}</template>
 	</FormTextarea>
 
 	<div class="_formBlock">
-		<FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ $ts.install }}</FormButton>
+		<FormButton :disabled="code == null" primary inline @click="install"><i class="fas fa-check"></i> {{ i18n.ts.install }}</FormButton>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, defineAsyncComponent, nextTick, ref } from 'vue';
 import { AiScript, parse } from '@syuilo/aiscript';
 import { serialize } from '@syuilo/aiscript/built/serializer';
 import { v4 as uuid } from 'uuid';
@@ -23,111 +23,101 @@ import FormInfo from '@/components/ui/info.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
 import { unisonReload } from '@/scripts/unison-reload';
+import { i18n } from '@/i18n';
 import * as symbols from '@/symbols';
 
-export default defineComponent({
-	components: {
-		FormTextarea,
-		FormButton,
-		FormInfo,
-	},
+const code = ref(null);
 
-	emits: ['info'],
+function installPlugin({ id, meta, ast, token }) {
+	ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
+		...meta,
+		id,
+		active: true,
+		configData: {},
+		token: token,
+		ast: ast
+	}));
+}
 
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts._plugin.install,
-				icon: 'fas fa-download',
-				bg: 'var(--bg)',
-			},
-			code: null,
-		}
-	},
+async function install() {
+	let ast;
+	try {
+		ast = parse(code.value);
+	} catch (err) {
+		os.alert({
+			type: 'error',
+			text: 'Syntax error :('
+		});
+		return;
+	}
 
-	methods: {
-		installPlugin({ id, meta, ast, token }) {
-			ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
-				...meta,
-				id,
-				active: true,
-				configData: {},
-				token: token,
-				ast: ast
-			}));
+	const meta = AiScript.collectMetadata(ast);
+	if (meta == null) {
+		os.alert({
+			type: 'error',
+			text: 'No metadata found :('
+		});
+		return;
+	}
+
+	const metadata = meta.get(null);
+	if (metadata == null) {
+		os.alert({
+			type: 'error',
+			text: 'No metadata found :('
+		});
+		return;
+	}
+
+	const { name, version, author, description, permissions, config } = metadata;
+	if (name == null || version == null || author == null) {
+		os.alert({
+			type: 'error',
+			text: 'Required property not found :('
+		});
+		return;
+	}
+
+	const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
+		os.popup(defineAsyncComponent(() => import('@/components/token-generate-window.vue')), {
+			title: i18n.ts.tokenRequested,
+			information: i18n.ts.pluginTokenRequestedDescription,
+			initialName: name,
+			initialPermissions: permissions
+		}, {
+			done: async result => {
+				const { name, permissions } = result;
+				const { token } = await os.api('miauth/gen-token', {
+					session: null,
+					name: name,
+					permission: permissions,
+				});
+				res(token);
+			}
+		}, 'closed');
+	});
+
+	installPlugin({
+		id: uuid(),
+		meta: {
+			name, version, author, description, permissions, config
 		},
+		token,
+		ast: serialize(ast)
+	});
 
-		async install() {
-			let ast;
-			try {
-				ast = parse(this.code);
-			} catch (e) {
-				os.alert({
-					type: 'error',
-					text: 'Syntax error :('
-				});
-				return;
-			}
-			const meta = AiScript.collectMetadata(ast);
-			if (meta == null) {
-				os.alert({
-					type: 'error',
-					text: 'No metadata found :('
-				});
-				return;
-			}
-			const data = meta.get(null);
-			if (data == null) {
-				os.alert({
-					type: 'error',
-					text: 'No metadata found :('
-				});
-				return;
-			}
-			const { name, version, author, description, permissions, config } = data;
-			if (name == null || version == null || author == null) {
-				os.alert({
-					type: 'error',
-					text: 'Required property not found :('
-				});
-				return;
-			}
+	os.success();
 
-			const token = permissions == null || permissions.length === 0 ? null : await new Promise((res, rej) => {
-				os.popup(import('@/components/token-generate-window.vue'), {
-					title: this.$ts.tokenRequested,
-					information: this.$ts.pluginTokenRequestedDescription,
-					initialName: name,
-					initialPermissions: permissions
-				}, {
-					done: async result => {
-						const { name, permissions } = result;
-						const { token } = await os.api('miauth/gen-token', {
-							session: null,
-							name: name,
-							permission: permissions,
-						});
+	nextTick(() => {
+		unisonReload();
+	});
+}
 
-						res(token);
-					}
-				}, 'closed');
-			});
-
-			this.installPlugin({
-				id: uuid(),
-				meta: {
-					name, version, author, description, permissions, config
-				},
-				token,
-				ast: serialize(ast)
-			});
-
-			os.success();
-
-			this.$nextTick(() => {
-				unisonReload();
-			});
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts._plugin.install,
+		icon: 'fas fa-download',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/plugin.vue b/packages/client/src/pages/settings/plugin.vue
index 7a3ab9d152..873a022cbc 100644
--- a/packages/client/src/pages/settings/plugin.vue
+++ b/packages/client/src/pages/settings/plugin.vue
@@ -1,38 +1,38 @@
 <template>
 <div class="_formRoot">
-	<FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._plugin.install }}</FormLink>
+	<FormLink to="/settings/plugin/install"><template #icon><i class="fas fa-download"></i></template>{{ i18n.ts._plugin.install }}</FormLink>
 
 	<FormSection>
-		<template #label>{{ $ts.manage }}</template>
+		<template #label>{{ i18n.ts.manage }}</template>
 		<div v-for="plugin in plugins" :key="plugin.id" class="_formBlock _panel" style="padding: 20px;">
 			<span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
 
-			<FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ $ts.makeActive }}</FormSwitch>
+			<FormSwitch class="_formBlock" :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</FormSwitch>
 
 			<MkKeyValue class="_formBlock">
-				<template #key>{{ $ts.author }}</template>
+				<template #key>{{ i18n.ts.author }}</template>
 				<template #value>{{ plugin.author }}</template>
 			</MkKeyValue>
 			<MkKeyValue class="_formBlock">
-				<template #key>{{ $ts.description }}</template>
+				<template #key>{{ i18n.ts.description }}</template>
 				<template #value>{{ plugin.description }}</template>
 			</MkKeyValue>
 			<MkKeyValue class="_formBlock">
-				<template #key>{{ $ts.permission }}</template>
+				<template #key>{{ i18n.ts.permission }}</template>
 				<template #value>{{ plugin.permission }}</template>
 			</MkKeyValue>
 
 			<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
-				<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ $ts.settings }}</MkButton>
-				<MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</MkButton>
+				<MkButton v-if="plugin.config" inline @click="config(plugin)"><i class="fas fa-cog"></i> {{ i18n.ts.settings }}</MkButton>
+				<MkButton inline danger @click="uninstall(plugin)"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</MkButton>
 			</div>
 		</div>
 	</FormSection>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, nextTick, ref } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import FormSwitch from '@/components/form/switch.vue';
 import FormSection from '@/components/form/section.vue';
@@ -41,67 +41,54 @@ import MkKeyValue from '@/components/key-value.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
 import * as symbols from '@/symbols';
+import { unisonReload } from '@/scripts/unison-reload';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormLink,
-		FormSwitch,
-		FormSection,
-		MkButton,
-		MkKeyValue,
-	},
+const plugins = ref(ColdDeviceStorage.get('plugins'));
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.plugins,
-				icon: 'fas fa-plug',
-				bg: 'var(--bg)',
-			},
-			plugins: ColdDeviceStorage.get('plugins'),
-		}
-	},
+function uninstall(plugin) {
+	ColdDeviceStorage.set('plugins', plugins.value.filter(x => x.id !== plugin.id));
+	os.success();
+	nextTick(() => {
+		unisonReload();
+	});
+}
 
-	methods: {
-		uninstall(plugin) {
-			ColdDeviceStorage.set('plugins', this.plugins.filter(x => x.id !== plugin.id));
-			os.success();
-			this.$nextTick(() => {
-				unisonReload();
-			});
-		},
+// TODO: ใ“ใฎๅ‡ฆ็†ใ‚’storeๅดใซactionใจใ—ใฆ็งปๅ‹•ใ—ใ€่จญๅฎš็”ป้ขใ‚’้–‹ใAiScriptAPIใ‚’ๅฎŸ่ฃ…ใงใใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹
+async function config(plugin) {
+	const config = plugin.config;
+	for (const key in plugin.configData) {
+		config[key].default = plugin.configData[key];
+	}
 
-		// TODO: ใ“ใฎๅ‡ฆ็†ใ‚’storeๅดใซactionใจใ—ใฆ็งปๅ‹•ใ—ใ€่จญๅฎš็”ป้ขใ‚’้–‹ใAiScriptAPIใ‚’ๅฎŸ่ฃ…ใงใใ‚‹ใ‚ˆใ†ใซใ™ใ‚‹
-		async config(plugin) {
-			const config = plugin.config;
-			for (const key in plugin.configData) {
-				config[key].default = plugin.configData[key];
-			}
+	const { canceled, result } = await os.form(plugin.name, config);
+	if (canceled) return;
 
-			const { canceled, result } = await os.form(plugin.name, config);
-			if (canceled) return;
+	const coldPlugins = ColdDeviceStorage.get('plugins');
+	coldPlugins.find(p => p.id === plugin.id)!.configData = result;
+	ColdDeviceStorage.set('plugins', coldPlugins);
 
-			const plugins = ColdDeviceStorage.get('plugins');
-			plugins.find(p => p.id === plugin.id).configData = result;
-			ColdDeviceStorage.set('plugins', plugins);
+	nextTick(() => {
+		location.reload();
+	});
+}
 
-			this.$nextTick(() => {
-				location.reload();
-			});
-		},
+function changeActive(plugin, active) {
+	const coldPlugins = ColdDeviceStorage.get('plugins');
+	coldPlugins.find(p => p.id === plugin.id)!.active = active;
+	ColdDeviceStorage.set('plugins', coldPlugins);
 
-		changeActive(plugin, active) {
-			const plugins = ColdDeviceStorage.get('plugins');
-			plugins.find(p => p.id === plugin.id).active = active;
-			ColdDeviceStorage.set('plugins', plugins);
+	nextTick(() => {
+		location.reload();
+	});
+}
 
-			this.$nextTick(() => {
-				location.reload();
-			});
-		}
-	},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.plugins,
+		icon: 'fas fa-plug',
+		bg: 'var(--bg)',
+	}
 });
 </script>
 
diff --git a/packages/client/src/pages/settings/profile.vue b/packages/client/src/pages/settings/profile.vue
index e991d725b6..b64dc93cc7 100644
--- a/packages/client/src/pages/settings/profile.vue
+++ b/packages/client/src/pages/settings/profile.vue
@@ -62,7 +62,7 @@
 </template>
 
 <script lang="ts" setup>
-import { defineComponent, reactive, watch } from 'vue';
+import { reactive, watch } from 'vue';
 import MkButton from '@/components/ui/button.vue';
 import FormInput from '@/components/form/input.vue';
 import FormTextarea from '@/components/form/textarea.vue';
@@ -132,8 +132,21 @@ function save() {
 
 function changeAvatar(ev) {
 	selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
+		let originalOrCropped = file;
+
+		const { canceled } = await os.confirm({
+			type: 'question',
+			text: i18n.t('cropImageAsk'),
+		});
+
+		if (!canceled) {
+			originalOrCropped = await os.cropImage(file, {
+				aspectRatio: 1,
+			});
+		}
+
 		const i = await os.apiWithDialog('i/update', {
-			avatarId: file.id,
+			avatarId: originalOrCropped.id,
 		});
 		$i.avatarId = i.avatarId;
 		$i.avatarUrl = i.avatarUrl;
@@ -142,8 +155,21 @@ function changeAvatar(ev) {
 
 function changeBanner(ev) {
 	selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
+		let originalOrCropped = file;
+
+		const { canceled } = await os.confirm({
+			type: 'question',
+			text: i18n.t('cropImageAsk'),
+		});
+
+		if (!canceled) {
+			originalOrCropped = await os.cropImage(file, {
+				aspectRatio: 2,
+			});
+		}
+
 		const i = await os.apiWithDialog('i/update', {
-			bannerId: file.id,
+			bannerId: originalOrCropped.id,
 		});
 		$i.bannerId = i.bannerId;
 		$i.bannerUrl = i.bannerUrl;
diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue
index a188ba353d..963ac81dfa 100644
--- a/packages/client/src/pages/settings/reaction.vue
+++ b/packages/client/src/pages/settings/reaction.vue
@@ -54,7 +54,7 @@
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { defineAsyncComponent, watch } from 'vue';
 import XDraggable from 'vuedraggable';
 import FormInput from '@/components/form/input.vue';
 import FormRadios from '@/components/form/radios.vue';
@@ -88,7 +88,7 @@ function remove(reaction, ev: MouseEvent) {
 }
 
 function preview(ev: MouseEvent) {
-	os.popup(import('@/components/emoji-picker-dialog.vue'), {
+	os.popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
 		asReactionPicker: true,
 		src: ev.currentTarget ?? ev.target,
 	}, {}, 'closed');
diff --git a/packages/client/src/pages/settings/security.vue b/packages/client/src/pages/settings/security.vue
index 6fb3f1c413..401648790a 100644
--- a/packages/client/src/pages/settings/security.vue
+++ b/packages/client/src/pages/settings/security.vue
@@ -1,17 +1,17 @@
 <template>
 <div class="_formRoot">
 	<FormSection>
-		<template #label>{{ $ts.password }}</template>
-		<FormButton primary @click="change()">{{ $ts.changePassword }}</FormButton>
+		<template #label>{{ i18n.ts.password }}</template>
+		<FormButton primary @click="change()">{{ i18n.ts.changePassword }}</FormButton>
 	</FormSection>
 
 	<FormSection>
-		<template #label>{{ $ts.twoStepAuthentication }}</template>
+		<template #label>{{ i18n.ts.twoStepAuthentication }}</template>
 		<X2fa/>
 	</FormSection>
 	
 	<FormSection>
-		<template #label>{{ $ts.signinHistory }}</template>
+		<template #label>{{ i18n.ts.signinHistory }}</template>
 		<MkPagination :pagination="pagination">
 			<template v-slot="{items}">
 				<div>
@@ -30,15 +30,15 @@
 
 	<FormSection>
 		<FormSlot>
-			<FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ $ts.regenerateLoginToken }}</FormButton>
-			<template #caption>{{ $ts.regenerateLoginTokenDescription }}</template>
+			<FormButton danger @click="regenerateToken"><i class="fas fa-sync-alt"></i> {{ i18n.ts.regenerateLoginToken }}</FormButton>
+			<template #caption>{{ i18n.ts.regenerateLoginTokenDescription }}</template>
 		</FormSlot>
 	</FormSection>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose } from 'vue';
 import FormSection from '@/components/form/section.vue';
 import FormSlot from '@/components/form/slot.vue';
 import FormButton from '@/components/ui/button.vue';
@@ -46,77 +46,63 @@ import MkPagination from '@/components/ui/pagination.vue';
 import X2fa from './2fa.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormSection,
-		FormButton,
-		MkPagination,
-		FormSlot,
-		X2fa,
-	},
+const pagination = {
+	endpoint: 'i/signin-history' as const,
+	limit: 5,
+};
+
+async function change() {
+	const { canceled: canceled1, result: currentPassword } = await os.inputText({
+		title: i18n.ts.currentPassword,
+		type: 'password'
+	});
+	if (canceled1) return;
+
+	const { canceled: canceled2, result: newPassword } = await os.inputText({
+		title: i18n.ts.newPassword,
+		type: 'password'
+	});
+	if (canceled2) return;
+
+	const { canceled: canceled3, result: newPassword2 } = await os.inputText({
+		title: i18n.ts.newPasswordRetype,
+		type: 'password'
+	});
+	if (canceled3) return;
+
+	if (newPassword !== newPassword2) {
+		os.alert({
+			type: 'error',
+			text: i18n.ts.retypedNotMatch
+		});
+		return;
+	}
 	
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.security,
-				icon: 'fas fa-lock',
-				bg: 'var(--bg)',
-			},
-			pagination: {
-				endpoint: 'i/signin-history' as const,
-				limit: 5,
-			},
-		}
-	},
+	os.apiWithDialog('i/change-password', {
+		currentPassword,
+		newPassword
+	});
+}
 
-	methods: {
-		async change() {
-			const { canceled: canceled1, result: currentPassword } = await os.inputText({
-				title: this.$ts.currentPassword,
-				type: 'password'
-			});
-			if (canceled1) return;
+function regenerateToken() {
+	os.inputText({
+		title: i18n.ts.password,
+		type: 'password'
+	}).then(({ canceled, result: password }) => {
+		if (canceled) return;
+		os.api('i/regenerate_token', {
+			password: password
+		});
+	});
+}
 
-			const { canceled: canceled2, result: newPassword } = await os.inputText({
-				title: this.$ts.newPassword,
-				type: 'password'
-			});
-			if (canceled2) return;
-
-			const { canceled: canceled3, result: newPassword2 } = await os.inputText({
-				title: this.$ts.newPasswordRetype,
-				type: 'password'
-			});
-			if (canceled3) return;
-
-			if (newPassword !== newPassword2) {
-				os.alert({
-					type: 'error',
-					text: this.$ts.retypedNotMatch
-				});
-				return;
-			}
-			
-			os.apiWithDialog('i/change-password', {
-				currentPassword,
-				newPassword
-			});
-		},
-
-		regenerateToken() {
-			os.inputText({
-				title: this.$ts.password,
-				type: 'password'
-			}).then(({ canceled, result: password }) => {
-				if (canceled) return;
-				os.api('i/regenerate_token', {
-					password: password
-				});
-			});
-		},
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.security,
+		icon: 'fas fa-lock',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/sounds.vue b/packages/client/src/pages/settings/sounds.vue
index 490a1b5514..d01e87c1f8 100644
--- a/packages/client/src/pages/settings/sounds.vue
+++ b/packages/client/src/pages/settings/sounds.vue
@@ -1,24 +1,24 @@
 <template>
 <div class="_formRoot">
 	<FormRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`" class="_formBlock">
-		<template #label>{{ $ts.masterVolume }}</template>
+		<template #label>{{ i18n.ts.masterVolume }}</template>
 	</FormRange>
 
 	<FormSection>
-		<template #label>{{ $ts.sounds }}</template>
+		<template #label>{{ i18n.ts.sounds }}</template>
 		<FormLink v-for="type in Object.keys(sounds)" :key="type" style="margin-bottom: 8px;" @click="edit(type)">
 			{{ $t('_sfx.' + type) }}
-			<template #suffix>{{ sounds[type].type || $ts.none }}</template>
+			<template #suffix>{{ sounds[type].type || i18n.ts.none }}</template>
 			<template #suffixIcon><i class="fas fa-chevron-down"></i></template>
 		</FormLink>
 	</FormSection>
 
-	<FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton>
+	<FormButton danger class="_formBlock" @click="reset()"><i class="fas fa-redo"></i> {{ i18n.ts.default }}</FormButton>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed, defineExpose, ref } from 'vue';
 import FormRange from '@/components/form/range.vue';
 import FormButton from '@/components/ui/button.vue';
 import FormLink from '@/components/form/link.vue';
@@ -27,6 +27,28 @@ import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
 import { playFile } from '@/scripts/sound';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
+
+const masterVolume = computed({
+	get: () => {
+		return ColdDeviceStorage.get('sound_masterVolume');
+	},
+	set: (value) => {
+		ColdDeviceStorage.set('sound_masterVolume', value);
+	}
+});
+
+const volumeIcon = computed(() => masterVolume.value === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up');
+
+const sounds = ref({
+    note: ColdDeviceStorage.get('sound_note'),
+    noteMy: ColdDeviceStorage.get('sound_noteMy'),
+    notification: ColdDeviceStorage.get('sound_notification'),
+    chat: ColdDeviceStorage.get('sound_chat'),
+    chatBg: ColdDeviceStorage.get('sound_chatBg'),
+    antenna: ColdDeviceStorage.get('sound_antenna'),
+    channel: ColdDeviceStorage.get('sound_channel'),
+});
 
 const soundsTypes = [
 	null,
@@ -55,94 +77,58 @@ const soundsTypes = [
 	'noizenecio/kick_gaba2',
 ];
 
-export default defineComponent({
-	components: {
-		FormLink,
-		FormButton,
-		FormRange,
-		FormSection,
-	},
-
-	emits: ['info'],
-
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.sounds,
-				icon: 'fas fa-music',
-				bg: 'var(--bg)',
-			},
-			sounds: {},
-		}
-	},
-
-	computed: {
-		masterVolume: { // TODO: (ๅค–้ƒจ)้–ขๆ•ฐใซcomputedใ‚’ไฝฟใ†ใฎใฏใ‚ขใƒฌใชใฎใง็›ดใ™
-			get() { return ColdDeviceStorage.get('sound_masterVolume'); },
-			set(value) { ColdDeviceStorage.set('sound_masterVolume', value); }
+async function edit(type) {
+	const { canceled, result } = await os.form(i18n.t('_sfx.' + type), {
+		type: {
+			type: 'enum',
+			enum: soundsTypes.map(x => ({
+				value: x,
+				label: x == null ? i18n.ts.none : x,
+			})),
+			label: i18n.ts.sound,
+			default: sounds.value[type].type,
 		},
-		volumeIcon() {
-			return this.masterVolume === 0 ? 'fas fa-volume-mute' : 'fas fa-volume-up';
-		}
-	},
-
-	created() {
-		this.sounds.note = ColdDeviceStorage.get('sound_note');
-		this.sounds.noteMy = ColdDeviceStorage.get('sound_noteMy');
-		this.sounds.notification = ColdDeviceStorage.get('sound_notification');
-		this.sounds.chat = ColdDeviceStorage.get('sound_chat');
-		this.sounds.chatBg = ColdDeviceStorage.get('sound_chatBg');
-		this.sounds.antenna = ColdDeviceStorage.get('sound_antenna');
-		this.sounds.channel = ColdDeviceStorage.get('sound_channel');
-	},
-
-	methods: {
-		async edit(type) {
-			const { canceled, result } = await os.form(this.$t('_sfx.' + type), {
-				type: {
-					type: 'enum',
-					enum: soundsTypes.map(x => ({
-						value: x,
-						label: x == null ? this.$ts.none : x,
-					})),
-					label: this.$ts.sound,
-					default: this.sounds[type].type,
-				},
-				volume: {
-					type: 'range',
-					mim: 0,
-					max: 1,
-					step: 0.05,
-					textConverter: (v) => `${Math.floor(v * 100)}%`,
-					label: this.$ts.volume,
-					default: this.sounds[type].volume
-				},
-				listen: {
-					type: 'button',
-					content: this.$ts.listen,
-					action: (_, values) => {
-						playFile(values.type, values.volume);
-					}
-				}
-			});
-			if (canceled) return;
-
-			const v = {
-				type: result.type,
-				volume: result.volume,
-			};
-
-			ColdDeviceStorage.set('sound_' + type, v);
-			this.sounds[type] = v;
+		volume: {
+			type: 'range',
+			mim: 0,
+			max: 1,
+			step: 0.05,
+			textConverter: (v) => `${Math.floor(v * 100)}%`,
+			label: i18n.ts.volume,
+			default: sounds.value[type].volume
 		},
-
-		reset() {
-			for (const sound of Object.keys(this.sounds)) {
-				const v = ColdDeviceStorage.default['sound_' + sound];
-				ColdDeviceStorage.set('sound_' + sound, v);
-				this.sounds[sound] = v;
+		listen: {
+			type: 'button',
+			content: i18n.ts.listen,
+			action: (_, values) => {
+				playFile(values.type, values.volume);
 			}
 		}
+	});
+	if (canceled) return;
+
+	const v = {
+		type: result.type,
+		volume: result.volume,
+	};
+
+	ColdDeviceStorage.set('sound_' + type, v);
+	sounds.value[type] = v;
+}
+
+function reset() {
+	for (const sound of Object.keys(sounds.value)) {
+		const v = ColdDeviceStorage.default['sound_' + sound];
+		ColdDeviceStorage.set('sound_' + sound, v);
+		sounds.value[sound] = v;
+	}
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.sounds,
+		icon: 'fas fa-music',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/theme.install.vue b/packages/client/src/pages/settings/theme.install.vue
index 2d3514342e..25fa6c012b 100644
--- a/packages/client/src/pages/settings/theme.install.vue
+++ b/packages/client/src/pages/settings/theme.install.vue
@@ -13,7 +13,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import * as JSON5 from 'json5';
+import JSON5 from 'json5';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormButton from '@/components/ui/button.vue';
 import { applyTheme, validateTheme } from '@/scripts/theme';
@@ -29,7 +29,7 @@ function parseThemeCode(code: string) {
 
 	try {
 		theme = JSON5.parse(code);
-	} catch (e) {
+	} catch (err) {
 		os.alert({
 			type: 'error',
 			text: i18n.ts._theme.invalid
diff --git a/packages/client/src/pages/settings/theme.manage.vue b/packages/client/src/pages/settings/theme.manage.vue
index a1e849b540..94b2d24455 100644
--- a/packages/client/src/pages/settings/theme.manage.vue
+++ b/packages/client/src/pages/settings/theme.manage.vue
@@ -1,95 +1,77 @@
 <template>
 <div class="_formRoot">
 	<FormSelect v-model="selectedThemeId" class="_formBlock">
-		<template #label>{{ $ts.theme }}</template>
-		<optgroup :label="$ts._theme.installedThemes">
+		<template #label>{{ i18n.ts.theme }}</template>
+		<optgroup :label="i18n.ts._theme.installedThemes">
 			<option v-for="x in installedThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
 		</optgroup>
-		<optgroup :label="$ts._theme.builtinThemes">
+		<optgroup :label="i18n.ts._theme.builtinThemes">
 			<option v-for="x in builtinThemes" :key="x.id" :value="x.id">{{ x.name }}</option>
 		</optgroup>
 	</FormSelect>
 	<template v-if="selectedTheme">
-		<FormInput readonly :modelValue="selectedTheme.author" class="_formBlock">
-			<template #label>{{ $ts.author }}</template>
+		<FormInput readonly :model-value="selectedTheme.author" class="_formBlock">
+			<template #label>{{ i18n.ts.author }}</template>
 		</FormInput>
-		<FormTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc" class="_formBlock">
-			<template #label>{{ $ts._theme.description }}</template>
+		<FormTextarea v-if="selectedTheme.desc" readonly :model-value="selectedTheme.desc" class="_formBlock">
+			<template #label>{{ i18n.ts._theme.description }}</template>
 		</FormTextarea>
-		<FormTextarea readonly tall :modelValue="selectedThemeCode" class="_formBlock">
-			<template #label>{{ $ts._theme.code }}</template>
-			<template #caption><button class="_textButton" @click="copyThemeCode()">{{ $ts.copy }}</button></template>
+		<FormTextarea readonly tall :model-value="selectedThemeCode" class="_formBlock">
+			<template #label>{{ i18n.ts._theme.code }}</template>
+			<template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template>
 		</FormTextarea>
-		<FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ $ts.uninstall }}</FormButton>
+		<FormButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" class="_formBlock" danger @click="uninstall()"><i class="fas fa-trash-alt"></i> {{ i18n.ts.uninstall }}</FormButton>
 	</template>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as JSON5 from 'json5';
+<script lang="ts" setup>
+import { computed, defineExpose, ref } from 'vue';
+import JSON5 from 'json5';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormSelect from '@/components/form/select.vue';
 import FormInput from '@/components/form/input.vue';
 import FormButton from '@/components/ui/button.vue';
-import { Theme, builtinThemes } from '@/scripts/theme';
+import { Theme, getBuiltinThemesRef } from '@/scripts/theme';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import * as os from '@/os';
-import { ColdDeviceStorage } from '@/store';
 import { getThemes, removeTheme } from '@/theme-store';
 import * as symbols from '@/symbols';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		FormTextarea,
-		FormSelect,
-		FormInput,
-		FormButton,
-	},
+const installedThemes = ref(getThemes());
+const builtinThemes = getBuiltinThemesRef();
+const selectedThemeId = ref(null);
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts._theme.manage,
-				icon: 'fas fa-folder-open',
-				bg: 'var(--bg)',
-			},
-			installedThemes: getThemes(),
-			builtinThemes,
-			selectedThemeId: null,
-		}
-	},
+const themes = computed(() => [ ...installedThemes.value, ...builtinThemes.value ]);
 
-	computed: {
-		themes(): Theme[] {
-			return this.builtinThemes.concat(this.installedThemes);
-		},
-	
-		selectedTheme() {
-			if (this.selectedThemeId == null) return null;
-			return this.themes.find(x => x.id === this.selectedThemeId);
-		},
+const selectedTheme = computed(() => {
+	if (selectedThemeId.value == null) return null;
+	return themes.value.find(x => x.id === selectedThemeId.value);
+});
 
-		selectedThemeCode() {
-			if (this.selectedTheme == null) return null;
-			return JSON5.stringify(this.selectedTheme, null, '\t');
-		},
-	},
+const selectedThemeCode = computed(() => {
+	if (selectedTheme.value == null) return null;
+	return JSON5.stringify(selectedTheme.value, null, '\t');
+});
 
-	methods: {
-		copyThemeCode() {
-			copyToClipboard(this.selectedThemeCode);
-			os.success();
-		},
+function copyThemeCode() {
+	copyToClipboard(selectedThemeCode.value);
+	os.success();
+}
 
-		uninstall() {
-			removeTheme(this.selectedTheme);
-			this.installedThemes = this.installedThemes.filter(t => t.id !== this.selectedThemeId);
-			this.selectedThemeId = null;
-			os.success();
-		},
+function uninstall() {
+	removeTheme(selectedTheme.value as Theme);
+	installedThemes.value = installedThemes.value.filter(t => t.id !== selectedThemeId.value);
+	selectedThemeId.value = null;
+	os.success();
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts._theme.manage,
+		icon: 'fas fa-folder-open',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index d134a092b6..5e7ffcff4b 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -85,116 +85,94 @@
 </div>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent, onActivated, onMounted, ref, watch } from 'vue';
-import * as JSON5 from 'json5';
+<script lang="ts" setup>
+import { computed, onActivated, ref, watch } from 'vue';
+import JSON5 from 'json5';
 import FormSwitch from '@/components/form/switch.vue';
 import FormSelect from '@/components/form/select.vue';
-import FormGroup from '@/components/form/group.vue';
 import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import FormButton from '@/components/ui/button.vue';
-import { builtinThemes } from '@/scripts/theme';
+import { getBuiltinThemesRef } from '@/scripts/theme';
 import { selectFile } from '@/scripts/select-file';
 import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
 import { ColdDeviceStorage } from '@/store';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
 import { instance } from '@/instance';
-import { concat, uniqueBy } from '@/scripts/array';
+import { uniqueBy } from '@/scripts/array';
 import { fetchThemes, getThemes } from '@/theme-store';
 import * as symbols from '@/symbols';
 
-export default defineComponent({
-	components: {
-		FormSwitch,
-		FormSelect,
-		FormGroup,
-		FormSection,
-		FormLink,
-		FormButton,
+const installedThemes = ref(getThemes());
+const builtinThemes = getBuiltinThemesRef();
+const instanceThemes = [];
+
+if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme));
+if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme));
+
+const themes = computed(() => uniqueBy([ ...instanceThemes, ...builtinThemes.value, ...installedThemes.value ], theme => theme.id));
+const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
+const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light'));
+const darkTheme = ColdDeviceStorage.ref('darkTheme');
+const darkThemeId = computed({
+	get() {
+		return darkTheme.value.id;
 	},
+	set(id) {
+		ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id));
+	}
+});
+const lightTheme = ColdDeviceStorage.ref('lightTheme');
+const lightThemeId = computed({
+	get() {
+		return lightTheme.value.id;
+	},
+	set(id) {
+		ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id));
+	}
+});
+const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
+const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
+const wallpaper = ref(localStorage.getItem('wallpaper'));
+const themesCount = installedThemes.value.length;
 
-	emits: ['info'],
+watch(syncDeviceDarkMode, () => {
+	if (syncDeviceDarkMode.value) {
+		defaultStore.set('darkMode', isDeviceDarkmode());
+	}
+});
 
-	setup(props, { emit }) {
-		const INFO = {
-			title: i18n.ts.theme,
-			icon: 'fas fa-palette',
-				bg: 'var(--bg)',
-		};
+watch(wallpaper, () => {
+	if (wallpaper.value == null) {
+		localStorage.removeItem('wallpaper');
+	} else {
+		localStorage.setItem('wallpaper', wallpaper.value);
+	}
+	location.reload();
+});
 
-		const installedThemes = ref(getThemes());
-		const instanceThemes = [];
-		if (instance.defaultLightTheme != null) instanceThemes.push(JSON5.parse(instance.defaultLightTheme));
-		if (instance.defaultDarkTheme != null) instanceThemes.push(JSON5.parse(instance.defaultDarkTheme));
-		const themes = computed(() => uniqueBy(instanceThemes.concat(builtinThemes.concat(installedThemes.value)), theme => theme.id));
-		const darkThemes = computed(() => themes.value.filter(t => t.base === 'dark' || t.kind === 'dark'));
-		const lightThemes = computed(() => themes.value.filter(t => t.base === 'light' || t.kind === 'light'));
-		const darkTheme = ColdDeviceStorage.ref('darkTheme');
-		const darkThemeId = computed({
-			get() {
-				return darkTheme.value.id;
-			},
-			set(id) {
-				ColdDeviceStorage.set('darkTheme', themes.value.find(x => x.id === id))
-			}
-		});
-		const lightTheme = ColdDeviceStorage.ref('lightTheme');
-		const lightThemeId = computed({
-			get() {
-				return lightTheme.value.id;
-			},
-			set(id) {
-				ColdDeviceStorage.set('lightTheme', themes.value.find(x => x.id === id))
-			}
-		});
-		const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
-		const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
-		const wallpaper = ref(localStorage.getItem('wallpaper'));
-		const themesCount = installedThemes.value.length;
+onActivated(() => {
+	fetchThemes().then(() => {
+		installedThemes.value = getThemes();
+	});
+});
 
-		watch(syncDeviceDarkMode, () => {
-			if (syncDeviceDarkMode.value) {
-				defaultStore.set('darkMode', isDeviceDarkmode());
-			}
-		});
+fetchThemes().then(() => {
+	installedThemes.value = getThemes();
+});
 
-		watch(wallpaper, () => {
-			if (wallpaper.value == null) {
-				localStorage.removeItem('wallpaper');
-			} else {
-				localStorage.setItem('wallpaper', wallpaper.value);
-			}
-			location.reload();
-		});
+function setWallpaper(event) {
+	selectFile(event.currentTarget ?? event.target, null).then(file => {
+		wallpaper.value = file.url;
+	});
+}
 
-		onActivated(() => {
-			fetchThemes().then(() => {
-				installedThemes.value = getThemes();
-			});
-		});
-
-		fetchThemes().then(() => {
-			installedThemes.value = getThemes();
-		});
-
-		return {
-			[symbols.PAGE_INFO]: INFO,
-			darkThemes,
-			lightThemes,
-			darkThemeId,
-			lightThemeId,
-			darkMode,
-			syncDeviceDarkMode,
-			themesCount,
-			wallpaper,
-			setWallpaper(e) {
-				selectFile(e.currentTarget ?? e.target, null).then(file => {
-					wallpaper.value = file.url;
-				});
-			},
-		};
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.theme,
+		icon: 'fas fa-palette',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/webhook.edit.vue b/packages/client/src/pages/settings/webhook.edit.vue
index bb3a25407e..3690526b41 100644
--- a/packages/client/src/pages/settings/webhook.edit.vue
+++ b/packages/client/src/pages/settings/webhook.edit.vue
@@ -43,6 +43,14 @@ import * as os from '@/os';
 import * as symbols from '@/symbols';
 import { i18n } from '@/i18n';
 
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: 'Edit webhook',
+		icon: 'fas fa-bolt',
+		bg: 'var(--bg)',
+	},
+});
+
 const webhook = await os.api('i/webhooks/show', {
 	webhookId: new URLSearchParams(window.location.search).get('id')
 });
@@ -78,12 +86,4 @@ async function save(): Promise<void> {
 		active,
 	});
 }
-
-defineExpose({
-	[symbols.PAGE_INFO]: {
-		title: 'Edit webhook',
-		icon: 'fas fa-bolt',
-		bg: 'var(--bg)',
-	},
-});
 </script>
diff --git a/packages/client/src/pages/settings/word-mute.vue b/packages/client/src/pages/settings/word-mute.vue
index c11707b6cf..6e1a4b2ccb 100644
--- a/packages/client/src/pages/settings/word-mute.vue
+++ b/packages/client/src/pages/settings/word-mute.vue
@@ -1,35 +1,35 @@
 <template>
 <div class="_formRoot">
 	<MkTab v-model="tab" class="_formBlock">
-		<option value="soft">{{ $ts._wordMute.soft }}</option>
-		<option value="hard">{{ $ts._wordMute.hard }}</option>
+		<option value="soft">{{ i18n.ts._wordMute.soft }}</option>
+		<option value="hard">{{ i18n.ts._wordMute.hard }}</option>
 	</MkTab>
 	<div class="_formBlock">
 		<div v-show="tab === 'soft'">
-			<MkInfo class="_formBlock">{{ $ts._wordMute.softDescription }}</MkInfo>
+			<MkInfo class="_formBlock">{{ i18n.ts._wordMute.softDescription }}</MkInfo>
 			<FormTextarea v-model="softMutedWords" class="_formBlock">
-				<span>{{ $ts._wordMute.muteWords }}</span>
-				<template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template>
+				<span>{{ i18n.ts._wordMute.muteWords }}</span>
+				<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
 			</FormTextarea>
 		</div>
 		<div v-show="tab === 'hard'">
-			<MkInfo class="_formBlock">{{ $ts._wordMute.hardDescription }} {{ $ts.reflectMayTakeTime }}</MkInfo>
+			<MkInfo class="_formBlock">{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo>
 			<FormTextarea v-model="hardMutedWords" class="_formBlock">
-				<span>{{ $ts._wordMute.muteWords }}</span>
-				<template #caption>{{ $ts._wordMute.muteWordsDescription }}<br>{{ $ts._wordMute.muteWordsDescription2 }}</template>
+				<span>{{ i18n.ts._wordMute.muteWords }}</span>
+				<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
 			</FormTextarea>
 			<MkKeyValue v-if="hardWordMutedNotesCount != null" class="_formBlock">
-				<template #key>{{ $ts._wordMute.mutedNotes }}</template>
+				<template #key>{{ i18n.ts._wordMute.mutedNotes }}</template>
 				<template #value>{{ number(hardWordMutedNotesCount) }}</template>
 			</MkKeyValue>
 		</div>
 	</div>
-	<MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
+	<MkButton primary inline :disabled="!changed" @click="save()"><i class="fas fa-save"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineExpose, ref, watch } from 'vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import MkKeyValue from '@/components/key-value.vue';
 import MkButton from '@/components/ui/button.vue';
@@ -38,114 +38,90 @@ import MkTab from '@/components/tab.vue';
 import * as os from '@/os';
 import number from '@/filters/number';
 import * as symbols from '@/symbols';
+import { defaultStore } from '@/store';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		FormTextarea,
-		MkKeyValue,
-		MkTab,
-		MkInfo,
-	},
+const render = (mutedWords) => mutedWords.map(x => {
+	if (Array.isArray(x)) {
+		return x.join(' ');
+	} else {
+		return x;
+	}
+}).join('\n');
 
-	emits: ['info'],
-	
-	data() {
-		return {
-			[symbols.PAGE_INFO]: {
-				title: this.$ts.wordMute,
-				icon: 'fas fa-comment-slash',
-				bg: 'var(--bg)',
-			},
-			tab: 'soft',
-			softMutedWords: '',
-			hardMutedWords: '',
-			hardWordMutedNotesCount: null,
-			changed: false,
-		}
-	},
+const tab = ref('soft');
+const softMutedWords = ref(render(defaultStore.state.mutedWords));
+const hardMutedWords = ref(render($i!.mutedWords));
+const hardWordMutedNotesCount = ref(null);
+const changed = ref(false);
 
-	watch: {
-		softMutedWords: {
-			handler() {
-				this.changed = true;
-			},
-			deep: true
-		},
-		hardMutedWords: {
-			handler() {
-				this.changed = true;
-			},
-			deep: true
-		},
-	},
+os.api('i/get-word-muted-notes-count', {}).then(response => {
+	hardWordMutedNotesCount.value = response?.count;
+});
 
-	async created() {
-		const render = (mutedWords) => mutedWords.map(x => {
-			if (Array.isArray(x)) {
-				return x.join(' ');
-			} else {
-				return x;
-			}
-		}).join('\n');
+watch(softMutedWords, () => {
+	changed.value = true;
+});
 
-		this.softMutedWords = render(this.$store.state.mutedWords);
-		this.hardMutedWords = render(this.$i.mutedWords);
+watch(hardMutedWords, () => {
+	changed.value = true;
+});
 
-		this.hardWordMutedNotesCount = (await os.api('i/get-word-muted-notes-count', {})).count;
-	},
+async function save() {
+	const parseMutes = (mutes, tab) => {
+		// split into lines, remove empty lines and unnecessary whitespace
+		let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== '');
 
-	methods: {
-		async save() {
-			const parseMutes = (mutes, tab) => {
-				// split into lines, remove empty lines and unnecessary whitespace
-				let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line != '');
-
-				// check each line if it is a RegExp or not
-				for (let i = 0; i < lines.length; i++) {
-					const line = lines[i]
-					const regexp = line.match(/^\/(.+)\/(.*)$/);
-					if (regexp) {
-						// check that the RegExp is valid
-						try {
-							new RegExp(regexp[1], regexp[2]);
-							// note that regex lines will not be split by spaces!
-						} catch (err) {
-							// invalid syntax: do not save, do not reset changed flag
-							os.alert({
-								type: 'error',
-								title: this.$ts.regexpError,
-								text: this.$t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString()
-							});
-							// re-throw error so these invalid settings are not saved
-							throw err;
-						}
-					} else {
-						lines[i] = line.split(' ');
-					}
+		// check each line if it is a RegExp or not
+		for (let i = 0; i < lines.length; i++) {
+			const line = lines[i];
+			const regexp = line.match(/^\/(.+)\/(.*)$/);
+			if (regexp) {
+				// check that the RegExp is valid
+				try {
+					new RegExp(regexp[1], regexp[2]);
+					// note that regex lines will not be split by spaces!
+				} catch (err: any) {
+					// invalid syntax: do not save, do not reset changed flag
+					os.alert({
+						type: 'error',
+						title: i18n.ts.regexpError,
+						text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + "\n" + err.toString()
+					});
+					// re-throw error so these invalid settings are not saved
+					throw err;
 				}
-
-				return lines;
-			};
-
-			let softMutes, hardMutes;
-			try {
-				softMutes = parseMutes(this.softMutedWords, this.$ts._wordMute.soft);
-				hardMutes = parseMutes(this.hardMutedWords, this.$ts._wordMute.hard);
-			} catch (err) {
-				// already displayed error message in parseMutes
-				return;
+			} else {
+				lines[i] = line.split(' ');
 			}
+		}
 
-			this.$store.set('mutedWords', softMutes);
-			await os.api('i/update', {
-				mutedWords: hardMutes,
-			});
+		return lines;
+	};
 
-			this.changed = false;
-		},
+	let softMutes, hardMutes;
+	try {
+		softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft);
+		hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard);
+	} catch (err) {
+		// already displayed error message in parseMutes
+		return;
+	}
 
-		number
+	defaultStore.set('mutedWords', softMutes);
+	await os.api('i/update', {
+		mutedWords: hardMutes,
+	});
+
+	changed.value = false;
+}
+
+defineExpose({
+	[symbols.PAGE_INFO]: {
+		title: i18n.ts.wordMute,
+		icon: 'fas fa-comment-slash',
+		bg: 'var(--bg)',
 	}
 });
 </script>
diff --git a/packages/client/src/pages/share.vue b/packages/client/src/pages/share.vue
index 4d77de5819..1700944f82 100644
--- a/packages/client/src/pages/share.vue
+++ b/packages/client/src/pages/share.vue
@@ -56,7 +56,7 @@ export default defineComponent({
 			localOnly: null as boolean | null,
 			files: [] as Misskey.entities.DriveFile[],
 			visibleUsers: [] as Misskey.entities.User[],
-		}
+		};
 	},
 
 	async created() {
@@ -153,11 +153,11 @@ export default defineComponent({
 				);
 			}
 			//#endregion
-		} catch (e) {
+		} catch (err) {
 			os.alert({
 				type: 'error',
-				title: e.message,
-				text: e.name
+				title: err.message,
+				text: err.name
 			});
 		}
 
diff --git a/packages/client/src/pages/theme-editor.vue b/packages/client/src/pages/theme-editor.vue
index a53e23c1c5..2a11c07fd2 100644
--- a/packages/client/src/pages/theme-editor.vue
+++ b/packages/client/src/pages/theme-editor.vue
@@ -67,15 +67,17 @@
 <script lang="ts" setup>
 import { watch } from 'vue';
 import { toUnicode } from 'punycode/';
-import * as tinycolor from 'tinycolor2';
-import { v4 as uuid} from 'uuid';
-import * as JSON5 from 'json5';
+import tinycolor from 'tinycolor2';
+import { v4 as uuid } from 'uuid';
+import JSON5 from 'json5';
 
 import FormButton from '@/components/ui/button.vue';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormFolder from '@/components/form/folder.vue';
 
-import { Theme, applyTheme, darkTheme, lightTheme } from '@/scripts/theme';
+import { Theme, applyTheme } from '@/scripts/theme';
+import lightTheme from '@/themes/_light.json5';
+import darkTheme from '@/themes/_dark.json5';
 import { host } from '@/config';
 import * as os from '@/os';
 import { ColdDeviceStorage, defaultStore } from '@/store';
@@ -124,11 +126,11 @@ let changed = $ref(false);
 useLeaveGuard($$(changed));
 
 function showPreview() {
-	os.pageWindow('preview');
+	os.pageWindow('/preview');
 }
 
 function setBgColor(color: typeof bgColors[number]) {
-	if (theme.base != color.kind) {
+	if (theme.base !== color.kind) {
 		const base = color.kind === 'dark' ? darkTheme : lightTheme;
 		for (const prop of Object.keys(base.props)) {
 			if (prop === 'accent') continue;
diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue
index 79f00c4b44..fe3dbc3cff 100644
--- a/packages/client/src/pages/timeline.vue
+++ b/packages/client/src/pages/timeline.vue
@@ -20,7 +20,7 @@
 <script lang="ts">
 export default {
 	name: 'MkTimelinePage',
-}
+};
 </script>
 
 <script lang="ts" setup>
diff --git a/packages/client/src/pages/user-info.vue b/packages/client/src/pages/user-info.vue
index 516ab4d440..54e1f13021 100644
--- a/packages/client/src/pages/user-info.vue
+++ b/packages/client/src/pages/user-info.vue
@@ -54,6 +54,9 @@
 				<FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
 			</FormSection>
 
+			<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
+			</MkObjectView>
+
 			<MkObjectView tall :value="user">
 			</MkObjectView>
 		</div>
@@ -232,10 +235,10 @@ export default defineComponent({
 				await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id });
 				os.success();
 			};
-			await process().catch(e => {
+			await process().catch(err => {
 				os.alert({
 					type: 'error',
-					text: e.toString()
+					text: err.toString(),
 				});
 			});
 			await this.refreshUser();
diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue
index 10a86243f9..a024dd28bc 100644
--- a/packages/client/src/pages/user/index.vue
+++ b/packages/client/src/pages/user/index.vue
@@ -125,7 +125,7 @@
 
 <script lang="ts">
 import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import * as age from 's-age';
+import age from 's-age';
 import XUserTimeline from './index.timeline.vue';
 import XNote from '@/components/note.vue';
 import MkFollowButton from '@/components/follow-button.vue';
@@ -141,6 +141,7 @@ import number from '@/filters/number';
 import { userPage, acct as getAcct } from '@/filters/user';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
+import { MisskeyNavigator } from '@/scripts/navigate';
 
 export default defineComponent({
 	components: {
@@ -190,33 +191,34 @@ export default defineComponent({
 					active: this.page === 'index',
 					title: this.$ts.overview,
 					icon: 'fas fa-home',
-					onClick: () => { this.$router.push('/@' + getAcct(this.user)); },
+					onClick: () => { this.mkNav.push('/@' + getAcct(this.user)); },
 				}, ...(this.$i && (this.$i.id === this.user.id)) || this.user.publicReactions ? [{
 					active: this.page === 'reactions',
 					title: this.$ts.reaction,
 					icon: 'fas fa-laugh',
-					onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/reactions'); },
+					onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/reactions'); },
 				}] : [], {
 					active: this.page === 'clips',
 					title: this.$ts.clips,
 					icon: 'fas fa-paperclip',
-					onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/clips'); },
+					onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/clips'); },
 				}, {
 					active: this.page === 'pages',
 					title: this.$ts.pages,
 					icon: 'fas fa-file-alt',
-					onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/pages'); },
+					onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/pages'); },
 				}, {
 					active: this.page === 'gallery',
 					title: this.$ts.gallery,
 					icon: 'fas fa-icons',
-					onClick: () => { this.$router.push('/@' + getAcct(this.user) + '/gallery'); },
+					onClick: () => { this.mkNav.push('/@' + getAcct(this.user) + '/gallery'); },
 				}],
 			} : null),
 			user: null,
 			error: null,
 			parallaxAnimationId: null,
 			narrow: null,
+			mkNav: new MisskeyNavigator(),
 		};
 	},
 
@@ -258,8 +260,8 @@ export default defineComponent({
 			this.user = null;
 			os.api('users/show', Acct.parse(this.acct)).then(user => {
 				this.user = user;
-			}).catch(e => {
-				this.error = e;
+			}).catch(err => {
+				this.error = err;
 			});
 		},
 
diff --git a/packages/client/src/pages/welcome.setup.vue b/packages/client/src/pages/welcome.setup.vue
index ec23b76e29..1a2f460283 100644
--- a/packages/client/src/pages/welcome.setup.vue
+++ b/packages/client/src/pages/welcome.setup.vue
@@ -41,7 +41,7 @@ export default defineComponent({
 			password: '',
 			submitting: false,
 			host,
-		}
+		};
 	},
 
 	methods: {
diff --git a/packages/client/src/pages/welcome.timeline.vue b/packages/client/src/pages/welcome.timeline.vue
index 38a85f67b1..bec9481ffd 100644
--- a/packages/client/src/pages/welcome.timeline.vue
+++ b/packages/client/src/pages/welcome.timeline.vue
@@ -39,7 +39,7 @@ export default defineComponent({
 		return {
 			notes: [],
 			isScrolling: false,
-		}
+		};
 	},
 
 	created() {
diff --git a/packages/client/src/router.ts b/packages/client/src/router.ts
index 839841f0fe..db39dd741c 100644
--- a/packages/client/src/router.ts
+++ b/packages/client/src/router.ts
@@ -1,4 +1,4 @@
-import { defineAsyncComponent, markRaw } from 'vue';
+import { AsyncComponentLoader, defineAsyncComponent, markRaw } from 'vue';
 import { createRouter, createWebHistory } from 'vue-router';
 import MkLoading from '@/pages/_loading_.vue';
 import MkError from '@/pages/_error_.vue';
@@ -6,8 +6,9 @@ import MkTimeline from '@/pages/timeline.vue';
 import { $i, iAmModerator } from './account';
 import { ui } from '@/config';
 
-const page = (path: string, ui?: string) => defineAsyncComponent({
-	loader: ui ? () => import(`./ui/${ui}/pages/${path}.vue`) : () => import(`./pages/${path}.vue`),
+// pathใซ/ใŒๅ…ฅใ‚‹ใจrollupใŒ่งฃๆฑบใ—ใฆใใ‚Œใชใ„ใฎใงใ€() => import('*.vue')ใ‚’ๆŒ‡ๅฎšใ™ใ‚‹ใ“ใจ
+const page = (path: string | AsyncComponentLoader<any>, uiName?: string) => defineAsyncComponent({
+	loader: typeof path === 'string' ? uiName ? () => import(`./ui/${ui}/pages/${path}.vue`) : () => import(`./pages/${path}.vue`) : path,
 	loadingComponent: MkLoading,
 	errorComponent: MkError,
 });
@@ -17,10 +18,10 @@ let indexScrollPos = 0;
 const defaultRoutes = [
 	// NOTE: MkTimelineใ‚’dynamic importใ™ใ‚‹ใจAsyncComponentWrapperใŒ้–“ใซๅ…ฅใ‚‹ใ›ใ„ใงkeep-aliveใฎใ‚ณใƒณใƒใƒผใƒใƒณใƒˆๆŒ‡ๅฎšใŒๅŠนใ‹ใชใใชใ‚‹
 	{ path: '/', name: 'index', component: $i ? MkTimeline : page('welcome') },
-	{ path: '/@:acct/:page?', name: 'user', component: page('user/index'), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) },
+	{ path: '/@:acct/:page?', name: 'user', component: page(() => import('./pages/user/index.vue')), props: route => ({ acct: route.params.acct, page: route.params.page || 'index' }) },
 	{ path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) },
-	{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
-	{ path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) },
+	{ path: '/@:user/pages/:pageName/view-source', component: page(() => import('./pages/page-editor/page-editor.vue')), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) },
+	{ path: '/settings/:page(.*)?', name: 'settings', component: page(() => import('./pages/settings/index.vue')), props: route => ({ initialPage: route.params.page || null }) },
 	{ path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) },
 	{ path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) },
 	{ path: '/announcements', component: page('announcements') },
@@ -35,12 +36,12 @@ const defaultRoutes = [
 	{ path: '/emojis', component: page('emojis') },
 	{ path: '/search', component: page('search'), props: route => ({ query: route.query.q, channel: route.query.channel }) },
 	{ path: '/pages', name: 'pages', component: page('pages') },
-	{ path: '/pages/new', component: page('page-editor/page-editor') },
-	{ path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) },
-	{ path: '/gallery', component: page('gallery/index') },
-	{ path: '/gallery/new', component: page('gallery/edit') },
-	{ path: '/gallery/:postId/edit', component: page('gallery/edit'), props: route => ({ postId: route.params.postId }) },
-	{ path: '/gallery/:postId', component: page('gallery/post'), props: route => ({ postId: route.params.postId }) },
+	{ path: '/pages/new', component: page(() => import('./pages/page-editor/page-editor.vue')) },
+	{ path: '/pages/edit/:pageId', component: page(() => import('./pages/page-editor/page-editor.vue')), props: route => ({ initPageId: route.params.pageId }) },
+	{ path: '/gallery', component: page(() => import('./pages/gallery/index.vue')) },
+	{ path: '/gallery/new', component: page(() => import('./pages/gallery/edit.vue')) },
+	{ path: '/gallery/:postId/edit', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) },
+	{ path: '/gallery/:postId', component: page(() => import('./pages/gallery/edit.vue')), props: route => ({ postId: route.params.postId }) },
 	{ path: '/channels', component: page('channels') },
 	{ path: '/channels/new', component: page('channel-editor') },
 	{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true },
@@ -52,23 +53,23 @@ const defaultRoutes = [
 	{ path: '/my/favorites', component: page('favorites') },
 	{ path: '/my/messages', component: page('messages') },
 	{ path: '/my/mentions', component: page('mentions') },
-	{ path: '/my/messaging', name: 'messaging', component: page('messaging/index') },
-	{ path: '/my/messaging/:user', component: page('messaging/messaging-room'), props: route => ({ userAcct: route.params.user }) },
-	{ path: '/my/messaging/group/:group', component: page('messaging/messaging-room'), props: route => ({ groupId: route.params.group }) },
+	{ path: '/my/messaging', name: 'messaging', component: page(() => import('./pages/messaging/index.vue')) },
+	{ path: '/my/messaging/:user', component: page(() => import('./pages/messaging/messaging-room.vue')), props: route => ({ userAcct: route.params.user }) },
+	{ path: '/my/messaging/group/:group', component: page(() => import('./pages/messaging/messaging-room.vue')), props: route => ({ groupId: route.params.group }) },
 	{ path: '/my/drive', name: 'drive', component: page('drive') },
 	{ path: '/my/drive/folder/:folder', component: page('drive') },
 	{ path: '/my/follow-requests', component: page('follow-requests') },
-	{ path: '/my/lists', component: page('my-lists/index') },
-	{ path: '/my/lists/:list', component: page('my-lists/list') },
-	{ path: '/my/groups', component: page('my-groups/index') },
-	{ path: '/my/groups/:group', component: page('my-groups/group'), props: route => ({ groupId: route.params.group }) },
-	{ path: '/my/antennas', component: page('my-antennas/index') },
-	{ path: '/my/antennas/create', component: page('my-antennas/create') },
-	{ path: '/my/antennas/:antennaId', component: page('my-antennas/edit'), props: true },
-	{ path: '/my/clips', component: page('my-clips/index') },
+	{ path: '/my/lists', component: page(() => import('./pages/my-lists/index.vue')) },
+	{ path: '/my/lists/:list', component: page(() => import('./pages/my-lists/list.vue')) },
+	{ path: '/my/groups', component: page(() => import('./pages/my-groups/index.vue')) },
+	{ path: '/my/groups/:group', component: page(() => import('./pages/my-groups/group.vue')), props: route => ({ groupId: route.params.group }) },
+	{ path: '/my/antennas', component: page(() => import('./pages/my-antennas/index.vue')) },
+	{ path: '/my/antennas/create', component: page(() => import('./pages/my-antennas/create.vue')) },
+	{ path: '/my/antennas/:antennaId', component: page(() => import('./pages/my-antennas/edit.vue')), props: true },
+	{ path: '/my/clips', component: page(() => import('./pages/my-clips/index.vue')) },
 	{ path: '/scratchpad', component: page('scratchpad') },
-	{ path: '/admin/:page(.*)?', component: iAmModerator ? page('admin/index') : page('not-found'), props: route => ({ initialPage: route.params.page || null }) },
-	{ path: '/admin', component: iAmModerator ? page('admin/index') : page('not-found') },
+	{ path: '/admin/:page(.*)?', component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page('not-found'), props: route => ({ initialPage: route.params.page || null }) },
+	{ path: '/admin', component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page('not-found') },
 	{ path: '/notes/:note', name: 'note', component: page('note'), props: route => ({ noteId: route.params.note }) },
 	{ path: '/tags/:tag', component: page('tag'), props: route => ({ tag: route.params.tag }) },
 	{ path: '/user-info/:user', component: page('user-info'), props: route => ({ userId: route.params.user }) },
diff --git a/packages/client/src/scripts/2fa.ts b/packages/client/src/scripts/2fa.ts
index 00363cffa6..d1b9581e72 100644
--- a/packages/client/src/scripts/2fa.ts
+++ b/packages/client/src/scripts/2fa.ts
@@ -1,11 +1,11 @@
-export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') {
+export function byteify(string: string, encoding: 'ascii' | 'base64' | 'hex') {
 	switch (encoding) {
 		case 'ascii':
-			return Uint8Array.from(data, c => c.charCodeAt(0));
+			return Uint8Array.from(string, c => c.charCodeAt(0));
 		case 'base64':
 			return Uint8Array.from(
 				atob(
-					data
+					string
 						.replace(/-/g, '+')
 						.replace(/_/g, '/')
 				),
@@ -13,7 +13,7 @@ export function byteify(data: string, encoding: 'ascii' | 'base64' | 'hex') {
 			);
 		case 'hex':
 			return new Uint8Array(
-				data
+				string
 					.match(/.{1,2}/g)
 					.map(byte => parseInt(byte, 16))
 			);
diff --git a/packages/client/src/scripts/autocomplete.ts b/packages/client/src/scripts/autocomplete.ts
index f4a3a4c0fc..8d9bdee8f5 100644
--- a/packages/client/src/scripts/autocomplete.ts
+++ b/packages/client/src/scripts/autocomplete.ts
@@ -1,5 +1,5 @@
-import { nextTick, Ref, ref } from 'vue';
-import * as getCaretCoordinates from 'textarea-caret';
+import { nextTick, Ref, ref, defineAsyncComponent } from 'vue';
+import getCaretCoordinates from 'textarea-caret';
 import { toASCII } from 'punycode/';
 import { popup } from '@/os';
 
@@ -74,21 +74,21 @@ export class Autocomplete {
 			emojiIndex,
 			mfmTagIndex);
 
-		if (max == -1) {
+		if (max === -1) {
 			this.close();
 			return;
 		}
 
-		const isMention = mentionIndex != -1;
-		const isHashtag = hashtagIndex != -1;
-		const isMfmTag = mfmTagIndex != -1;
-		const isEmoji = emojiIndex != -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
+		const isMention = mentionIndex !== -1;
+		const isHashtag = hashtagIndex !== -1;
+		const isMfmTag = mfmTagIndex !== -1;
+		const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
 
 		let opened = false;
 
 		if (isMention) {
 			const username = text.substr(mentionIndex + 1);
-			if (username != '' && username.match(/^[a-zA-Z0-9_]+$/)) {
+			if (username !== '' && username.match(/^[a-zA-Z0-9_]+$/)) {
 				this.open('user', username);
 				opened = true;
 			} else if (username === '') {
@@ -130,7 +130,7 @@ export class Autocomplete {
 	 * ใ‚ตใ‚ธใ‚งใ‚นใƒˆใ‚’ๆ็คบใ—ใพใ™ใ€‚
 	 */
 	private async open(type: string, q: string | null) {
-		if (type != this.currentType) {
+		if (type !== this.currentType) {
 			this.close();
 		}
 		if (this.opening) return;
@@ -157,7 +157,7 @@ export class Autocomplete {
 			const _y = ref(y);
 			const _q = ref(q);
 
-			const { dispose } = await popup(import('@/components/autocomplete.vue'), {
+			const { dispose } = await popup(defineAsyncComponent(() => import('@/components/autocomplete.vue')), {
 				textarea: this.textarea,
 				close: this.close,
 				type: type,
@@ -201,7 +201,7 @@ export class Autocomplete {
 
 		const caret = this.textarea.selectionStart;
 
-		if (type == 'user') {
+		if (type === 'user') {
 			const source = this.text;
 
 			const before = source.substr(0, caret);
@@ -219,7 +219,7 @@ export class Autocomplete {
 				const pos = trimmedBefore.length + (acct.length + 2);
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type == 'hashtag') {
+		} else if (type === 'hashtag') {
 			const source = this.text;
 
 			const before = source.substr(0, caret);
@@ -235,7 +235,7 @@ export class Autocomplete {
 				const pos = trimmedBefore.length + (value.length + 2);
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type == 'emoji') {
+		} else if (type === 'emoji') {
 			const source = this.text;
 
 			const before = source.substr(0, caret);
@@ -251,7 +251,7 @@ export class Autocomplete {
 				const pos = trimmedBefore.length + value.length;
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type == 'mfmTag') {
+		} else if (type === 'mfmTag') {
 			const source = this.text;
 
 			const before = source.substr(0, caret);
diff --git a/packages/client/src/scripts/contains.ts b/packages/client/src/scripts/contains.ts
index 770bda63bb..256e09d293 100644
--- a/packages/client/src/scripts/contains.ts
+++ b/packages/client/src/scripts/contains.ts
@@ -2,7 +2,7 @@ export default (parent, child, checkSame = true) => {
 	if (checkSame && parent === child) return true;
 	let node = child.parentNode;
 	while (node) {
-		if (node == parent) return true;
+		if (node === parent) return true;
 		node = node.parentNode;
 	}
 	return false;
diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts
index bd8689e4f8..4196170d24 100644
--- a/packages/client/src/scripts/emojilist.ts
+++ b/packages/client/src/scripts/emojilist.ts
@@ -8,4 +8,4 @@ export type UnicodeEmojiDef = {
 }
 
 // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb
-export const emojilist = require('../emojilist.json') as UnicodeEmojiDef[];
+export const emojilist = (await import('../emojilist.json')).default as UnicodeEmojiDef[];
diff --git a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts
index 123ab7a06d..af517f2672 100644
--- a/packages/client/src/scripts/extract-avg-color-from-blurhash.ts
+++ b/packages/client/src/scripts/extract-avg-color-from-blurhash.ts
@@ -1,5 +1,5 @@
 export function extractAvgColorFromBlurhash(hash: string) {
-	return typeof hash == 'string'
+	return typeof hash === 'string'
 		? '#' + [...hash.slice(2, 6)]
 				.map(x => '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~'.indexOf(x))
 				.reduce((a, c) => a * 83 + c, 0)
diff --git a/packages/client/src/scripts/format-time-string.ts b/packages/client/src/scripts/format-time-string.ts
index bfb2c397ae..fb4718c007 100644
--- a/packages/client/src/scripts/format-time-string.ts
+++ b/packages/client/src/scripts/format-time-string.ts
@@ -13,7 +13,7 @@ const defaultLocaleStringFormats: {[index: string]: string} = {
 function formatLocaleString(date: Date, format: string): string {
 	return format.replace(/\{\{(\w+)(:(\w+))?\}\}/g, (match: string, kind: string, unused?, option?: string) => {
 		if (['weekday', 'era', 'year', 'month', 'day', 'hour', 'minute', 'second', 'timeZoneName'].includes(kind)) {
-			return date.toLocaleString(window.navigator.language, {[kind]: option ? option : defaultLocaleStringFormats[kind]});
+			return date.toLocaleString(window.navigator.language, { [kind]: option ? option : defaultLocaleStringFormats[kind] });
 		} else {
 			return match;
 		}
@@ -24,8 +24,8 @@ export function formatDateTimeString(date: Date, format: string): string {
 	return format
 		.replace(/yyyy/g, date.getFullYear().toString())
 		.replace(/yy/g, date.getFullYear().toString().slice(-2))
-		.replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long'}))
-		.replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short'}))
+		.replace(/MMMM/g, date.toLocaleString(window.navigator.language, { month: 'long' }))
+		.replace(/MMM/g, date.toLocaleString(window.navigator.language, { month: 'short' }))
 		.replace(/MM/g, (`0${date.getMonth() + 1}`).slice(-2))
 		.replace(/M/g, (date.getMonth() + 1).toString())
 		.replace(/dd/g, (`0${date.getDate()}`).slice(-2))
diff --git a/packages/client/src/scripts/get-account-from-id.ts b/packages/client/src/scripts/get-account-from-id.ts
index ba3adceecc..1da897f176 100644
--- a/packages/client/src/scripts/get-account-from-id.ts
+++ b/packages/client/src/scripts/get-account-from-id.ts
@@ -3,5 +3,5 @@ import { get } from '@/scripts/idb-proxy';
 export async function getAccountFromId(id: string) {
 	const accounts = await get('accounts') as { token: string; id: string; }[];
 	if (!accounts) console.log('Accounts are not recorded');
-	return accounts.find(e => e.id === id);
+	return accounts.find(account => account.id === id);
 }
diff --git a/packages/client/src/scripts/get-md5.ts b/packages/client/src/scripts/get-md5.ts
deleted file mode 100644
index b002d762b1..0000000000
--- a/packages/client/src/scripts/get-md5.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-// ใ‚นใ‚ฏใƒชใƒ—ใƒˆใ‚ตใ‚คใ‚บใŒใƒ‡ใ‚ซใ„
-//import * as crypto from 'crypto';
-
-export default (data: ArrayBuffer) => {
-	//const buf = new Buffer(data);
-	//const hash = crypto.createHash('md5');
-	//hash.update(buf);
-	//return hash.digest('hex');
-	return '';
-};
diff --git a/packages/client/src/scripts/get-note-menu.ts b/packages/client/src/scripts/get-note-menu.ts
index b19656d3cc..78749ad6bb 100644
--- a/packages/client/src/scripts/get-note-menu.ts
+++ b/packages/client/src/scripts/get-note-menu.ts
@@ -1,4 +1,4 @@
-import { Ref } from 'vue';
+import { defineAsyncComponent, Ref } from 'vue';
 import * as misskey from 'misskey-js';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
@@ -22,7 +22,7 @@ export function getNoteMenu(props: {
 		props.note.poll == null
 	);
 
-	let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
+	const appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
 
 	function del(): void {
 		os.confirm({
@@ -83,8 +83,8 @@ export function getNoteMenu(props: {
 	function togglePin(pin: boolean): void {
 		os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', {
 			noteId: appearNote.id
-		}, undefined, null, e => {
-			if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
+		}, undefined, null, res => {
+			if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
 				os.alert({
 					type: 'error',
 					text: i18n.ts.pinLimitExceeded
@@ -209,7 +209,7 @@ export function getNoteMenu(props: {
 			text: i18n.ts.clip,
 			action: () => clip()
 		},
-		(appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? {
+		(appearNote.userId !== $i.id) ? statePromise.then(state => state.isWatching ? {
 			icon: 'fas fa-eye-slash',
 			text: i18n.ts.unwatch,
 			action: () => toggleWatch(false)
@@ -227,7 +227,7 @@ export function getNoteMenu(props: {
 			text: i18n.ts.muteThread,
 			action: () => toggleThreadMute(true)
 		}),
-		appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
+		appearNote.userId === $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
 			icon: 'fas fa-thumbtack',
 			text: i18n.ts.unpin,
 			action: () => togglePin(false)
@@ -246,14 +246,14 @@ export function getNoteMenu(props: {
 			}]
 			: []
 		),*/
-		...(appearNote.userId != $i.id ? [
+		...(appearNote.userId !== $i.id ? [
 			null,
 			{
 				icon: 'fas fa-exclamation-circle',
 				text: i18n.ts.reportAbuse,
 				action: () => {
 					const u = appearNote.url || appearNote.uri || `${url}/notes/${appearNote.id}`;
-					os.popup(import('@/components/abuse-report-window.vue'), {
+					os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
 						user: appearNote.user,
 						initialComment: `Note: ${u}\n-----\n`
 					}, {}, 'closed');
@@ -261,9 +261,9 @@ export function getNoteMenu(props: {
 			}]
 			: []
 		),
-		...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [
+		...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
 			null,
-			appearNote.userId == $i.id ? {
+			appearNote.userId === $i.id ? {
 				icon: 'fas fa-edit',
 				text: i18n.ts.deleteAndEdit,
 				action: delEdit
diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts
index 54b8d109d6..d57e1c3029 100644
--- a/packages/client/src/scripts/get-note-summary.ts
+++ b/packages/client/src/scripts/get-note-summary.ts
@@ -24,7 +24,7 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
 	}
 
 	// ใƒ•ใ‚กใ‚คใƒซใŒๆทปไป˜ใ•ใ‚Œใฆใ„ใ‚‹ใจใ
-	if ((note.files || []).length != 0) {
+	if ((note.files || []).length !== 0) {
 		summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`;
 	}
 
diff --git a/packages/client/src/scripts/get-user-menu.ts b/packages/client/src/scripts/get-user-menu.ts
index 192d14b83e..091338efd6 100644
--- a/packages/client/src/scripts/get-user-menu.ts
+++ b/packages/client/src/scripts/get-user-menu.ts
@@ -6,6 +6,7 @@ import * as os from '@/os';
 import { userActions } from '@/store';
 import { router } from '@/router';
 import { $i, iAmModerator } from '@/account';
+import { defineAsyncComponent } from 'vue';
 
 export function getUserMenu(user) {
 	const meId = $i ? $i.id : null;
@@ -127,7 +128,7 @@ export function getUserMenu(user) {
 	}
 
 	function reportAbuse() {
-		os.popup(import('@/components/abuse-report-window.vue'), {
+		os.popup(defineAsyncComponent(() => import('@/components/abuse-report-window.vue')), {
 			user: user,
 		}, {}, 'closed');
 	}
@@ -147,7 +148,7 @@ export function getUserMenu(user) {
 			userId: user.id
 		}).then(() => {
 			user.isFollowed = !user.isFollowed;
-		})
+		});
 	}
 
 	let menu = [{
@@ -168,7 +169,7 @@ export function getUserMenu(user) {
 		action: () => {
 			os.post({ specified: user });
 		}
-	}, meId != user.id ? {
+	}, meId !== user.id ? {
 		type: 'link',
 		icon: 'fas fa-comments',
 		text: i18n.ts.startMessaging,
@@ -177,13 +178,13 @@ export function getUserMenu(user) {
 		icon: 'fas fa-list-ul',
 		text: i18n.ts.addToList,
 		action: pushList
-	}, meId != user.id ? {
+	}, meId !== user.id ? {
 		icon: 'fas fa-users',
 		text: i18n.ts.inviteToGroup,
 		action: inviteGroup
 	} : undefined] as any;
 
-	if ($i && meId != user.id) {
+	if ($i && meId !== user.id) {
 		menu = menu.concat([null, {
 			icon: user.isMuted ? 'fas fa-eye' : 'fas fa-eye-slash',
 			text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
diff --git a/packages/client/src/scripts/get-user-name.ts b/packages/client/src/scripts/get-user-name.ts
new file mode 100644
index 0000000000..d499ea0203
--- /dev/null
+++ b/packages/client/src/scripts/get-user-name.ts
@@ -0,0 +1,3 @@
+export default function(user: { name?: string | null, username: string }): string {
+	return user.name || user.username;
+}
diff --git a/packages/client/src/scripts/hotkey.ts b/packages/client/src/scripts/hotkey.ts
index 2b3f491fd8..fd9c74f6c8 100644
--- a/packages/client/src/scripts/hotkey.ts
+++ b/packages/client/src/scripts/hotkey.ts
@@ -53,34 +53,34 @@ const parseKeymap = (keymap: Keymap) => Object.entries(keymap).map(([patterns, c
 
 const ignoreElemens = ['input', 'textarea'];
 
-function match(e: KeyboardEvent, patterns: Action['patterns']): boolean {
-	const key = e.code.toLowerCase();
+function match(ev: KeyboardEvent, patterns: Action['patterns']): boolean {
+	const key = ev.code.toLowerCase();
 	return patterns.some(pattern => pattern.which.includes(key) &&
-		pattern.ctrl === e.ctrlKey &&
-		pattern.shift === e.shiftKey &&
-		pattern.alt === e.altKey &&
-		!e.metaKey
+		pattern.ctrl === ev.ctrlKey &&
+		pattern.shift === ev.shiftKey &&
+		pattern.alt === ev.altKey &&
+		!ev.metaKey
 	);
 }
 
 export const makeHotkey = (keymap: Keymap) => {
 	const actions = parseKeymap(keymap);
 
-	return (e: KeyboardEvent) => {
+	return (ev: KeyboardEvent) => {
 		if (document.activeElement) {
 			if (ignoreElemens.some(el => document.activeElement!.matches(el))) return;
 			if (document.activeElement.attributes['contenteditable']) return;
 		}
 
 		for (const action of actions) {
-			const matched = match(e, action.patterns);
+			const matched = match(ev, action.patterns);
 
 			if (matched) {
-				if (!action.allowRepeat && e.repeat) return;
+				if (!action.allowRepeat && ev.repeat) return;
 
-				e.preventDefault();
-				e.stopPropagation();
-				action.callback(e);
+				ev.preventDefault();
+				ev.stopPropagation();
+				action.callback(ev);
 				break;
 			}
 		}
diff --git a/packages/client/src/scripts/hpml/evaluator.ts b/packages/client/src/scripts/hpml/evaluator.ts
index 6329c0860e..8106687b61 100644
--- a/packages/client/src/scripts/hpml/evaluator.ts
+++ b/packages/client/src/scripts/hpml/evaluator.ts
@@ -36,7 +36,7 @@ export class Hpml {
 		if (this.opts.enableAiScript) {
 			this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({
 				storageKey: 'pages:' + this.page.id
-			}), ...initAiLib(this)}, {
+			}), ...initAiLib(this) }, {
 				in: (q) => {
 					return new Promise(ok => {
 						os.inputText({
@@ -85,7 +85,7 @@ export class Hpml {
 	public eval() {
 		try {
 			this.vars.value = this.evaluateVars();
-		} catch (e) {
+		} catch (err) {
 			//this.onError(e);
 		}
 	}
@@ -103,7 +103,7 @@ export class Hpml {
 	public callAiScript(fn: string) {
 		try {
 			if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []);
-		} catch (e) {}
+		} catch (err) {}
 	}
 
 	@autobind
@@ -185,7 +185,7 @@ export class Hpml {
 				if (this.aiscript) {
 					try {
 						return utils.valToJs(this.aiscript.scope.get(expr.value));
-					} catch (e) {
+					} catch (err) {
 						return null;
 					}
 				} else {
@@ -194,7 +194,7 @@ export class Hpml {
 			}
 
 			// Define user function
-			if (expr.type == 'fn') {
+			if (expr.type === 'fn') {
 				return {
 					slots: expr.value.slots.map(x => x.name),
 					exec: (slotArg: Record<string, any>) => {
diff --git a/packages/client/src/scripts/hpml/lib.ts b/packages/client/src/scripts/hpml/lib.ts
index 2a1ac73a40..01a44ffcdf 100644
--- a/packages/client/src/scripts/hpml/lib.ts
+++ b/packages/client/src/scripts/hpml/lib.ts
@@ -1,9 +1,9 @@
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 import { Hpml } from './evaluator';
 import { values, utils } from '@syuilo/aiscript';
 import { Fn, HpmlScope } from '.';
 import { Expr } from './expr';
-import * as seedrandom from 'seedrandom';
+import seedrandom from 'seedrandom';
 
 /* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color
 // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
diff --git a/packages/client/src/scripts/idb-proxy.ts b/packages/client/src/scripts/idb-proxy.ts
index 5f76ae30bb..d462a0d7ce 100644
--- a/packages/client/src/scripts/idb-proxy.ts
+++ b/packages/client/src/scripts/idb-proxy.ts
@@ -13,8 +13,8 @@ let idbAvailable = typeof window !== 'undefined' ? !!window.indexedDB : true;
 if (idbAvailable) {
 	try {
 		await iset('idb-test', 'test');
-	} catch (e) {
-		console.error('idb error', e);
+	} catch (err) {
+		console.error('idb error', err);
 		idbAvailable = false;
 	}
 }
diff --git a/packages/client/src/scripts/initialize-sw.ts b/packages/client/src/scripts/initialize-sw.ts
index d6dbd5dbd4..7bacfbdf00 100644
--- a/packages/client/src/scripts/initialize-sw.ts
+++ b/packages/client/src/scripts/initialize-sw.ts
@@ -4,26 +4,26 @@ import { api } from '@/os';
 import { lang } from '@/config';
 
 export async function initializeSw() {
-	if (instance.swPublickey &&
-		('serviceWorker' in navigator) &&
-		('PushManager' in window) &&
-		$i && $i.token) {
-		navigator.serviceWorker.register(`/sw.js`);
+	if (!('serviceWorker' in navigator)) return;
 
-		navigator.serviceWorker.ready.then(registration => {
-			registration.active?.postMessage({
-				msg: 'initialize',
-				lang,
-			});
+	navigator.serviceWorker.register(`/sw.js`, { scope: '/', type: 'classic' });
+	navigator.serviceWorker.ready.then(registration => {
+		registration.active?.postMessage({
+			msg: 'initialize',
+			lang,
+		});
+
+		if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) {
 			// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
 			registration.pushManager.subscribe({
 				userVisibleOnly: true,
 				applicationServerKey: urlBase64ToUint8Array(instance.swPublickey)
-			}).then(subscription => {
+			})
+			.then(subscription => {
 				function encode(buffer: ArrayBuffer | null) {
 					return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
 				}
-
+		
 				// Register
 				api('sw/register', {
 					endpoint: subscription.endpoint,
@@ -37,15 +37,15 @@ export async function initializeSw() {
 				if (err.name === 'NotAllowedError') {
 					return;
 				}
-
+		
 				// ้•ใ†applicationServerKey (ใพใŸใฏ gcm_sender_id)ใฎใ‚ตใƒ–ใ‚นใ‚ฏใƒชใƒ—ใ‚ทใƒงใƒณใŒ
 				// ๆ—ขใซๅญ˜ๅœจใ—ใฆใ„ใ‚‹ใ“ใจใŒๅŽŸๅ› ใงใ‚จใƒฉใƒผใซใชใฃใŸๅฏ่ƒฝๆ€งใŒใ‚ใ‚‹ใฎใงใ€
 				// ใใฎใ‚ตใƒ–ใ‚นใ‚ฏใƒชใƒ—ใ‚ทใƒงใƒณใ‚’่งฃ้™คใ—ใฆใŠใ
 				const subscription = await registration.pushManager.getSubscription();
 				if (subscription) subscription.unsubscribe();
 			});
-		});
-	}
+		}
+	});
 }
 
 /**
diff --git a/packages/client/src/scripts/lookup-user.ts b/packages/client/src/scripts/lookup-user.ts
index 8de5c84ce8..2d00e51621 100644
--- a/packages/client/src/scripts/lookup-user.ts
+++ b/packages/client/src/scripts/lookup-user.ts
@@ -25,12 +25,12 @@ export async function lookupUser() {
 			_notFound = true;
 		}
 	};
-	usernamePromise.then(show).catch(e => {
-		if (e.code === 'NO_SUCH_USER') {
+	usernamePromise.then(show).catch(err => {
+		if (err.code === 'NO_SUCH_USER') {
 			notFound();
 		}
 	});
-	idPromise.then(show).catch(e => {
+	idPromise.then(show).catch(err => {
 		notFound();
 	});
 }
diff --git a/packages/client/src/scripts/navigate.ts b/packages/client/src/scripts/navigate.ts
new file mode 100644
index 0000000000..08b891ec5b
--- /dev/null
+++ b/packages/client/src/scripts/navigate.ts
@@ -0,0 +1,34 @@
+import { inject } from 'vue';
+import { router } from '@/router';
+import { defaultStore } from '@/store';
+
+export type Navigate = (path: string, record?: boolean) => void;
+
+export class MisskeyNavigator {
+	public readonly navHook: Navigate | null = null;
+	public readonly sideViewHook: Navigate | null = null;
+
+	// It should be constructed during vue creating in order for inject function to work
+	constructor() {
+		this.navHook = inject<Navigate | null>('navHook', null);
+		this.sideViewHook = inject<Navigate | null>('sideViewHook', null);
+	}
+
+	// Use this method instead of router.push()
+	public push(path: string, record = true) {
+		if (this.navHook) {
+			this.navHook(path, record);
+		} else {
+			if (defaultStore.state.defaultSideView && this.sideViewHook && path !== '/') {
+				return this.sideViewHook(path, record);
+			}
+	
+			if (router.currentRoute.value.path === path) {
+				window.scroll({ top: 0, behavior: 'smooth' });
+			} else {
+				if (record) router.push(path);
+				else router.replace(path);
+			}
+		}
+	}
+}
diff --git a/packages/client/src/scripts/physics.ts b/packages/client/src/scripts/physics.ts
index 36e476b6f9..9e657906c2 100644
--- a/packages/client/src/scripts/physics.ts
+++ b/packages/client/src/scripts/physics.ts
@@ -41,9 +41,9 @@ export function physics(container: HTMLElement) {
 
 	const groundThickness = 1024;
 	const ground = Matter.Bodies.rectangle(containerCenterX, containerHeight + (groundThickness / 2), containerWidth, groundThickness, {
-		isStatic:     true,
-		restitution:  0.1,
-		friction:     2
+		isStatic: true,
+		restitution: 0.1,
+		friction: 2
 	});
 
 	//const wallRight = Matter.Bodies.rectangle(window.innerWidth+50, window.innerHeight/2, 100, window.innerHeight, wallopts);
diff --git a/packages/client/src/scripts/please-login.ts b/packages/client/src/scripts/please-login.ts
index aeaafa124b..e21a6d2ed3 100644
--- a/packages/client/src/scripts/please-login.ts
+++ b/packages/client/src/scripts/please-login.ts
@@ -1,14 +1,21 @@
+import { defineAsyncComponent } from 'vue';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
-import { alert } from '@/os';
+import { popup } from '@/os';
 
-export function pleaseLogin() {
+export function pleaseLogin(path?: string) {
 	if ($i) return;
 
-	alert({
-		title: i18n.ts.signinRequired,
-		text: null
-	});
+	popup(defineAsyncComponent(() => import('@/components/signin-dialog.vue')), {
+		autoSet: true,
+		message: i18n.ts.signinRequired
+	}, {
+		cancelled: () => {
+			if (path) {
+				window.location.href = path;
+			}
+		},
+	}, 'closed');
 
 	throw new Error('signin required');
 }
diff --git a/packages/client/src/scripts/popout.ts b/packages/client/src/scripts/popout.ts
index b8286a2a76..580031d0a3 100644
--- a/packages/client/src/scripts/popout.ts
+++ b/packages/client/src/scripts/popout.ts
@@ -1,8 +1,9 @@
 import * as config from '@/config';
+import { appendQuery } from './url';
 
 export function popout(path: string, w?: HTMLElement) {
-	let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + "/" + path;
-	url += '?zen';
+	let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path;
+	url = appendQuery(url, 'zen');
 	if (w) {
 		const position = w.getBoundingClientRect();
 		const width = parseInt(getComputedStyle(w, '').width, 10);
diff --git a/packages/client/src/scripts/reaction-picker.ts b/packages/client/src/scripts/reaction-picker.ts
index 3ac1f63430..b7699cae4a 100644
--- a/packages/client/src/scripts/reaction-picker.ts
+++ b/packages/client/src/scripts/reaction-picker.ts
@@ -1,4 +1,4 @@
-import { Ref, ref } from 'vue';
+import { defineAsyncComponent, Ref, ref } from 'vue';
 import { popup } from '@/os';
 
 class ReactionPicker {
@@ -12,7 +12,7 @@ class ReactionPicker {
 	}
 
 	public async init() {
-		await popup(import('@/components/emoji-picker-dialog.vue'), {
+		await popup(defineAsyncComponent(() => import('@/components/emoji-picker-dialog.vue')), {
 			src: this.src,
 			asReactionPicker: true,
 			manualShowing: this.manualShowing
diff --git a/packages/client/src/scripts/select-file.ts b/packages/client/src/scripts/select-file.ts
index 23df4edf54..461d613b42 100644
--- a/packages/client/src/scripts/select-file.ts
+++ b/packages/client/src/scripts/select-file.ts
@@ -4,6 +4,7 @@ import { stream } from '@/stream';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
 import { DriveFile } from 'misskey-js/built/entities';
+import { uploadFile } from '@/scripts/upload';
 
 function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> {
 	return new Promise((res, rej) => {
@@ -14,14 +15,14 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
 			input.type = 'file';
 			input.multiple = multiple;
 			input.onchange = () => {
-				const promises = Array.from(input.files).map(file => os.upload(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value));
+				const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value));
 
 				Promise.all(promises).then(driveFiles => {
 					res(multiple ? driveFiles : driveFiles[0]);
-				}).catch(e => {
+				}).catch(err => {
 					os.alert({
 						type: 'error',
-						text: e
+						text: err
 					});
 				});
 
@@ -53,9 +54,9 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
 				const marker = Math.random().toString(); // TODO: UUIDใจใ‹ไฝฟใ†
 
 				const connection = stream.useChannel('main');
-				connection.on('urlUploadFinished', data => {
-					if (data.marker === marker) {
-						res(multiple ? [data.file] : data.file);
+				connection.on('urlUploadFinished', urlResponse => {
+					if (urlResponse.marker === marker) {
+						res(multiple ? [urlResponse.file] : urlResponse.file);
 						connection.dispose();
 					}
 				});
diff --git a/packages/client/src/scripts/theme-editor.ts b/packages/client/src/scripts/theme-editor.ts
index 3d69d2836a..2c917e280d 100644
--- a/packages/client/src/scripts/theme-editor.ts
+++ b/packages/client/src/scripts/theme-editor.ts
@@ -1,4 +1,4 @@
-import { v4 as uuid} from 'uuid';
+import { v4 as uuid } from 'uuid';
 
 import { themeProps, Theme } from './theme';
 
diff --git a/packages/client/src/scripts/theme.ts b/packages/client/src/scripts/theme.ts
index 2cb78fae5c..dec9fb355c 100644
--- a/packages/client/src/scripts/theme.ts
+++ b/packages/client/src/scripts/theme.ts
@@ -1,5 +1,6 @@
+import { ref } from 'vue';
 import { globalEvents } from '@/events';
-import * as tinycolor from 'tinycolor2';
+import tinycolor from 'tinycolor2';
 
 export type Theme = {
 	id: string;
@@ -10,30 +11,38 @@ export type Theme = {
 	props: Record<string, string>;
 };
 
-export const lightTheme: Theme = require('@/themes/_light.json5');
-export const darkTheme: Theme = require('@/themes/_dark.json5');
+import lightTheme from '@/themes/_light.json5';
+import darkTheme from '@/themes/_dark.json5';
 
 export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
 
-export const builtinThemes = [
-	require('@/themes/l-light.json5'),
-	require('@/themes/l-coffee.json5'),
-	require('@/themes/l-apricot.json5'),
-	require('@/themes/l-rainy.json5'),
-	require('@/themes/l-vivid.json5'),
-	require('@/themes/l-cherry.json5'),
-	require('@/themes/l-sushi.json5'),
+export const getBuiltinThemes = () => Promise.all(
+	[
+		'l-light',
+		'l-coffee',
+		'l-apricot',
+		'l-rainy',
+		'l-vivid',
+		'l-cherry',
+		'l-sushi',
 
-	require('@/themes/d-dark.json5'),
-	require('@/themes/d-persimmon.json5'),
-	require('@/themes/d-astro.json5'),
-	require('@/themes/d-future.json5'),
-	require('@/themes/d-botanical.json5'),
-	require('@/themes/d-cherry.json5'),
-	require('@/themes/d-ice.json5'),
-	require('@/themes/d-pumpkin.json5'),
-	require('@/themes/d-black.json5'),
-] as Theme[];
+		'd-dark',
+		'd-persimmon',
+		'd-astro',
+		'd-future',
+		'd-botanical',
+		'd-cherry',
+		'd-ice',
+		'd-pumpkin',
+		'd-black',
+	].map(name => import(`../themes/${name}.json5`).then(({ default: _default }): Theme => _default))
+);
+
+export const getBuiltinThemesRef = () => {
+	const builtinThemes = ref<Theme[]>([]);
+	getBuiltinThemes().then(themes => builtinThemes.value = themes);
+	return builtinThemes;
+};
 
 let timeout = null;
 
diff --git a/packages/client/src/scripts/upload.ts b/packages/client/src/scripts/upload.ts
new file mode 100644
index 0000000000..2f7b30b58d
--- /dev/null
+++ b/packages/client/src/scripts/upload.ts
@@ -0,0 +1,114 @@
+import { reactive, ref } from 'vue';
+import { defaultStore } from '@/store';
+import { apiUrl } from '@/config';
+import * as Misskey from 'misskey-js';
+import { $i } from '@/account';
+import { readAndCompressImage } from 'browser-image-resizer';
+import { alert } from '@/os';
+
+type Uploading = {
+	id: string;
+	name: string;
+	progressMax: number | undefined;
+	progressValue: number | undefined;
+	img: string;
+};
+export const uploads = ref<Uploading[]>([]);
+
+const compressTypeMap = {
+	'image/jpeg': { quality: 0.85, mimeType: 'image/jpeg' },
+	'image/webp': { quality: 0.85, mimeType: 'image/jpeg' },
+	'image/svg+xml': { quality: 1, mimeType: 'image/png' },
+} as const;
+
+const mimeTypeMap = {
+	'image/webp': 'webp',
+	'image/jpeg': 'jpg',
+	'image/png': 'png',
+} as const;
+
+export function uploadFile(
+	file: File,
+	folder?: any,
+	name?: string,
+	keepOriginal: boolean = defaultStore.state.keepOriginalUploading
+): Promise<Misskey.entities.DriveFile> {
+	if (folder && typeof folder === 'object') folder = folder.id;
+
+	return new Promise((resolve, reject) => {
+		const id = Math.random().toString();
+
+		const reader = new FileReader();
+		reader.onload = async (ev) => {
+			const ctx = reactive<Uploading>({
+				id: id,
+				name: name || file.name || 'untitled',
+				progressMax: undefined,
+				progressValue: undefined,
+				img: window.URL.createObjectURL(file)
+			});
+
+			uploads.value.push(ctx);
+
+			let resizedImage: any;
+			if (!keepOriginal && file.type in compressTypeMap) {
+				const imgConfig = compressTypeMap[file.type];
+
+				const config = {
+					maxWidth: 2048,
+					maxHeight: 2048,
+					debug: true,
+					...imgConfig,
+				};
+
+				try {
+					resizedImage = await readAndCompressImage(file, config);
+					ctx.name = file.type !== imgConfig.mimeType ? `${ctx.name}.${mimeTypeMap[compressTypeMap[file.type].mimeType]}` : ctx.name;
+				} catch (err) {
+					console.error('Failed to resize image', err);
+				}
+			}
+
+			const formData = new FormData();
+			formData.append('i', $i.token);
+			formData.append('force', 'true');
+			formData.append('file', resizedImage || file);
+			formData.append('name', ctx.name);
+			if (folder) formData.append('folderId', folder);
+
+			const xhr = new XMLHttpRequest();
+			xhr.open('POST', apiUrl + '/drive/files/create', true);
+			xhr.onload = (ev) => {
+				if (xhr.status !== 200 || ev.target == null || ev.target.response == null) {
+					// TODO: ๆถˆใ™ใฎใงใฏใชใใฆๅ†้€ใงใใ‚‹ใ‚ˆใ†ใซใ—ใŸใ„
+					uploads.value = uploads.value.filter(x => x.id !== id);
+
+					alert({
+						type: 'error',
+						title: 'Failed to upload',
+						text: `${JSON.stringify(ev.target?.response)}, ${JSON.stringify(xhr.response)}`
+					});
+
+					reject();
+					return;
+				}
+
+				const driveFile = JSON.parse(ev.target.response);
+
+				resolve(driveFile);
+
+				uploads.value = uploads.value.filter(x => x.id !== id);
+			};
+
+			xhr.upload.onprogress = ev => {
+				if (ev.lengthComputable) {
+					ctx.progressMax = ev.total;
+					ctx.progressValue = ev.loaded;
+				}
+			};
+
+			xhr.send(formData);
+		};
+		reader.readAsArrayBuffer(file);
+	});
+}
diff --git a/packages/client/src/scripts/url.ts b/packages/client/src/scripts/url.ts
index c7f2b7c1e7..542b00e0f0 100644
--- a/packages/client/src/scripts/url.ts
+++ b/packages/client/src/scripts/url.ts
@@ -4,7 +4,7 @@ export function query(obj: {}): string {
 		.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
 
 	return Object.entries(params)
-		.map((e) => `${e[0]}=${encodeURIComponent(e[1])}`)
+		.map((p) => `${p[0]}=${encodeURIComponent(p[1])}`)
 		.join('&');
 }
 
diff --git a/packages/client/src/scripts/use-note-capture.ts b/packages/client/src/scripts/use-note-capture.ts
index b2a96062c7..f1f976693e 100644
--- a/packages/client/src/scripts/use-note-capture.ts
+++ b/packages/client/src/scripts/use-note-capture.ts
@@ -11,8 +11,8 @@ export function useNoteCapture(props: {
 	const note = props.note;
 	const connection = $i ? stream : null;
 
-	function onStreamNoteUpdated(data): void {
-		const { type, id, body } = data;
+	function onStreamNoteUpdated(noteData): void {
+		const { type, id, body } = noteData;
 
 		if (id !== note.value.id) return;
 
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index b9800ec607..deee23951e 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -255,10 +255,13 @@ type Plugin = {
 /**
  * ๅธธใซใƒกใƒขใƒชใซใƒญใƒผใƒ‰ใ—ใฆใŠใๅฟ…่ฆใŒใชใ„ใ‚ˆใ†ใช่จญๅฎšๆƒ…ๅ ฑใ‚’ไฟ็ฎกใ™ใ‚‹ใ‚นใƒˆใƒฌใƒผใ‚ธ(้žใƒชใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–)
  */
+import lightTheme from '@/themes/l-light.json5';
+import darkTheme from '@/themes/d-dark.json5';
+
 export class ColdDeviceStorage {
 	public static default = {
-		lightTheme: require('@/themes/l-light.json5') as Theme,
-		darkTheme: require('@/themes/d-dark.json5') as Theme,
+		lightTheme,
+		darkTheme,
 		syncDeviceDarkMode: true,
 		plugins: [] as Plugin[],
 		mediaVolume: 0.5,
diff --git a/packages/client/src/sw/compose-notification.ts b/packages/client/src/sw/compose-notification.ts
deleted file mode 100644
index e271d30949..0000000000
--- a/packages/client/src/sw/compose-notification.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * Notification composer of Service Worker
- */
-declare var self: ServiceWorkerGlobalScope;
-
-import * as misskey from 'misskey-js';
-
-function getUserName(user: misskey.entities.User): string {
-	return user.name || user.username;
-}
-
-export default async function(type, data, i18n): Promise<[string, NotificationOptions] | null | undefined> {
-	if (!i18n) {
-		console.log('no i18n');
-		return;
-	}
-
-	switch (type) {
-		case 'driveFileCreated': // TODO (Server Side)
-			return [i18n.t('_notification.fileUploaded'), {
-				body: data.name,
-				icon: data.url
-			}];
-		case 'notification':
-			switch (data.type) {
-				case 'mention':
-					return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), {
-						body: data.note.text,
-						icon: data.user.avatarUrl
-					}];
-
-				case 'reply':
-					return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), {
-						body: data.note.text,
-						icon: data.user.avatarUrl
-					}];
-
-				case 'renote':
-					return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), {
-						body: data.note.text,
-						icon: data.user.avatarUrl
-					}];
-
-				case 'quote':
-					return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), {
-						body: data.note.text,
-						icon: data.user.avatarUrl
-					}];
-
-				case 'reaction':
-					return [`${data.reaction} ${getUserName(data.user)}`, {
-						body: data.note.text,
-						icon: data.user.avatarUrl
-					}];
-
-				case 'pollVote':
-					return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), {
-						body: data.note.text,
-						icon: data.user.avatarUrl
-					}];
-
-				case 'pollEnded':
-					return [i18n.t('_notification.pollEnded'), {
-						body: data.note.text,
-					}];
-
-				case 'follow':
-					return [i18n.t('_notification.youWereFollowed'), {
-						body: getUserName(data.user),
-						icon: data.user.avatarUrl
-					}];
-
-				case 'receiveFollowRequest':
-					return [i18n.t('_notification.youReceivedFollowRequest'), {
-						body: getUserName(data.user),
-						icon: data.user.avatarUrl
-					}];
-
-				case 'followRequestAccepted':
-					return [i18n.t('_notification.yourFollowRequestAccepted'), {
-						body: getUserName(data.user),
-						icon: data.user.avatarUrl
-					}];
-
-				case 'groupInvited':
-					return [i18n.t('_notification.youWereInvitedToGroup'), {
-						body: data.group.name
-					}];
-
-				default:
-					return null;
-			}
-		case 'unreadMessagingMessage':
-			if (data.groupId === null) {
-				return [i18n.t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.user) }), {
-					icon: data.user.avatarUrl,
-					tag: `messaging:user:${data.user.id}`
-				}];
-			}
-			return [i18n.t('_notification.youGotMessagingMessageFromGroup', { name: data.group.name }), {
-				icon: data.user.avatarUrl,
-				tag: `messaging:group:${data.group.id}`
-			}];
-		default:
-			return null;
-	}
-}
diff --git a/packages/client/src/sw/sw.ts b/packages/client/src/sw/sw.ts
deleted file mode 100644
index 68c650c771..0000000000
--- a/packages/client/src/sw/sw.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-/**
- * Service Worker
- */
-declare var self: ServiceWorkerGlobalScope;
-
-import { get, set } from 'idb-keyval';
-import composeNotification from '@/sw/compose-notification';
-import { I18n } from '@/scripts/i18n';
-
-//#region Variables
-const version = _VERSION_;
-const cacheName = `mk-cache-${version}`;
-
-let lang: string;
-let i18n: I18n<any>;
-let pushesPool: any[] = [];
-//#endregion
-
-//#region Startup
-get('lang').then(async prelang => {
-	if (!prelang) return;
-	lang = prelang;
-	return fetchLocale();
-});
-//#endregion
-
-//#region Lifecycle: Install
-self.addEventListener('install', ev => {
-	self.skipWaiting();
-});
-//#endregion
-
-//#region Lifecycle: Activate
-self.addEventListener('activate', ev => {
-	ev.waitUntil(
-		caches.keys()
-			.then(cacheNames => Promise.all(
-				cacheNames
-					.filter((v) => v !== cacheName)
-					.map(name => caches.delete(name))
-			))
-			.then(() => self.clients.claim())
-	);
-});
-//#endregion
-
-//#region When: Fetching
-self.addEventListener('fetch', ev => {
-	// Nothing to do
-});
-//#endregion
-
-//#region When: Caught Notification
-self.addEventListener('push', ev => {
-	// ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆๅ–ๅพ—
-	ev.waitUntil(self.clients.matchAll({
-		includeUncontrolled: true
-	}).then(async clients => {
-		// ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใŒใ‚ใฃใŸใ‚‰ใ‚นใƒˆใƒชใƒผใƒ ใซๆŽฅ็ถšใ—ใฆใ„ใ‚‹ใจใ„ใ†ใ“ใจใชใฎใง้€š็Ÿฅใ—ใชใ„
-		if (clients.length != 0) return;
-
-		const { type, body } = ev.data?.json();
-
-		// localeใ‚’่ชญใฟ่พผใ‚ใฆใŠใ‚‰ใši18nใŒundefinedใ ใฃใŸๅ ดๅˆใฏpushesPoolใซใŸใ‚ใฆใŠใ
-		if (!i18n) return pushesPool.push({ type, body });
-
-		const n = await composeNotification(type, body, i18n);
-		if (n) return self.registration.showNotification(...n);
-	}));
-});
-//#endregion
-
-//#region When: Caught a message from the client
-self.addEventListener('message', ev => {
-	switch(ev.data) {
-		case 'clear':
-			return; // TODO
-		default:
-			break;
-	}
-
-	if (typeof ev.data === 'object') {
-		// E.g. '[object Array]' โ†’ 'array'
-		const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase();
-
-		if (otype === 'object') {
-			if (ev.data.msg === 'initialize') {
-				lang = ev.data.lang;
-				set('lang', lang);
-				fetchLocale();
-			}
-		}
-	}
-});
-//#endregion
-
-//#region Function: (Re)Load i18n instance
-async function fetchLocale() {
-	//#region localeใƒ•ใ‚กใ‚คใƒซใฎ่ชญใฟ่พผใฟ
-	// Service Workerใฏไฝ•ๅบฆใ‚‚่ตทๅ‹•ใ—ใใฎใŸใณใซlocaleใ‚’่ชญใฟ่พผใ‚€ใฎใงใ€CacheStorageใ‚’ไฝฟใ†
-	const localeUrl = `/assets/locales/${lang}.${version}.json`;
-	let localeRes = await caches.match(localeUrl);
-
-	if (!localeRes) {
-		localeRes = await fetch(localeUrl);
-		const clone = localeRes?.clone();
-		if (!clone?.clone().ok) return;
-
-		caches.open(cacheName).then(cache => cache.put(localeUrl, clone));
-	}
-
-	i18n = new I18n(await localeRes.json());
-	//#endregion
-
-	//#region i18nใ‚’ใใกใ‚“ใจ่ชญใฟ่พผใ‚“ใ ๅพŒใซใ‚„ใ‚ŠใŸใ„ๅ‡ฆ็†
-	for (const { type, body } of pushesPool) {
-		const n = await composeNotification(type, body, i18n);
-		if (n) self.registration.showNotification(...n);
-	}
-	pushesPool = [];
-	//#endregion
-}
-//#endregion
diff --git a/packages/client/src/theme-store.ts b/packages/client/src/theme-store.ts
index e7962e7e8e..fdc92ed793 100644
--- a/packages/client/src/theme-store.ts
+++ b/packages/client/src/theme-store.ts
@@ -14,9 +14,9 @@ export async function fetchThemes(): Promise<void> {
 	try {
 		const themes = await api('i/registry/get', { scope: ['client'], key: 'themes' });
 		localStorage.setItem(lsCacheKey, JSON.stringify(themes));
-	} catch (e) {
-		if (e.code === 'NO_SUCH_KEY') return;
-		throw e;
+	} catch (err) {
+		if (err.code === 'NO_SUCH_KEY') return;
+		throw err;
 	}
 }
 
@@ -28,7 +28,7 @@ export async function addTheme(theme: Theme): Promise<void> {
 }
 
 export async function removeTheme(theme: Theme): Promise<void> {
-	const themes = getThemes().filter(t => t.id != theme.id);
+	const themes = getThemes().filter(t => t.id !== theme.id);
 	await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
 	localStorage.setItem(lsCacheKey, JSON.stringify(themes));
 }
diff --git a/packages/client/src/ui/_common_/common.vue b/packages/client/src/ui/_common_/common.vue
index 05688d7c53..9f7388db53 100644
--- a/packages/client/src/ui/_common_/common.vue
+++ b/packages/client/src/ui/_common_/common.vue
@@ -17,9 +17,11 @@
 
 <script lang="ts">
 import { defineAsyncComponent, defineComponent } from 'vue';
-import { popup, popups, uploads, pendingApiRequestsCount } from '@/os';
+import { popup, popups, pendingApiRequestsCount } from '@/os';
+import { uploads } from '@/scripts/upload';
 import * as sound from '@/scripts/sound';
 import { $i } from '@/account';
+import { swInject } from './sw-inject';
 import { stream } from '@/stream';
 
 export default defineComponent({
@@ -37,7 +39,7 @@ export default defineComponent({
 					id: notification.id
 				});
 
-				popup(import('@/components/notification-toast.vue'), {
+				popup(defineAsyncComponent(() => import('@/components/notification-toast.vue')), {
 					notification
 				}, {}, 'closed');
 			}
@@ -48,6 +50,11 @@ export default defineComponent({
 		if ($i) {
 			const connection = stream.useChannel('main', null, 'UI');
 			connection.on('notification', onNotification);
+
+			//#region Listen message from SW
+			if ('serviceWorker' in navigator) {
+				swInject();
+			}
 		}
 
 		return {
diff --git a/packages/client/src/ui/_common_/sidebar-for-mobile.vue b/packages/client/src/ui/_common_/sidebar-for-mobile.vue
index afcc50725b..41d0837233 100644
--- a/packages/client/src/ui/_common_/sidebar-for-mobile.vue
+++ b/packages/client/src/ui/_common_/sidebar-for-mobile.vue
@@ -33,7 +33,7 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, ref, toRef, watch } from 'vue';
+import { computed, defineAsyncComponent, defineComponent, ref, toRef, watch } from 'vue';
 import { host } from '@/config';
 import { search } from '@/scripts/search';
 import * as os from '@/os';
@@ -61,13 +61,13 @@ export default defineComponent({
 			otherMenuItemIndicated,
 			post: os.post,
 			search,
-			openAccountMenu:(ev) => {
+			openAccountMenu: (ev) => {
 				openAccountMenu({
 					withExtraOperation: true,
 				}, ev);
 			},
 			more: () => {
-				os.popup(import('@/components/launch-pad.vue'), {}, {
+				os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {}, {
 				}, 'closed');
 			},
 		};
diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue
index a23b7d4152..d65e776d86 100644
--- a/packages/client/src/ui/_common_/sidebar.vue
+++ b/packages/client/src/ui/_common_/sidebar.vue
@@ -33,7 +33,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, ref, watch } from 'vue';
 import * as os from '@/os';
 import { menuDef } from '@/menu';
 import { $i, openAccountMenu as openAccountMenu_ } from '@/account';
@@ -69,7 +69,7 @@ function openAccountMenu(ev: MouseEvent) {
 }
 
 function more(ev: MouseEvent) {
-	os.popup(import('@/components/launch-pad.vue'), {
+	os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {
 		src: ev.currentTarget ?? ev.target,
 	}, {
 	}, 'closed');
diff --git a/packages/client/src/ui/_common_/sw-inject.ts b/packages/client/src/ui/_common_/sw-inject.ts
new file mode 100644
index 0000000000..371f80ca15
--- /dev/null
+++ b/packages/client/src/ui/_common_/sw-inject.ts
@@ -0,0 +1,44 @@
+import { inject } from 'vue';
+import { post } from '@/os';
+import { $i, login } from '@/account';
+import { defaultStore } from '@/store';
+import { getAccountFromId } from '@/scripts/get-account-from-id';
+import { router } from '@/router';
+
+export function swInject() {
+	const navHook = inject('navHook', null);
+	const sideViewHook = inject('sideViewHook', null);
+
+	navigator.serviceWorker.addEventListener('message', ev => {
+		if (_DEV_) {
+			console.log('sw msg', ev.data);
+		}
+
+		if (ev.data.type !== 'order') return;
+
+		if (ev.data.loginId !== $i?.id) {
+			return getAccountFromId(ev.data.loginId).then(account => {
+				if (!account) return;
+				return login(account.token, ev.data.url);
+			});
+		}
+
+		switch (ev.data.order) {
+			case 'post':
+				return post(ev.data.options);
+			case 'push':
+				if (router.currentRoute.value.path === ev.data.url) {
+					return window.scroll({ top: 0, behavior: 'smooth' });
+				}
+				if (navHook) {
+					return navHook(ev.data.url);
+				}
+				if (sideViewHook && defaultStore.state.defaultSideView && ev.data.url !== '/') {
+					return sideViewHook(ev.data.url);
+				}
+				return router.push(ev.data.url);
+			default:
+				return;
+		}
+	});
+}
diff --git a/packages/client/src/ui/_common_/upload.vue b/packages/client/src/ui/_common_/upload.vue
index ab7678a505..f3703d0e8f 100644
--- a/packages/client/src/ui/_common_/upload.vue
+++ b/packages/client/src/ui/_common_/upload.vue
@@ -20,8 +20,8 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import * as os from '@/os';
+import { uploads } from '@/scripts/upload';
 
-const uploads = os.uploads;
 const zIndex = os.claimZIndex('high');
 </script>
 
diff --git a/packages/client/src/ui/classic.header.vue b/packages/client/src/ui/classic.header.vue
index c7fddbc491..57008aeaed 100644
--- a/packages/client/src/ui/classic.header.vue
+++ b/packages/client/src/ui/classic.header.vue
@@ -39,7 +39,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import { host } from '@/config';
 import { search } from '@/scripts/search';
 import * as os from '@/os';
@@ -101,7 +101,7 @@ export default defineComponent({
 		},
 
 		more(ev) {
-			os.popup(import('@/components/launch-pad.vue'), {
+			os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {
 				src: ev.currentTarget ?? ev.target,
 				anchor: { x: 'center', y: 'bottom' },
 			}, {
diff --git a/packages/client/src/ui/classic.sidebar.vue b/packages/client/src/ui/classic.sidebar.vue
index 3364ee39be..6c0ce023e4 100644
--- a/packages/client/src/ui/classic.sidebar.vue
+++ b/packages/client/src/ui/classic.sidebar.vue
@@ -41,7 +41,7 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineAsyncComponent, defineComponent } from 'vue';
 import { host } from '@/config';
 import { search } from '@/scripts/search';
 import * as os from '@/os';
@@ -121,12 +121,12 @@ export default defineComponent({
 		},
 
 		more(ev) {
-			os.popup(import('@/components/launch-pad.vue'), {
+			os.popup(defineAsyncComponent(() => import('@/components/launch-pad.vue')), {
 				src: ev.currentTarget ?? ev.target,
 			}, {}, 'closed');
 		},
 
-		openAccountMenu:(ev) => {
+		openAccountMenu: (ev) => {
 			openAccountMenu({
 				withExtraOperation: true,
 			}, ev);
diff --git a/packages/client/src/ui/classic.widgets.vue b/packages/client/src/ui/classic.widgets.vue
index f42f27e926..6f9d18bde5 100644
--- a/packages/client/src/ui/classic.widgets.vue
+++ b/packages/client/src/ui/classic.widgets.vue
@@ -44,13 +44,13 @@ export default defineComponent({
 		},
 
 		removeWidget(widget) {
-			this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id));
+			this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id !== widget.id));
 		},
 
 		updateWidget({ id, data }) {
 			this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? {
 				...w,
-				data: data
+				data,
 			} : w));
 		},
 
diff --git a/packages/client/src/ui/deck.vue b/packages/client/src/ui/deck.vue
index 1e0d9a1652..e538a93f06 100644
--- a/packages/client/src/ui/deck.vue
+++ b/packages/client/src/ui/deck.vue
@@ -17,7 +17,7 @@
 			:key="ids[0]"
 			class="column"
 			:column="columns.find(c => c.id === ids[0])"
-			 :is-stacked="false"
+			:is-stacked="false"
 			:style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }"
 			@parent-focus="moveFocus(ids[0], $event)"
 		/>
diff --git a/packages/client/src/ui/deck/antenna-column.vue b/packages/client/src/ui/deck/antenna-column.vue
index e0f56c2800..f12f5c6b25 100644
--- a/packages/client/src/ui/deck/antenna-column.vue
+++ b/packages/client/src/ui/deck/antenna-column.vue
@@ -22,8 +22,8 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'loaded'): void;
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'loaded'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 let timeline = $ref<InstanceType<typeof XTimeline>>();
diff --git a/packages/client/src/ui/deck/column-core.vue b/packages/client/src/ui/deck/column-core.vue
index 485e89a062..2667b6d745 100644
--- a/packages/client/src/ui/deck/column-core.vue
+++ b/packages/client/src/ui/deck/column-core.vue
@@ -29,7 +29,7 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 /*
diff --git a/packages/client/src/ui/deck/column.vue b/packages/client/src/ui/deck/column.vue
index 5f8da8cf8f..6db3549fbb 100644
--- a/packages/client/src/ui/deck/column.vue
+++ b/packages/client/src/ui/deck/column.vue
@@ -61,8 +61,8 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-	(e: 'change-active-state', v: boolean): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'change-active-state', v: boolean): void;
 }>();
 
 let body = $ref<HTMLDivElement>();
@@ -94,7 +94,6 @@ onBeforeUnmount(() => {
 	os.deckGlobalEvents.off('column.dragEnd', onOtherDragEnd);
 });
 
-
 function onOtherDragStart() {
 	dropready = true;
 }
@@ -193,9 +192,9 @@ function goTop() {
 	});
 }
 
-function onDragstart(e) {
-	e.dataTransfer.effectAllowed = 'move';
-	e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id);
+function onDragstart(ev) {
+	ev.dataTransfer.effectAllowed = 'move';
+	ev.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, props.column.id);
 
 	// Chromeใฎใƒใ‚ฐใงใ€Dragstartใƒใƒณใƒ‰ใƒฉๅ†…ใงใ™ใใซDOMใ‚’ๅค‰ๆ›ดใ™ใ‚‹(=ใƒชใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใชใƒ—ใƒญใƒ‘ใƒ†ใ‚ฃใ‚’ๅค‰ๆ›ดใ™ใ‚‹)ใจDragใŒ็ต‚ไบ†ใ—ใฆใ—ใพใ†
 	// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
@@ -204,35 +203,34 @@ function onDragstart(e) {
 	}, 10);
 }
 
-function onDragend(e) {
+function onDragend(ev) {
 	dragging = false;
 }
 
-function onDragover(e) {
+function onDragover(ev) {
 	// ่‡ชๅˆ†่‡ช่บซใŒใƒ‰ใƒฉใƒƒใ‚ฐใ•ใ‚Œใฆใ„ใ‚‹ๅ ดๅˆ
 	if (dragging) {
 		// ่‡ชๅˆ†่‡ช่บซใซใฏใƒ‰ใƒญใƒƒใƒ—ใ•ใ›ใชใ„
-		e.dataTransfer.dropEffect = 'none';
-		return;
+		ev.dataTransfer.dropEffect = 'none';
+	} else {
+		const isDeckColumn = ev.dataTransfer.types[0] === _DATA_TRANSFER_DECK_COLUMN_;
+
+		ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
+
+		if (isDeckColumn) draghover = true;
 	}
-
-	const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_;
-
-	e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
-
-	if (!dragging && isDeckColumn) draghover = true;
 }
 
 function onDragleave() {
 	draghover = false;
 }
 
-function onDrop(e) {
+function onDrop(ev) {
 	draghover = false;
 	os.deckGlobalEvents.emit('column.dragEnd');
 
-	const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
-	if (id != null && id != '') {
+	const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
+	if (id != null && id !== '') {
 		swapColumn(props.column.id, id);
 	}
 }
diff --git a/packages/client/src/ui/deck/deck-store.ts b/packages/client/src/ui/deck/deck-store.ts
index f7c39ad8fd..c847bf2b43 100644
--- a/packages/client/src/ui/deck/deck-store.ts
+++ b/packages/client/src/ui/deck/deck-store.ts
@@ -72,8 +72,8 @@ export const loadDeck = async () => {
 			scope: ['client', 'deck', 'profiles'],
 			key: deckStore.state.profile,
 		});
-	} catch (e) {
-		if (e.code === 'NO_SUCH_KEY') {
+	} catch (err) {
+		if (err.code === 'NO_SUCH_KEY') {
 			// ๅพŒๆ–นไบ’ๆ›ๆ€งใฎใŸใ‚
 			if (deckStore.state.profile === 'default') {
 				saveDeck();
@@ -94,7 +94,7 @@ export const loadDeck = async () => {
 			deckStore.set('layout', [['a'], ['b']]);
 			return;
 		}
-		throw e;
+		throw err;
 	}
 
 	deckStore.set('columns', deck.columns);
@@ -114,7 +114,7 @@ export const saveDeck = throttle(1000, () => {
 });
 
 export function addColumn(column: Column) {
-	if (column.name == undefined) column.name = null;
+	if (column.name === undefined) column.name = null;
 	deckStore.push('columns', column);
 	deckStore.push('layout', [column.id]);
 	saveDeck();
@@ -129,10 +129,10 @@ export function removeColumn(id: Column['id']) {
 }
 
 export function swapColumn(a: Column['id'], b: Column['id']) {
-	const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) != -1);
-	const aY = deckStore.state.layout[aX].findIndex(id => id == a);
-	const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) != -1);
-	const bY = deckStore.state.layout[bX].findIndex(id => id == b);
+	const aX = deckStore.state.layout.findIndex(ids => ids.indexOf(a) !== -1);
+	const aY = deckStore.state.layout[aX].findIndex(id => id === a);
+	const bX = deckStore.state.layout.findIndex(ids => ids.indexOf(b) !== -1);
+	const bY = deckStore.state.layout[bX].findIndex(id => id === b);
 	const layout = copy(deckStore.state.layout);
 	layout[aX][aY] = b;
 	layout[bX][bY] = a;
@@ -259,7 +259,7 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
 	const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
 	const column = copy(deckStore.state.columns[columnIndex]);
 	if (column == null) return;
-	column.widgets = column.widgets.filter(w => w.id != widget.id);
+	column.widgets = column.widgets.filter(w => w.id !== widget.id);
 	columns[columnIndex] = column;
 	deckStore.set('columns', columns);
 	saveDeck();
@@ -276,14 +276,14 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
 	saveDeck();
 }
 
-export function updateColumnWidget(id: Column['id'], widgetId: string, data: any) {
+export function updateColumnWidget(id: Column['id'], widgetId: string, WidgetData: any) {
 	const columns = copy(deckStore.state.columns);
 	const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
 	const column = copy(deckStore.state.columns[columnIndex]);
 	if (column == null) return;
 	column.widgets = column.widgets.map(w => w.id === widgetId ? {
 		...w,
-		data: data
+		data: widgetData,
 	} : w);
 	columns[columnIndex] = column;
 	deckStore.set('columns', columns);
diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue
index ebaba574f4..4837c0ce38 100644
--- a/packages/client/src/ui/deck/direct-column.vue
+++ b/packages/client/src/ui/deck/direct-column.vue
@@ -18,7 +18,7 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 const pagination = {
diff --git a/packages/client/src/ui/deck/list-column.vue b/packages/client/src/ui/deck/list-column.vue
index b990516d05..843a3bd1cb 100644
--- a/packages/client/src/ui/deck/list-column.vue
+++ b/packages/client/src/ui/deck/list-column.vue
@@ -22,8 +22,8 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'loaded'): void;
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'loaded'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 let timeline = $ref<InstanceType<typeof XTimeline>>();
diff --git a/packages/client/src/ui/deck/main-column.vue b/packages/client/src/ui/deck/main-column.vue
index 57caab44cb..3c97cd4867 100644
--- a/packages/client/src/ui/deck/main-column.vue
+++ b/packages/client/src/ui/deck/main-column.vue
@@ -35,7 +35,7 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 let pageInfo = $ref<Record<string, any> | null>(null);
diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue
index a7a012a7fb..0b6ca3a239 100644
--- a/packages/client/src/ui/deck/mentions-column.vue
+++ b/packages/client/src/ui/deck/mentions-column.vue
@@ -18,7 +18,7 @@ defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 const pagination = {
diff --git a/packages/client/src/ui/deck/notifications-column.vue b/packages/client/src/ui/deck/notifications-column.vue
index 50ee12a275..6dd040cb8d 100644
--- a/packages/client/src/ui/deck/notifications-column.vue
+++ b/packages/client/src/ui/deck/notifications-column.vue
@@ -7,7 +7,7 @@
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { defineAsyncComponent } from 'vue';
 import XColumn from './column.vue';
 import XNotifications from '@/components/notifications.vue';
 import * as os from '@/os';
@@ -20,11 +20,11 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 function func() {
-	os.popup(import('@/components/notification-setting-window.vue'), {
+	os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
 		includingTypes: props.column.includingTypes,
 	}, {
 		done: async (res) => {
diff --git a/packages/client/src/ui/deck/tl-column.vue b/packages/client/src/ui/deck/tl-column.vue
index 02b9ef83a1..f3ecda5aa4 100644
--- a/packages/client/src/ui/deck/tl-column.vue
+++ b/packages/client/src/ui/deck/tl-column.vue
@@ -35,8 +35,8 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'loaded'): void;
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'loaded'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 let disabled = $ref(false);
diff --git a/packages/client/src/ui/deck/widgets-column.vue b/packages/client/src/ui/deck/widgets-column.vue
index a2edc38357..10c6f5adf6 100644
--- a/packages/client/src/ui/deck/widgets-column.vue
+++ b/packages/client/src/ui/deck/widgets-column.vue
@@ -3,7 +3,7 @@
 	<template #header><i class="fas fa-window-maximize" style="margin-right: 8px;"></i>{{ column.name }}</template>
 
 	<div class="wtdtxvec">
-		<XWidgets v-if="column.widgets" :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
+		<XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
 	</div>
 </XColumn>
 </template>
@@ -20,7 +20,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(e: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
+	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
 let edit = $ref(false);
diff --git a/packages/client/src/ui/universal.widgets.vue b/packages/client/src/ui/universal.widgets.vue
index 2660e80368..7aed083886 100644
--- a/packages/client/src/ui/universal.widgets.vue
+++ b/packages/client/src/ui/universal.widgets.vue
@@ -3,7 +3,7 @@
 	<XWidgets :edit="editMode" :widgets="defaultStore.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
 
 	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="fas fa-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
-	<button v-else class="_textButton" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button>
+	<button v-else class="_textButton mk-widget-edit" style="font-size: 0.9em;" @click="editMode = true"><i class="fas fa-pencil-alt"></i> {{ i18n.ts.editWidgets }}</button>
 </div>
 </template>
 
@@ -14,7 +14,7 @@ import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
 
 const emit = defineEmits<{
-	(e: 'mounted', el: Element): void;
+	(ev: 'mounted', el: Element): void;
 }>();
 
 let editMode = $ref(false);
@@ -32,13 +32,13 @@ function addWidget(widget) {
 }
 
 function removeWidget(widget) {
-	defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id != widget.id));
+	defaultStore.set('widgets', defaultStore.state.widgets.filter(w => w.id !== widget.id));
 }
 
 function updateWidget({ id, data }) {
 	defaultStore.set('widgets', defaultStore.state.widgets.map(w => w.id === id ? {
 		...w,
-		data: data
+		data,
 	} : w));
 }
 
diff --git a/packages/client/src/widgets/activity.calendar.vue b/packages/client/src/widgets/activity.calendar.vue
index b833bd65ca..33b95b00db 100644
--- a/packages/client/src/widgets/activity.calendar.vue
+++ b/packages/client/src/widgets/activity.calendar.vue
@@ -1,13 +1,13 @@
 <template>
 <svg viewBox="0 0 21 7">
-	<rect v-for="record in data" class="day"
+	<rect v-for="record in activity" class="day"
 		width="1" height="1"
 		:x="record.x" :y="record.date.weekday"
 		rx="1" ry="1"
 		fill="transparent">
 		<title>{{ record.date.year }}/{{ record.date.month + 1 }}/{{ record.date.day }}</title>
 	</rect>
-	<rect v-for="record in data" class="day"
+	<rect v-for="record in activity" class="day"
 		:width="record.v" :height="record.v"
 		:x="record.x + ((1 - record.v) / 2)" :y="record.date.weekday + ((1 - record.v) / 2)"
 		rx="1" ry="1"
@@ -15,7 +15,7 @@
 		style="pointer-events: none;"/>
 	<rect class="today"
 		width="1" height="1"
-		:x="data[0].x" :y="data[0].date.weekday"
+		:x="activity[0].x" :y="activity[0].date.weekday"
 		rx="1" ry="1"
 		fill="none"
 		stroke-width="0.1"
@@ -23,45 +23,41 @@
 </svg>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@/os';
+<script lang="ts" setup>
+const props = defineProps<{
+	activity: any[]
+}>();
 
-export default defineComponent({
-	props: ['data'],
-	created() {
-		for (const d of this.data) {
-			d.total = d.notes + d.replies + d.renotes;
-		}
-		const peak = Math.max.apply(null, this.data.map(d => d.total));
+for (const d of props.activity) {
+	d.total = d.notes + d.replies + d.renotes;
+}
+const peak = Math.max(...props.activity.map(d => d.total));
 
-		const now = new Date();
-		const year = now.getFullYear();
-		const month = now.getMonth();
-		const day = now.getDate();
+const now = new Date();
+const year = now.getFullYear();
+const month = now.getMonth();
+const day = now.getDate();
 
-		let x = 20;
-		this.data.slice().forEach((d, i) => {
-			d.x = x;
+let x = 20;
+props.activity.slice().forEach((d, i) => {
+	d.x = x;
 
-			const date = new Date(year, month, day - i);
-			d.date = {
-				year: date.getFullYear(),
-				month: date.getMonth(),
-				day: date.getDate(),
-				weekday: date.getDay()
-			};
+	const date = new Date(year, month, day - i);
+	d.date = {
+		year: date.getFullYear(),
+		month: date.getMonth(),
+		day: date.getDate(),
+		weekday: date.getDay()
+	};
 
-			d.v = peak === 0 ? 0 : d.total / (peak / 2);
-			if (d.v > 1) d.v = 1;
-			const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170;
-			const cs = d.v * 100;
-			const cl = 15 + ((1 - d.v) * 80);
-			d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
+	d.v = peak === 0 ? 0 : d.total / (peak / 2);
+	if (d.v > 1) d.v = 1;
+	const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170;
+	const cs = d.v * 100;
+	const cl = 15 + ((1 - d.v) * 80);
+	d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
 
-			if (d.date.weekday === 0) x--;
-		});
-	}
+	if (d.date.weekday === 0) x--;
 });
 </script>
 
diff --git a/packages/client/src/widgets/activity.chart.vue b/packages/client/src/widgets/activity.chart.vue
index 9702d66663..b7db2af580 100644
--- a/packages/client/src/widgets/activity.chart.vue
+++ b/packages/client/src/widgets/activity.chart.vue
@@ -24,9 +24,19 @@
 </svg>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import * as os from '@/os';
+<script lang="ts" setup>
+const props = defineProps<{
+	activity: any[]
+}>();
+
+let viewBoxX: number = $ref(147);
+let viewBoxY: number = $ref(60);
+let zoom: number = $ref(1);
+let pos: number = $ref(0);
+let pointsNote: any = $ref(null);
+let pointsReply: any = $ref(null);
+let pointsRenote: any = $ref(null);
+let pointsTotal: any = $ref(null);
 
 function dragListen(fn) {
 	window.addEventListener('mousemove',  fn);
@@ -40,60 +50,35 @@ function dragClear(fn) {
 	window.removeEventListener('mouseup',    dragClear);
 }
 
-export default defineComponent({
-	props: ['data'],
-	data() {
-		return {
-			viewBoxX: 147,
-			viewBoxY: 60,
-			zoom: 1,
-			pos: 0,
-			pointsNote: null,
-			pointsReply: null,
-			pointsRenote: null,
-			pointsTotal: null
-		};
-	},
-	created() {
-		for (const d of this.data) {
-			d.total = d.notes + d.replies + d.renotes;
-		}
+function onMousedown(ev) {
+	const clickX = ev.clientX;
+	const clickY = ev.clientY;
+	const baseZoom = zoom;
+	const basePos = pos;
 
-		this.render();
-	},
-	methods: {
-		render() {
-			const peak = Math.max.apply(null, this.data.map(d => d.total));
-			if (peak != 0) {
-				const data = this.data.slice().reverse();
-				this.pointsNote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' ');
-				this.pointsReply = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' ');
-				this.pointsRenote = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' ');
-				this.pointsTotal = data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
-			}
-		},
-		onMousedown(e) {
-			const clickX = e.clientX;
-			const clickY = e.clientY;
-			const baseZoom = this.zoom;
-			const basePos = this.pos;
+	// ๅ‹•ใ‹ใ—ใŸๆ™‚
+	dragListen(me => {
+		let moveLeft = me.clientX - clickX;
+		let moveTop = me.clientY - clickY;
 
-			// ๅ‹•ใ‹ใ—ใŸๆ™‚
-			dragListen(me => {
-				let moveLeft = me.clientX - clickX;
-				let moveTop = me.clientY - clickY;
+		zoom = Math.max(1, baseZoom + (-moveTop / 20));
+		pos = Math.min(0, basePos + moveLeft);
+		if (pos < -(((props.activity.length - 1) * zoom) - viewBoxX)) pos = -(((props.activity.length - 1) * zoom) - viewBoxX);
 
-				this.zoom = baseZoom + (-moveTop / 20);
-				this.pos = basePos + moveLeft;
-				if (this.zoom < 1) this.zoom = 1;
-				if (this.pos > 0) this.pos = 0;
-				if (this.pos < -(((this.data.length - 1) * this.zoom) - this.viewBoxX)) this.pos = -(((this.data.length - 1) * this.zoom) - this.viewBoxX);
+		render();
+	});
+}
 
-				this.render();
-			});
-		}
+function render() {
+	const peak = Math.max(...props.activity.map(d => d.total));
+	if (peak !== 0) {
+		const activity = props.activity.slice().reverse();
+		pointsNote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.notes / peak)) * viewBoxY}`).join(' ');
+		pointsReply = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.replies / peak)) * viewBoxY}`).join(' ');
+		pointsRenote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.renotes / peak)) * viewBoxY}`).join(' ');
+		pointsTotal = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.total / peak)) * viewBoxY}`).join(' ');
 	}
-});
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/widgets/activity.vue b/packages/client/src/widgets/activity.vue
index acbbb7a97a..7fb9f5894c 100644
--- a/packages/client/src/widgets/activity.vue
+++ b/packages/client/src/widgets/activity.vue
@@ -1,13 +1,13 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent">
+<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" class="mkw-activity">
 	<template #header><i class="fas fa-chart-bar"></i>{{ $ts._widgets.activity }}</template>
 	<template #func><button class="_button" @click="toggleView()"><i class="fas fa-sort"></i></button></template>
 
 	<div>
 		<MkLoading v-if="fetching"/>
 		<template v-else>
-			<XCalendar v-show="widgetProps.view === 0" :data="[].concat(activity)"/>
-			<XChart v-show="widgetProps.view === 1" :data="[].concat(activity)"/>
+			<XCalendar v-show="widgetProps.view === 0" :activity="[].concat(activity)"/>
+			<XChart v-show="widgetProps.view === 1" :activity="[].concat(activity)"/>
 		</template>
 	</div>
 </MkContainer>
@@ -47,7 +47,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/aichan.vue b/packages/client/src/widgets/aichan.vue
index 03e394b976..cdd367cc84 100644
--- a/packages/client/src/widgets/aichan.vue
+++ b/packages/client/src/widgets/aichan.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false">
+<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-aichan">
 	<iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe>
 </MkContainer>
 </template>
@@ -24,7 +24,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/aiscript.vue b/packages/client/src/widgets/aiscript.vue
index 0a5c0d614d..9fed292a69 100644
--- a/packages/client/src/widgets/aiscript.vue
+++ b/packages/client/src/widgets/aiscript.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader">
+<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscript">
 	<template #header><i class="fas fa-terminal"></i>{{ $ts._widgets.aiscript }}</template>
 
 	<div class="uylguesu _monospace">
@@ -43,7 +43,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -94,7 +94,7 @@ const run = async () => {
 	let ast;
 	try {
 		ast = parse(widgetProps.script);
-	} catch (e) {
+	} catch (err) {
 		os.alert({
 			type: 'error',
 			text: 'Syntax error :(',
@@ -103,10 +103,10 @@ const run = async () => {
 	}
 	try {
 		await aiscript.exec(ast);
-	} catch (e) {
+	} catch (err) {
 		os.alert({
 			type: 'error',
-			text: e,
+			text: err,
 		});
 	}
 };
diff --git a/packages/client/src/widgets/button.vue b/packages/client/src/widgets/button.vue
index a33afd6e7a..ee4e9c6423 100644
--- a/packages/client/src/widgets/button.vue
+++ b/packages/client/src/widgets/button.vue
@@ -40,7 +40,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -73,7 +73,7 @@ const run = async () => {
 	let ast;
 	try {
 		ast = parse(widgetProps.script);
-	} catch (e) {
+	} catch (err) {
 		os.alert({
 			type: 'error',
 			text: 'Syntax error :(',
@@ -82,10 +82,10 @@ const run = async () => {
 	}
 	try {
 		await aiscript.exec(ast);
-	} catch (e) {
+	} catch (err) {
 		os.alert({
 			type: 'error',
-			text: e,
+			text: err,
 		});
 	}
 };
diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue
index c6a69b3fb8..2a2b035541 100644
--- a/packages/client/src/widgets/calendar.vue
+++ b/packages/client/src/widgets/calendar.vue
@@ -53,7 +53,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/clock.vue b/packages/client/src/widgets/clock.vue
index 6acb10d74d..fbd2f9e899 100644
--- a/packages/client/src/widgets/clock.vue
+++ b/packages/client/src/widgets/clock.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false">
+<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-clock">
 	<div class="vubelbmv">
 		<MkAnalogClock class="clock" :thickness="widgetProps.thickness"/>
 	</div>
@@ -39,7 +39,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/digital-clock.vue b/packages/client/src/widgets/digital-clock.vue
index 62f052a692..a17ed040c9 100644
--- a/packages/client/src/widgets/digital-clock.vue
+++ b/packages/client/src/widgets/digital-clock.vue
@@ -41,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/federation.vue b/packages/client/src/widgets/federation.vue
index 5f1131dce1..a3862077bb 100644
--- a/packages/client/src/widgets/federation.vue
+++ b/packages/client/src/widgets/federation.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable">
+<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" class="mkw-federation">
 	<template #header><i class="fas fa-globe"></i>{{ $ts._widgets.federation }}</template>
 
 	<div class="wbrkwalb">
@@ -41,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/job-queue.vue b/packages/client/src/widgets/job-queue.vue
index 4a2a3cf233..8897f240bd 100644
--- a/packages/client/src/widgets/job-queue.vue
+++ b/packages/client/src/widgets/job-queue.vue
@@ -73,7 +73,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/memo.vue b/packages/client/src/widgets/memo.vue
index 450598f65a..8670cb2bac 100644
--- a/packages/client/src/widgets/memo.vue
+++ b/packages/client/src/widgets/memo.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader">
+<MkContainer :show-header="widgetProps.showHeader" class="mkw-memo">
 	<template #header><i class="fas fa-sticky-note"></i>{{ $ts._widgets.memo }}</template>
 
 	<div class="otgbylcu">
@@ -32,7 +32,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/notifications.vue b/packages/client/src/widgets/notifications.vue
index 8cf29c9271..18c546ee74 100644
--- a/packages/client/src/widgets/notifications.vue
+++ b/packages/client/src/widgets/notifications.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true">
+<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true" class="mkw-notifications">
 	<template #header><i class="fas fa-bell"></i>{{ $ts.notifications }}</template>
 	<template #func><button class="_button" @click="configureNotification()"><i class="fas fa-cog"></i></button></template>
 
@@ -15,6 +15,7 @@ import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExp
 import MkContainer from '@/components/ui/container.vue';
 import XNotifications from '@/components/notifications.vue';
 import * as os from '@/os';
+import { defineAsyncComponent } from 'vue';
 
 const name = 'notifications';
 
@@ -40,7 +41,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -49,7 +50,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
 );
 
 const configureNotification = () => {
-	os.popup(import('@/components/notification-setting-window.vue'), {
+	os.popup(defineAsyncComponent(() => import('@/components/notification-setting-window.vue')), {
 		includingTypes: widgetProps.includingTypes,
 	}, {
 		done: async (res) => {
diff --git a/packages/client/src/widgets/online-users.vue b/packages/client/src/widgets/online-users.vue
index 1746a8314e..eb3184fe9d 100644
--- a/packages/client/src/widgets/online-users.vue
+++ b/packages/client/src/widgets/online-users.vue
@@ -27,7 +27,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/photos.vue b/packages/client/src/widgets/photos.vue
index 8f948dc643..5d9b9e2984 100644
--- a/packages/client/src/widgets/photos.vue
+++ b/packages/client/src/widgets/photos.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null">
+<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" class="mkw-photos">
 	<template #header><i class="fas fa-camera"></i>{{ $ts._widgets.photos }}</template>
 
 	<div class="">
@@ -43,7 +43,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/post-form.vue b/packages/client/src/widgets/post-form.vue
index 51aa8fcf6b..b542913357 100644
--- a/packages/client/src/widgets/post-form.vue
+++ b/packages/client/src/widgets/post-form.vue
@@ -1,5 +1,5 @@
 <template>
-<XPostForm class="_panel" :fixed="true" :autofocus="false"/>
+<XPostForm class="_panel mkw-postForm" :fixed="true" :autofocus="false"/>
 </template>
 
 <script lang="ts" setup>
@@ -19,7 +19,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/rss.vue b/packages/client/src/widgets/rss.vue
index 9e2e503602..fc65f11813 100644
--- a/packages/client/src/widgets/rss.vue
+++ b/packages/client/src/widgets/rss.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader">
+<MkContainer :show-header="widgetProps.showHeader" class="mkw-rss">
 	<template #header><i class="fas fa-rss-square"></i>RSS</template>
 	<template #func><button class="_button" @click="configure"><i class="fas fa-cog"></i></button></template>
 
@@ -38,7 +38,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/server-metric/cpu-mem.vue b/packages/client/src/widgets/server-metric/cpu-mem.vue
index ad9e6a8b0f..00c3a10c9b 100644
--- a/packages/client/src/widgets/server-metric/cpu-mem.vue
+++ b/packages/client/src/widgets/server-metric/cpu-mem.vue
@@ -69,79 +69,72 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onBeforeUnmount } from 'vue';
 import { v4 as uuid } from 'uuid';
 
-export default defineComponent({
-	props: {
-		connection: {
-			required: true,
-		},
-		meta: {
-			required: true,
-		}
-	},
-	data() {
-		return {
-			viewBoxX: 50,
-			viewBoxY: 30,
-			stats: [],
-			cpuGradientId: uuid(),
-			cpuMaskId: uuid(),
-			memGradientId: uuid(),
-			memMaskId: uuid(),
-			cpuPolylinePoints: '',
-			memPolylinePoints: '',
-			cpuPolygonPoints: '',
-			memPolygonPoints: '',
-			cpuHeadX: null,
-			cpuHeadY: null,
-			memHeadX: null,
-			memHeadY: null,
-			cpuP: '',
-			memP: ''
-		};
-	},
-	mounted() {
-		this.connection.on('stats', this.onStats);
-		this.connection.on('statsLog', this.onStatsLog);
-		this.connection.send('requestLog', {
-			id: Math.random().toString().substr(2, 8)
-		});
-	},
-	beforeUnmount() {
-		this.connection.off('stats', this.onStats);
-		this.connection.off('statsLog', this.onStatsLog);
-	},
-	methods: {
-		onStats(stats) {
-			this.stats.push(stats);
-			if (this.stats.length > 50) this.stats.shift();
+const props = defineProps<{
+	connection: any,
+	meta: any
+}>();
 
-			const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu) * this.viewBoxY]);
-			const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.active / this.meta.mem.total)) * this.viewBoxY]);
-			this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-			this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+let viewBoxX: number = $ref(50);
+let viewBoxY: number = $ref(30);
+let stats: any[] = $ref([]);
+const cpuGradientId = uuid();
+const cpuMaskId = uuid();
+const memGradientId = uuid();
+const memMaskId = uuid();
+let cpuPolylinePoints: string = $ref('');
+let memPolylinePoints: string = $ref('');
+let cpuPolygonPoints: string = $ref('');
+let memPolygonPoints: string = $ref('');
+let cpuHeadX: any = $ref(null);
+let cpuHeadY: any = $ref(null);
+let memHeadX: any = $ref(null);
+let memHeadY: any = $ref(null);
+let cpuP: string = $ref('');
+let memP: string = $ref('');
 
-			this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
-			this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
-
-			this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0];
-			this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1];
-			this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0];
-			this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1];
-
-			this.cpuP = (stats.cpu * 100).toFixed(0);
-			this.memP = (stats.mem.active / this.meta.mem.total * 100).toFixed(0);
-		},
-		onStatsLog(statsLog) {
-			for (const stats of [...statsLog].reverse()) {
-				this.onStats(stats);
-			}
-		}
-	}
+onMounted(() => {
+	props.connection.on('stats', onStats);
+	props.connection.on('statsLog', onStatsLog);
+	props.connection.send('requestLog', {
+		id: Math.random().toString().substr(2, 8)
+	});
 });
+
+onBeforeUnmount(() => {
+	props.connection.off('stats', onStats);
+	props.connection.off('statsLog', onStatsLog);
+});
+
+function onStats(connStats) {
+	stats.push(connStats);
+	if (stats.length > 50) stats.shift();
+
+	let cpuPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - s.cpu) * viewBoxY]);
+	let memPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.mem.active / props.meta.mem.total)) * viewBoxY]);
+	cpuPolylinePoints = cpuPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	memPolylinePoints = memPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+
+	cpuPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`;
+	memPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`;
+
+	cpuHeadX = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][0];
+	cpuHeadY = cpuPolylinePointsStats[cpuPolylinePointsStats.length - 1][1];
+	memHeadX = memPolylinePointsStats[memPolylinePointsStats.length - 1][0];
+	memHeadY = memPolylinePointsStats[memPolylinePointsStats.length - 1][1];
+
+	cpuP = (connStats.cpu * 100).toFixed(0);
+	memP = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0);
+}
+
+function onStatsLog(statsLog) {
+	for (const revStats of [...statsLog].reverse()) {
+		onStats(revStats);
+	}
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/widgets/server-metric/cpu.vue b/packages/client/src/widgets/server-metric/cpu.vue
index 4478ee3065..baf802cb8f 100644
--- a/packages/client/src/widgets/server-metric/cpu.vue
+++ b/packages/client/src/widgets/server-metric/cpu.vue
@@ -9,38 +9,27 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onBeforeUnmount } from 'vue';
 import XPie from './pie.vue';
 
-export default defineComponent({
-	components: {
-		XPie
-	},
-	props: {
-		connection: {
-			required: true,
-		},
-		meta: {
-			required: true,
-		}
-	},
-	data() {
-		return {
-			usage: 0,
-		};
-	},
-	mounted() {
-		this.connection.on('stats', this.onStats);
-	},
-	beforeUnmount() {
-		this.connection.off('stats', this.onStats);
-	},
-	methods: {
-		onStats(stats) {
-			this.usage = stats.cpu;
-		}
-	}
+const props = defineProps<{
+	connection: any,
+	meta: any
+}>();
+
+let usage: number = $ref(0);
+
+function onStats(stats) {
+	usage = stats.cpu;
+}
+
+onMounted(() => {
+	props.connection.on('stats', onStats);
+});
+
+onBeforeUnmount(() => {
+	props.connection.off('stats', onStats);
 });
 </script>
 
diff --git a/packages/client/src/widgets/server-metric/index.vue b/packages/client/src/widgets/server-metric/index.vue
index 2caa73fa74..9e86b811d1 100644
--- a/packages/client/src/widgets/server-metric/index.vue
+++ b/packages/client/src/widgets/server-metric/index.vue
@@ -50,7 +50,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -65,7 +65,7 @@ os.api('server-info', {}).then(res => {
 });
 
 const toggleView = () => {
-	if (widgetProps.view == 4) {
+	if (widgetProps.view === 4) {
 		widgetProps.view = 0;
 	} else {
 		widgetProps.view++;
diff --git a/packages/client/src/widgets/server-metric/mem.vue b/packages/client/src/widgets/server-metric/mem.vue
index a6ca7b1175..6018eb4265 100644
--- a/packages/client/src/widgets/server-metric/mem.vue
+++ b/packages/client/src/widgets/server-metric/mem.vue
@@ -10,46 +10,34 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onBeforeUnmount } from 'vue';
 import XPie from './pie.vue';
 import bytes from '@/filters/bytes';
 
-export default defineComponent({
-	components: {
-		XPie
-	},
-	props: {
-		connection: {
-			required: true,
-		},
-		meta: {
-			required: true,
-		}
-	},
-	data() {
-		return {
-			usage: 0,
-			total: 0,
-			used: 0,
-			free: 0,
-		};
-	},
-	mounted() {
-		this.connection.on('stats', this.onStats);
-	},
-	beforeUnmount() {
-		this.connection.off('stats', this.onStats);
-	},
-	methods: {
-		onStats(stats) {
-			this.usage = stats.mem.active / this.meta.mem.total;
-			this.total = this.meta.mem.total;
-			this.used = stats.mem.active;
-			this.free = this.meta.mem.total - stats.mem.active;
-		},
-		bytes
-	}
+const props = defineProps<{
+	connection: any,
+	meta: any
+}>();
+
+let usage: number = $ref(0);
+let total: number = $ref(0);
+let used: number = $ref(0);
+let free: number = $ref(0);
+
+function onStats(stats) {
+	usage = stats.mem.active / props.meta.mem.total;
+	total = props.meta.mem.total;
+	used = stats.mem.active;
+	free = total - used;
+}
+
+onMounted(() => {
+	props.connection.on('stats', onStats);
+});
+
+onBeforeUnmount(() => {
+	props.connection.off('stats', onStats);
 });
 </script>
 
diff --git a/packages/client/src/widgets/server-metric/net.vue b/packages/client/src/widgets/server-metric/net.vue
index 23c148eeb6..b698953f97 100644
--- a/packages/client/src/widgets/server-metric/net.vue
+++ b/packages/client/src/widgets/server-metric/net.vue
@@ -43,79 +43,71 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, onBeforeUnmount } from 'vue';
 import bytes from '@/filters/bytes';
 
-export default defineComponent({
-	props: {
-		connection: {
-			required: true,
-		},
-		meta: {
-			required: true,
-		}
-	},
-	data() {
-		return {
-			viewBoxX: 50,
-			viewBoxY: 30,
-			stats: [],
-			inPolylinePoints: '',
-			outPolylinePoints: '',
-			inPolygonPoints: '',
-			outPolygonPoints: '',
-			inHeadX: null,
-			inHeadY: null,
-			outHeadX: null,
-			outHeadY: null,
-			inRecent: 0,
-			outRecent: 0
-		};
-	},
-	mounted() {
-		this.connection.on('stats', this.onStats);
-		this.connection.on('statsLog', this.onStatsLog);
-		this.connection.send('requestLog', {
-			id: Math.random().toString().substr(2, 8)
-		});
-	},
-	beforeUnmount() {
-		this.connection.off('stats', this.onStats);
-		this.connection.off('statsLog', this.onStatsLog);
-	},
-	methods: {
-		onStats(stats) {
-			this.stats.push(stats);
-			if (this.stats.length > 50) this.stats.shift();
+const props = defineProps<{
+	connection: any,
+	meta: any
+}>();
 
-			const inPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.rx)));
-			const outPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.tx)));
+let viewBoxX: number = $ref(50);
+let viewBoxY: number = $ref(30);
+let stats: any[] = $ref([]);
+let inPolylinePoints: string = $ref('');
+let outPolylinePoints: string = $ref('');
+let inPolygonPoints: string = $ref('');
+let outPolygonPoints: string = $ref('');
+let inHeadX: any = $ref(null);
+let inHeadY: any = $ref(null);
+let outHeadX: any = $ref(null);
+let outHeadY: any = $ref(null);
+let inRecent: number = $ref(0);
+let outRecent: number = $ref(0);
 
-			const inPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * this.viewBoxY]);
-			const outPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * this.viewBoxY]);
-			this.inPolylinePoints = inPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-			this.outPolylinePoints = outPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-
-			this.inPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.inPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
-			this.outPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.outPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`;
-
-			this.inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0];
-			this.inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1];
-			this.outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0];
-			this.outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1];
-
-			this.inRecent = stats.net.rx;
-			this.outRecent = stats.net.tx;
-		},
-		onStatsLog(statsLog) {
-			for (const stats of [...statsLog].reverse()) {
-				this.onStats(stats);
-			}
-		},
-		bytes
-	}
+onMounted(() => {
+	props.connection.on('stats', onStats);
+	props.connection.on('statsLog', onStatsLog);
+	props.connection.send('requestLog', {
+		id: Math.random().toString().substr(2, 8)
+	});
 });
+
+onBeforeUnmount(() => {
+	props.connection.off('stats', onStats);
+	props.connection.off('statsLog', onStatsLog);
+});
+
+function onStats(connStats) {
+	stats.push(connStats);
+	if (stats.length > 50) stats.shift();
+
+	const inPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.rx)));
+	const outPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.tx)));
+
+	let inPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * viewBoxY]);
+	let outPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * viewBoxY]);
+	inPolylinePoints = inPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	outPolylinePoints = outPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+
+	inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
+	outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
+
+	inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
+	inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
+	outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
+	outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
+
+	inRecent = connStats.net.rx;
+	outRecent = connStats.net.tx;
+}
+
+function onStatsLog(statsLog) {
+	for (const revStats of [...statsLog].reverse()) {
+		onStats(revStats);
+	}
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/widgets/slideshow.vue b/packages/client/src/widgets/slideshow.vue
index 7b2e539685..fd78edbe40 100644
--- a/packages/client/src/widgets/slideshow.vue
+++ b/packages/client/src/widgets/slideshow.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="kvausudm _panel" :style="{ height: widgetProps.height + 'px' }">
+<div class="kvausudm _panel mkw-slideshow" :style="{ height: widgetProps.height + 'px' }">
 	<div @click="choose">
 		<p v-if="widgetProps.folderId == null">
 			{{ $ts.folder }}
@@ -37,7 +37,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -51,7 +51,7 @@ const slideA = ref<HTMLElement>();
 const slideB = ref<HTMLElement>();
 
 const change = () => {
-	if (images.value.length == 0) return;
+	if (images.value.length === 0) return;
 
 	const index = Math.floor(Math.random() * images.value.length);
 	const img = `url(${ images.value[index].url })`;
diff --git a/packages/client/src/widgets/timeline.vue b/packages/client/src/widgets/timeline.vue
index 34e3b20e36..3bcad1ae29 100644
--- a/packages/client/src/widgets/timeline.vue
+++ b/packages/client/src/widgets/timeline.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true">
+<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" class="mkw-timeline">
 	<template #header>
 		<button class="_button" @click="choose">
 			<i v-if="widgetProps.src === 'home'" class="fas fa-home"></i>
@@ -63,7 +63,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -103,19 +103,19 @@ const choose = async (ev) => {
 	os.popupMenu([{
 		text: i18n.ts._timelines.home,
 		icon: 'fas fa-home',
-		action: () => { setSrc('home') }
+		action: () => { setSrc('home'); }
 	}, {
 		text: i18n.ts._timelines.local,
 		icon: 'fas fa-comments',
-		action: () => { setSrc('local') }
+		action: () => { setSrc('local'); }
 	}, {
 		text: i18n.ts._timelines.social,
 		icon: 'fas fa-share-alt',
-		action: () => { setSrc('social') }
+		action: () => { setSrc('social'); }
 	}, {
 		text: i18n.ts._timelines.global,
 		icon: 'fas fa-globe',
-		action: () => { setSrc('global') }
+		action: () => { setSrc('global'); }
 	}, antennaItems.length > 0 ? null : undefined, ...antennaItems, listItems.length > 0 ? null : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => {
 		menuOpened.value = false;
 	});
diff --git a/packages/client/src/widgets/trends.vue b/packages/client/src/widgets/trends.vue
index a34710eae7..9680f1c892 100644
--- a/packages/client/src/widgets/trends.vue
+++ b/packages/client/src/widgets/trends.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader">
+<MkContainer :show-header="widgetProps.showHeader" class="mkw-trends">
 	<template #header><i class="fas fa-hashtag"></i>{{ $ts._widgets.trends }}</template>
 
 	<div class="wbrkwala">
@@ -40,7 +40,7 @@ type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 //const props = defineProps<WidgetComponentProps<WidgetProps>>();
 //const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (e: 'updateProps', props: WidgetProps); }>();
+const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/client/src/widgets/widget.ts b/packages/client/src/widgets/widget.ts
index 81239bfb3b..9626d01619 100644
--- a/packages/client/src/widgets/widget.ts
+++ b/packages/client/src/widgets/widget.ts
@@ -13,7 +13,7 @@ export type WidgetComponentProps<P extends Record<string, unknown>> = {
 };
 
 export type WidgetComponentEmits<P extends Record<string, unknown>> = {
-	(e: 'updateProps', props: P);
+	(ev: 'updateProps', props: P);
 };
 
 export type WidgetComponentExpose = {
@@ -45,7 +45,7 @@ export const useWidgetPropsManager = <F extends Form & Record<string, { default:
 	}, { deep: true, immediate: true, });
 
 	const save = throttle(3000, () => {
-		emit('updateProps', widgetProps)
+		emit('updateProps', widgetProps);
 	});
 
 	const configure = async () => {
diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json
index b44cf2f895..f7320a7251 100644
--- a/packages/client/tsconfig.json
+++ b/packages/client/tsconfig.json
@@ -18,6 +18,9 @@
 		"strictNullChecks": true,
 		"experimentalDecorators": true,
 		"resolveJsonModule": true,
+		"allowSyntheticDefaultImports": true,
+		"isolatedModules": true,
+		"useDefineForClassFields": true,
 		"baseUrl": ".",
 		"paths": {
 			"@/*": ["./src/*"],
@@ -26,14 +29,17 @@
 			"node_modules/@types",
 			"@types",
 		],
+		"types": [
+			"vite/client",
+		],
 		"lib": [
 			"esnext",
-			"dom",
-			"webworker"
+			"dom"
 		]
 	},
 	"compileOnSave": false,
 	"include": [
+		".eslintrc.js",
 		"./**/*.ts",
 		"./**/*.vue"
 	]
diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts
new file mode 100644
index 0000000000..af13e646c6
--- /dev/null
+++ b/packages/client/vite.config.ts
@@ -0,0 +1,72 @@
+import * as fs from 'fs';
+import pluginVue from '@vitejs/plugin-vue';
+import pluginJson5 from './vite.json5';
+import { defineConfig } from 'vite';
+
+import locales from '../../locales';
+import meta from '../../package.json';
+
+const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
+
+export default defineConfig(({ command, mode }) => {
+	fs.mkdirSync(__dirname + '/../../built', { recursive: true });
+	fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8');
+
+	return {
+		base: '/assets/',
+
+		plugins: [
+			pluginVue({
+				reactivityTransform: true,
+			}),
+			pluginJson5(),
+		],
+
+		resolve: {
+			extensions,
+			alias: {
+				'@/': __dirname + '/src/',
+				'/client-assets/': __dirname + '/assets/',
+				'/static-assets/': __dirname + '/../backend/assets/',
+			},
+		},
+
+		define: {
+			_VERSION_: JSON.stringify(meta.version),
+			_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
+			_ENV_: JSON.stringify(process.env.NODE_ENV),
+			_DEV_: process.env.NODE_ENV !== 'production',
+			_PERF_PREFIX_: JSON.stringify('Misskey:'),
+			_DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'),
+			_DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'),
+			_DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'),
+			__VUE_OPTIONS_API__: true,
+			__VUE_PROD_DEVTOOLS__: false,
+		},
+
+		build: {
+			target: [
+				'chrome100',
+				'firefox100',
+				'safari15',
+			],
+			manifest: 'manifest.json',
+			rollupOptions: {
+				input: {
+					app: './src/init.ts',
+				},
+				output: {
+					manualChunks: {
+						vue: ['vue', 'vue-router'],
+					},
+				},
+			},
+			cssCodeSplit: true,
+			outDir: __dirname + '/../../built/_client_dist_',
+			assetsDir: '.',
+			emptyOutDir: false,
+			sourcemap: process.env.NODE_ENV !== 'production',
+			reportCompressedSize: false,
+		},
+	}
+});
diff --git a/packages/client/vite.json5.ts b/packages/client/vite.json5.ts
new file mode 100644
index 0000000000..693ee7be06
--- /dev/null
+++ b/packages/client/vite.json5.ts
@@ -0,0 +1,38 @@
+// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json
+
+import JSON5 from 'json5';
+import { Plugin } from 'rollup';
+import { createFilter, dataToEsm } from '@rollup/pluginutils';
+import { RollupJsonOptions } from '@rollup/plugin-json';
+
+export default function json5(options: RollupJsonOptions = {}): Plugin {
+  const filter = createFilter(options.include, options.exclude);
+  const indent = 'indent' in options ? options.indent : '\t';
+
+  return {
+    name: 'json5',
+
+    // eslint-disable-next-line no-shadow
+    transform(json, id) {
+      if (id.slice(-6) !== '.json5' || !filter(id)) return null;
+
+      try {
+        const parsed = JSON5.parse(json);
+        return {
+          code: dataToEsm(parsed, {
+            preferConst: options.preferConst,
+            compact: options.compact,
+            namedExports: options.namedExports,
+            indent
+          }),
+          map: { mappings: '' }
+        };
+      } catch (err) {
+        const message = 'Could not parse JSON file';
+        const position = parseInt(/[\d]/.exec(err.message)[0], 10);
+        this.warn({ message, id, position });
+        return null;
+      }
+    }
+  };
+}
diff --git a/packages/client/webpack.config.js b/packages/client/webpack.config.js
deleted file mode 100644
index a50851e17f..0000000000
--- a/packages/client/webpack.config.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
- * webpack configuration
- */
-
-const fs = require('fs');
-const webpack = require('webpack');
-const { VueLoaderPlugin } = require('vue-loader');
-
-class WebpackOnBuildPlugin {
-	constructor(callback) {
-		this.callback = callback;
-	}
-
-	apply(compiler) {
-		compiler.hooks.done.tap('WebpackOnBuildPlugin', this.callback);
-	}
-}
-
-const isProduction = process.env.NODE_ENV === 'production';
-
-const locales = require('../../locales');
-const meta = require('../../package.json');
-
-const postcss = {
-	loader: 'postcss-loader',
-	options: {
-		postcssOptions: {
-			plugins: [
-				require('cssnano')({
-					preset: 'default'
-				})
-			]
-		}
-	},
-};
-
-module.exports = {
-	entry: {
-		app: './src/init.ts',
-		sw: './src/sw/sw.ts'
-	},
-	module: {
-		rules: [{
-			test: /\.vue$/,
-			exclude: /node_modules/,
-			use: [{
-				loader: 'vue-loader',
-				options: {
-					cssSourceMap: false,
-					reactivityTransform: true,
-					compilerOptions: {
-						preserveWhitespace: false
-					}
-				}
-			}]
-		}, {
-			test: /\.scss?$/,
-			exclude: /node_modules/,
-			oneOf: [{
-				resourceQuery: /module/,
-				use: [{
-					loader: 'vue-style-loader'
-				}, {
-					loader: 'css-loader',
-					options: {
-						modules: true,
-						esModule: false, // TODO: trueใซใ™ใ‚‹ใจๅฃŠใ‚Œใ‚‹ใ€‚Vue3็งป่กŒใฎๆŠ˜ใซใฏtrueใซใงใใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใชใ„
-						url: false,
-					}
-				}, postcss, {
-					loader: 'sass-loader',
-					options: {
-						implementation: require('sass'),
-						sassOptions: {
-							fiber: false
-						}
-					}
-				}]
-			}, {
-				use: [{
-					loader: 'vue-style-loader'
-				}, {
-					loader: 'css-loader',
-					options: {
-						url: false,
-						esModule: false, // TODO: trueใซใ™ใ‚‹ใจๅฃŠใ‚Œใ‚‹ใ€‚Vue3็งป่กŒใฎๆŠ˜ใซใฏtrueใซใงใใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใชใ„
-					}
-				}, postcss, {
-					loader: 'sass-loader',
-					options: {
-						implementation: require('sass'),
-						sassOptions: {
-							fiber: false
-						}
-					}
-				}]
-			}]
-		}, {
-			test: /\.css$/,
-			oneOf: [{
-				resourceQuery: /module/,
-				use: [{
-					loader: 'vue-style-loader'
-				}, {
-					loader: 'css-loader',
-					options: {
-						modules: true,
-						esModule: false, // TODO: trueใซใ™ใ‚‹ใจๅฃŠใ‚Œใ‚‹ใ€‚Vue3็งป่กŒใฎๆŠ˜ใซใฏtrueใซใงใใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใชใ„
-					}
-				}, postcss]
-			}, {
-				use: [{
-					loader: 'vue-style-loader'
-				}, {
-					loader: 'css-loader',
-					options: {
-						esModule: false, // TODO: trueใซใ™ใ‚‹ใจๅฃŠใ‚Œใ‚‹ใ€‚Vue3็งป่กŒใฎๆŠ˜ใซใฏtrueใซใงใใ‚‹ใ‹ใ‚‚ใ—ใ‚Œใชใ„
-					}
-				}, postcss]
-			}]
-		}, {
-			test: /\.svg$/,
-			use: [
-				'vue-loader',
-				'vue-svg-loader',
-			],
-		}, {
-			test: /\.(eot|woff|woff2|svg|ttf)([?]?.*)$/,
-			type: 'asset/resource'
-		}, {
-			test: /\.json5$/,
-			loader: 'json5-loader',
-			options: {
-				esModule: false,
-			},
-			type: 'javascript/auto'
-		}, {
-			test: /\.ts$/,
-			exclude: /node_modules/,
-			use: [{
-				loader: 'ts-loader',
-				options: {
-					happyPackMode: true,
-					transpileOnly: true,
-					configFile: __dirname + '/tsconfig.json',
-					appendTsSuffixTo: [/\.vue$/]
-				}
-			}]
-		}]
-	},
-	plugins: [
-		new webpack.ProgressPlugin({}),
-		new webpack.DefinePlugin({
-			_VERSION_: JSON.stringify(meta.version),
-			_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
-			_ENV_: JSON.stringify(process.env.NODE_ENV),
-			_DEV_: process.env.NODE_ENV !== 'production',
-			_PERF_PREFIX_: JSON.stringify('Misskey:'),
-			_DATA_TRANSFER_DRIVE_FILE_: JSON.stringify('mk_drive_file'),
-			_DATA_TRANSFER_DRIVE_FOLDER_: JSON.stringify('mk_drive_folder'),
-			_DATA_TRANSFER_DECK_COLUMN_: JSON.stringify('mk_deck_column'),
-			__VUE_OPTIONS_API__: true,
-			__VUE_PROD_DEVTOOLS__: false,
-		}),
-		new VueLoaderPlugin(),
-		new WebpackOnBuildPlugin(() => {
-			fs.mkdirSync(__dirname + '/../../built', { recursive: true });
-			fs.writeFileSync(__dirname + '/../../built/meta.json', JSON.stringify({ version: meta.version }), 'utf-8');
-		}),
-	],
-	output: {
-		path: __dirname + '/../../built/_client_dist_',
-		filename: `[name].${meta.version}.js`,
-		publicPath: `/assets/`,
-		pathinfo: false,
-	},
-	resolve: {
-		extensions: [
-			'.js', '.ts', '.json'
-		],
-		alias: {
-			'@': __dirname + '/src/',
-		}
-	},
-	resolveLoader: {
-		modules: ['node_modules']
-	},
-	experiments: {
-		topLevelAwait: true
-	},
-	devtool: false, //'source-map',
-	mode: isProduction ? 'production' : 'development'
-};
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index a4ac6d8712..796c72304a 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -2,27 +2,11 @@
 # yarn lockfile v1
 
 
-"@babel/code-frame@^7.0.0":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
-  integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
-  dependencies:
-    "@babel/highlight" "^7.12.13"
-
 "@babel/helper-validator-identifier@^7.12.11":
   version "7.12.11"
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
   integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
 
-"@babel/highlight@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.12.13.tgz#8ab538393e00370b26271b01fa08f7f27f2e795c"
-  integrity sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==
-  dependencies:
-    "@babel/helper-validator-identifier" "^7.12.11"
-    chalk "^2.0.0"
-    js-tokens "^4.0.0"
-
 "@babel/parser@^7.16.4":
   version "7.16.6"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
@@ -56,6 +40,105 @@
     lodash "^4.17.19"
     to-fast-properties "^2.0.0"
 
+"@cropper/element-canvas@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-canvas/-/element-canvas-2.0.0-beta.tgz#9501e6a2512a78c7503f2974b1fc65f90c7fecca"
+  integrity sha512-cKbox0AsUx3pMCjT7mQZx3i5FoZTR/Lzz9awuRR8/EciViMN4KkfodGHWSUrIX3zSr0fECsrb2CyNKV8DKZdpQ==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-crosshair@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-crosshair/-/element-crosshair-2.0.0-beta.tgz#9d6ee1e6ed90196b6d4d2425f84909b83ffc66df"
+  integrity sha512-V58xxH3+8TrT9PrUzNouRhcyucyX/xBV5hBv03g0zCu09C5p0BZjrhaPo3hkt8oQvnhYT9SbMTe+k5hIoZgkbQ==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-grid@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-grid/-/element-grid-2.0.0-beta.tgz#af6f3fce213307403ad83d9935839bde39c9beeb"
+  integrity sha512-F+qVLrjuHjJbaut1Gd6qSruMqYOHudhDB/r0dcLtnRW4b1yPd/QyhM5F0KLtCX7Lh6GUvpz2V9Vb/EYQLZuOkw==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-handle@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-handle/-/element-handle-2.0.0-beta.tgz#bd55667e133df402616d44a694110fd0e61eef0b"
+  integrity sha512-Ty12mLpiUM8XRGQN0lRNB7TKP5SOXbTWaW2Uvli1Tu3Y6iLTtXUvs2VZ/fGR8XvhB7v7Lvo+OPfzuxIRx4gwKg==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-image@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-image/-/element-image-2.0.0-beta.tgz#170dbdfbeef75de2f2c0089d4739ad980d69390a"
+  integrity sha512-CrHEMBo5svjj72qePBPGV4ut70RTI6n5U2k2YKcZihHSNU2h6SUEx8zkN8lNIgelsv2Bpb/PvSd1eu26BrJbtA==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/element-canvas" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-selection@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-selection/-/element-selection-2.0.0-beta.tgz#7e1e498773bc26bb09ddaf09b0cafbe5b359ed7b"
+  integrity sha512-MEK+pn2Bma5cXf1N9mC3fRKNvzi6Aj9V2TdhaCl6KdOn6Bp10a+SR8y555MXd80zzFAU/eR1e7TMTyJiPRJFcw==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/element-canvas" "^2.0.0-beta"
+    "@cropper/element-image" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-shade@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-shade/-/element-shade-2.0.0-beta.tgz#55400aec3e352d959a706bfff1b82afca955d33e"
+  integrity sha512-vfKTTkRFio/bi0ueIbdyg2ukhS35/ufsgA13dfzOgkyUT/TUsqTLONNJA2fxO0WLKSajTtvrl1ShdrSXE+EKCQ==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/element-canvas" "^2.0.0-beta"
+    "@cropper/element-selection" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element-viewer@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element-viewer/-/element-viewer-2.0.0-beta.tgz#9a83b670f5cc667d7fc0071f08a1476817e0ed4e"
+  integrity sha512-ZsqdOWJ8OIrK1JR00ibmYrvVMYQVFXOudXezYtf8C5lc7DdtN4elmjVOfLQQM2kxG0WvflIVo6oqqyOzFnsAFg==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/element-canvas" "^2.0.0-beta"
+    "@cropper/element-image" "^2.0.0-beta"
+    "@cropper/element-selection" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/element@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/element/-/element-2.0.0-beta.tgz#7833a92471a16e8860530e10658add42e8781959"
+  integrity sha512-seS8oDe2+Vpsy+yyqUIHzjIP6WUQRxwhFjLml/s2e+L6jF9o+g0KHzLJkBCV/ASKBnyb00aLjAt0dBXPLW/KgQ==
+  dependencies:
+    "@cropper/utils" "^2.0.0-beta"
+
+"@cropper/elements@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/elements/-/elements-2.0.0-beta.tgz#e73a4edaeff7e41dcca8d096bd1bc2bdc6a376e9"
+  integrity sha512-Huyptek2Q6141fRiuejhOyec/viX4zmUeMnpi+5h7OBuorTYUowZ823mmfgBZ4bb7+VPdAl79vUECV9EYq/ciw==
+  dependencies:
+    "@cropper/element" "^2.0.0-beta"
+    "@cropper/element-canvas" "^2.0.0-beta"
+    "@cropper/element-crosshair" "^2.0.0-beta"
+    "@cropper/element-grid" "^2.0.0-beta"
+    "@cropper/element-handle" "^2.0.0-beta"
+    "@cropper/element-image" "^2.0.0-beta"
+    "@cropper/element-selection" "^2.0.0-beta"
+    "@cropper/element-shade" "^2.0.0-beta"
+    "@cropper/element-viewer" "^2.0.0-beta"
+
+"@cropper/utils@^2.0.0-beta":
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/@cropper/utils/-/utils-2.0.0-beta.tgz#7290b03c8c1dc7a2f33406c8aecc80b339425f0e"
+  integrity sha512-Bb3hCyHK2w0l0i8OtRw6C9Q5ytUC5qN+l+kx7F3GiAAFZMX7jGyfPB0uLiZ2TwDm5mosnWjyLVXmCGDcTUnYaQ==
+
 "@cypress/request@^2.88.10":
   version "2.88.10"
   resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
@@ -88,34 +171,29 @@
     debug "^3.1.0"
     lodash.once "^4.1.1"
 
-"@discordapp/twemoji@13.1.1":
-  version "13.1.1"
-  resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91"
-  integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A==
+"@discordapp/twemoji@14.0.2":
+  version "14.0.2"
+  resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-14.0.2.tgz#50cc19f6f3769dc6b36eb251421b5f5d4629e837"
+  integrity sha512-eYJpFsjViDTYwq3f6v+tRu8iRc+yLAeGrlh6kmNRvvC6rroUE2bMlBfEQ/WNh+2Q1FtSEFXpxzuQPOHzRzbAyA==
   dependencies:
     fs-extra "^8.0.1"
     jsonfile "^5.0.0"
-    twemoji-parser "13.1.0"
+    twemoji-parser "14.0.0"
     universalify "^0.1.2"
 
-"@discoveryjs/json-ext@^0.5.0":
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752"
-  integrity sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==
-
-"@eslint/eslintrc@^1.2.1":
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6"
-  integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==
+"@eslint/eslintrc@^1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
+  integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
-    espree "^9.3.1"
-    globals "^13.9.0"
+    espree "^9.3.2"
+    globals "^13.15.0"
     ignore "^5.2.0"
     import-fresh "^3.2.1"
     js-yaml "^4.1.0"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
 "@fortawesome/fontawesome-free@6.1.1":
@@ -170,6 +248,29 @@
     "@nodelib/fs.scandir" "2.1.3"
     fastq "^1.6.0"
 
+"@rollup/plugin-alias@3.1.9":
+  version "3.1.9"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz#a5d267548fe48441f34be8323fb64d1d4a1b3fdf"
+  integrity sha512-QI5fsEvm9bDzt32k39wpOwZhVzRcL5ydcffUHMyLVaVaLeC70I8TJZ17F1z1eMoLu4E/UOcH9BWVkKpIKdrfiw==
+  dependencies:
+    slash "^3.0.0"
+
+"@rollup/plugin-json@4.1.0":
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
+  integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==
+  dependencies:
+    "@rollup/pluginutils" "^3.0.8"
+
+"@rollup/pluginutils@^3.0.8":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
+  integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
+  dependencies:
+    "@types/estree" "0.0.39"
+    estree-walker "^1.0.1"
+    picomatch "^2.2.2"
+
 "@sideway/address@^4.1.0":
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.2.tgz#811b84333a335739d3969cfc434736268170cad1"
@@ -198,16 +299,6 @@
     stringz "2.1.0"
     uuid "7.0.3"
 
-"@trysound/sax@0.2.0":
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
-  integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
-
-"@types/anymatch@*":
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
-  integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
-
 "@types/color-name@^1.1.1":
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@@ -218,39 +309,10 @@
   resolved "https://registry.yarnpkg.com/@types/escape-regexp/-/escape-regexp-0.0.1.tgz#f1a977ccdf2ef059e9862bd3af5e92cbbe723e0e"
   integrity sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw==
 
-"@types/eslint-scope@^3.7.0":
-  version "3.7.0"
-  resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86"
-  integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==
-  dependencies:
-    "@types/eslint" "*"
-    "@types/estree" "*"
-
-"@types/eslint-scope@^3.7.3":
-  version "3.7.3"
-  resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224"
-  integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g==
-  dependencies:
-    "@types/eslint" "*"
-    "@types/estree" "*"
-
-"@types/eslint@*":
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.0.tgz#eb5c5b575237334df24c53195e37b53d66478d7b"
-  integrity sha512-LpUXkr7fnmPXWGxB0ZuLEzNeTURuHPavkC5zuU4sg62/TgL5ZEjamr5Y8b6AftwHtx2bPJasI+CL0TT2JwQ7aA==
-  dependencies:
-    "@types/estree" "*"
-    "@types/json-schema" "*"
-
-"@types/estree@*", "@types/estree@^0.0.46":
-  version "0.0.46"
-  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe"
-  integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==
-
-"@types/estree@^0.0.51":
-  version "0.0.51"
-  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
-  integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
+"@types/estree@0.0.39":
+  version "0.0.39"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
+  integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
 
 "@types/events@*":
   version "3.0.0"
@@ -309,21 +371,6 @@
   resolved "https://registry.yarnpkg.com/@types/is-url/-/is-url-1.2.30.tgz#85567e8bee4fee69202bc3448f9fb34b0d56c50a"
   integrity sha512-AnlNFwjzC8XLda5VjRl4ItSd8qp8pSNowvsut0WwQyBWHpOxjxRJm8iO6uETWqEyLdYdb9/1j+Qd9gQ4l5I4fw==
 
-"@types/json-schema@*":
-  version "7.0.5"
-  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
-  integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
-
-"@types/json-schema@^7.0.6":
-  version "7.0.6"
-  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
-  integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==
-
-"@types/json-schema@^7.0.7":
-  version "7.0.8"
-  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
-  integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg==
-
 "@types/json-schema@^7.0.9":
   version "7.0.9"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
@@ -349,10 +396,10 @@
   resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
   integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
 
-"@types/mocha@9.1.0":
-  version "9.1.0"
-  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.0.tgz#baf17ab2cca3fcce2d322ebc30454bff487efad5"
-  integrity sha512-QCWHkbMv4Y5U9oW10Uxbr45qMMSzl4OzijsozynUAgx3kEHUdXB00udx2dWDQ7f2TU2a2uuiFaRZjCe3unPpeg==
+"@types/mocha@9.1.1":
+  version "9.1.1"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.1.1.tgz#e7c4f1001eefa4b8afbd1eee27a237fee3bf29c4"
+  integrity sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==
 
 "@types/node@*":
   version "16.6.2"
@@ -371,26 +418,11 @@
   dependencies:
     "@types/node" "*"
 
-"@types/parse-json@^4.0.0":
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
-  integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
-
-"@types/parse5@6.0.3":
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb"
-  integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==
-
 "@types/punycode@2.1.0":
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/@types/punycode/-/punycode-2.1.0.tgz#89e4f3d09b3f92e87a80505af19be7e0c31d4e83"
   integrity sha512-PG5aLpW6PJOeV2fHRslP4IOMWn+G+Uq8CfnyJ+PDS8ndCbU+soO+fB3NKCKo0p/Jh2Y4aPaiQZsrOXFdzpcA6g==
 
-"@types/q@^1.5.1":
-  version "1.5.2"
-  resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
-  integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
-
 "@types/qrcode@1.4.2":
   version "1.4.2"
   resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74"
@@ -418,33 +450,16 @@
   resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
   integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
 
-"@types/source-list-map@*":
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9"
-  integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==
-
-"@types/tapable@^1":
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.7.tgz#545158342f949e8fd3bfd813224971ecddc3fac4"
-  integrity sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ==
-
-"@types/throttle-debounce@2.1.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-2.1.0.tgz#1c3df624bfc4b62f992d3012b84c56d41eab3776"
-  integrity sha512-5eQEtSCoESnh2FsiLTxE121IiE60hnMqcb435fShf4bpLRjEu1Eoekht23y6zXS9Ts3l+Szu3TARnTsA0GkOkQ==
+"@types/throttle-debounce@5.0.0":
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/@types/throttle-debounce/-/throttle-debounce-5.0.0.tgz#8208087f0af85107bcc681c50fa837fc9505483e"
+  integrity sha512-Pb7k35iCGFcGPECoNE4DYp3Oyf2xcTd3FbFQxXUI9hEYKUl6YX+KLf7HrBmgVcD05nl50LIH6i+80js4iYmWbw==
 
 "@types/tinycolor2@1.4.3":
   version "1.4.3"
   resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706"
   integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==
 
-"@types/uglify-js@*":
-  version "3.9.0"
-  resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.0.tgz#4490a140ca82aa855ad68093829e7fd6ae94ea87"
-  integrity sha512-3ZcoyPYHVOCcLpnfZwD47KFLr8W/mpUcgjpf1M4Q78TMJIw7KMAHSjiCLJp1z3ZrBR9pTLbe191O0TldFK5zcw==
-  dependencies:
-    source-map "^0.6.1"
-
 "@types/undertaker-registry@*":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@types/undertaker-registry/-/undertaker-registry-1.0.1.tgz#4306d4a03d7acedb974b66530832b90729e1d1da"
@@ -479,44 +494,6 @@
     "@types/expect" "^1.20.4"
     "@types/node" "*"
 
-"@types/webpack-sources@*":
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.7.tgz#0a330a9456113410c74a5d64180af0cbca007141"
-  integrity sha512-XyaHrJILjK1VHVC4aVlKsdNN5KBTwufMb43cQs+flGxtPAf/1Qwl8+Q0tp5BwEGaI8D6XT1L+9bSWXckgkjTLw==
-  dependencies:
-    "@types/node" "*"
-    "@types/source-list-map" "*"
-    source-map "^0.6.1"
-
-"@types/webpack-stream@3.2.12":
-  version "3.2.12"
-  resolved "https://registry.yarnpkg.com/@types/webpack-stream/-/webpack-stream-3.2.12.tgz#cf13e64067a662a7acd8cd0524b3f64c86b0ecb6"
-  integrity sha512-znMUl4kKT0V0SwkUgRgwUNSAO7J5I/jdTCBNy3utkCsgMJ3IHp4FBTDwsQC+tfQ73TWeKIH05QNmbUYmeGThGw==
-  dependencies:
-    "@types/node" "*"
-    "@types/webpack" "^4"
-
-"@types/webpack@5.28.0":
-  version "5.28.0"
-  resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.0.tgz#78dde06212f038d77e54116cfe69e88ae9ed2c03"
-  integrity sha512-8cP0CzcxUiFuA9xGJkfeVpqmWTk9nx6CWwamRGCj95ph1SmlRRk9KlCZ6avhCbZd4L68LvYT6l1kpdEnQXrF8w==
-  dependencies:
-    "@types/node" "*"
-    tapable "^2.2.0"
-    webpack "^5"
-
-"@types/webpack@^4":
-  version "4.41.27"
-  resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.27.tgz#f47da488c8037e7f1b2dbf2714fbbacb61ec0ffc"
-  integrity sha512-wK/oi5gcHi72VMTbOaQ70VcDxSQ1uX8S2tukBK9ARuGXrYM/+u4ou73roc7trXDNmCxCoerE8zruQqX/wuHszA==
-  dependencies:
-    "@types/anymatch" "*"
-    "@types/node" "*"
-    "@types/tapable" "^1"
-    "@types/uglify-js" "*"
-    "@types/webpack-sources" "*"
-    source-map "^0.6.0"
-
 "@types/websocket@1.0.5":
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c"
@@ -538,454 +515,190 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d"
-  integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A==
+"@typescript-eslint/eslint-plugin@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.27.1.tgz#fdf59c905354139046b41b3ed95d1609913d0758"
+  integrity sha512-6dM5NKT57ZduNnJfpY81Phe9nc9wolnMCnknb1im6brWi1RYv84nbMS3olJa27B6+irUVV1X/Wb+Am0FjJdGFw==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/type-utils" "5.18.0"
-    "@typescript-eslint/utils" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/type-utils" "5.27.1"
+    "@typescript-eslint/utils" "5.27.1"
+    debug "^4.3.4"
     functional-red-black-tree "^1.0.1"
-    ignore "^5.1.8"
+    ignore "^5.2.0"
     regexpp "^3.2.0"
-    semver "^7.3.5"
+    semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/parser@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6"
-  integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ==
+"@typescript-eslint/parser@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639"
+  integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/typescript-estree" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/typescript-estree" "5.27.1"
+    debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505"
-  integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ==
+"@typescript-eslint/scope-manager@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d"
+  integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
 
-"@typescript-eslint/type-utils@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74"
-  integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA==
+"@typescript-eslint/type-utils@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.27.1.tgz#369f695199f74c1876e395ebea202582eb1d4166"
+  integrity sha512-+UC1vVUWaDHRnC2cQrCJ4QtVjpjjCgjNFpg8b03nERmkHv9JV9X5M19D7UFMd+/G7T/sgFwX2pGmWK38rqyvXw==
   dependencies:
-    "@typescript-eslint/utils" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/utils" "5.27.1"
+    debug "^4.3.4"
     tsutils "^3.21.0"
 
-"@typescript-eslint/types@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e"
-  integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw==
+"@typescript-eslint/types@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1"
+  integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==
 
-"@typescript-eslint/typescript-estree@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474"
-  integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ==
+"@typescript-eslint/typescript-estree@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808"
+  integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
-    debug "^4.3.2"
-    globby "^11.0.4"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
+    debug "^4.3.4"
+    globby "^11.1.0"
     is-glob "^4.0.3"
-    semver "^7.3.5"
+    semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/utils@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855"
-  integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA==
+"@typescript-eslint/utils@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.27.1.tgz#b4678b68a94bc3b85bf08f243812a6868ac5128f"
+  integrity sha512-mZ9WEn1ZLDaVrhRaYgzbkXBkTPghPFsup8zDbbsYTxC5OmqrFE7skkKS/sraVsLP3TcT3Ki5CSyEFBRkLH/H/w==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/typescript-estree" "5.18.0"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/typescript-estree" "5.27.1"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/visitor-keys@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60"
-  integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg==
+"@typescript-eslint/visitor-keys@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af"
+  integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    eslint-visitor-keys "^3.0.0"
+    "@typescript-eslint/types" "5.27.1"
+    eslint-visitor-keys "^3.3.0"
 
 "@ungap/promise-all-settled@1.1.2":
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
   integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
 
-"@vue/compiler-core@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89"
-  integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==
+"@vitejs/plugin-vue@2.3.3":
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.3.tgz#fbf80cc039b82ac21a1acb0f0478de8f61fbf600"
+  integrity sha512-SmQLDyhz+6lGJhPELsBdzXGc+AcaT8stgkbiTFGpXPe8Tl1tJaBw1A6pxDqDuRsVkD8uscrkx3hA7QDOoKYtyw==
+
+"@vue/compiler-core@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.37.tgz#b3c42e04c0e0f2c496ff1784e543fbefe91e215a"
+  integrity sha512-81KhEjo7YAOh0vQJoSmAD68wLfYqJvoiD4ulyedzF+OEk/bk6/hx3fTNVfuzugIIaTrOx4PGx6pAiBRe5e9Zmg==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/shared" "3.2.31"
+    "@vue/shared" "3.2.37"
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
-"@vue/compiler-dom@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e"
-  integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==
+"@vue/compiler-dom@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.37.tgz#10d2427a789e7c707c872da9d678c82a0c6582b5"
+  integrity sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==
   dependencies:
-    "@vue/compiler-core" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-core" "3.2.37"
+    "@vue/shared" "3.2.37"
 
-"@vue/compiler-sfc@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f"
-  integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==
+"@vue/compiler-sfc@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.37.tgz#3103af3da2f40286edcd85ea495dcb35bc7f5ff4"
+  integrity sha512-+7i/2+9LYlpqDv+KTtWhOZH+pa8/HnX/905MdVmAcI/mPQOBwkHHIzrsEsucyOIZQYMkXUiTkmZq5am/NyXKkg==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.31"
-    "@vue/compiler-dom" "3.2.31"
-    "@vue/compiler-ssr" "3.2.31"
-    "@vue/reactivity-transform" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-core" "3.2.37"
+    "@vue/compiler-dom" "3.2.37"
+    "@vue/compiler-ssr" "3.2.37"
+    "@vue/reactivity-transform" "3.2.37"
+    "@vue/shared" "3.2.37"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
     postcss "^8.1.10"
     source-map "^0.6.1"
 
-"@vue/compiler-ssr@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c"
-  integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==
+"@vue/compiler-ssr@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.37.tgz#4899d19f3a5fafd61524a9d1aee8eb0505313cff"
+  integrity sha512-7mQJD7HdXxQjktmsWp/J67lThEIcxLemz1Vb5I6rYJHR5vI+lON3nPGOH3ubmbvYGt8xEUaAr1j7/tIFWiEOqw==
   dependencies:
-    "@vue/compiler-dom" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-dom" "3.2.37"
+    "@vue/shared" "3.2.37"
 
 "@vue/devtools-api@^6.0.0":
   version "6.0.12"
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.12.tgz#7b57cce215ae9f37a86984633b3aa3d595aa5b46"
   integrity sha512-iO/4FIezHKXhiDBdKySCvJVh8/mZPxHpiQrTy+PXVqJZgpTPTdHy4q8GXulaY+UKEagdkBb0onxNQZ0LNiqVhw==
 
-"@vue/reactivity-transform@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
-  integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==
+"@vue/reactivity-transform@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.37.tgz#0caa47c4344df4ae59f5a05dde2a8758829f8eca"
+  integrity sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==
   dependencies:
     "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-core" "3.2.37"
+    "@vue/shared" "3.2.37"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
-"@vue/reactivity@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd"
-  integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==
+"@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:
-    "@vue/shared" "3.2.31"
+    "@vue/shared" "3.2.37"
 
-"@vue/runtime-core@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
-  integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==
+"@vue/runtime-core@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.37.tgz#7ba7c54bb56e5d70edfc2f05766e1ca8519966e3"
+  integrity sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==
   dependencies:
-    "@vue/reactivity" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/reactivity" "3.2.37"
+    "@vue/shared" "3.2.37"
 
-"@vue/runtime-dom@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff"
-  integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==
+"@vue/runtime-dom@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.37.tgz#002bdc8228fa63949317756fb1e92cdd3f9f4bbd"
+  integrity sha512-HimKdh9BepShW6YozwRKAYjYQWg9mQn63RGEiSswMbW+ssIht1MILYlVGkAGGQbkhSh31PCdoUcfiu4apXJoPw==
   dependencies:
-    "@vue/runtime-core" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/runtime-core" "3.2.37"
+    "@vue/shared" "3.2.37"
     csstype "^2.6.8"
 
-"@vue/server-renderer@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141"
-  integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==
+"@vue/server-renderer@3.2.37":
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.37.tgz#840a29c8dcc29bddd9b5f5ffa22b95c0e72afdfc"
+  integrity sha512-kLITEJvaYgZQ2h47hIzPh2K3jG8c1zCVbp/o/bzQOyvzaKiCquKS7AaioPI28GNxIsE/zSx+EwWYsNxDCX95MA==
   dependencies:
-    "@vue/compiler-ssr" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-ssr" "3.2.37"
+    "@vue/shared" "3.2.37"
 
-"@vue/shared@3.2.31":
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e"
-  integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==
-
-"@webassemblyjs/ast@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.0.tgz#a5aa679efdc9e51707a4207139da57920555961f"
-  integrity sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==
-  dependencies:
-    "@webassemblyjs/helper-numbers" "1.11.0"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.0"
-
-"@webassemblyjs/ast@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
-  integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==
-  dependencies:
-    "@webassemblyjs/helper-numbers" "1.11.1"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
-
-"@webassemblyjs/floating-point-hex-parser@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz#34d62052f453cd43101d72eab4966a022587947c"
-  integrity sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==
-
-"@webassemblyjs/floating-point-hex-parser@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f"
-  integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==
-
-"@webassemblyjs/helper-api-error@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz#aaea8fb3b923f4aaa9b512ff541b013ffb68d2d4"
-  integrity sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==
-
-"@webassemblyjs/helper-api-error@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16"
-  integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==
-
-"@webassemblyjs/helper-buffer@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz#d026c25d175e388a7dbda9694e91e743cbe9b642"
-  integrity sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==
-
-"@webassemblyjs/helper-buffer@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5"
-  integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==
-
-"@webassemblyjs/helper-numbers@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz#7ab04172d54e312cc6ea4286d7d9fa27c88cd4f9"
-  integrity sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==
-  dependencies:
-    "@webassemblyjs/floating-point-hex-parser" "1.11.0"
-    "@webassemblyjs/helper-api-error" "1.11.0"
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/helper-numbers@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae"
-  integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==
-  dependencies:
-    "@webassemblyjs/floating-point-hex-parser" "1.11.1"
-    "@webassemblyjs/helper-api-error" "1.11.1"
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/helper-wasm-bytecode@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz#85fdcda4129902fe86f81abf7e7236953ec5a4e1"
-  integrity sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==
-
-"@webassemblyjs/helper-wasm-bytecode@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1"
-  integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==
-
-"@webassemblyjs/helper-wasm-section@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz#9ce2cc89300262509c801b4af113d1ca25c1a75b"
-  integrity sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.0"
-    "@webassemblyjs/helper-buffer" "1.11.0"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.0"
-    "@webassemblyjs/wasm-gen" "1.11.0"
-
-"@webassemblyjs/helper-wasm-section@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a"
-  integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.1"
-    "@webassemblyjs/helper-buffer" "1.11.1"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
-    "@webassemblyjs/wasm-gen" "1.11.1"
-
-"@webassemblyjs/ieee754@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz#46975d583f9828f5d094ac210e219441c4e6f5cf"
-  integrity sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==
-  dependencies:
-    "@xtuc/ieee754" "^1.2.0"
-
-"@webassemblyjs/ieee754@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614"
-  integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==
-  dependencies:
-    "@xtuc/ieee754" "^1.2.0"
-
-"@webassemblyjs/leb128@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.0.tgz#f7353de1df38aa201cba9fb88b43f41f75ff403b"
-  integrity sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==
-  dependencies:
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/leb128@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5"
-  integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==
-  dependencies:
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/utf8@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.0.tgz#86e48f959cf49e0e5091f069a709b862f5a2cadf"
-  integrity sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==
-
-"@webassemblyjs/utf8@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff"
-  integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==
-
-"@webassemblyjs/wasm-edit@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz#ee4a5c9f677046a210542ae63897094c2027cb78"
-  integrity sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.0"
-    "@webassemblyjs/helper-buffer" "1.11.0"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.0"
-    "@webassemblyjs/helper-wasm-section" "1.11.0"
-    "@webassemblyjs/wasm-gen" "1.11.0"
-    "@webassemblyjs/wasm-opt" "1.11.0"
-    "@webassemblyjs/wasm-parser" "1.11.0"
-    "@webassemblyjs/wast-printer" "1.11.0"
-
-"@webassemblyjs/wasm-edit@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6"
-  integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.1"
-    "@webassemblyjs/helper-buffer" "1.11.1"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
-    "@webassemblyjs/helper-wasm-section" "1.11.1"
-    "@webassemblyjs/wasm-gen" "1.11.1"
-    "@webassemblyjs/wasm-opt" "1.11.1"
-    "@webassemblyjs/wasm-parser" "1.11.1"
-    "@webassemblyjs/wast-printer" "1.11.1"
-
-"@webassemblyjs/wasm-gen@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz#3cdb35e70082d42a35166988dda64f24ceb97abe"
-  integrity sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.0"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.0"
-    "@webassemblyjs/ieee754" "1.11.0"
-    "@webassemblyjs/leb128" "1.11.0"
-    "@webassemblyjs/utf8" "1.11.0"
-
-"@webassemblyjs/wasm-gen@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76"
-  integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.1"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
-    "@webassemblyjs/ieee754" "1.11.1"
-    "@webassemblyjs/leb128" "1.11.1"
-    "@webassemblyjs/utf8" "1.11.1"
-
-"@webassemblyjs/wasm-opt@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz#1638ae188137f4bb031f568a413cd24d32f92978"
-  integrity sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.0"
-    "@webassemblyjs/helper-buffer" "1.11.0"
-    "@webassemblyjs/wasm-gen" "1.11.0"
-    "@webassemblyjs/wasm-parser" "1.11.0"
-
-"@webassemblyjs/wasm-opt@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2"
-  integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.1"
-    "@webassemblyjs/helper-buffer" "1.11.1"
-    "@webassemblyjs/wasm-gen" "1.11.1"
-    "@webassemblyjs/wasm-parser" "1.11.1"
-
-"@webassemblyjs/wasm-parser@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz#3e680b8830d5b13d1ec86cc42f38f3d4a7700754"
-  integrity sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.0"
-    "@webassemblyjs/helper-api-error" "1.11.0"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.0"
-    "@webassemblyjs/ieee754" "1.11.0"
-    "@webassemblyjs/leb128" "1.11.0"
-    "@webassemblyjs/utf8" "1.11.0"
-
-"@webassemblyjs/wasm-parser@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199"
-  integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.1"
-    "@webassemblyjs/helper-api-error" "1.11.1"
-    "@webassemblyjs/helper-wasm-bytecode" "1.11.1"
-    "@webassemblyjs/ieee754" "1.11.1"
-    "@webassemblyjs/leb128" "1.11.1"
-    "@webassemblyjs/utf8" "1.11.1"
-
-"@webassemblyjs/wast-printer@1.11.0":
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz#680d1f6a5365d6d401974a8e949e05474e1fab7e"
-  integrity sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.0"
-    "@xtuc/long" "4.2.2"
-
-"@webassemblyjs/wast-printer@1.11.1":
-  version "1.11.1"
-  resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0"
-  integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==
-  dependencies:
-    "@webassemblyjs/ast" "1.11.1"
-    "@xtuc/long" "4.2.2"
-
-"@webpack-cli/configtest@^1.1.1":
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356"
-  integrity sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==
-
-"@webpack-cli/info@^1.4.1":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.4.1.tgz#2360ea1710cbbb97ff156a3f0f24556e0fc1ebea"
-  integrity sha512-PKVGmazEq3oAo46Q63tpMr4HipI3OPfP7LiNOEJg963RMgT0rqheag28NCML0o3GIzA3DmxP1ZIAv9oTX1CUIA==
-  dependencies:
-    envinfo "^7.7.3"
-
-"@webpack-cli/serve@^1.6.1":
-  version "1.6.1"
-  resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.6.1.tgz#0de2875ac31b46b6c5bb1ae0a7d7f0ba5678dffe"
-  integrity sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==
-
-"@xtuc/ieee754@^1.2.0":
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
-  integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==
-
-"@xtuc/long@4.2.2":
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
-  integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+"@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==
 
 abort-controller@3.0.0:
   version "3.0.0"
@@ -994,40 +707,20 @@ abort-controller@3.0.0:
   dependencies:
     event-target-shim "^5.0.0"
 
-acorn-import-assertions@^1.7.6:
-  version "1.7.6"
-  resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
-  integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
-
-acorn-jsx@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
-  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+acorn-jsx@^5.3.2:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
 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.0.4:
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe"
-  integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA==
-
-acorn@^8.4.1:
-  version "8.4.1"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.4.1.tgz#56c36251fc7cabc7096adc18f05afe814321a28c"
-  integrity sha512-asabaBSkEKosYKMITunzX177CXxQ4Q8BSSzMTKD+FefUhipQC70gfW5SiUDhYQ3vk8G+81HqQk7Fv9OXwwn9KA==
-
-acorn@^8.5.0:
-  version "8.5.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
-  integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
-
-acorn@^8.7.0:
-  version "8.7.0"
-  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
-  integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
+acorn@^8.7.1:
+  version "8.7.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
+  integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
 
 aggregate-error@^3.0.0:
   version "3.1.0"
@@ -1037,12 +730,7 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
-ajv-keywords@^3.5.2:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
-  integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
-
-ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
+ajv@^6.10.0, ajv@^6.12.4:
   version "6.12.5"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
   integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
@@ -1069,13 +757,6 @@ ansi-regex@^5.0.0, ansi-regex@^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:
   version "4.2.1"
   resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
@@ -1097,13 +778,6 @@ arch@^2.2.0:
   resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
   integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
 
-argparse@^1.0.7:
-  version "1.0.10"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
-  integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
-  dependencies:
-    sprintf-js "~1.0.2"
-
 argparse@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -1161,17 +835,10 @@ astral-regex@^2.0.0:
   resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
   integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
 
-async@^2.6.0:
-  version "2.6.3"
-  resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
-  integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
-  dependencies:
-    lodash "^4.17.14"
-
 async@^3.2.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8"
-  integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
+  integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
 
 asynckit@^0.4.0:
   version "0.4.0"
@@ -1241,16 +908,6 @@ bcrypt-pbkdf@^1.0.0:
   dependencies:
     tweetnacl "^0.14.3"
 
-big-integer@^1.6.16:
-  version "1.6.48"
-  resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
-  integrity sha512-j51egjPa7/i+RdiRuJbPdJ2FIUYYPhvYLjzoYbcMMm62ooO6F94fETG4MTs46zPAF9Brs04OajboA/qTGuz78w==
-
-big.js@^5.2.2:
-  version "5.2.2"
-  resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
-  integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
-
 binary-extensions@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
@@ -1271,7 +928,7 @@ blurhash@1.1.5:
   resolved "https://registry.yarnpkg.com/blurhash/-/blurhash-1.1.5.tgz#3034104cd5dce5a3e5caa871ae2f0f1f2d0ab566"
   integrity sha512-a+LO3A2DfxTaTztsmkbLYmUzUeApi0LZuKalwbNmqAHR6HhJGMt1qSV/R3wc+w4DL28holjqO3Bg74aUGavGjg==
 
-boolbase@^1.0.0, boolbase@~1.0.0:
+boolbase@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
   integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
@@ -1284,53 +941,47 @@ brace-expansion@^1.1.7:
     balanced-match "^1.0.0"
     concat-map "0.0.1"
 
-braces@^3.0.1, braces@~3.0.2:
+brace-expansion@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
+  integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
+  dependencies:
+    balanced-match "^1.0.0"
+
+braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
   integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
   dependencies:
     fill-range "^7.0.1"
 
-broadcast-channel@4.10.0:
-  version "4.10.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198"
-  integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ==
+broadcast-channel@4.13.0:
+  version "4.13.0"
+  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.13.0.tgz#21387b2602b9e9ec3b97b03bd8a8d2c198352ff6"
+  integrity sha512-fcDr8QNJ4SOb6jyjUNZatVNmcHtSWfW4PFcs4xIEFZAtorKCIFoEYtjIjaQ4c0jrbr/Bl8NIwOWiLSyspoAnEQ==
   dependencies:
     "@babel/runtime" "^7.16.0"
     detect-node "^2.1.0"
-    microseconds "0.2.0"
-    nano-time "1.0.0"
-    oblivious-set "1.0.0"
+    microtime "3.0.0"
+    oblivious-set "1.1.1"
     p-queue "6.6.2"
     rimraf "3.0.2"
     unload "2.3.1"
 
+"browser-image-resizer@git+https://github.com/misskey-dev/browser-image-resizer#v2.2.1-misskey.2":
+  version "2.2.1-misskey.2"
+  resolved "git+https://github.com/misskey-dev/browser-image-resizer#a58834f5fe2af9f9f31ff115121aef3de6f9d416"
+
 browser-stdout@1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
   integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
 
-browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.6:
-  version "4.19.1"
-  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3"
-  integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==
-  dependencies:
-    caniuse-lite "^1.0.30001286"
-    electron-to-chromium "^1.4.17"
-    escalade "^3.1.1"
-    node-releases "^2.0.1"
-    picocolors "^1.0.0"
-
 buffer-crc32@~0.2.3:
   version "0.2.13"
   resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
   integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
 
-buffer-from@^1.0.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
-  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
-
 buffer@^5.6.0:
   version "5.7.1"
   resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0"
@@ -1374,21 +1025,6 @@ camelcase@^6.0.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
   integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
 
-caniuse-api@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
-  integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==
-  dependencies:
-    browserslist "^4.0.0"
-    caniuse-lite "^1.0.0"
-    lodash.memoize "^4.1.2"
-    lodash.uniq "^4.5.0"
-
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001286:
-  version "1.0.30001311"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz#682ef3f4e617f1a177ad943de59775ed3032e511"
-  integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A==
-
 caseless@~0.12.0:
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
@@ -1402,15 +1038,6 @@ chalk@4.0.0:
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chalk@^2.0.0, chalk@^2.4.1:
-  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.0"
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a"
@@ -1431,20 +1058,20 @@ character-parser@^2.2.0:
   dependencies:
     is-regex "^1.0.3"
 
-chart.js@3.7.1:
-  version "3.7.1"
-  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada"
-  integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==
+chart.js@3.8.0:
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.0.tgz#c6c14c457b9dc3ce7f1514a59e9b262afd6f1a94"
+  integrity sha512-cr8xhrXjLIXVLOBZPkBZVF6NDeiVIrPLHcMhnON7UufudL+CNeRrD+wpYanswlm8NpudMdrt3CHoLMQMxJhHRg==
 
 chartjs-adapter-date-fns@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-2.0.0.tgz#5e53b2f660b993698f936f509c86dddf9ed44c6b"
   integrity sha512-rmZINGLe+9IiiEB0kb57vH3UugAtYw33anRiw5kS2Tu87agpetDDoouquycWc9pRsKtQo5j+vLsYHyr8etAvFw==
 
-chartjs-plugin-gradient@0.2.2:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/chartjs-plugin-gradient/-/chartjs-plugin-gradient-0.2.2.tgz#e271d8cbaa9cb52581addc99f2facc4adae40e43"
-  integrity sha512-fb38h1Zl5DDkHvpempZ/rY/lWg9/dgF0I56GI1dbqRj5P/ZoHSX4hx+P+5Az/JMNZ1s1/2zo5TmTTHQo8xJUXQ==
+chartjs-plugin-gradient@0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/chartjs-plugin-gradient/-/chartjs-plugin-gradient-0.5.0.tgz#907b15102ce164fc32640d43f9c3bad2f5ae3fd5"
+  integrity sha512-VHys58pMPNYRXngCrN5kvQZb1EiAvl/BhU3G9wNXxf2hETWiPYgN63Ud6RK1hyST+nZdZ61x4us546djZX2rYQ==
 
 chartjs-plugin-zoom@1.2.1:
   version "1.2.1"
@@ -1458,7 +1085,7 @@ check-more-types@2.24.0, check-more-types@^2.24.0:
   resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
   integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
 
-chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.2:
+chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.3:
   version "3.3.1"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
   integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
@@ -1473,13 +1100,6 @@ chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.3.1, chokidar@^3.5.2:
   optionalDependencies:
     fsevents "~2.1.2"
 
-chrome-trace-event@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
-  integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==
-  dependencies:
-    tslib "^1.9.0"
-
 ci-info@^3.1.1:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6"
@@ -1532,31 +1152,6 @@ cliui@^7.0.2:
     strip-ansi "^6.0.0"
     wrap-ansi "^7.0.0"
 
-clone-deep@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
-  integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
-  dependencies:
-    is-plain-object "^2.0.4"
-    kind-of "^6.0.2"
-    shallow-clone "^3.0.0"
-
-coa@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
-  integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
-  dependencies:
-    "@types/q" "^1.5.1"
-    chalk "^2.4.1"
-    q "^1.1.2"
-
-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"
@@ -1564,31 +1159,16 @@ 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==
 
-colord@^2.9.1:
-  version "2.9.1"
-  resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e"
-  integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==
-
 colorette@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
   integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
 
-colorette@^2.0.14:
-  version "2.0.15"
-  resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.15.tgz#8e634aa0429b110d24be82eac4d42f5ea65ab2d5"
-  integrity sha512-lIFQhufWaVvwi4wOlX9Gx5b0Nmw3XAZ8HzHNH9dfxhe+JaKNTmX6QLk4o7UHyI+tUY8ClvyfaHUm5bf61O3psA==
-
 colors@1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
@@ -1601,31 +1181,21 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
   dependencies:
     delayed-stream "~1.0.0"
 
-commander@^2.20.0:
-  version "2.20.3"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
-  integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
-
 commander@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae"
   integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==
 
-commander@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-7.0.0.tgz#3e2bbfd8bb6724760980988fb5b22b7ee6b71ab2"
-  integrity sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA==
-
-commander@^7.2.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
-  integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
-
-commander@^8.0.0, commander@^8.3.0:
+commander@^8.0.0:
   version "8.3.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
   integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
 
+commander@^9.0.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9"
+  integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==
+
 common-tags@^1.8.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.8.0.tgz#8e3153e542d4a39e9b10554434afaaf98956a937"
@@ -1661,16 +1231,13 @@ core-util-is@1.0.2:
   resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
   integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
 
-cosmiconfig@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
-  integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
+cropperjs@2.0.0-beta:
+  version "2.0.0-beta"
+  resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-2.0.0-beta.tgz#bf3f9c19c426657d63c1e6dd55f635546ccec0a5"
+  integrity sha512-mwupI1Ct84PUynnC9S7KenCtgXiuRYAfLwzxPlJwc392iNX8fZUPP6a8gEpmRQTgvsE9Ubme1tXLM6/HLXksiQ==
   dependencies:
-    "@types/parse-json" "^4.0.0"
-    import-fresh "^3.2.1"
-    parse-json "^5.0.0"
-    path-type "^4.0.0"
-    yaml "^1.10.0"
+    "@cropper/elements" "^2.0.0-beta"
+    "@cropper/utils" "^2.0.0-beta"
 
 cross-env@7.0.3:
   version "7.0.3"
@@ -1688,162 +1255,20 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
-css-declaration-sorter@^6.2.2:
-  version "6.2.2"
-  resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz#bfd2f6f50002d6a3ae779a87d3a0c5d5b10e0f02"
-  integrity sha512-Ufadglr88ZLsrvS11gjeu/40Lw74D9Am/Jpr3LlYm5Q4ZP5KdlUhG+6u2EjyXeZcxmZ2h1ebCKngDjolpeLHpg==
-
-css-loader@6.7.1:
-  version "6.7.1"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e"
-  integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw==
-  dependencies:
-    icss-utils "^5.1.0"
-    postcss "^8.4.7"
-    postcss-modules-extract-imports "^3.0.0"
-    postcss-modules-local-by-default "^4.0.0"
-    postcss-modules-scope "^3.0.0"
-    postcss-modules-values "^4.0.0"
-    postcss-value-parser "^4.2.0"
-    semver "^7.3.5"
-
-css-select-base-adapter@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
-  integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
-
-css-select@^2.0.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef"
-  integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==
-  dependencies:
-    boolbase "^1.0.0"
-    css-what "^3.2.1"
-    domutils "^1.7.0"
-    nth-check "^1.0.2"
-
-css-select@^4.1.3:
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067"
-  integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA==
-  dependencies:
-    boolbase "^1.0.0"
-    css-what "^5.0.0"
-    domhandler "^4.2.0"
-    domutils "^2.6.0"
-    nth-check "^2.0.0"
-
-css-tree@1.0.0-alpha.37:
-  version "1.0.0-alpha.37"
-  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
-  integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
-  dependencies:
-    mdn-data "2.0.4"
-    source-map "^0.6.1"
-
-css-tree@1.0.0-alpha.39:
-  version "1.0.0-alpha.39"
-  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb"
-  integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==
-  dependencies:
-    mdn-data "2.0.6"
-    source-map "^0.6.1"
-
-css-tree@^1.1.2, css-tree@^1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
-  integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
-  dependencies:
-    mdn-data "2.0.14"
-    source-map "^0.6.1"
-
-css-what@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1"
-  integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==
-
-css-what@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe"
-  integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==
-
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
   integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
 
-cssnano-preset-default@^5.2.7:
-  version "5.2.7"
-  resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.7.tgz#791e3603fb8f1b46717ac53b47e3c418e950f5f3"
-  integrity sha512-JiKP38ymZQK+zVKevphPzNSGHSlTI+AOwlasoSRtSVMUU285O7/6uZyd5NbW92ZHp41m0sSHe6JoZosakj63uA==
-  dependencies:
-    css-declaration-sorter "^6.2.2"
-    cssnano-utils "^3.1.0"
-    postcss-calc "^8.2.3"
-    postcss-colormin "^5.3.0"
-    postcss-convert-values "^5.1.0"
-    postcss-discard-comments "^5.1.1"
-    postcss-discard-duplicates "^5.1.0"
-    postcss-discard-empty "^5.1.1"
-    postcss-discard-overridden "^5.1.0"
-    postcss-merge-longhand "^5.1.4"
-    postcss-merge-rules "^5.1.1"
-    postcss-minify-font-values "^5.1.0"
-    postcss-minify-gradients "^5.1.1"
-    postcss-minify-params "^5.1.2"
-    postcss-minify-selectors "^5.2.0"
-    postcss-normalize-charset "^5.1.0"
-    postcss-normalize-display-values "^5.1.0"
-    postcss-normalize-positions "^5.1.0"
-    postcss-normalize-repeat-style "^5.1.0"
-    postcss-normalize-string "^5.1.0"
-    postcss-normalize-timing-functions "^5.1.0"
-    postcss-normalize-unicode "^5.1.0"
-    postcss-normalize-url "^5.1.0"
-    postcss-normalize-whitespace "^5.1.1"
-    postcss-ordered-values "^5.1.1"
-    postcss-reduce-initial "^5.1.0"
-    postcss-reduce-transforms "^5.1.0"
-    postcss-svgo "^5.1.0"
-    postcss-unique-selectors "^5.1.1"
-
-cssnano-utils@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861"
-  integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==
-
-cssnano@5.1.7:
-  version "5.1.7"
-  resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.7.tgz#99858bef6c76c9240f0cdc9239570bc7db8368be"
-  integrity sha512-pVsUV6LcTXif7lvKKW9ZrmX+rGRzxkEdJuVJcp5ftUjWITgwam5LMZOgaTvUrWPkcORBey6he7JKb4XAJvrpKg==
-  dependencies:
-    cssnano-preset-default "^5.2.7"
-    lilconfig "^2.0.3"
-    yaml "^1.10.2"
-
-csso@^4.0.2:
-  version "4.0.3"
-  resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903"
-  integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==
-  dependencies:
-    css-tree "1.0.0-alpha.39"
-
-csso@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
-  integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
-  dependencies:
-    css-tree "^1.1.2"
-
 csstype@^2.6.8:
   version "2.6.13"
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
   integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
 
-cypress@9.5.3:
-  version "9.5.3"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.3.tgz#7c56b50fc1f1aa69ef10b271d895aeb4a1d7999e"
-  integrity sha512-ItelIVmqMTnKYbo1JrErhsGgQGjWOxCpHT1TfMvwnIXKXN/OSlPjEK7rbCLYDZhejQL99PmUqul7XORI24Ik0A==
+cypress@10.0.3:
+  version "10.0.3"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.0.3.tgz#889b4bef863b7d1ef1b608b85b964394ad350c5f"
+  integrity sha512-8C82XTybsEmJC9POYSNITGUhMLCRwB9LadP0x33H+52QVoBjhsWvIzrI+ybCe0+TyxaF0D5/9IL2kSTgjqCB9A==
   dependencies:
     "@cypress/request" "^2.88.10"
     "@cypress/xvfb" "^1.2.4"
@@ -1920,10 +1345,10 @@ debug@4.3.2, debug@^4.3.2:
   dependencies:
     ms "2.1.2"
 
-debug@4.3.3:
-  version "4.3.3"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
-  integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
+debug@4.3.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"
 
@@ -1963,7 +1388,7 @@ deep-is@^0.1.3:
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
   integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
 
-define-properties@^1.1.2, define-properties@^1.1.3:
+define-properties@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
   integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
@@ -1975,11 +1400,6 @@ delayed-stream@~1.0.0:
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
   integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
 
-detect-file@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
-  integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
-
 detect-node@2.1.0, detect-node@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
@@ -2021,69 +1441,6 @@ doctypes@^1.1.0:
   resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
   integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=
 
-dom-serializer@0:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51"
-  integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==
-  dependencies:
-    domelementtype "^2.0.1"
-    entities "^2.0.0"
-
-dom-serializer@^1.0.1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be"
-  integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==
-  dependencies:
-    domelementtype "^2.0.1"
-    domhandler "^4.0.0"
-    entities "^2.0.0"
-
-domelementtype@1:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
-  integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
-
-domelementtype@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.0.1.tgz#1f8bdfe91f5a78063274e803b4bdcedf6e94f94d"
-  integrity sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==
-
-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.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.1.0.tgz#c1d8d494d5ec6db22de99e46a149c2a4d23ddd43"
-  integrity sha512-/6/kmsGlMY4Tup/nGVutdrK9yQi4YjWVcVeoQmixpzjOUK1U7pQkvAPHBJeUxOgxF0J8f8lwCJSlCfD0V4CMGQ==
-  dependencies:
-    domelementtype "^2.2.0"
-
-domhandler@^4.2.0:
-  version "4.2.2"
-  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
-  integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w==
-  dependencies:
-    domelementtype "^2.2.0"
-
-domutils@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
-  integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==
-  dependencies:
-    dom-serializer "0"
-    domelementtype "1"
-
-domutils@^2.6.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
-  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
-  dependencies:
-    dom-serializer "^1.0.1"
-    domelementtype "^2.2.0"
-    domhandler "^4.2.0"
-
 duplexer@~0.1.1:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@@ -2097,21 +1454,11 @@ ecc-jsbn@~0.1.1:
     jsbn "~0.1.0"
     safer-buffer "^2.1.0"
 
-electron-to-chromium@^1.4.17:
-  version "1.4.68"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.68.tgz#d79447b6bd1bec9183f166bb33d4bef0d5e4e568"
-  integrity sha512-cId+QwWrV8R1UawO6b9BR1hnkJ4EJPCPAr4h315vliHUtVUJDk39Sg1PMNnaWKfj5x+93ssjeJ9LKL6r8LaMiA==
-
 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==
 
-emojis-list@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
-  integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
-
 encode-utf8@^1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda"
@@ -2124,30 +1471,6 @@ end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
-enhanced-resolve@^5.0.0:
-  version "5.8.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.0.tgz#d9deae58f9d3773b6a111a5a46831da5be5c9ac0"
-  integrity sha512-Sl3KRpJA8OpprrtaIswVki3cWPiPKxXuFxJXBp+zNb6s6VwNWwFRUdtmzd2ReUut8n+sCPx7QCtQ7w5wfJhSgQ==
-  dependencies:
-    graceful-fs "^4.2.4"
-    tapable "^2.2.0"
-
-enhanced-resolve@^5.7.0:
-  version "5.7.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz#525c5d856680fbd5052de453ac83e32049958b5c"
-  integrity sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==
-  dependencies:
-    graceful-fs "^4.2.4"
-    tapable "^2.2.0"
-
-enhanced-resolve@^5.9.2:
-  version "5.9.2"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9"
-  integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==
-  dependencies:
-    graceful-fs "^4.2.4"
-    tapable "^2.2.0"
-
 enquirer@^2.3.6:
   version "2.3.6"
   resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
@@ -2155,40 +1478,6 @@ enquirer@^2.3.6:
   dependencies:
     ansi-colors "^4.1.1"
 
-entities@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
-  integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
-
-envinfo@^7.7.3:
-  version "7.7.3"
-  resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.7.3.tgz#4b2d8622e3e7366afb8091b23ed95569ea0208cc"
-  integrity sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==
-
-error-ex@^1.3.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
-  integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
-  dependencies:
-    is-arrayish "^0.2.1"
-
-es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
-  version "1.17.5"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9"
-  integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==
-  dependencies:
-    es-to-primitive "^1.2.1"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-symbols "^1.0.1"
-    is-callable "^1.1.5"
-    is-regex "^1.0.5"
-    object-inspect "^1.7.0"
-    object-keys "^1.1.1"
-    object.assign "^4.1.0"
-    string.prototype.trimleft "^2.1.1"
-    string.prototype.trimright "^2.1.1"
-
 es-abstract@^1.19.0, es-abstract@^1.19.1:
   version "1.19.1"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3"
@@ -2215,16 +1504,6 @@ es-abstract@^1.19.0, es-abstract@^1.19.1:
     string.prototype.trimstart "^1.0.4"
     unbox-primitive "^1.0.1"
 
-es-module-lexer@^0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.4.0.tgz#21f4181cc8b7eee06855f1c59e6087c7bc4f77b0"
-  integrity sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ==
-
-es-module-lexer@^0.9.0:
-  version "0.9.0"
-  resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.0.tgz#fe4c4621977bc668e285c5f1f70ca3b451095fda"
-  integrity sha512-qU2eN/XHsrl3E4y7mK1wdWnyy5c8gXtCbfP6Xcsemm7fPUR1PIV1JhZfP7ojcN0Fzp69CfrS3u76h2tusvfKiQ==
-
 es-to-primitive@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
@@ -2260,6 +1539,132 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3:
     d "^1.0.1"
     ext "^1.1.2"
 
+esbuild-android-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.38.tgz#5b94a1306df31d55055f64a62ff6b763a47b7f64"
+  integrity sha512-aRFxR3scRKkbmNuGAK+Gee3+yFxkTJO/cx83Dkyzo4CnQl/2zVSurtG6+G86EQIZ+w+VYngVyK7P3HyTBKu3nw==
+
+esbuild-android-arm64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.38.tgz#78acc80773d16007de5219ccce544c036abd50b8"
+  integrity sha512-L2NgQRWuHFI89IIZIlpAcINy9FvBk6xFVZ7xGdOwIm8VyhX1vNCEqUJO3DPSSy945Gzdg98cxtNt8Grv1CsyhA==
+
+esbuild-darwin-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.38.tgz#e02b1291f629ebdc2aa46fabfacc9aa28ff6aa46"
+  integrity sha512-5JJvgXkX87Pd1Og0u/NJuO7TSqAikAcQQ74gyJ87bqWRVeouky84ICoV4sN6VV53aTW+NE87qLdGY4QA2S7KNA==
+
+esbuild-darwin-arm64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.38.tgz#01eb6650ec010b18c990e443a6abcca1d71290a9"
+  integrity sha512-eqF+OejMI3mC5Dlo9Kdq/Ilbki9sQBw3QlHW3wjLmsLh+quNfHmGMp3Ly1eWm981iGBMdbtSS9+LRvR2T8B3eQ==
+
+esbuild-freebsd-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.38.tgz#790b8786729d4aac7be17648f9ea8e0e16475b5e"
+  integrity sha512-epnPbhZUt93xV5cgeY36ZxPXDsQeO55DppzsIgWM8vgiG/Rz+qYDLmh5ts3e+Ln1wA9dQ+nZmVHw+RjaW3I5Ig==
+
+esbuild-freebsd-arm64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.38.tgz#b66340ab28c09c1098e6d9d8ff656db47d7211e6"
+  integrity sha512-/9icXUYJWherhk+y5fjPI5yNUdFPtXHQlwP7/K/zg8t8lQdHVj20SqU9/udQmeUo5pDFHMYzcEFfJqgOVeKNNQ==
+
+esbuild-linux-32@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.38.tgz#7927f950986fd39f0ff319e92839455912b67f70"
+  integrity sha512-QfgfeNHRFvr2XeHFzP8kOZVnal3QvST3A0cgq32ZrHjSMFTdgXhMhmWdKzRXP/PKcfv3e2OW9tT9PpcjNvaq6g==
+
+esbuild-linux-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.38.tgz#4893d07b229d9cfe34a2b3ce586399e73c3ac519"
+  integrity sha512-uuZHNmqcs+Bj1qiW9k/HZU3FtIHmYiuxZ/6Aa+/KHb/pFKr7R3aVqvxlAudYI9Fw3St0VCPfv7QBpUITSmBR1Q==
+
+esbuild-linux-arm64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.38.tgz#8442402e37d0b8ae946ac616784d9c1a2041056a"
+  integrity sha512-HlMGZTEsBrXrivr64eZ/EO0NQM8H8DuSENRok9d+Jtvq8hOLzrxfsAT9U94K3KOGk2XgCmkaI2KD8hX7F97lvA==
+
+esbuild-linux-arm@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.38.tgz#d5dbf32d38b7f79be0ec6b5fb2f9251fd9066986"
+  integrity sha512-FiFvQe8J3VKTDXG01JbvoVRXQ0x6UZwyrU4IaLBZeq39Bsbatd94Fuc3F1RGqPF5RbIWW7RvkVQjn79ejzysnA==
+
+esbuild-linux-mips64le@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.38.tgz#95081e42f698bbe35d8ccee0e3a237594b337eb5"
+  integrity sha512-qd1dLf2v7QBiI5wwfil9j0HG/5YMFBAmMVmdeokbNAMbcg49p25t6IlJFXAeLzogv1AvgaXRXvgFNhScYEUXGQ==
+
+esbuild-linux-ppc64le@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.38.tgz#dceb0a1b186f5df679618882a7990bd422089b47"
+  integrity sha512-mnbEm7o69gTl60jSuK+nn+pRsRHGtDPfzhrqEUXyCl7CTOCLtWN2bhK8bgsdp6J/2NyS/wHBjs1x8aBWwP2X9Q==
+
+esbuild-linux-riscv64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.38.tgz#61fb8edb75f475f9208c4a93ab2bfab63821afd2"
+  integrity sha512-+p6YKYbuV72uikChRk14FSyNJZ4WfYkffj6Af0/Tw63/6TJX6TnIKE+6D3xtEc7DeDth1fjUOEqm+ApKFXbbVQ==
+
+esbuild-linux-s390x@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.38.tgz#34c7126a4937406bf6a5e69100185fd702d12fe0"
+  integrity sha512-0zUsiDkGJiMHxBQ7JDU8jbaanUY975CdOW1YDrurjrM0vWHfjv9tLQsW9GSyEb/heSK1L5gaweRjzfUVBFoybQ==
+
+esbuild-netbsd-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.38.tgz#322ea9937d9e529183ee281c7996b93eb38a5d95"
+  integrity sha512-cljBAApVwkpnJZfnRVThpRBGzCi+a+V9Ofb1fVkKhtrPLDYlHLrSYGtmnoTVWDQdU516qYI8+wOgcGZ4XIZh0Q==
+
+esbuild-openbsd-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.38.tgz#1ca29bb7a2bf09592dcc26afdb45108f08a2cdbd"
+  integrity sha512-CDswYr2PWPGEPpLDUO50mL3WO/07EMjnZDNKpmaxUPsrW+kVM3LoAqr/CE8UbzugpEiflYqJsGPLirThRB18IQ==
+
+esbuild-sunos-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.38.tgz#c9446f7d8ebf45093e7bb0e7045506a88540019b"
+  integrity sha512-2mfIoYW58gKcC3bck0j7lD3RZkqYA7MmujFYmSn9l6TiIcAMpuEvqksO+ntBgbLep/eyjpgdplF7b+4T9VJGOA==
+
+esbuild-windows-32@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.38.tgz#f8e9b4602fd0ccbd48e5c8d117ec0ba4040f2ad1"
+  integrity sha512-L2BmEeFZATAvU+FJzJiRLFUP+d9RHN+QXpgaOrs2klshoAm1AE6Us4X6fS9k33Uy5SzScn2TpcgecbqJza1Hjw==
+
+esbuild-windows-64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.38.tgz#280f58e69f78535f470905ce3e43db1746518107"
+  integrity sha512-Khy4wVmebnzue8aeSXLC+6clo/hRYeNIm0DyikoEqX+3w3rcvrhzpoix0S+MF9vzh6JFskkIGD7Zx47ODJNyCw==
+
+esbuild-windows-arm64@0.14.38:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.38.tgz#d97e9ac0f95a4c236d9173fa9f86c983d6a53f54"
+  integrity sha512-k3FGCNmHBkqdJXuJszdWciAH77PukEyDsdIryEHn9cKLQFxzhT39dSumeTuggaQcXY57UlmLGIkklWZo2qzHpw==
+
+esbuild@^0.14.27:
+  version "0.14.38"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.38.tgz#99526b778cd9f35532955e26e1709a16cca2fb30"
+  integrity sha512-12fzJ0fsm7gVZX1YQ1InkOE5f9Tl7cgf6JPYXRJtPIoE0zkWAbHdPHVPPaLi9tYAcEBqheGzqLn/3RdTOyBfcA==
+  optionalDependencies:
+    esbuild-android-64 "0.14.38"
+    esbuild-android-arm64 "0.14.38"
+    esbuild-darwin-64 "0.14.38"
+    esbuild-darwin-arm64 "0.14.38"
+    esbuild-freebsd-64 "0.14.38"
+    esbuild-freebsd-arm64 "0.14.38"
+    esbuild-linux-32 "0.14.38"
+    esbuild-linux-64 "0.14.38"
+    esbuild-linux-arm "0.14.38"
+    esbuild-linux-arm64 "0.14.38"
+    esbuild-linux-mips64le "0.14.38"
+    esbuild-linux-ppc64le "0.14.38"
+    esbuild-linux-riscv64 "0.14.38"
+    esbuild-linux-s390x "0.14.38"
+    esbuild-netbsd-64 "0.14.38"
+    esbuild-openbsd-64 "0.14.38"
+    esbuild-sunos-64 "0.14.38"
+    esbuild-windows-32 "0.14.38"
+    esbuild-windows-64 "0.14.38"
+    esbuild-windows-arm64 "0.14.38"
+
 escalade@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -2315,17 +1720,20 @@ eslint-plugin-import@2.26.0:
     resolve "^1.22.0"
     tsconfig-paths "^3.14.1"
 
-eslint-plugin-vue@8.6.0:
-  version "8.6.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.6.0.tgz#fbdf0f13f8d208a4cba752bf54042661a1aec5c3"
-  integrity sha512-abXiF2J18n/7ZPy9foSlJyouKf54IqpKlNvNmzhM93N0zs3QUxZG/oBd3tVPOJTKg7SlhBUtPxugpqzNbgGpQQ==
+eslint-plugin-vue@9.1.0:
+  version "9.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.1.0.tgz#b528941325e26a24bc5d5c5030c0a8996c36659c"
+  integrity sha512-EPCeInPicQ/YyfOWJDr1yfEeSNoFCMzUus107lZyYi37xejdOolNzS5MXGXp8+9bkoKZMdv/1AcZzQebME6r+g==
   dependencies:
     eslint-utils "^3.0.0"
     natural-compare "^1.4.0"
+    nth-check "^2.0.1"
+    postcss-selector-parser "^6.0.9"
     semver "^7.3.5"
-    vue-eslint-parser "^8.0.1"
+    vue-eslint-parser "^9.0.1"
+    xml-name-validator "^4.0.0"
 
-eslint-scope@5.1.1, eslint-scope@^5.1.1:
+eslint-scope@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
   integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -2333,14 +1741,6 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1:
     esrecurse "^4.3.0"
     estraverse "^4.1.1"
 
-eslint-scope@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-6.0.0.tgz#9cf45b13c5ac8f3d4c50f46a5121f61b3e318978"
-  integrity sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==
-  dependencies:
-    esrecurse "^4.3.0"
-    estraverse "^5.2.0"
-
 eslint-scope@^7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
@@ -2361,22 +1761,17 @@ eslint-visitor-keys@^2.0.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
   integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
 
-eslint-visitor-keys@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186"
-  integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==
-
 eslint-visitor-keys@^3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
   integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
 
-eslint@8.13.0:
-  version "8.13.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7"
-  integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==
+eslint@8.17.0:
+  version "8.17.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.17.0.tgz#1cfc4b6b6912f77d24b874ca1506b0fe09328c21"
+  integrity sha512-gq0m0BTJfci60Fz4nczYxNAlED+sMcihltndR8t9t1evnU/azx53x3t2UHXC/uRjcbvRw/XctpaNygSTcQD+Iw==
   dependencies:
-    "@eslint/eslintrc" "^1.2.1"
+    "@eslint/eslintrc" "^1.3.0"
     "@humanwhocodes/config-array" "^0.9.2"
     ajv "^6.10.0"
     chalk "^4.0.0"
@@ -2387,14 +1782,14 @@ eslint@8.13.0:
     eslint-scope "^7.1.1"
     eslint-utils "^3.0.0"
     eslint-visitor-keys "^3.3.0"
-    espree "^9.3.1"
+    espree "^9.3.2"
     esquery "^1.4.0"
     esutils "^2.0.2"
     fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
     functional-red-black-tree "^1.0.1"
     glob-parent "^6.0.1"
-    globals "^13.6.0"
+    globals "^13.15.0"
     ignore "^5.2.0"
     import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
@@ -2403,7 +1798,7 @@ eslint@8.13.0:
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.4.1"
     lodash.merge "^4.6.2"
-    minimatch "^3.0.4"
+    minimatch "^3.1.2"
     natural-compare "^1.4.0"
     optionator "^0.9.1"
     regexpp "^3.2.0"
@@ -2412,29 +1807,15 @@ eslint@8.13.0:
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
-espree@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-9.0.0.tgz#e90a2965698228502e771c7a58489b1a9d107090"
-  integrity sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==
+espree@^9.3.1, espree@^9.3.2:
+  version "9.3.2"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
+  integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
   dependencies:
-    acorn "^8.5.0"
-    acorn-jsx "^5.3.1"
-    eslint-visitor-keys "^3.0.0"
-
-espree@^9.3.1:
-  version "9.3.1"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.1.tgz#8793b4bc27ea4c778c19908e0719e7b8f4115bcd"
-  integrity sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==
-  dependencies:
-    acorn "^8.7.0"
-    acorn-jsx "^5.3.1"
+    acorn "^8.7.1"
+    acorn-jsx "^5.3.2"
     eslint-visitor-keys "^3.3.0"
 
-esprima@^4.0.0:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
-  integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
-
 esquery@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
@@ -2464,6 +1845,11 @@ estraverse@^5.2.0:
   resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
   integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
 
+estree-walker@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
+  integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
+
 estree-walker@^2.0.2:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
@@ -2502,11 +1888,6 @@ eventemitter3@4.0.7, eventemitter3@^4.0.4, eventemitter3@^4.0.7:
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
 
-events@^3.2.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379"
-  integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==
-
 execa@4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a"
@@ -2537,21 +1918,6 @@ execa@5.1.1:
     signal-exit "^3.0.3"
     strip-final-newline "^2.0.0"
 
-execa@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/execa/-/execa-5.0.0.tgz#4029b0007998a841fbd1032e5f4de86a3c1e3376"
-  integrity sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==
-  dependencies:
-    cross-spawn "^7.0.3"
-    get-stream "^6.0.0"
-    human-signals "^2.1.0"
-    is-stream "^2.0.0"
-    merge-stream "^2.0.0"
-    npm-run-path "^4.0.1"
-    onetime "^5.1.2"
-    signal-exit "^3.0.3"
-    strip-final-newline "^2.0.0"
-
 executable@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c"
@@ -2559,13 +1925,6 @@ executable@^4.1.1:
   dependencies:
     pify "^2.2.0"
 
-expand-tilde@^2.0.0, expand-tilde@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
-  integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=
-  dependencies:
-    homedir-polyfill "^1.0.1"
-
 ext@^1.1.2:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
@@ -2621,6 +1980,17 @@ fast-glob@^3.1.1:
     micromatch "^4.0.2"
     picomatch "^2.2.1"
 
+fast-glob@^3.2.9:
+  version "3.2.11"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+  integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
+  dependencies:
+    "@nodelib/fs.stat" "^2.0.2"
+    "@nodelib/fs.walk" "^1.2.3"
+    glob-parent "^5.1.2"
+    merge2 "^1.3.0"
+    micromatch "^4.0.4"
+
 fast-json-stable-stringify@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@@ -2631,11 +2001,6 @@ fast-levenshtein@^2.0.6:
   resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
   integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
 
-fastest-levenshtein@^1.0.12:
-  version "1.0.12"
-  resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
-  integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
-
 fastq@^1.6.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.8.0.tgz#550e1f9f59bbc65fe185cb6a9b4d95357107f481"
@@ -2678,14 +2043,6 @@ fill-range@^7.0.1:
   dependencies:
     to-regex-range "^5.0.1"
 
-find-node-modules@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c"
-  integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug==
-  dependencies:
-    findup-sync "^4.0.0"
-    merge "^2.1.0"
-
 find-up@5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
@@ -2701,7 +2058,7 @@ find-up@^2.1.0:
   dependencies:
     locate-path "^2.0.0"
 
-find-up@^4.0.0, find-up@^4.1.0:
+find-up@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
   integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
@@ -2709,16 +2066,6 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
-findup-sync@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0"
-  integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==
-  dependencies:
-    detect-file "^1.0.0"
-    is-glob "^4.0.0"
-    micromatch "^4.0.2"
-    resolve-dir "^1.0.1"
-
 flat-cache@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@@ -2790,6 +2137,11 @@ fsevents@~2.1.2:
   resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
   integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
 
+fsevents@~2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+  integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
 function-bind@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@@ -2848,7 +2200,7 @@ getpass@^0.1.1:
   dependencies:
     assert-plus "^1.0.0"
 
-glob-parent@^5.1.0, glob-parent@~5.1.0:
+glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -2862,11 +2214,6 @@ glob-parent@^6.0.1:
   dependencies:
     is-glob "^4.0.3"
 
-glob-to-regexp@^0.4.1:
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
-  integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-
 glob@7.2.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
@@ -2898,37 +2245,10 @@ global-dirs@^3.0.0:
   dependencies:
     ini "2.0.0"
 
-global-modules@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
-  integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==
-  dependencies:
-    global-prefix "^1.0.1"
-    is-windows "^1.0.1"
-    resolve-dir "^1.0.0"
-
-global-prefix@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe"
-  integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=
-  dependencies:
-    expand-tilde "^2.0.2"
-    homedir-polyfill "^1.0.1"
-    ini "^1.3.4"
-    is-windows "^1.0.1"
-    which "^1.2.14"
-
-globals@^13.6.0:
-  version "13.7.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795"
-  integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==
-  dependencies:
-    type-fest "^0.20.2"
-
-globals@^13.9.0:
-  version "13.9.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb"
-  integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==
+globals@^13.15.0:
+  version "13.15.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac"
+  integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog==
   dependencies:
     type-fest "^0.20.2"
 
@@ -2944,7 +2264,19 @@ globby@^11.0.4:
     merge2 "^1.3.0"
     slash "^3.0.0"
 
-graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.4:
+globby@^11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+  integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+  dependencies:
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.2.9"
+    ignore "^5.2.0"
+    merge2 "^1.4.1"
+    slash "^3.0.0"
+
+graceful-fs@^4.1.6:
   version "4.2.4"
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
   integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
@@ -2954,16 +2286,6 @@ graceful-fs@^4.2.0:
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
   integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
 
-graceful-fs@^4.2.9:
-  version "4.2.9"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
-  integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
-
-growl@1.10.5:
-  version "1.10.5"
-  resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
-  integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
-
 hammerjs@^2.0.8:
   version "2.0.8"
   resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
@@ -2974,17 +2296,12 @@ has-bigints@^1.0.1:
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
   integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
 
-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.0, has-symbols@^1.0.1:
+has-symbols@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
   integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
@@ -3008,28 +2325,11 @@ has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
-hash-sum@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
-  integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
-
-hash-sum@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a"
-  integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==
-
 he@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
 
-homedir-polyfill@^1.0.1:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"
-  integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==
-  dependencies:
-    parse-passwd "^1.0.0"
-
 http-signature@~1.3.6:
   version "1.3.6"
   resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"
@@ -3049,11 +2349,6 @@ human-signals@^2.1.0:
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
-icss-utils@^5.0.0, icss-utils@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
-  integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
-
 idb-keyval@6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041"
@@ -3071,11 +2366,6 @@ ignore@^5.1.4:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
   integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
-ignore@^5.1.8:
-  version "5.1.9"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb"
-  integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==
-
 ignore@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
@@ -3094,14 +2384,6 @@ import-fresh@^3.0.0, import-fresh@^3.2.1:
     parent-module "^1.0.0"
     resolve-from "^4.0.0"
 
-import-local@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
-  integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==
-  dependencies:
-    pkg-dir "^4.2.0"
-    resolve-cwd "^3.0.0"
-
 imurmurhash@^0.1.4:
   version "0.1.4"
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -3112,11 +2394,6 @@ indent-string@^4.0.0:
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
   integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
 
-indexes-of@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
-  integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
-
 inflight@^1.0.4:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -3135,11 +2412,6 @@ ini@2.0.0:
   resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
   integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
 
-ini@^1.3.4:
-  version "1.3.7"
-  resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84"
-  integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==
-
 insert-text-at-cursor@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.3.0.tgz#1819607680ec1570618347c4cd475e791faa25da"
@@ -3154,27 +2426,6 @@ internal-slot@^1.0.3:
     has "^1.0.3"
     side-channel "^1.0.4"
 
-interpret@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
-  integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
-
-ip-address@^7.1.0:
-  version "7.1.0"
-  resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-7.1.0.tgz#4a9c699e75b51cbeb18b38de8ed216efa1a490c5"
-  integrity sha512-V9pWC/VJf2lsXqP7IWJ+pe3P1/HCYGBMZrrnT62niLGjAfCbeiwXMUxaeHvnVlz19O27pvXP4azs+Pj/A0x+SQ==
-  dependencies:
-    jsbn "1.1.0"
-    sprintf-js "1.1.2"
-
-ip-cidr@3.0.4:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/ip-cidr/-/ip-cidr-3.0.4.tgz#a915c47e00f47ea8d5f8ed662ea6161471c44375"
-  integrity sha512-pKNiqmBlTvEkhaLAa3+FOmYSY0/jjADVxxjA3NbujZZTT8mjLI90Q+6mwg6kd0fNm0RuAOkWJ1u1a/ETmlrPNQ==
-  dependencies:
-    ip-address "^7.1.0"
-    jsbn "^1.1.0"
-
 ip-regex@^4.0.0, ip-regex@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
@@ -3185,11 +2436,6 @@ ipaddr.js@^2.0.1:
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
   integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
 
-is-arrayish@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-  integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
-
 is-bigint@^1.0.1:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@@ -3212,7 +2458,7 @@ is-boolean-object@^1.1.0:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
-is-callable@^1.1.4, is-callable@^1.1.5:
+is-callable@^1.1.4:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
   integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
@@ -3229,13 +2475,6 @@ is-ci@^3.0.0:
   dependencies:
     ci-info "^3.1.1"
 
-is-core-module@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.0.0.tgz#58531b70aed1db7c0e8d4eb1a0a2d1ddd64bd12d"
-  integrity sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==
-  dependencies:
-    has "^1.0.3"
-
 is-core-module@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
@@ -3300,13 +2539,6 @@ is-negative-zero@^2.0.1:
   resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
   integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
 
-is-number-like@^1.0.3:
-  version "1.0.8"
-  resolved "https://registry.yarnpkg.com/is-number-like/-/is-number-like-1.0.8.tgz#2e129620b50891042e44e9bbbb30593e75cfbbe3"
-  integrity sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==
-  dependencies:
-    lodash.isfinite "^3.3.2"
-
 is-number-object@^1.0.4:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.6.tgz#6a7aaf838c7f0686a50b4553f7e54a96494e89f0"
@@ -3329,19 +2561,12 @@ is-plain-obj@^2.1.0:
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
   integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
 
-is-plain-object@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
-  integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
-  dependencies:
-    isobject "^3.0.1"
-
 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, is-regex@^1.0.5:
+is-regex@^1.0.3:
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae"
   integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==
@@ -3404,44 +2629,16 @@ is-weakref@^1.0.1:
   dependencies:
     call-bind "^1.0.0"
 
-is-windows@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
-  integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
-
 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=
 
-isobject@^3.0.1:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-  integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
-
 isstream@~0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
   integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
 
-jest-worker@^26.6.2:
-  version "26.6.2"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
-  integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
-  dependencies:
-    "@types/node" "*"
-    merge-stream "^2.0.0"
-    supports-color "^7.0.0"
-
-jest-worker@^27.0.2:
-  version "27.0.6"
-  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.0.6.tgz#a5fdb1e14ad34eb228cfe162d9f729cdbfa28aed"
-  integrity sha512-qupxcj/dRuA3xHPMUd40gr2EaAurFbkwzOh7wfPaeE9id7hyjURRQoqNfHifHK3XjJU6YJJUQKILGUnwGPEOCA==
-  dependencies:
-    "@types/node" "*"
-    merge-stream "^2.0.0"
-    supports-color "^8.0.0"
-
 joi@^17.4.0:
   version "17.4.2"
   resolved "https://registry.yarnpkg.com/joi/-/joi-17.4.2.tgz#02f4eb5cf88e515e614830239379dcbbe28ce7f7"
@@ -3458,11 +2655,6 @@ js-stringify@^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==
-
 js-yaml@4.1.0, js-yaml@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@@ -3470,34 +2662,11 @@ js-yaml@4.1.0, js-yaml@^4.1.0:
   dependencies:
     argparse "^2.0.1"
 
-js-yaml@^3.13.1:
-  version "3.14.1"
-  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
-  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
-  dependencies:
-    argparse "^1.0.7"
-    esprima "^4.0.0"
-
-jsbn@1.1.0, jsbn@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
-  integrity sha1-sBMHyym2GKHtJux56RH4A8TaAEA=
-
 jsbn@~0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
   integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
 
-json-parse-better-errors@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
-  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
-
-json-parse-even-better-errors@^2.3.0:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
-  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
-
 json-schema-traverse@^0.4.1:
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@@ -3518,16 +2687,7 @@ json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
   resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
   integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
 
-json5-loader@4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/json5-loader/-/json5-loader-4.0.1.tgz#6d17a1181e8f3c3d9204dca2a4ce4627306c8498"
-  integrity sha512-c9viNZlZTz0MTIcf/4qvek5Dz1/PU3DNCB4PwUhlEZIV3qb1bSD6vQQymlV17/Wm6ncra1aCvmIPsuRj+KfEEg==
-  dependencies:
-    json5 "^2.1.3"
-    loader-utils "^2.0.0"
-    schema-utils "^3.0.0"
-
-json5@2.2.1:
+json5@2.2.1, json5@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
   integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
@@ -3539,13 +2699,6 @@ json5@^1.0.1:
   dependencies:
     minimist "^1.2.0"
 
-json5@^2.1.2, json5@^2.1.3:
-  version "2.1.3"
-  resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
-  integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
-  dependencies:
-    minimist "^1.2.5"
-
 jsonfile@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@@ -3589,28 +2742,13 @@ jstransformer@1.0.0:
     is-promise "^2.0.0"
     promise "^7.0.1"
 
-katex@0.15.3:
-  version "0.15.3"
-  resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.3.tgz#08781a7ed26800b20380d959d1ffcd62bca0ec14"
-  integrity sha512-Al6V7RJsmjklT9QItyHWGaQCt+NYTle1bZwB1e9MR/tLoIT1MXaHy9UpfGSB7eaqDgjjqqRxQOaQGrALCrEyBQ==
+katex@0.15.6:
+  version "0.15.6"
+  resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.6.tgz#c4e2f6ced2ac4de1ef6f737fe7c67d3026baa0e5"
+  integrity sha512-UpzJy4yrnqnhXvRPhjEuLA4lcPn6eRngixW7Q3TJErjg3Aw2PuLFBzTkdUb89UtumxjhHTqL3a5GDGETMSwgJA==
   dependencies:
     commander "^8.0.0"
 
-kind-of@^6.0.2:
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
-  integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
-
-klona@^2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
-  integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
-
-klona@^2.0.5:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
-  integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
-
 lazy-ass@1.6.0, lazy-ass@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513"
@@ -3624,16 +2762,6 @@ levn@^0.4.1:
     prelude-ls "^1.2.1"
     type-check "~0.4.0"
 
-lilconfig@^2.0.3:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.3.tgz#68f3005e921dafbd2a2afb48379986aa6d2579fd"
-  integrity sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==
-
-lines-and-columns@^1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
-  integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
-
 listr2@^3.8.3:
   version "3.11.0"
   resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.11.0.tgz#9771b02407875aa78e73d6e0ff6541bbec0aaee9"
@@ -3647,29 +2775,6 @@ listr2@^3.8.3:
     through "^2.3.8"
     wrap-ansi "^7.0.0"
 
-loader-runner@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
-  integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
-
-loader-utils@^1.0.2:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
-  integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==
-  dependencies:
-    big.js "^5.2.2"
-    emojis-list "^3.0.0"
-    json5 "^1.0.1"
-
-loader-utils@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
-  integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
-  dependencies:
-    big.js "^5.2.2"
-    emojis-list "^3.0.0"
-    json5 "^2.1.2"
-
 locate-path@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -3692,16 +2797,6 @@ locate-path@^6.0.0:
   dependencies:
     p-locate "^5.0.0"
 
-lodash.isfinite@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz#fb89b65a9a80281833f0b7478b3a5104f898ebb3"
-  integrity sha1-+4m2WpqAKBgz8LdHizpRBPiY67M=
-
-lodash.memoize@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
-  integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
-
 lodash.merge@^4.6.2:
   version "4.6.2"
   resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@@ -3712,12 +2807,7 @@ lodash.once@^4.1.1:
   resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
   integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
 
-lodash.uniq@^4.5.0:
-  version "4.5.0"
-  resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
-  integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
-
-lodash@^4.17.14, lodash@^4.17.19, lodash@^4.17.21:
+lodash@^4.17.19, lodash@^4.17.21:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3764,44 +2854,24 @@ matter-js@0.18.0:
   resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.18.0.tgz#083ced04eb6768f7664dc7ca8948a10e46ad3ed6"
   integrity sha512-/ZVem4WygUnbmo/iE4oHZpZS97btfBtYy5Iwn1396vUZU7YhgVEN8J4UWwfZwY1ZqoTYlPgjvSw9WXauuXL0mg==
 
-mdn-data@2.0.14:
-  version "2.0.14"
-  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
-  integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
-
-mdn-data@2.0.4:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
-  integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
-
-mdn-data@2.0.6:
-  version "2.0.6"
-  resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978"
-  integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==
-
 merge-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
-merge2@^1.3.0:
+merge2@^1.3.0, merge2@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
-merge@^2.1.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98"
-  integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==
-
-mfm-js@0.21.0:
-  version "0.21.0"
-  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.21.0.tgz#954cc6e7071700b0b1872c78a90bada10be7f772"
-  integrity sha512-nyQXaipa7rmAw9ER9uYigMvGcdCwhSv93abZBwccnSnPOc1W3S/WW0+sN28g3YSmlHDCA0i2q9aAFc9EgOi5KA==
+mfm-js@0.22.1:
+  version "0.22.1"
+  resolved "https://registry.yarnpkg.com/mfm-js/-/mfm-js-0.22.1.tgz#ad5f0b95cc903ca5a5e414e2edf64ac4648dc8c2"
+  integrity sha512-UV5zvDKlWPpBFeABhyCzuOTJ3RwrNrmVpJ+zz/dFX6D/ntEywljgxkfsLamcy0ZSwUAr0O+WQxGHvAwyxUgsAQ==
   dependencies:
-    twemoji-parser "13.1.x"
+    twemoji-parser "14.0.x"
 
-micromatch@^4.0.0, micromatch@^4.0.2:
+micromatch@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
   integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
@@ -3809,17 +2879,28 @@ micromatch@^4.0.0, micromatch@^4.0.2:
     braces "^3.0.1"
     picomatch "^2.0.5"
 
-microseconds@0.2.0:
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
-  integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
+micromatch@^4.0.4:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+  integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+  dependencies:
+    braces "^3.0.2"
+    picomatch "^2.3.1"
+
+microtime@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/microtime/-/microtime-3.0.0.tgz#d140914bde88aa89b4f9fd2a18620b435af0f39b"
+  integrity sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ==
+  dependencies:
+    node-addon-api "^1.2.0"
+    node-gyp-build "^3.8.0"
 
 mime-db@1.44.0:
   version "1.44.0"
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
   integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
 
-mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19:
+mime-types@^2.1.12, mime-types@~2.1.19:
   version "2.1.27"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
   integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
@@ -3831,12 +2912,12 @@ mimic-fn@^2.1.0:
   resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
   integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
 
-minimatch@4.2.1:
-  version "4.2.1"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-4.2.1.tgz#40d9d511a46bdc4e563c22c3080cde9c0d8299b4"
-  integrity sha512-9Uq1ChtSZO+Mxa/CL1eGizn2vRn3MlLgzhT0Iz8zaY8NdvxvB0d5QdPFmCKf7JKA9Lerx5vRrnwO03jsSfGG9g==
+minimatch@5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
+  integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
   dependencies:
-    brace-expansion "^1.1.7"
+    brace-expansion "^2.0.1"
 
 minimatch@^3.0.4:
   version "3.0.4"
@@ -3866,39 +2947,30 @@ misskey-js@0.0.14:
     eventemitter3 "^4.0.7"
     reconnecting-websocket "^4.4.0"
 
-mkdirp@~0.5.1:
-  version "0.5.5"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
-  integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
-  dependencies:
-    minimist "^1.2.5"
-
-mocha@9.2.2:
-  version "9.2.2"
-  resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9"
-  integrity sha512-L6XC3EdwT6YrIk0yXpavvLkn8h+EU+Y5UcCHKECyMbdUIxyMuZj4bX4U9e1nvnvUUvQVsV2VHQr5zLdcUkhW/g==
+mocha@10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9"
+  integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==
   dependencies:
     "@ungap/promise-all-settled" "1.1.2"
     ansi-colors "4.1.1"
     browser-stdout "1.3.1"
     chokidar "3.5.3"
-    debug "4.3.3"
+    debug "4.3.4"
     diff "5.0.0"
     escape-string-regexp "4.0.0"
     find-up "5.0.0"
     glob "7.2.0"
-    growl "1.10.5"
     he "1.2.0"
     js-yaml "4.1.0"
     log-symbols "4.1.0"
-    minimatch "4.2.1"
+    minimatch "5.0.1"
     ms "2.1.3"
-    nanoid "3.3.1"
+    nanoid "3.3.3"
     serialize-javascript "6.0.0"
     strip-json-comments "3.1.1"
     supports-color "8.1.1"
-    which "2.0.2"
-    workerpool "6.2.0"
+    workerpool "6.2.1"
     yargs "16.2.0"
     yargs-parser "20.2.4"
     yargs-unparser "2.0.0"
@@ -3918,33 +2990,21 @@ ms@2.1.3, ms@^2.1.1:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
 
-mylas@^2.1.6:
-  version "2.1.6"
-  resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.6.tgz#40f3ac6faf77b966c2c2f7b9c0d21ea65b3d9800"
-  integrity sha512-5ggCu4hVRJZE6NpQ309y6ArykK5vujK6LfSAXvsrmBNSX/9Gfq7D9zjxhHyjSR/sbFzCe2hI9LO1EY9KXv/XkQ==
+mylas@^2.1.9:
+  version "2.1.9"
+  resolved "https://registry.yarnpkg.com/mylas/-/mylas-2.1.9.tgz#8329626f95c0ce522ca7d3c192eca6221d172cdc"
+  integrity sha512-pa+cQvmhoM8zzgitPYZErmDt9EdTNVnXsH1XFjMeM4TyG4FFcgxrvK1+jwabVFwUOEDaSWuXBMjg43kqt/Ydlg==
 
-nano-time@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
-  integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=
-  dependencies:
-    big-integer "^1.6.16"
-
-nanoid@3.3.1, nanoid@^3.1.20, nanoid@^3.3.1:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
-  integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
+nanoid@3.3.3, nanoid@^3.3.3:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
+  integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
 
 natural-compare@^1.4.0:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
-neo-async@^2.6.2:
-  version "2.6.2"
-  resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
-  integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
-
 nested-property@4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/nested-property/-/nested-property-4.0.0.tgz#a67b5a31991e701e03cdbaa6453bc5b1011bb88d"
@@ -3960,26 +3020,26 @@ next-tick@~1.0.0:
   resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
   integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
 
+node-addon-api@^1.2.0:
+  version "1.7.2"
+  resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d"
+  integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==
+
+node-gyp-build@^3.8.0:
+  version "3.9.0"
+  resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25"
+  integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A==
+
 node-gyp-build@~3.7.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d"
   integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w==
 
-node-releases@^2.0.1:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
-  integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
-
 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==
 
-normalize-url@^6.0.1:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
-  integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
-
 npm-run-path@^4.0.0, npm-run-path@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
@@ -3987,17 +3047,10 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1:
   dependencies:
     path-key "^3.0.0"
 
-nth-check@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
-  integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==
-  dependencies:
-    boolbase "~1.0.0"
-
-nth-check@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125"
-  integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==
+nth-check@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2"
+  integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w==
   dependencies:
     boolbase "^1.0.0"
 
@@ -4016,26 +3069,11 @@ object-inspect@^1.11.0, object-inspect@^1.9.0:
   resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1"
   integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==
 
-object-inspect@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
-  integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
-
-object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
+object-keys@^1.0.12, object-keys@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
   integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
 
-object.assign@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
-  integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
-  dependencies:
-    define-properties "^1.1.2"
-    function-bind "^1.1.1"
-    has-symbols "^1.0.0"
-    object-keys "^1.0.11"
-
 object.assign@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
@@ -4046,24 +3084,6 @@ object.assign@^4.1.2:
     has-symbols "^1.0.1"
     object-keys "^1.1.1"
 
-object.getownpropertydescriptors@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
-  integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.0-next.1"
-
-object.values@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
-  integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.0-next.1"
-    function-bind "^1.1.1"
-    has "^1.0.3"
-
 object.values@^1.1.5:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac"
@@ -4073,10 +3093,10 @@ object.values@^1.1.5:
     define-properties "^1.1.3"
     es-abstract "^1.19.1"
 
-oblivious-set@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
-  integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
+oblivious-set@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.1.1.tgz#d9d38e9491d51f27a5c3ec1681d2ba40aa81e98b"
+  integrity sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==
 
 once@^1.3.0, once@^1.3.1, once@^1.4.0:
   version "1.4.0"
@@ -4135,13 +3155,6 @@ p-limit@^3.0.2:
   dependencies:
     p-try "^2.0.0"
 
-p-limit@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
-  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
-  dependencies:
-    yocto-queue "^0.1.0"
-
 p-locate@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
@@ -4202,26 +3215,6 @@ parent-module@^1.0.0:
   dependencies:
     callsites "^3.0.0"
 
-parse-json@^5.0.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.1.0.tgz#f96088cdf24a8faa9aea9a009f2d9d942c999646"
-  integrity sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==
-  dependencies:
-    "@babel/code-frame" "^7.0.0"
-    error-ex "^1.3.1"
-    json-parse-even-better-errors "^2.3.0"
-    lines-and-columns "^1.1.6"
-
-parse-passwd@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
-  integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
-
-parse5@6.0.1:
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
-  integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
-
 path-exists@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
@@ -4269,10 +3262,10 @@ performance-now@^2.1.0:
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
   integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
 
-photoswipe@5.2.4:
-  version "5.2.4"
-  resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.2.4.tgz#918fd64c6b41b6a693743e5d70ee1a59747f491d"
-  integrity sha512-7p+VH7ELUnW9/3rCULCmyXcUCEuZwcsxvxPQYzR4wk3EaXcLCiINMCspc9Qq9AJYNsqYo1qGVL1y1Tch8uKAjw==
+photoswipe@5.2.7:
+  version "5.2.7"
+  resolved "https://registry.yarnpkg.com/photoswipe/-/photoswipe-5.2.7.tgz#9ff2aaf2a3e03c817ac2835dc6dee0f901e8159d"
+  integrity sha512-AogMba7W/O5gOtDIZ8cQuou1ltwxlaLNoZY1qi1s+kbYXpZk9D6rXxnNGAfDppl+bfe+sKLW2w2sx+3uQ8oPzg==
 
 picocolors@^1.0.0:
   version "1.0.0"
@@ -4284,267 +3277,28 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.0.7, picomatch@^2.2.1:
   resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
   integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
 
+picomatch@^2.2.2, picomatch@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
 pify@^2.2.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
   integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
 
-pkg-dir@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
-  integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+plimit-lit@^1.2.6:
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/plimit-lit/-/plimit-lit-1.2.6.tgz#8c1336f26a042b6e9f1acc665be5eee4c2a55fb3"
+  integrity sha512-EuVnKyDeFgr58aidKf2G7DI41r23bxphlvBKAZ8e8dT9of0Ez2g9w6JbJGUP1YBNC2yG9+ZCCbjLj4yS1P5Gzw==
   dependencies:
-    find-up "^4.0.0"
+    queue-lit "^1.2.7"
 
 pngjs@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
   integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
 
-portscanner@2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/portscanner/-/portscanner-2.2.0.tgz#6059189b3efa0965c9d96a56b958eb9508411cf1"
-  integrity sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==
-  dependencies:
-    async "^2.6.0"
-    is-number-like "^1.0.3"
-
-postcss-calc@^8.2.3:
-  version "8.2.4"
-  resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5"
-  integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==
-  dependencies:
-    postcss-selector-parser "^6.0.9"
-    postcss-value-parser "^4.2.0"
-
-postcss-colormin@^5.3.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a"
-  integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==
-  dependencies:
-    browserslist "^4.16.6"
-    caniuse-api "^3.0.0"
-    colord "^2.9.1"
-    postcss-value-parser "^4.2.0"
-
-postcss-convert-values@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.0.tgz#f8d3abe40b4ce4b1470702a0706343eac17e7c10"
-  integrity sha512-GkyPbZEYJiWtQB0KZ0X6qusqFHUepguBCNFi9t5JJc7I2OTXG7C0twbTLvCfaKOLl3rSXmpAwV7W5txd91V84g==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-discard-comments@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.1.tgz#e90019e1a0e5b99de05f63516ce640bd0df3d369"
-  integrity sha512-5JscyFmvkUxz/5/+TB3QTTT9Gi9jHkcn8dcmmuN68JQcv3aQg4y88yEHHhwFB52l/NkaJ43O0dbksGMAo49nfQ==
-
-postcss-discard-duplicates@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848"
-  integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==
-
-postcss-discard-empty@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c"
-  integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==
-
-postcss-discard-overridden@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e"
-  integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==
-
-postcss-loader@6.2.1:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef"
-  integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==
-  dependencies:
-    cosmiconfig "^7.0.0"
-    klona "^2.0.5"
-    semver "^7.3.5"
-
-postcss-merge-longhand@^5.1.4:
-  version "5.1.4"
-  resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.4.tgz#0f46f8753989a33260efc47de9a0cdc571f2ec5c"
-  integrity sha512-hbqRRqYfmXoGpzYKeW0/NCZhvNyQIlQeWVSao5iKWdyx7skLvCfQFGIUsP9NUs3dSbPac2IC4Go85/zG+7MlmA==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-    stylehacks "^5.1.0"
-
-postcss-merge-rules@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.1.tgz#d327b221cd07540bcc8d9ff84446d8b404d00162"
-  integrity sha512-8wv8q2cXjEuCcgpIB1Xx1pIy8/rhMPIQqYKNzEdyx37m6gpq83mQQdCxgIkFgliyEnKvdwJf/C61vN4tQDq4Ww==
-  dependencies:
-    browserslist "^4.16.6"
-    caniuse-api "^3.0.0"
-    cssnano-utils "^3.1.0"
-    postcss-selector-parser "^6.0.5"
-
-postcss-minify-font-values@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b"
-  integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-minify-gradients@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c"
-  integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==
-  dependencies:
-    colord "^2.9.1"
-    cssnano-utils "^3.1.0"
-    postcss-value-parser "^4.2.0"
-
-postcss-minify-params@^5.1.2:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.2.tgz#77e250780c64198289c954884ebe3ee4481c3b1c"
-  integrity sha512-aEP+p71S/urY48HWaRHasyx4WHQJyOYaKpQ6eXl8k0kxg66Wt/30VR6/woh8THgcpRbonJD5IeD+CzNhPi1L8g==
-  dependencies:
-    browserslist "^4.16.6"
-    cssnano-utils "^3.1.0"
-    postcss-value-parser "^4.2.0"
-
-postcss-minify-selectors@^5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.0.tgz#17c2be233e12b28ffa8a421a02fc8b839825536c"
-  integrity sha512-vYxvHkW+iULstA+ctVNx0VoRAR4THQQRkG77o0oa4/mBS0OzGvvzLIvHDv/nNEM0crzN2WIyFU5X7wZhaUK3RA==
-  dependencies:
-    postcss-selector-parser "^6.0.5"
-
-postcss-modules-extract-imports@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
-  integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
-
-postcss-modules-local-by-default@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
-  integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
-  dependencies:
-    icss-utils "^5.0.0"
-    postcss-selector-parser "^6.0.2"
-    postcss-value-parser "^4.1.0"
-
-postcss-modules-scope@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
-  integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
-  dependencies:
-    postcss-selector-parser "^6.0.4"
-
-postcss-modules-values@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
-  integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
-  dependencies:
-    icss-utils "^5.0.0"
-
-postcss-normalize-charset@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed"
-  integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==
-
-postcss-normalize-display-values@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8"
-  integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-positions@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.0.tgz#902a7cb97cf0b9e8b1b654d4a43d451e48966458"
-  integrity sha512-8gmItgA4H5xiUxgN/3TVvXRoJxkAWLW6f/KKhdsH03atg0cB8ilXnrB5PpSshwVu/dD2ZsRFQcR1OEmSBDAgcQ==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-repeat-style@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.0.tgz#f6d6fd5a54f51a741cc84a37f7459e60ef7a6398"
-  integrity sha512-IR3uBjc+7mcWGL6CtniKNQ4Rr5fTxwkaDHwMBDGGs1x9IVRkYIT/M4NelZWkAOBdV6v3Z9S46zqaKGlyzHSchw==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-string@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228"
-  integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-timing-functions@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb"
-  integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-unicode@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75"
-  integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==
-  dependencies:
-    browserslist "^4.16.6"
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-url@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc"
-  integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==
-  dependencies:
-    normalize-url "^6.0.1"
-    postcss-value-parser "^4.2.0"
-
-postcss-normalize-whitespace@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa"
-  integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-ordered-values@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.1.tgz#0b41b610ba02906a3341e92cab01ff8ebc598adb"
-  integrity sha512-7lxgXF0NaoMIgyihL/2boNAEZKiW0+HkMhdKMTD93CjW8TdCy2hSdj8lsAo+uwm7EDG16Da2Jdmtqpedl0cMfw==
-  dependencies:
-    cssnano-utils "^3.1.0"
-    postcss-value-parser "^4.2.0"
-
-postcss-reduce-initial@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6"
-  integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==
-  dependencies:
-    browserslist "^4.16.6"
-    caniuse-api "^3.0.0"
-
-postcss-reduce-transforms@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9"
-  integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==
-  dependencies:
-    postcss-value-parser "^4.2.0"
-
-postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
-  version "6.0.4"
-  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
-  integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
-  dependencies:
-    cssesc "^3.0.0"
-    indexes-of "^1.0.1"
-    uniq "^1.0.1"
-    util-deprecate "^1.0.2"
-
-postcss-selector-parser@^6.0.5:
-  version "6.0.6"
-  resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea"
-  integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==
-  dependencies:
-    cssesc "^3.0.0"
-    util-deprecate "^1.0.2"
-
 postcss-selector-parser@^6.0.9:
   version "6.0.9"
   resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f"
@@ -4553,55 +3307,12 @@ postcss-selector-parser@^6.0.9:
     cssesc "^3.0.0"
     util-deprecate "^1.0.2"
 
-postcss-svgo@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d"
-  integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==
+postcss@^8.1.10, postcss@^8.4.13:
+  version "8.4.13"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.13.tgz#7c87bc268e79f7f86524235821dfdf9f73e5d575"
+  integrity sha512-jtL6eTBrza5MPzy8oJLFuUscHDXTV5KcLlqAWHl5q5WYRfnNRGSmOZmOZ1T6Gy7A99mOZfqungmZMpMmCVJ8ZA==
   dependencies:
-    postcss-value-parser "^4.2.0"
-    svgo "^2.7.0"
-
-postcss-unique-selectors@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6"
-  integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==
-  dependencies:
-    postcss-selector-parser "^6.0.5"
-
-postcss-value-parser@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
-  integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
-
-postcss-value-parser@^4.2.0:
-  version "4.2.0"
-  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
-  integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-
-postcss@8.4.12:
-  version "8.4.12"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
-  integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
-  dependencies:
-    nanoid "^3.3.1"
-    picocolors "^1.0.0"
-    source-map-js "^1.0.2"
-
-postcss@^8.1.10:
-  version "8.2.8"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
-  integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
-  dependencies:
-    colorette "^1.2.2"
-    nanoid "^3.1.20"
-    source-map "^0.6.1"
-
-postcss@^8.4.7:
-  version "8.4.8"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
-  integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
-  dependencies:
-    nanoid "^3.3.1"
+    nanoid "^3.3.3"
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
@@ -4615,10 +3326,10 @@ pretty-bytes@^5.6.0:
   resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
   integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==
 
-prismjs@1.27.0:
-  version "1.27.0"
-  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"
-  integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
+prismjs@1.28.0:
+  version "1.28.0"
+  resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6"
+  integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==
 
 private-ip@2.3.3:
   version "2.3.3"
@@ -4775,11 +3486,6 @@ punycode@2.1.1, punycode@^2.1.0, punycode@^2.1.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-q@^1.1.2:
-  version "1.5.1"
-  resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
-  integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
-
 qrcode@1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b"
@@ -4800,6 +3506,11 @@ querystring@0.2.1:
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
   integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
 
+queue-lit@^1.2.7:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/queue-lit/-/queue-lit-1.2.7.tgz#69081656c9e7b81f09770bb2de6aa007f1a90763"
+  integrity sha512-K/rTdggORRcmf3+c89ijPlgJ/ldGP4oBj6Sm7VcTup4B2clf03Jo8QaXTnMst4EEQwkUbOZFN4frKocq2I85gw==
+
 random-seed@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd"
@@ -4826,13 +3537,6 @@ readdirp@~3.3.0:
   dependencies:
     picomatch "^2.0.7"
 
-rechoir@^0.7.0:
-  version "0.7.0"
-  resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.0.tgz#32650fd52c21ab252aa5d65b19310441c7e03aca"
-  integrity sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==
-  dependencies:
-    resolve "^1.9.0"
-
 reconnecting-websocket@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
@@ -4870,31 +3574,11 @@ require-main-filename@^2.0.0:
   resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
   integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
 
-resolve-cwd@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
-  integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
-  dependencies:
-    resolve-from "^5.0.0"
-
-resolve-dir@^1.0.0, resolve-dir@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43"
-  integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=
-  dependencies:
-    expand-tilde "^2.0.0"
-    global-modules "^1.0.0"
-
 resolve-from@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
   integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
 
-resolve-from@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
-  integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-
 resolve@^1.15.1, resolve@^1.20.0:
   version "1.20.0"
   resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
@@ -4912,14 +3596,6 @@ resolve@^1.22.0:
     path-parse "^1.0.7"
     supports-preserve-symlinks-flag "^1.0.0"
 
-resolve@^1.9.0:
-  version "1.18.1"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.18.1.tgz#018fcb2c5b207d2a6424aee361c5a266da8f4130"
-  integrity sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==
-  dependencies:
-    is-core-module "^2.0.0"
-    path-parse "^1.0.6"
-
 restore-cursor@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
@@ -4948,6 +3624,20 @@ rndstr@1.0.0:
     rangestr "0.0.1"
     seedrandom "2.4.2"
 
+rollup@2.75.6:
+  version "2.75.6"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.6.tgz#ac4dc8600f95942a0180f61c7c9d6200e374b439"
+  integrity sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==
+  optionalDependencies:
+    fsevents "~2.3.2"
+
+rollup@^2.59.0:
+  version "2.70.2"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.2.tgz#808d206a8851628a065097b7ba2053bd83ba0c0d"
+  integrity sha512-EitogNZnfku65I1DD5Mxe8JYRUCy0hkK5X84IlDtUs+O6JRMpRciXTzyCUuX11b5L5pvjH+OmFXiQ3XjabcXgg==
+  optionalDependencies:
+    fsevents "~2.3.2"
+
 run-parallel@^1.1.9:
   version "1.1.9"
   resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679"
@@ -4992,46 +3682,20 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
   resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
-sass-loader@12.6.0:
-  version "12.6.0"
-  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb"
-  integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==
-  dependencies:
-    klona "^2.0.4"
-    neo-async "^2.6.2"
-
-sass@1.50.0:
-  version "1.50.0"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.50.0.tgz#3e407e2ebc53b12f1e35ce45efb226ea6063c7c8"
-  integrity sha512-cLsD6MEZ5URXHStxApajEh7gW189kkjn4Rc8DQweMyF+o5HF5nfEz8QYLMlPsTOD88DknatTmBWkOcw5/LnJLQ==
+sass@1.52.3:
+  version "1.52.3"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.52.3.tgz#b7cc7ffea2341ccc9a0c4fd372bf1b3f9be1b6cb"
+  integrity sha512-LNNPJ9lafx+j1ArtA7GyEJm9eawXN8KlA1+5dF6IZyoONg1Tyo/g+muOsENWJH/2Q1FHbbV4UwliU0cXMa/VIA==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
     source-map-js ">=0.6.2 <2.0.0"
 
-sax@^1.2.4, sax@~1.2.4:
+sax@^1.2.4:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
-schema-utils@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef"
-  integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==
-  dependencies:
-    "@types/json-schema" "^7.0.6"
-    ajv "^6.12.5"
-    ajv-keywords "^3.5.2"
-
-schema-utils@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.0.tgz#95986eb604f66daadeed56e379bfe7a7f963cdb9"
-  integrity sha512-tTEaeYkyIhEZ9uWgAjDerWov3T9MgX8dhhy2r0IGeeX4W8ngtGl1++dUve/RUqzuaASSh7shwCDJjEzthxki8w==
-  dependencies:
-    "@types/json-schema" "^7.0.7"
-    ajv "^6.12.5"
-    ajv-keywords "^3.5.2"
-
 seedrandom@2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.2.tgz#18d78c41287d13aff8eadb29e235938b248aa9ff"
@@ -5042,7 +3706,7 @@ seedrandom@3.0.5:
   resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
   integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
 
-semver@^7.3.2, semver@^7.3.4:
+semver@^7.3.2:
   version "7.3.4"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
   integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
@@ -5056,32 +3720,25 @@ semver@^7.3.5:
   dependencies:
     lru-cache "^6.0.0"
 
-serialize-javascript@6.0.0, serialize-javascript@^6.0.0:
+semver@^7.3.6, 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"
+
+serialize-javascript@6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
   integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
   dependencies:
     randombytes "^2.1.0"
 
-serialize-javascript@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4"
-  integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==
-  dependencies:
-    randombytes "^2.1.0"
-
 set-blocking@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
 
-shallow-clone@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
-  integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
-  dependencies:
-    kind-of "^6.0.2"
-
 shebang-command@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@@ -5136,39 +3793,16 @@ sortablejs@1.10.2:
   resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.10.2.tgz#6e40364d913f98b85a14f6678f92b5c1221f5290"
   integrity sha512-YkPGufevysvfwn5rfdlGyrGjt7/CRHwvRPogD/lC+TnvcN29jDpCifKP+rBqf+LRldfXSTh+0CGLcSg0VIxq3A==
 
-source-list-map@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
-  integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
-
-"source-map-js@>=0.6.2 <2.0.0":
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
-  integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
-
-source-map-js@^1.0.2:
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
   integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
 
-source-map-support@~0.5.19:
-  version "0.5.19"
-  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
-  integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
-  dependencies:
-    buffer-from "^1.0.0"
-    source-map "^0.6.0"
-
-source-map@^0.6.0, 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.2:
-  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"
@@ -5181,16 +3815,6 @@ split@0.3:
   dependencies:
     through "2"
 
-sprintf-js@1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
-  integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
-
-sprintf-js@~1.0.2:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
-  integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
-
 sshpk@^1.14.1:
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
@@ -5206,11 +3830,6 @@ sshpk@^1.14.1:
     safer-buffer "^2.0.2"
     tweetnacl "~0.14.0"
 
-stable@^0.1.8:
-  version "0.1.8"
-  resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
-  integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
-
 start-server-and-test@1.14.0:
   version "1.14.0"
   resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.14.0.tgz#c57f04f73eac15dd51733b551d775b40837fdde3"
@@ -5245,14 +3864,6 @@ string-width@^4.1.0, string-width@^4.2.0:
     is-fullwidth-code-point "^3.0.0"
     strip-ansi "^6.0.0"
 
-string.prototype.trimend@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
-  integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.5"
-
 string.prototype.trimend@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
@@ -5261,32 +3872,6 @@ string.prototype.trimend@^1.0.4:
     call-bind "^1.0.2"
     define-properties "^1.1.3"
 
-string.prototype.trimleft@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz#4408aa2e5d6ddd0c9a80739b087fbc067c03b3cc"
-  integrity sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.5"
-    string.prototype.trimstart "^1.0.0"
-
-string.prototype.trimright@^2.1.1:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz#c76f1cef30f21bbad8afeb8db1511496cfb0f2a3"
-  integrity sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.5"
-    string.prototype.trimend "^1.0.0"
-
-string.prototype.trimstart@^1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
-  integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.5"
-
 string.prototype.trimstart@^1.0.4:
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
@@ -5331,34 +3916,14 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
-style-loader@3.3.1:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575"
-  integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==
-
-stylehacks@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520"
-  integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==
-  dependencies:
-    browserslist "^4.16.6"
-    postcss-selector-parser "^6.0.4"
-
-supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1:
+supports-color@8.1.1, supports-color@^8.1.1:
   version "8.1.1"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
   integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
   dependencies:
     has-flag "^4.0.0"
 
-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.0.0, supports-color@^7.1.0:
+supports-color@^7.1.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
   integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
@@ -5370,90 +3935,11 @@ 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==
 
-svgo@^1.3.2:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
-  integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==
-  dependencies:
-    chalk "^2.4.1"
-    coa "^2.0.2"
-    css-select "^2.0.0"
-    css-select-base-adapter "^0.1.1"
-    css-tree "1.0.0-alpha.37"
-    csso "^4.0.2"
-    js-yaml "^3.13.1"
-    mkdirp "~0.5.1"
-    object.values "^1.1.0"
-    sax "~1.2.4"
-    stable "^0.1.8"
-    unquote "~1.1.1"
-    util.promisify "~1.0.0"
-
-svgo@^2.7.0:
-  version "2.8.0"
-  resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
-  integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
-  dependencies:
-    "@trysound/sax" "0.2.0"
-    commander "^7.2.0"
-    css-select "^4.1.3"
-    css-tree "^1.1.3"
-    csso "^4.2.0"
-    picocolors "^1.0.0"
-    stable "^0.1.8"
-
 syuilo-password-strength@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/syuilo-password-strength/-/syuilo-password-strength-0.0.1.tgz#08f71a8f0ecb77db649f3d9a6424510d9d945f52"
   integrity sha1-CPcajw7Ld9tknz2aZCRRDZ2UX1I=
 
-tapable@^2.1.1, tapable@^2.2.0:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.0.tgz#5c373d281d9c672848213d0e037d1c4165ab426b"
-  integrity sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==
-
-terser-webpack-plugin@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz#7effadee06f7ecfa093dbbd3e9ab23f5f3ed8673"
-  integrity sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==
-  dependencies:
-    jest-worker "^26.6.2"
-    p-limit "^3.1.0"
-    schema-utils "^3.0.0"
-    serialize-javascript "^5.0.1"
-    source-map "^0.6.1"
-    terser "^5.5.1"
-
-terser-webpack-plugin@^5.1.3:
-  version "5.1.4"
-  resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.1.4.tgz#c369cf8a47aa9922bd0d8a94fe3d3da11a7678a1"
-  integrity sha512-C2WkFwstHDhVEmsmlCxrXUtVklS+Ir1A7twrYzrDrQQOIMOaVAYykaoo/Aq1K0QRkMoY2hhvDQY1cm4jnIMFwA==
-  dependencies:
-    jest-worker "^27.0.2"
-    p-limit "^3.1.0"
-    schema-utils "^3.0.0"
-    serialize-javascript "^6.0.0"
-    source-map "^0.6.1"
-    terser "^5.7.0"
-
-terser@^5.5.1:
-  version "5.5.1"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
-  integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
-  dependencies:
-    commander "^2.20.0"
-    source-map "~0.7.2"
-    source-map-support "~0.5.19"
-
-terser@^5.7.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-5.7.1.tgz#2dc7a61009b66bb638305cb2a824763b116bf784"
-  integrity sha512-b3e+d5JbHAe/JSjwsC3Zn55wsBIM7AsHLjKxT31kGCldgbpFePaFo+PiddtO6uwRZWRw7sPXmAN8dTW61xmnSg==
-  dependencies:
-    commander "^2.20.0"
-    source-map "~0.7.2"
-    source-map-support "~0.5.19"
-
 text-table@^0.2.0:
   version "0.2.0"
   resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -5464,15 +3950,15 @@ textarea-caret@3.1.0:
   resolved "https://registry.yarnpkg.com/textarea-caret/-/textarea-caret-3.1.0.tgz#5d5a35bb035fd06b2ff0e25d5359e97f2655087f"
   integrity sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==
 
-three@0.139.2:
-  version "0.139.2"
-  resolved "https://registry.yarnpkg.com/three/-/three-0.139.2.tgz#b110799a15736df673b9293e31653a4ac73648dd"
-  integrity sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg==
+three@0.141.0:
+  version "0.141.0"
+  resolved "https://registry.yarnpkg.com/three/-/three-0.141.0.tgz#16677a12b9dd0c3e1568ebad0fd09de15d5a8216"
+  integrity sha512-JaSDAPWuk4RTzG5BYRQm8YZbERUxTfTDVouWgHMisS2to4E5fotMS9F2zPFNOIJyEFTTQDDKPpsgZVThKU3pXA==
 
-throttle-debounce@4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-4.0.0.tgz#ec763b1c050c3a8f73eddd2e853a720893102a40"
-  integrity sha512-bO2OiH++k8Z3cTNZccOJRlxY5Sk3Tx3Kz6cQl3VY5pTRcEgqbPxwEKtrC00whFAo2jIBQlaH1ZG5mtfrBef3qw==
+throttle-debounce@5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-5.0.0.tgz#a17a4039e82a2ed38a5e7268e4132d6960d41933"
+  integrity sha512-2iQTSgkkc1Zyk0MeVrt/3BvuOXYPl/R8Z0U2xxo9rjwNciaHDG3R+Lm6dh4EeUci49DanvBnuqI6jshoQQRGEg==
 
 throttleit@^1.0.0:
   version "1.0.0"
@@ -5521,29 +4007,28 @@ tough-cookie@~2.5.0:
     psl "^1.1.28"
     punycode "^2.1.1"
 
-ts-loader@9.2.8:
-  version "9.2.8"
-  resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48"
-  integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==
+tsc-alias@1.6.9:
+  version "1.6.9"
+  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.6.9.tgz#d04d95124b95ad8eea55e52d45cf65a744c26baa"
+  integrity sha512-5lv5uAHn0cgxY1XfpXIdquUSz2xXq3ryQyNtxC6DYH7YT5rt/W+9Gsft2uyLFTh+ozk4qU8iCSP3VemjT69xlQ==
   dependencies:
-    chalk "^4.1.0"
-    enhanced-resolve "^5.0.0"
-    micromatch "^4.0.0"
-    semver "^7.3.4"
-
-tsc-alias@1.5.0:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.5.0.tgz#bc26f8dccf96e4ea350adc3f64ad3d2325cad967"
-  integrity sha512-Pb3y7ZjULKFHEV2US5dS58/hV76sE9Sn5iehiPjYqHcm/lx4eCGAJYoSmrVXQMPX+6baTnDFJD0MGOyqn94dIg==
-  dependencies:
-    chokidar "^3.5.2"
-    commander "^8.3.0"
-    find-node-modules "^2.1.2"
+    chokidar "^3.5.3"
+    commander "^9.0.0"
     globby "^11.0.4"
-    mylas "^2.1.6"
+    mylas "^2.1.9"
     normalize-path "^3.0.0"
+    plimit-lit "^1.2.6"
 
-tsconfig-paths@3.14.1, tsconfig-paths@^3.14.1:
+tsconfig-paths@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-4.0.0.tgz#1082f5d99fd127b72397eef4809e4dd06d229b64"
+  integrity sha512-SLBg2GBKlR6bVtMgJJlud/o3waplKtL7skmLkExomIiaAtLGtVsoXIqP3SYdjbcH9lq/KVv7pMZeCBpLYOit6Q==
+  dependencies:
+    json5 "^2.2.1"
+    minimist "^1.2.6"
+    strip-bom "^3.0.0"
+
+tsconfig-paths@^3.14.1:
   version "3.14.1"
   resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
   integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==
@@ -5582,12 +4067,7 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0:
   resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
   integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=
 
-twemoji-parser@13.1.0, twemoji-parser@13.1.x:
-  version "13.1.0"
-  resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4"
-  integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg==
-
-twemoji-parser@14.0.0:
+twemoji-parser@14.0.0, twemoji-parser@14.0.x:
   version "14.0.0"
   resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-14.0.0.tgz#13dabcb6d3a261d9efbf58a1666b182033bf2b62"
   integrity sha512-9DUOTGLOWs0pFWnh1p6NF+C3CkQ96PWmEFwhOVmT3WbecRC+68AIqpsnJXygfkFcp4aXbOp8Dwbhh/HQgvoRxA==
@@ -5626,10 +4106,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@4.6.3:
-  version "4.6.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
-  integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
+typescript@4.7.3:
+  version "4.7.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
+  integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
 
 unbox-primitive@^1.0.1:
   version "1.0.1"
@@ -5641,11 +4121,6 @@ unbox-primitive@^1.0.1:
     has-symbols "^1.0.2"
     which-boxed-primitive "^1.0.2"
 
-uniq@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
-  integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
-
 universalify@^0.1.0, universalify@^0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
@@ -5664,11 +4139,6 @@ unload@2.3.1:
     "@babel/runtime" "^7.6.2"
     detect-node "2.1.0"
 
-unquote@~1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
-  integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=
-
 untildify@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
@@ -5693,16 +4163,6 @@ util-deprecate@^1.0.2:
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
 
-util.promisify@~1.0.0:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
-  integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
-  dependencies:
-    define-properties "^1.1.3"
-    es-abstract "^1.17.2"
-    has-symbols "^1.0.1"
-    object.getownpropertydescriptors "^2.1.0"
-
 uuid@7.0.3:
   version "7.0.3"
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b"
@@ -5737,72 +4197,58 @@ verror@1.10.0:
     core-util-is "1.0.2"
     extsprintf "^1.2.0"
 
+vite@2.9.10:
+  version "2.9.10"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.10.tgz#f574d96655622c2e0fbc662edd0ed199c60fe91a"
+  integrity sha512-TwZRuSMYjpTurLqXspct+HZE7ONiW9d+wSWgvADGxhDPPyoIcNywY+RX4ng+QpK30DCa1l/oZgi2PLZDibhzbQ==
+  dependencies:
+    esbuild "^0.14.27"
+    postcss "^8.4.13"
+    resolve "^1.22.0"
+    rollup "^2.59.0"
+  optionalDependencies:
+    fsevents "~2.3.2"
+
 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=
 
-vue-eslint-parser@^8.0.1:
-  version "8.0.1"
-  resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.0.1.tgz#25e08b20a414551531f3e19f999902e1ecf45f13"
-  integrity sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A==
+vue-eslint-parser@^9.0.1:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.2.tgz#d2535516f3f55adb387939427fe741065eb7948a"
+  integrity sha512-uCPQwTGjOtAYrwnU+76pYxalhjsh7iFBsHwBqDHiOPTxtICDaraO4Szw54WFTNZTAEsgHHzqFOu1mmnBOBRzDA==
   dependencies:
-    debug "^4.3.2"
-    eslint-scope "^6.0.0"
-    eslint-visitor-keys "^3.0.0"
-    espree "^9.0.0"
+    debug "^4.3.4"
+    eslint-scope "^7.1.1"
+    eslint-visitor-keys "^3.3.0"
+    espree "^9.3.1"
     esquery "^1.4.0"
     lodash "^4.17.21"
-    semver "^7.3.5"
-
-vue-loader@17.0.0:
-  version "17.0.0"
-  resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-17.0.0.tgz#2eaa80aab125b19f00faa794b5bd867b17f85acb"
-  integrity sha512-OWSXjrzIvbF2LtOUmxT3HYgwwubbfFelN8PAP9R9dwpIkj48TVioHhWWSx7W7fk+iF5cgg3CBJRxwTdtLU4Ecg==
-  dependencies:
-    chalk "^4.1.0"
-    hash-sum "^2.0.0"
-    loader-utils "^2.0.0"
+    semver "^7.3.6"
 
 vue-prism-editor@2.0.0-alpha.2:
   version "2.0.0-alpha.2"
   resolved "https://registry.yarnpkg.com/vue-prism-editor/-/vue-prism-editor-2.0.0-alpha.2.tgz#aa53a88efaaed628027cbb282c2b1d37fc7c5c69"
   integrity sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==
 
-vue-router@4.0.14:
-  version "4.0.14"
-  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.14.tgz#ce2028c1c5c33e30c7287950c973f397fce1bd65"
-  integrity sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw==
+vue-router@4.0.16:
+  version "4.0.16"
+  resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.16.tgz#9477beeeef36e80e04d041a1738801a55e6e862e"
+  integrity sha512-JcO7cb8QJLBWE+DfxGUL3xUDOae/8nhM1KVdnudadTAORbuxIC/xAydC5Zr/VLHUDQi1ppuTF5/rjBGzgzrJNA==
   dependencies:
     "@vue/devtools-api" "^6.0.0"
 
-vue-style-loader@4.1.3:
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35"
-  integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==
+vue@3.2.37:
+  version "3.2.37"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.37.tgz#da220ccb618d78579d25b06c7c21498ca4e5452e"
+  integrity sha512-bOKEZxrm8Eh+fveCqS1/NkG/n6aMidsI6hahas7pa0w/l7jkbssJVsRhVDs07IdDq7h9KHswZOgItnwJAgtVtQ==
   dependencies:
-    hash-sum "^1.0.2"
-    loader-utils "^1.0.2"
-
-vue-svg-loader@0.17.0-beta.2:
-  version "0.17.0-beta.2"
-  resolved "https://registry.yarnpkg.com/vue-svg-loader/-/vue-svg-loader-0.17.0-beta.2.tgz#954b2a08b5488998dd81ec371ab5fb5ea4182ef7"
-  integrity sha512-iMUGJTKEcuNAG8VXOchjA8443IqEmEi2Aw6EVIHWma2cC4TUQ7Oet5Yry9IFfqXQXXvyzXz5EyttVvfRGTNH4Q==
-  dependencies:
-    loader-utils "^2.0.0"
-    semver "^7.3.2"
-    svgo "^1.3.2"
-
-vue@3.2.31:
-  version "3.2.31"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6"
-  integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==
-  dependencies:
-    "@vue/compiler-dom" "3.2.31"
-    "@vue/compiler-sfc" "3.2.31"
-    "@vue/runtime-dom" "3.2.31"
-    "@vue/server-renderer" "3.2.31"
-    "@vue/shared" "3.2.31"
+    "@vue/compiler-dom" "3.2.37"
+    "@vue/compiler-sfc" "3.2.37"
+    "@vue/runtime-dom" "3.2.37"
+    "@vue/server-renderer" "3.2.37"
+    "@vue/shared" "3.2.37"
 
 vuedraggable@4.0.1:
   version "4.0.1"
@@ -5822,120 +4268,6 @@ wait-on@6.0.0:
     minimist "^1.2.5"
     rxjs "^7.1.0"
 
-watchpack@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.0.tgz#b12248f32f0fd4799b7be0802ad1f6573a45955c"
-  integrity sha512-xSdCxxYZWNk3VK13bZRYhsQpfa8Vg63zXG+3pyU8ouqSLRCv4IGXIp9Kr226q6GBkGRlZrST2wwKtjfKz2m7Cg==
-  dependencies:
-    glob-to-regexp "^0.4.1"
-    graceful-fs "^4.1.2"
-
-watchpack@^2.3.1:
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25"
-  integrity sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==
-  dependencies:
-    glob-to-regexp "^0.4.1"
-    graceful-fs "^4.1.2"
-
-webpack-cli@4.9.2:
-  version "4.9.2"
-  resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.9.2.tgz#77c1adaea020c3f9e2db8aad8ea78d235c83659d"
-  integrity sha512-m3/AACnBBzK/kMTcxWHcZFPrw/eQuY4Df1TxvIWfWM2x7mRqBQCqKEd96oCUa9jkapLBaFfRce33eGDb4Pr7YQ==
-  dependencies:
-    "@discoveryjs/json-ext" "^0.5.0"
-    "@webpack-cli/configtest" "^1.1.1"
-    "@webpack-cli/info" "^1.4.1"
-    "@webpack-cli/serve" "^1.6.1"
-    colorette "^2.0.14"
-    commander "^7.0.0"
-    execa "^5.0.0"
-    fastest-levenshtein "^1.0.12"
-    import-local "^3.0.2"
-    interpret "^2.2.0"
-    rechoir "^0.7.0"
-    webpack-merge "^5.7.3"
-
-webpack-merge@^5.7.3:
-  version "5.7.3"
-  resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.7.3.tgz#2a0754e1877a25a8bbab3d2475ca70a052708213"
-  integrity sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==
-  dependencies:
-    clone-deep "^4.0.1"
-    wildcard "^2.0.0"
-
-webpack-sources@^2.1.1:
-  version "2.2.0"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac"
-  integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==
-  dependencies:
-    source-list-map "^2.0.1"
-    source-map "^0.6.1"
-
-webpack-sources@^3.2.3:
-  version "3.2.3"
-  resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
-  integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
-
-webpack@5.72.0:
-  version "5.72.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.72.0.tgz#f8bc40d9c6bb489a4b7a8a685101d6022b8b6e28"
-  integrity sha512-qmSmbspI0Qo5ld49htys8GY9XhS9CGqFoHTsOVAnjBdg0Zn79y135R+k4IR4rKK6+eKaabMhJwiVB7xw0SJu5w==
-  dependencies:
-    "@types/eslint-scope" "^3.7.3"
-    "@types/estree" "^0.0.51"
-    "@webassemblyjs/ast" "1.11.1"
-    "@webassemblyjs/wasm-edit" "1.11.1"
-    "@webassemblyjs/wasm-parser" "1.11.1"
-    acorn "^8.4.1"
-    acorn-import-assertions "^1.7.6"
-    browserslist "^4.14.5"
-    chrome-trace-event "^1.0.2"
-    enhanced-resolve "^5.9.2"
-    es-module-lexer "^0.9.0"
-    eslint-scope "5.1.1"
-    events "^3.2.0"
-    glob-to-regexp "^0.4.1"
-    graceful-fs "^4.2.9"
-    json-parse-better-errors "^1.0.2"
-    loader-runner "^4.2.0"
-    mime-types "^2.1.27"
-    neo-async "^2.6.2"
-    schema-utils "^3.1.0"
-    tapable "^2.1.1"
-    terser-webpack-plugin "^5.1.3"
-    watchpack "^2.3.1"
-    webpack-sources "^3.2.3"
-
-webpack@^5:
-  version "5.33.2"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.33.2.tgz#c049717c9b038febf5a72fd2f53319ad59a8c1fc"
-  integrity sha512-X4b7F1sYBmJx8mlh2B7mV5szEkE0jYNJ2y3akgAP0ERi0vLCG1VvdsIxt8lFd4st6SUy0lf7W0CCQS566MBpJg==
-  dependencies:
-    "@types/eslint-scope" "^3.7.0"
-    "@types/estree" "^0.0.46"
-    "@webassemblyjs/ast" "1.11.0"
-    "@webassemblyjs/wasm-edit" "1.11.0"
-    "@webassemblyjs/wasm-parser" "1.11.0"
-    acorn "^8.0.4"
-    browserslist "^4.14.5"
-    chrome-trace-event "^1.0.2"
-    enhanced-resolve "^5.7.0"
-    es-module-lexer "^0.4.0"
-    eslint-scope "^5.1.1"
-    events "^3.2.0"
-    glob-to-regexp "^0.4.1"
-    graceful-fs "^4.2.4"
-    json-parse-better-errors "^1.0.2"
-    loader-runner "^4.2.0"
-    mime-types "^2.1.27"
-    neo-async "^2.6.2"
-    schema-utils "^3.0.0"
-    tapable "^2.1.1"
-    terser-webpack-plugin "^5.1.1"
-    watchpack "^2.0.0"
-    webpack-sources "^2.1.1"
-
 websocket@1.0.34:
   version "1.0.34"
   resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111"
@@ -5964,25 +4296,13 @@ which-module@^2.0.0:
   resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
   integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
 
-which@2.0.2, which@^2.0.1:
+which@^2.0.1:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
   integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
   dependencies:
     isexe "^2.0.0"
 
-which@^1.2.14:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
-  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
-  dependencies:
-    isexe "^2.0.0"
-
-wildcard@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
-  integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
-
 with@^7.0.0:
   version "7.0.2"
   resolved "https://registry.yarnpkg.com/with/-/with-7.0.2.tgz#ccee3ad542d25538a7a7a80aad212b9828495bac"
@@ -5998,10 +4318,10 @@ word-wrap@^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==
 
-workerpool@6.2.0:
-  version "6.2.0"
-  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
-  integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
+workerpool@6.2.1:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
+  integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
 
 wrap-ansi@^6.2.0:
   version "6.2.0"
@@ -6026,10 +4346,10 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-ws@8.5.0:
-  version "8.5.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
-  integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
+ws@8.8.0:
+  version "8.8.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769"
+  integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==
 
 xml-js@^1.6.11:
   version "1.6.11"
@@ -6038,6 +4358,11 @@ xml-js@^1.6.11:
   dependencies:
     sax "^1.2.4"
 
+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==
+
 y18n@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4"
@@ -6058,16 +4383,6 @@ yallist@^4.0.0:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
   integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
 
-yaml@^1.10.0:
-  version "1.10.0"
-  resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
-  integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
-
-yaml@^1.10.2:
-  version "1.10.2"
-  resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
-  integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
-
 yargs-parser@20.2.4, yargs-parser@^20.2.2:
   version "20.2.4"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
@@ -6128,8 +4443,3 @@ yauzl@^2.10.0:
   dependencies:
     buffer-crc32 "~0.2.3"
     fd-slicer "~1.1.0"
-
-yocto-queue@^0.1.0:
-  version "0.1.0"
-  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
-  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js
index 2d3356c3a6..4d9d6f2c85 100644
--- a/packages/shared/.eslintrc.js
+++ b/packages/shared/.eslintrc.js
@@ -68,5 +68,8 @@ module.exports = {
 		}],
 		'import/no-unresolved': ['off'],
 		'import/no-default-export': ['warn'],
+		'import/order': ['warn', {
+			'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
+		}]
 	},
 };
diff --git a/packages/sw/.eslintrc.js b/packages/sw/.eslintrc.js
new file mode 100644
index 0000000000..9d56daca83
--- /dev/null
+++ b/packages/sw/.eslintrc.js
@@ -0,0 +1,22 @@
+module.exports = {
+	root: true,
+	env: {
+		"node": false
+	},
+	parserOptions: {
+		"parser": "@typescript-eslint/parser",
+		tsconfigRootDir: __dirname,
+		//project: ['./tsconfig.json'],
+	},
+	extends: [
+		//"../shared/.eslintrc.js",
+	],
+	globals: {
+		"require": false,
+		"_DEV_": false,
+		"_LANGS_": false,
+		"_VERSION_": false,
+		"_ENV_": false,
+		"_PERF_PREFIX_": false,
+	}
+}
diff --git a/packages/sw/.npmrc b/packages/sw/.npmrc
new file mode 100644
index 0000000000..6b5f38e890
--- /dev/null
+++ b/packages/sw/.npmrc
@@ -0,0 +1,2 @@
+save-exact = true
+package-lock = false
diff --git a/packages/sw/.yarnrc b/packages/sw/.yarnrc
new file mode 100644
index 0000000000..788570fcd5
--- /dev/null
+++ b/packages/sw/.yarnrc
@@ -0,0 +1 @@
+network-timeout 600000
diff --git a/packages/sw/build.js b/packages/sw/build.js
new file mode 100644
index 0000000000..72d9db9c0f
--- /dev/null
+++ b/packages/sw/build.js
@@ -0,0 +1,37 @@
+const esbuild = require('esbuild');
+const locales = require('../../locales');
+const meta = require('../../package.json');
+const watch = process.argv[2]?.includes('watch');
+
+console.log('Starting SW building...');
+
+esbuild.build({
+	entryPoints: [ `${__dirname}/src/sw.ts` ],
+	bundle: true,
+	format: 'esm',
+	treeShaking: true,
+	minify: process.env.NODE_ENV === 'production',
+	absWorkingDir: __dirname,
+	outbase: `${__dirname}/src`,
+	outdir: `${__dirname}/../../built/_sw_dist_`,
+	loader: {
+		'.ts': 'ts'
+	},
+	tsconfig: `${__dirname}/tsconfig.json`,
+	define: {
+		_VERSION_: JSON.stringify(meta.version),
+		_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
+		_ENV_: JSON.stringify(process.env.NODE_ENV),
+		_DEV_: process.env.NODE_ENV !== 'production',
+		_PERF_PREFIX_: JSON.stringify('Misskey:'),
+	},
+	watch: watch ? {
+		onRebuild(error, result) {
+      if (error) console.error('SW: watch build failed:', error);
+      else console.log('SW: watch build succeeded:', result);
+		},
+	} : false,
+}).then(result => {
+	if (watch) console.log('watching...');
+	else console.log('done,', JSON.stringify(result));
+});
diff --git a/packages/sw/package.json b/packages/sw/package.json
new file mode 100644
index 0000000000..41dfe19b85
--- /dev/null
+++ b/packages/sw/package.json
@@ -0,0 +1,17 @@
+{
+	"private": true,
+	"scripts": {
+		"watch": "node build.js watch",
+		"build": "node build.js",
+		"lint": "eslint --quiet src/**/*.{ts}"
+	},
+	"resolutions": {},
+	"dependencies": {
+		"esbuild": "^0.14.13",
+		"idb-keyval": "^6.0.3",
+		"misskey-js": "0.0.14"
+	},
+	"devDependencies": {
+		"eslint": "^8.2.0"
+	}
+}
diff --git a/packages/sw/src/filters/user.ts b/packages/sw/src/filters/user.ts
new file mode 100644
index 0000000000..09437eb19a
--- /dev/null
+++ b/packages/sw/src/filters/user.ts
@@ -0,0 +1,14 @@
+import * as misskey from 'misskey-js';
+import * as Acct from 'misskey-js/built/acct';
+
+export const acct = (user: misskey.Acct) => {
+	return Acct.toString(user);
+};
+
+export const userName = (user: misskey.entities.User) => {
+	return user.name || user.username;
+};
+
+export const userPage = (user: misskey.Acct, path?, absolute = false) => {
+	return `${absolute ? origin : ''}/@${acct(user)}${(path ? `/${path}` : '')}`;
+};
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
new file mode 100644
index 0000000000..6d7ba7d524
--- /dev/null
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -0,0 +1,237 @@
+/*
+ * Notification manager for SW
+ */
+declare var self: ServiceWorkerGlobalScope;
+
+import { swLang } from '@/scripts/lang';
+import { cli } from '@/scripts/operations';
+import { pushNotificationDataMap } from '@/types';
+import getUserName from '@/scripts/get-user-name';
+import { I18n } from '@/scripts/i18n';
+import { getAccountFromId } from '@/scripts/get-account-from-id';
+
+export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) {
+	const n = await composeNotification(data);
+
+	if (n) {
+		return self.registration.showNotification(...n);
+	} else {
+		console.error('Could not compose notification', data);
+		return createEmptyNotification();
+	}
+}
+
+async function composeNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]): Promise<[string, NotificationOptions] | null> {
+	if (!swLang.i18n) swLang.fetchLocale();
+	const i18n = await swLang.i18n as I18n<any>;
+	const { t } = i18n;
+	switch (data.type) {
+		/*
+		case 'driveFileCreated': // TODO (Server Side)
+			return [t('_notification.fileUploaded'), {
+				body: body.name,
+				icon: body.url,
+				data
+			}];
+		*/
+		case 'notification':
+			switch (data.body.type) {
+				case 'follow':
+					// users/showใฎๅž‹ๅฎš็พฉใ‚’swos.apiใธๅฝ“ใฆใฏใ‚ใ‚‹ใฎใŒๅ›ฐ้›ฃใชใฎใงapiFetch.requestใ‚’็›ดๆŽฅไฝฟ็”จ
+					const account = await getAccountFromId(data.userId);
+					if (!account) return null;
+					const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token);
+					return [t('_notification.youWereFollowed'), {
+						body: getUserName(data.body.user),
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: userDetail.isFollowing ? [] : [
+							{
+								action: 'follow',
+								title: t('_notification._actions.followBack')
+							}
+						],
+					}];
+
+				case 'mention':
+					return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), {
+						body: data.body.note.text || '',
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: [
+							{
+								action: 'reply',
+								title: t('_notification._actions.reply')
+							}
+						],
+					}];
+
+				case 'reply':
+					return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), {
+						body: data.body.note.text || '',
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: [
+							{
+								action: 'reply',
+								title: t('_notification._actions.reply')
+							}
+						],
+					}];
+
+				case 'renote':
+					return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), {
+						body: data.body.note.text || '',
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: [
+							{
+								action: 'showUser',
+								title: getUserName(data.body.user)
+							}
+						],
+					}];
+
+				case 'quote':
+					return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), {
+						body: data.body.note.text || '',
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: [
+							{
+								action: 'reply',
+								title: t('_notification._actions.reply')
+							},
+							...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [
+							{
+								action: 'renote',
+								title: t('_notification._actions.renote')
+							}
+							] : [])
+						],
+					}];
+
+				case 'reaction':
+					return [`${data.body.reaction} ${getUserName(data.body.user)}`, {
+						body: data.body.note.text || '',
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: [
+							{
+								action: 'showUser',
+								title: getUserName(data.body.user)
+							}
+						],
+					}];
+
+				case 'pollVote':
+					return [t('_notification.youGotPoll', { name: getUserName(data.body.user) }), {
+						body: data.body.note.text || '',
+						icon: data.body.user.avatarUrl,
+						data,
+					}];
+
+				case 'pollEnded':
+					return [t('_notification.pollEnded'), {
+						body: data.body.note.text || '',
+						data,
+					}];
+
+				case 'receiveFollowRequest':
+					return [t('_notification.youReceivedFollowRequest'), {
+						body: getUserName(data.body.user),
+						icon: data.body.user.avatarUrl,
+						data,
+						actions: [
+							{
+								action: 'accept',
+								title: t('accept')
+							},
+							{
+								action: 'reject',
+								title: t('reject')
+							}
+						],
+					}];
+
+				case 'followRequestAccepted':
+					return [t('_notification.yourFollowRequestAccepted'), {
+						body: getUserName(data.body.user),
+						icon: data.body.user.avatarUrl,
+						data,
+					}];
+
+				case 'groupInvited':
+					return [t('_notification.youWereInvitedToGroup', { userName: getUserName(data.body.user) }), {
+						body: data.body.invitation.group.name,
+						data,
+						actions: [
+							{
+								action: 'accept',
+								title: t('accept')
+							},
+							{
+								action: 'reject',
+								title: t('reject')
+							}
+						],
+					}];
+
+				case 'app':
+						return [data.body.header || data.body.body, {
+							body: data.body.header && data.body.body,
+							icon: data.body.icon,
+							data
+						}];
+
+				default:
+					return null;
+			}
+		case 'unreadMessagingMessage':
+			if (data.body.groupId === null) {
+				return [t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.body.user) }), {
+					icon: data.body.user.avatarUrl,
+					tag: `messaging:user:${data.body.userId}`,
+					data,
+					renotify: true,
+				}];
+			}
+			return [t('_notification.youGotMessagingMessageFromGroup', { name: data.body.group.name }), {
+				icon: data.body.user.avatarUrl,
+				tag: `messaging:group:${data.body.groupId}`,
+				data,
+				renotify: true,
+			}];
+		default:
+			return null;
+	}
+}
+
+export async function createEmptyNotification() {
+	return new Promise<void>(async res => {
+		if (!swLang.i18n) swLang.fetchLocale();
+		const i18n = await swLang.i18n as I18n<any>;
+		const { t } = i18n;
+	
+		await self.registration.showNotification(
+			t('_notification.emptyPushNotificationMessage'),
+			{
+				silent: true,
+				tag: 'read_notification',
+			}
+		);
+
+		res();
+
+		setTimeout(async () => {
+			for (const n of
+				[
+					...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })),
+					...(await self.registration.getNotifications({ tag: 'read_notification' }))
+				]
+			) {
+				n.close();
+			}
+		}, 1000);
+	});
+}
diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts
new file mode 100644
index 0000000000..be4cfaeba4
--- /dev/null
+++ b/packages/sw/src/scripts/get-account-from-id.ts
@@ -0,0 +1,7 @@
+import { get } from 'idb-keyval';
+
+export async function getAccountFromId(id: string) {
+	const accounts = await get('accounts') as { token: string; id: string; }[];
+	if (!accounts) console.log('Accounts are not recorded');
+	return accounts.find(e => e.id === id);
+}
diff --git a/packages/sw/src/scripts/get-user-name.ts b/packages/sw/src/scripts/get-user-name.ts
new file mode 100644
index 0000000000..d499ea0203
--- /dev/null
+++ b/packages/sw/src/scripts/get-user-name.ts
@@ -0,0 +1,3 @@
+export default function(user: { name?: string | null, username: string }): string {
+	return user.name || user.username;
+}
diff --git a/packages/sw/src/scripts/i18n.ts b/packages/sw/src/scripts/i18n.ts
new file mode 100644
index 0000000000..3fe88e5514
--- /dev/null
+++ b/packages/sw/src/scripts/i18n.ts
@@ -0,0 +1,29 @@
+export class I18n<T extends Record<string, any>> {
+	public ts: T;
+
+	constructor(locale: T) {
+		this.ts = locale;
+
+		//#region BIND
+		this.t = this.t.bind(this);
+		//#endregion
+	}
+
+	// string ใซใ—ใฆใ„ใ‚‹ใฎใฏใ€ใƒ‰ใƒƒใƒˆๅŒบๅˆ‡ใ‚Šใงใฎใƒ‘ใ‚นๆŒ‡ๅฎšใ‚’่จฑๅฏใ™ใ‚‹ใŸใ‚
+	// ใชใ‚‹ในใใ“ใฎใƒกใ‚ฝใƒƒใƒ‰ไฝฟใ†ใ‚ˆใ‚Šใ‚‚locale็›ดๆŽฅๅ‚็…งใฎๆ–นใŒvueใฎใ‚ญใƒฃใƒƒใ‚ทใƒฅๅŠนใ„ใฆใƒ‘ใƒ•ใ‚ฉใƒผใƒžใƒณใ‚นใŒ่‰ฏใ„ใ‹ใ‚‚
+	public t(key: string, args?: Record<string, string>): string {
+		try {
+			let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
+
+			if (args) {
+				for (const [k, v] of Object.entries(args)) {
+					str = str.replace(`{${k}}`, v);
+				}
+			}
+			return str;
+		} catch (err) {
+			console.warn(`missing localization '${key}'`);
+			return key;
+		}
+	}
+}
diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts
new file mode 100644
index 0000000000..2d05404ef9
--- /dev/null
+++ b/packages/sw/src/scripts/lang.ts
@@ -0,0 +1,47 @@
+/*
+ * Language manager for SW
+ */
+declare var self: ServiceWorkerGlobalScope;
+
+import { get, set } from 'idb-keyval';
+import { I18n } from '@/scripts/i18n';
+
+class SwLang {
+	public cacheName = `mk-cache-${_VERSION_}`;
+
+	public lang: Promise<string> = get('lang').then(async prelang => {
+		if (!prelang) return 'en-US';
+		return prelang;
+	});
+
+	public setLang(newLang: string) {
+		this.lang = Promise.resolve(newLang);
+		set('lang', newLang);
+		return this.fetchLocale();
+	}
+
+	public i18n: Promise<I18n<any>> | null = null;
+
+	public fetchLocale() {
+		return this.i18n = this._fetch();
+	}
+
+	private async _fetch() {
+		// Service Workerใฏไฝ•ๅบฆใ‚‚่ตทๅ‹•ใ—ใใฎใŸใณใซlocaleใ‚’่ชญใฟ่พผใ‚€ใฎใงใ€CacheStorageใ‚’ไฝฟใ†
+		const localeUrl = `/assets/locales/${await this.lang}.${_VERSION_}.json`;
+		let localeRes = await caches.match(localeUrl);
+
+		// _DEV_ใŒtrueใฎๅ ดๅˆใฏๅธธใซๆœ€ๆ–ฐๅŒ–
+		if (!localeRes || _DEV_) {
+			localeRes = await fetch(localeUrl);
+			const clone = localeRes?.clone();
+			if (!clone?.clone().ok) Error('locale fetching error');
+
+			caches.open(this.cacheName).then(cache => cache.put(localeUrl, clone));
+		}
+
+		return new I18n(await localeRes.json());
+	}
+}
+
+export const swLang = new SwLang();
diff --git a/packages/sw/src/scripts/login-id.ts b/packages/sw/src/scripts/login-id.ts
new file mode 100644
index 0000000000..0f9c6be4a9
--- /dev/null
+++ b/packages/sw/src/scripts/login-id.ts
@@ -0,0 +1,11 @@
+export function getUrlWithLoginId(url: string, loginId: string) {
+	const u = new URL(url, origin);
+	u.searchParams.append('loginId', loginId);
+	return u.toString();
+}
+
+export function getUrlWithoutLoginId(url: string) {
+	const u = new URL(url);
+	u.searchParams.delete('loginId');
+	return u.toString();
+}
diff --git a/packages/sw/src/scripts/notification-read.ts b/packages/sw/src/scripts/notification-read.ts
new file mode 100644
index 0000000000..8433f902b4
--- /dev/null
+++ b/packages/sw/src/scripts/notification-read.ts
@@ -0,0 +1,50 @@
+declare var self: ServiceWorkerGlobalScope;
+
+import { get } from 'idb-keyval';
+import { pushNotificationDataMap } from '@/types';
+import { api } from '@/scripts/operations';
+
+type Accounts = {
+	[x: string]: {
+		queue: string[],
+		timeout: number | null
+	}
+};
+
+class SwNotificationReadManager {
+	private accounts: Accounts = {};
+
+	public async construct() {
+		const accounts = await get('accounts');
+		if (!accounts) Error('Accounts are not recorded');
+
+		this.accounts = accounts.reduce((acc, e) => {
+			acc[e.id] = {
+				queue: [],
+				timeout: null
+			};
+			return acc;
+		}, {} as Accounts);
+
+		return this;
+	}
+
+	// ใƒ—ใƒƒใ‚ทใƒฅ้€š็Ÿฅใฎๆ—ข่ชญใ‚’ใ‚ตใƒผใƒใƒผใซ้€ไฟก
+	public async read<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) {
+		if (data.type !== 'notification' || !(data.userId in this.accounts)) return;
+
+		const account = this.accounts[data.userId];
+
+		account.queue.push(data.body.id as string);
+
+		// ๆœ€ๅพŒใฎๅ‘ผใณๅ‡บใ—ใ‹ใ‚‰200msๅพ…ใฃใฆใพใจใ‚ใฆๅ‡ฆ็†ใ™ใ‚‹
+		if (account.timeout) clearTimeout(account.timeout);
+		account.timeout = setTimeout(() => {
+			account.timeout = null;
+
+			api('notifications/read', data.userId, { notificationIds: account.queue });
+		}, 200);
+	}
+}
+
+export const swNotificationRead = (new SwNotificationReadManager()).construct();
diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts
new file mode 100644
index 0000000000..02cf0d96cf
--- /dev/null
+++ b/packages/sw/src/scripts/operations.ts
@@ -0,0 +1,70 @@
+/*
+ * Operations
+ * ๅ„็จฎๆ“ไฝœ
+ */
+declare var self: ServiceWorkerGlobalScope;
+
+import * as Misskey from 'misskey-js';
+import { SwMessage, swMessageOrderType } from '@/types';
+import { acct as getAcct } from '@/filters/user';
+import { getAccountFromId } from '@/scripts/get-account-from-id';
+import { getUrlWithLoginId } from '@/scripts/login-id';
+
+export const cli = new Misskey.api.APIClient({ origin, fetch: (...args) => fetch(...args) });
+
+export async function api<E extends keyof Misskey.Endpoints>(endpoint: E, userId: string, options?: Misskey.Endpoints[E]['req']) {
+	const account = await getAccountFromId(userId);
+	if (!account) return;
+
+	return cli.request(endpoint, options, account.token);
+}
+
+// rendered acctใ‹ใ‚‰ใƒฆใƒผใ‚ถใƒผใ‚’้–‹ใ
+export function openUser(acct: string, loginId: string) {
+	return openClient('push', `/@${acct}`, loginId, { acct });
+}
+
+// noteIdใ‹ใ‚‰ใƒŽใƒผใƒˆใ‚’้–‹ใ
+export function openNote(noteId: string, loginId: string) {
+	return openClient('push', `/notes/${noteId}`, loginId, { noteId });
+}
+
+export async function openChat(body: any, loginId: string) {
+	if (body.groupId === null) {
+		return openClient('push', `/my/messaging/${getAcct(body.user)}`, loginId, { body });
+	} else {
+		return openClient('push', `/my/messaging/group/${body.groupId}`, loginId, { body });
+	}
+}
+
+// post-formใฎใ‚ชใƒ—ใ‚ทใƒงใƒณใ‹ใ‚‰ๆŠ•็จฟใƒ•ใ‚ฉใƒผใƒ ใ‚’้–‹ใ
+export async function openPost(options: any, loginId: string) {
+	// ใ‚ฏใ‚จใƒชใ‚’ไฝœๆˆใ—ใฆใŠใ
+	let url = `/share?`;
+	if (options.initialText) url += `text=${options.initialText}&`;
+	if (options.reply) url += `replyId=${options.reply.id}&`;
+	if (options.renote) url += `renoteId=${options.renote.id}&`;
+
+	return openClient('post', url, loginId, { options });
+}
+
+export async function openClient(order: swMessageOrderType, url: string, loginId: string, query: any = {}) {
+	const client = await findClient();
+
+	if (client) {
+		client.postMessage({ type: 'order', ...query, order, loginId, url } as SwMessage);
+		return client;
+	}
+
+	return self.clients.openWindow(getUrlWithLoginId(url, loginId));
+}
+
+export async function findClient() {
+	const clients = await self.clients.matchAll({
+		type: 'window'
+	});
+	for (const c of clients) {
+		if (c.url.indexOf('?zen') < 0) return c;
+	}
+	return null;
+}
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
new file mode 100644
index 0000000000..0ba6a6e4af
--- /dev/null
+++ b/packages/sw/src/sw.ts
@@ -0,0 +1,200 @@
+declare var self: ServiceWorkerGlobalScope;
+
+import { createEmptyNotification, createNotification } from '@/scripts/create-notification';
+import { swLang } from '@/scripts/lang';
+import { swNotificationRead } from '@/scripts/notification-read';
+import { pushNotificationDataMap } from '@/types';
+import * as swos from '@/scripts/operations';
+import { acct as getAcct } from '@/filters/user';
+
+self.addEventListener('install', ev => {
+	ev.waitUntil(self.skipWaiting());
+});
+
+self.addEventListener('activate', ev => {
+	ev.waitUntil(
+		caches.keys()
+			.then(cacheNames => Promise.all(
+				cacheNames
+					.filter((v) => v !== swLang.cacheName)
+					.map(name => caches.delete(name))
+			))
+			.then(() => self.clients.claim())
+	);
+});
+
+self.addEventListener('fetch', ev => {
+	ev.respondWith(
+		fetch(ev.request)
+		.catch(() => new Response(`Offline. Service Worker @${_VERSION_}`, { status: 200 }))
+	);
+});
+
+self.addEventListener('push', ev => {
+	// ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆๅ–ๅพ—
+	ev.waitUntil(self.clients.matchAll({
+		includeUncontrolled: true,
+		type: 'window'
+	}).then(async <K extends keyof pushNotificationDataMap>(clients: readonly WindowClient[]) => {
+		const data: pushNotificationDataMap[K] = ev.data?.json();
+
+		switch (data.type) {
+			// case 'driveFileCreated':
+			case 'notification':
+			case 'unreadMessagingMessage':
+				// ใ‚ฏใƒฉใ‚คใ‚ขใƒณใƒˆใŒใ‚ใฃใŸใ‚‰ใ‚นใƒˆใƒชใƒผใƒ ใซๆŽฅ็ถšใ—ใฆใ„ใ‚‹ใจใ„ใ†ใ“ใจใชใฎใง้€š็Ÿฅใ—ใชใ„
+				if (clients.length != 0) return;
+				return createNotification(data);
+			case 'readAllNotifications':
+				for (const n of await self.registration.getNotifications()) {
+					if (n?.data?.type === 'notification') n.close();
+				}
+				break;
+			case 'readAllMessagingMessages':
+				for (const n of await self.registration.getNotifications()) {
+					if (n?.data?.type === 'unreadMessagingMessage') n.close();
+				}
+				break;
+			case 'readNotifications':
+				for (const n of await self.registration.getNotifications()) {
+					if (data.body?.notificationIds?.includes(n.data.body.id)) {
+						n.close();
+					}
+				}
+				break;
+			case 'readAllMessagingMessagesOfARoom':
+				for (const n of await self.registration.getNotifications()) {
+					if (n.data.type === 'unreadMessagingMessage'
+						&& ('userId' in data.body
+							? data.body.userId === n.data.body.userId
+							: data.body.groupId === n.data.body.groupId)
+						) {
+							n.close();
+						}
+				}
+				break;
+		}
+
+		return createEmptyNotification();
+	}));
+});
+
+self.addEventListener('notificationclick', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
+	ev.waitUntil((async () => {
+		if (_DEV_) {
+			console.log('notificationclick', ev.action, ev.notification.data);
+		}
+	
+		const { action, notification } = ev;
+		const data: pushNotificationDataMap[K] = notification.data;
+		const { userId: id } = data;
+		let client: WindowClient | null = null;
+	
+		switch (data.type) {
+			case 'notification':
+				switch (action) {
+					case 'follow':
+						if ('userId' in data.body) await swos.api('following/create', id, { userId: data.body.userId });
+						break;
+					case 'showUser':
+						if ('user' in data.body) client = await swos.openUser(getAcct(data.body.user), id);
+						break;
+					case 'reply':
+						if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, id);
+						break;
+					case 'renote':
+						if ('note' in data.body) await swos.api('notes/create', id, { renoteId: data.body.note.id });
+						break;
+					case 'accept':
+						switch (data.body.type) {
+							case 'receiveFollowRequest':
+								await swos.api('following/requests/accept', id, { userId: data.body.userId });
+								break;
+							case 'groupInvited':
+								await swos.api('users/groups/invitations/accept', id, { invitationId: data.body.invitation.id });
+								break;
+						}
+						break;
+					case 'reject':
+						switch (data.body.type) {
+							case 'receiveFollowRequest':
+								await swos.api('following/requests/reject', id, { userId: data.body.userId });
+								break;
+							case 'groupInvited':
+								await swos.api('users/groups/invitations/reject', id, { invitationId: data.body.invitation.id });
+								break;
+						}
+						break;
+					case 'showFollowRequests':
+						client = await swos.openClient('push', '/my/follow-requests', id);
+						break;
+					default:
+						switch (data.body.type) {
+							case 'receiveFollowRequest':
+								client = await swos.openClient('push', '/my/follow-requests', id);
+								break;
+							case 'groupInvited':
+								client = await swos.openClient('push', '/my/groups', id);
+								break;
+							case 'reaction':
+								client = await swos.openNote(data.body.note.id, id);
+								break;
+							default:
+								if ('note' in data.body) {
+									client = await swos.openNote(data.body.note.id, id);
+								} else if ('user' in data.body) {
+									client = await swos.openUser(getAcct(data.body.user), id);
+								}
+								break;
+						}
+				}
+				break;
+			case 'unreadMessagingMessage':
+				client = await swos.openChat(data.body, id);
+				break;
+		}
+	
+		if (client) {
+			client.focus();
+		}
+		if (data.type === 'notification') {
+			swNotificationRead.then(that => that.read(data));
+		}
+	
+		notification.close();
+	
+	})());
+});
+
+self.addEventListener('notificationclose', <K extends keyof pushNotificationDataMap>(ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
+	const data: pushNotificationDataMap[K] = ev.notification.data;
+
+	if (data.type === 'notification') {
+		swNotificationRead.then(that => that.read(data));
+	}
+});
+
+self.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
+	ev.waitUntil((async () => {
+		switch (ev.data) {
+			case 'clear':
+				// Cache Storageๅ…จๅ‰Š้™ค
+				await caches.keys()
+					.then(cacheNames => Promise.all(
+						cacheNames.map(name => caches.delete(name))
+					));
+				return; // TODO
+		}
+	
+		if (typeof ev.data === 'object') {
+			// E.g. '[object Array]' โ†’ 'array'
+			const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase();
+	
+			if (otype === 'object') {
+				if (ev.data.msg === 'initialize') {
+					swLang.setLang(ev.data.lang);
+				}
+			}
+		}
+	})());
+});
diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts
new file mode 100644
index 0000000000..6aa3726eac
--- /dev/null
+++ b/packages/sw/src/types.ts
@@ -0,0 +1,31 @@
+import * as Misskey from 'misskey-js';
+
+export type swMessageOrderType = 'post' | 'push';
+
+export type SwMessage = {
+	type: 'order';
+	order: swMessageOrderType;
+	loginId: string;
+	url: string;
+	[x: string]: any;
+};
+
+// Defined also @/services/push-notification.ts#L7-L14
+type pushNotificationDataSourceMap = {
+	notification: Misskey.entities.Notification;
+	unreadMessagingMessage: Misskey.entities.MessagingMessage;
+	readNotifications: { notificationIds: string[] };
+	readAllNotifications: undefined;
+	readAllMessagingMessages: undefined;
+	readAllMessagingMessagesOfARoom: { userId: string } | { groupId: string };
+};
+
+export type pushNotificationData<K extends keyof pushNotificationDataSourceMap> = {
+	type: K;
+	body: pushNotificationDataSourceMap[K];
+	userId: string;
+};
+
+export type pushNotificationDataMap = {
+	[K in keyof pushNotificationDataSourceMap]: pushNotificationData<K>;
+};
diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json
new file mode 100644
index 0000000000..c3a845f12a
--- /dev/null
+++ b/packages/sw/tsconfig.json
@@ -0,0 +1,39 @@
+{
+	"compilerOptions": {
+		"allowJs": true,
+		"noEmitOnError": false,
+		"noImplicitAny": false,
+		"noImplicitReturns": true,
+		"noUnusedParameters": false,
+		"noUnusedLocals": true,
+		"noFallthroughCasesInSwitch": true,
+		"declaration": false,
+		"sourceMap": false,
+		"target": "es2017",
+		"module": "esnext",
+		"moduleResolution": "node",
+		"removeComments": false,
+		"noLib": false,
+		"strict": true,
+		"strictNullChecks": true,
+		"experimentalDecorators": true,
+		"resolveJsonModule": true,
+		"isolatedModules": true,
+		"baseUrl": ".",
+		"paths": {
+			"@/*": ["./src/*"],
+		},
+		"typeRoots": [
+			"node_modules/@types",
+			"@types",
+		],
+		"lib": [
+			"esnext",
+			"webworker"
+		]
+	},
+	"compileOnSave": false,
+	"include": [
+		"./**/*.ts"
+	]
+}
diff --git a/packages/sw/yarn.lock b/packages/sw/yarn.lock
new file mode 100644
index 0000000000..e6d683bc42
--- /dev/null
+++ b/packages/sw/yarn.lock
@@ -0,0 +1,710 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@eslint/eslintrc@^1.0.5":
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318"
+  integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ==
+  dependencies:
+    ajv "^6.12.4"
+    debug "^4.3.2"
+    espree "^9.2.0"
+    globals "^13.9.0"
+    ignore "^4.0.6"
+    import-fresh "^3.2.1"
+    js-yaml "^4.1.0"
+    minimatch "^3.0.4"
+    strip-json-comments "^3.1.1"
+
+"@humanwhocodes/config-array@^0.9.2":
+  version "0.9.3"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.3.tgz#f2564c744b387775b436418491f15fce6601f63e"
+  integrity sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==
+  dependencies:
+    "@humanwhocodes/object-schema" "^1.2.1"
+    debug "^4.1.1"
+    minimatch "^3.0.4"
+
+"@humanwhocodes/object-schema@^1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
+  integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+
+acorn-jsx@^5.3.1:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn@^8.7.0:
+  version "8.7.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
+  integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
+
+ajv@^6.10.0, ajv@^6.12.4:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
+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@^4.1.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+  dependencies:
+    color-convert "^2.0.1"
+
+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==
+
+autobind-decorator@^2.4.0:
+  version "2.4.0"
+  resolved "https://registry.yarnpkg.com/autobind-decorator/-/autobind-decorator-2.4.0.tgz#ea9e1c98708cf3b5b356f7cf9f10f265ff18239c"
+  integrity sha512-OGYhWUO72V6DafbF8PM8rm3EPbfuyMZcJhtm5/n26IDwO18pohE4eNazLoCGhPiXOCD0gEGmrbU3849QvM8bbw==
+
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+callsites@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+  integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+chalk@^4.0.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"
+
+color-convert@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+  dependencies:
+    color-name "~1.1.4"
+
+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==
+
+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=
+
+cross-spawn@^7.0.2:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+  integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+  dependencies:
+    path-key "^3.1.0"
+    shebang-command "^2.0.0"
+    which "^2.0.1"
+
+debug@^4.1.1, debug@^4.3.2:
+  version "4.3.3"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
+  integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
+  dependencies:
+    ms "2.1.2"
+
+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==
+
+doctrine@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+  integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+  dependencies:
+    esutils "^2.0.2"
+
+esbuild-android-arm64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.17.tgz#7216810cb8d5b8cd03ce70bdc241dcdd90c34755"
+  integrity sha512-y7EJm8ADC9qKbo/dJ2zBXwNdIILJ76tTv7JDGvOkbLT8HJXIsgbpa0NJk7iFhyvP4GpsYvXTbvEQNn0DhyBhLA==
+
+esbuild-darwin-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.17.tgz#1419e020f41814f8a74ce92b2dcab29a6d47e510"
+  integrity sha512-V2JAP8yyVbW6qR4SVXsEDqRicYM0x5niUuB05IFiE5itPI45k8j2dA2l+DtirR2SGXr+LEqgX347+2VA6eyTiA==
+
+esbuild-darwin-arm64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.17.tgz#95acf1022066d48346a63ffc5e4d36a07b83c9b0"
+  integrity sha512-ENkSKpjF4SImyA2TdHhKiZqtYc1DkMykICe1KSBw0YNF1sentjFI6wu+CRiYMpC7REf/3TQXoems2XPqIqDMlQ==
+
+esbuild-freebsd-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.17.tgz#a3455199862110854937b05a0eecbed3e1aeec41"
+  integrity sha512-2i0nTNJM8ftNTvtR00vdqkru8XpHwAbkR2MBLoK2IDSzjsLStwCj+mxf6v83eVM9Abe3QA8xP+irqOdBlwDQ2g==
+
+esbuild-freebsd-arm64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.17.tgz#8a70f2a36f5b0da7d2efdd6fd02aa78611007fd0"
+  integrity sha512-QOmRi1n+uly2G7BbMbHb86YiFA5aM7B2T96A6OF1VG57LNwXwy8LPVM0PVjl7f9cV3pE3fy3VtXPJHJo8XggTA==
+
+esbuild-linux-32@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.17.tgz#b7123f6e4780687e017454604d909fbe558862e9"
+  integrity sha512-qG5NDk7FHHUVw01rjHESON0HvigF2X80b645TUlgTKsWRlrbzzHhMCmQguA01O5PiCimKnyoxti8aJIFNHpQnQ==
+
+esbuild-linux-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.17.tgz#47a6b510c2f7faef595a4d6257a629e65385fdc3"
+  integrity sha512-De8OcmNvfNyFfQRLWbfuZqau6NpYBJxNTLP7Ls/PqQcw0HAwfaYThutY8ozHpPbKFPa7wgqabXlIC4NVSWT0/A==
+
+esbuild-linux-arm64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.17.tgz#dfd9022b7215ca660d464fcb20597b88887c7e64"
+  integrity sha512-WDEOD/YRA4J1lxhETKZff3gRxGYqqZEiVwIOqNfvCh2YcwWU2y6UmNGZsxcuKk18wot4dAXCXQyNZgBkVUTCLw==
+
+esbuild-linux-arm@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.17.tgz#e6f6bb9fe52def5260d7d49b790fbec0e7c6d9cb"
+  integrity sha512-ZwsgFUk3gR2pEMJdh5z4Ds18fvGETgElPqmNdx1NtZTCOVlFMAwFB5u/tOR2FrXbMFv+LkGnNxPDh48PYPDz9A==
+
+esbuild-linux-mips64le@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.17.tgz#bceaad33ff18a822b6da0396c6497a231397b6c3"
+  integrity sha512-Lf4X9NB7r6imzp/11TaGs4kWL0DUn1JxI9gAAKotnKh6T8Y/0sLvZSvQS8WvSZcr0V8RRCrRZwiQqjOALUU/9g==
+
+esbuild-linux-ppc64le@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.17.tgz#9562f094d1e5e6c3b61b776b15a9bbd657042654"
+  integrity sha512-aExhxbrK7/Mh9FArdiC9MbvrQz2bGCDI8cBALKJbmhKg0h7LNt6y1E1S9GGBZ/ZXkHDvV9FFVrXXZKFVU5Qpiw==
+
+esbuild-linux-s390x@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.17.tgz#2963cfe62c227bbf1da64e36d4ca0b23db8008fe"
+  integrity sha512-b0T20rNcS7POi5YLw5dFlsiC+riobR5IfppQGn5NWer6QiIkdL1vOx9eC9CUD3z1itpkLboRAZYieZfKfhCA2Q==
+
+esbuild-netbsd-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.17.tgz#1d156023f9ae6be79b8627ab0cda2d7feb7f3a48"
+  integrity sha512-pFgTaAa2JF18nqNfCND9wOu1jbZ/mbDSaMxUp5fTkLlofyHhXeb5aChgXUkeipty2Pgq0OwOnxjHmiAxMI7N4g==
+
+esbuild-openbsd-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.17.tgz#3fc44102c9b65375385112f4ce5899ae5e38f349"
+  integrity sha512-K5+plb6gsAfBcFqB0EG4KvLbgBKslVAfEyJggicwt/QoDwQGJAzao4M6zOA4PG7LlXOwWSqv7VmSFbH+b6DyKw==
+
+esbuild-sunos-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.17.tgz#5bd24e7a7e863ea89d7e4eafd5364a155c9ea507"
+  integrity sha512-o1FINkbHRi9JB1YteOSXZdkDOmVUbmnCxRmTLkHvk8pfCFNpv/5/7ktt95teYKbEiJna2dEt3M4ckJ/+UVnW+w==
+
+esbuild-windows-32@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.17.tgz#8bda31c550fb6b425707114141d2c6ba034dab9b"
+  integrity sha512-Qutilz0I7OADWBtWrC/FD+2O/TNAkhwbZ+wIns7kF87lxIMtmqpBt3KnMk1e4F47aTrZRr0oH55Zhztd7m2PAA==
+
+esbuild-windows-64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.17.tgz#50b42c06908d3ce9fab8f0f9673199de5d0f9cbc"
+  integrity sha512-b21/oRV+PHrav0HkRpKjbM2yNRVe34gAfbdMppbZFea416wa8SrjcmVfSd7n4jgqoTQG0xe+MGgOpwXtjiB3DQ==
+
+esbuild-windows-arm64@0.14.17:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.17.tgz#62d3921a810b64a03fcace76dad4db51d2128b45"
+  integrity sha512-4HN9E1idllewYvptcrrdfTA6DIWgg11kK0Zrv6yjxstJZLJeKxfilGBEaksLGs4Pst2rAYMx3H2vbYq7AWLQNA==
+
+esbuild@^0.14.13:
+  version "0.14.17"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.17.tgz#6a634e56447aa0e90b34c42091d472d802d399e5"
+  integrity sha512-JLgyC6Uv31mv9T9Mm2xF1LntUMCNBSzvg2n32d8cTKZMwFr1wmMFY2FkVum98TSoEsDff0cR+Aj49H2sbBcjKQ==
+  optionalDependencies:
+    esbuild-android-arm64 "0.14.17"
+    esbuild-darwin-64 "0.14.17"
+    esbuild-darwin-arm64 "0.14.17"
+    esbuild-freebsd-64 "0.14.17"
+    esbuild-freebsd-arm64 "0.14.17"
+    esbuild-linux-32 "0.14.17"
+    esbuild-linux-64 "0.14.17"
+    esbuild-linux-arm "0.14.17"
+    esbuild-linux-arm64 "0.14.17"
+    esbuild-linux-mips64le "0.14.17"
+    esbuild-linux-ppc64le "0.14.17"
+    esbuild-linux-s390x "0.14.17"
+    esbuild-netbsd-64 "0.14.17"
+    esbuild-openbsd-64 "0.14.17"
+    esbuild-sunos-64 "0.14.17"
+    esbuild-windows-32 "0.14.17"
+    esbuild-windows-64 "0.14.17"
+    esbuild-windows-arm64 "0.14.17"
+
+escape-string-regexp@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+eslint-scope@^7.1.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153"
+  integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^5.2.0"
+
+eslint-utils@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+  integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+  dependencies:
+    eslint-visitor-keys "^2.0.0"
+
+eslint-visitor-keys@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
+  integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
+
+eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.2.0.tgz#6fbb166a6798ee5991358bc2daa1ba76cc1254a1"
+  integrity sha512-IOzT0X126zn7ALX0dwFiUQEdsfzrm4+ISsQS8nukaJXwEyYKRSnEIIDULYg1mCtGp7UUXgfGl7BIolXREQK+XQ==
+
+eslint@^8.2.0:
+  version "8.8.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d"
+  integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ==
+  dependencies:
+    "@eslint/eslintrc" "^1.0.5"
+    "@humanwhocodes/config-array" "^0.9.2"
+    ajv "^6.10.0"
+    chalk "^4.0.0"
+    cross-spawn "^7.0.2"
+    debug "^4.3.2"
+    doctrine "^3.0.0"
+    escape-string-regexp "^4.0.0"
+    eslint-scope "^7.1.0"
+    eslint-utils "^3.0.0"
+    eslint-visitor-keys "^3.2.0"
+    espree "^9.3.0"
+    esquery "^1.4.0"
+    esutils "^2.0.2"
+    fast-deep-equal "^3.1.3"
+    file-entry-cache "^6.0.1"
+    functional-red-black-tree "^1.0.1"
+    glob-parent "^6.0.1"
+    globals "^13.6.0"
+    ignore "^5.2.0"
+    import-fresh "^3.0.0"
+    imurmurhash "^0.1.4"
+    is-glob "^4.0.0"
+    js-yaml "^4.1.0"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    levn "^0.4.1"
+    lodash.merge "^4.6.2"
+    minimatch "^3.0.4"
+    natural-compare "^1.4.0"
+    optionator "^0.9.1"
+    regexpp "^3.2.0"
+    strip-ansi "^6.0.1"
+    strip-json-comments "^3.1.0"
+    text-table "^0.2.0"
+    v8-compile-cache "^2.0.3"
+
+espree@^9.2.0, espree@^9.3.0:
+  version "9.3.0"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8"
+  integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ==
+  dependencies:
+    acorn "^8.7.0"
+    acorn-jsx "^5.3.1"
+    eslint-visitor-keys "^3.1.0"
+
+esquery@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
+  integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
+  dependencies:
+    estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+  dependencies:
+    estraverse "^5.2.0"
+
+estraverse@^5.1.0, 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==
+
+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==
+
+eventemitter3@^4.0.7:
+  version "4.0.7"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
+  integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-json-stable-stringify@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+  integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+  integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+
+file-entry-cache@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+  integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+  dependencies:
+    flat-cache "^3.0.4"
+
+flat-cache@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
+  integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
+  dependencies:
+    flatted "^3.1.0"
+    rimraf "^3.0.2"
+
+flatted@^3.1.0:
+  version "3.2.5"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
+  integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
+
+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=
+
+functional-red-black-tree@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
+  integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
+
+glob-parent@^6.0.1:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+  integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+  dependencies:
+    is-glob "^4.0.3"
+
+glob@^7.1.3:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
+  integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.0.4"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+globals@^13.6.0, globals@^13.9.0:
+  version "13.12.1"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.1.tgz#ec206be932e6c77236677127577aa8e50bf1c5cb"
+  integrity sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==
+  dependencies:
+    type-fest "^0.20.2"
+
+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==
+
+idb-keyval@^6.0.3:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.1.0.tgz#e659cff41188e6097d7fadd69926f6adbbe70041"
+  integrity sha512-u/qHZ75rlD3gH+Zah8dAJVJcGW/RfCnfNrFkElC5RpRCnpsCXXhqjVk+6MoVKJ3WhmNbRYdI6IIVP88e+5sxGw==
+  dependencies:
+    safari-14-idb-fix "^3.0.0"
+
+ignore@^4.0.6:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
+  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
+
+ignore@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
+  integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
+
+import-fresh@^3.0.0, import-fresh@^3.2.1:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+  integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
+imurmurhash@^0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+  integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-extglob@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+  integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+
+is-glob@^4.0.0, is-glob@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  dependencies:
+    is-extglob "^2.1.1"
+
+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=
+
+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"
+
+json-schema-traverse@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+  integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+  integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
+
+levn@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+  integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+  dependencies:
+    prelude-ls "^1.2.1"
+    type-check "~0.4.0"
+
+lodash.merge@^4.6.2:
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+minimatch@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+  integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+misskey-js@0.0.14:
+  version "0.0.14"
+  resolved "https://registry.yarnpkg.com/misskey-js/-/misskey-js-0.0.14.tgz#1a616bdfbe81c6ee6900219eaf425bb5c714dd4d"
+  integrity sha512-bvLx6U3OwQwqHfp/WKwIVwdvNYAAPk0+YblXyxmSG3dwlzCgBRRLcB8o6bNruUDyJgh3t73pLDcOz3myxcUmww==
+  dependencies:
+    autobind-decorator "^2.4.0"
+    eventemitter3 "^4.0.7"
+    reconnecting-websocket "^4.4.0"
+
+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==
+
+natural-compare@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+  integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+  dependencies:
+    wrappy "1"
+
+optionator@^0.9.1:
+  version "0.9.1"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
+  integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+  dependencies:
+    deep-is "^0.1.3"
+    fast-levenshtein "^2.0.6"
+    levn "^0.4.1"
+    prelude-ls "^1.2.1"
+    type-check "^0.4.0"
+    word-wrap "^1.2.3"
+
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+  dependencies:
+    callsites "^3.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=
+
+path-key@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+prelude-ls@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+  integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+punycode@^2.1.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+  integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+reconnecting-websocket@^4.4.0:
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
+  integrity sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==
+
+regexpp@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
+  integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
+
+resolve-from@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+  integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+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"
+
+safari-14-idb-fix@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-3.0.0.tgz#450fc049b996ec7f3fd9ca2f89d32e0761583440"
+  integrity sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==
+
+shebang-command@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+  dependencies:
+    shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
+strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@^7.1.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  dependencies:
+    has-flag "^4.0.0"
+
+text-table@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+  integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
+
+type-check@^0.4.0, type-check@~0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+  integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+  dependencies:
+    prelude-ls "^1.2.1"
+
+type-fest@^0.20.2:
+  version "0.20.2"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+uri-js@^4.2.2:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+  integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+  dependencies:
+    punycode "^2.1.0"
+
+v8-compile-cache@^2.0.3:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
+  integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
+
+which@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+  dependencies:
+    isexe "^2.0.0"
+
+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==
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
diff --git a/scripts/build.js b/scripts/build.js
index 783af78271..608648b953 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -17,6 +17,14 @@ const execa = require('execa');
 		stderr: process.stderr,
 	});
 
+	console.log('building packages/sw ...');
+
+	await execa('npm', ['run', 'build'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
+
 	console.log('build finishing ...');
 
 	await execa('npm', ['run', 'gulp'], {
diff --git a/scripts/clean-all.js b/scripts/clean-all.js
index 814ff3f257..456b88032b 100644
--- a/scripts/clean-all.js
+++ b/scripts/clean-all.js
@@ -7,6 +7,9 @@ const fs = require('fs');
 	fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/client/node_modules', { recursive: true, force: true });
 
+	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
+
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true });
 })();
diff --git a/scripts/clean.js b/scripts/clean.js
index a14f1fb35b..70b9d882b5 100644
--- a/scripts/clean.js
+++ b/scripts/clean.js
@@ -3,5 +3,6 @@ const fs = require('fs');
 (async () => {
 	fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/client/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 })();
diff --git a/scripts/dev.js b/scripts/dev.js
index b7dd870c41..c5dbb7b35a 100644
--- a/scripts/dev.js
+++ b/scripts/dev.js
@@ -25,6 +25,12 @@ const execa = require('execa');
 		stderr: process.stderr,
 	});
 
+	execa('npm', ['run', 'watch'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
+
 	const start = async () => {
 		try {
 			await execa('npm', ['run', 'start'], {
diff --git a/scripts/install-packages.js b/scripts/install-packages.js
index c25063b29a..d1dea3ebe5 100644
--- a/scripts/install-packages.js
+++ b/scripts/install-packages.js
@@ -3,7 +3,7 @@ const execa = require('execa');
 (async () => {
 	console.log('installing dependencies of packages/backend ...');
 
-	await execa('yarn', ['install'], {
+	await execa('yarn', ['--force', 'install'], {
 		cwd: __dirname + '/../packages/backend',
 		stdout: process.stdout,
 		stderr: process.stderr,
@@ -16,4 +16,12 @@ const execa = require('execa');
 		stdout: process.stdout,
 		stderr: process.stderr,
 	});
+
+	console.log('installing dependencies of packages/sw ...');
+
+	await execa('yarn', ['install'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
 })();
diff --git a/scripts/lint.js b/scripts/lint.js
index 11aa4909b1..72a63f4ba3 100644
--- a/scripts/lint.js
+++ b/scripts/lint.js
@@ -14,4 +14,11 @@ const execa = require('execa');
 		stdout: process.stdout,
 		stderr: process.stderr,
 	});
+
+	console.log('linting packages/sw ...');
+	await execa('npm', ['run', 'lint'], {
+		cwd: __dirname + '/../packages/sw',
+		stdout: process.stdout,
+		stderr: process.stderr,
+	});
 })();
diff --git a/yarn.lock b/yarn.lock
index 2dc0d12ecf..327eac0172 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -194,49 +194,49 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/parser@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.18.0.tgz#2bcd4ff21df33621df33e942ccb21cb897f004c6"
-  integrity sha512-+08nYfurBzSSPndngnHvFw/fniWYJ5ymOrn/63oMIbgomVQOvIDhBoJmYZ9lwQOCnQV9xHGvf88ze3jFGUYooQ==
+"@typescript-eslint/parser@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.27.1.tgz#3a4dcaa67e45e0427b6ca7bb7165122c8b569639"
+  integrity sha512-7Va2ZOkHi5NP+AZwb5ReLgNF6nWLGTeUJfxdkVUAPPSaAdbWNnFZzLZ4EGGmmiCTg+AwlbE1KyUYTBglosSLHQ==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.18.0"
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/typescript-estree" "5.18.0"
-    debug "^4.3.2"
+    "@typescript-eslint/scope-manager" "5.27.1"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/typescript-estree" "5.27.1"
+    debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505"
-  integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ==
+"@typescript-eslint/scope-manager@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.27.1.tgz#4d1504392d01fe5f76f4a5825991ec78b7b7894d"
+  integrity sha512-fQEOSa/QroWE6fAEg+bJxtRZJTH8NTskggybogHt4H9Da8zd4cJji76gA5SBlR0MgtwF7rebxTbDKB49YUCpAg==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
 
-"@typescript-eslint/types@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e"
-  integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw==
+"@typescript-eslint/types@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.27.1.tgz#34e3e629501349d38be6ae97841298c03a6ffbf1"
+  integrity sha512-LgogNVkBhCTZU/m8XgEYIWICD6m4dmEDbKXESCbqOXfKZxRKeqpiJXQIErv66sdopRKZPo5l32ymNqibYEH/xg==
 
-"@typescript-eslint/typescript-estree@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474"
-  integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ==
+"@typescript-eslint/typescript-estree@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.27.1.tgz#7621ee78607331821c16fffc21fc7a452d7bc808"
+  integrity sha512-DnZvvq3TAJ5ke+hk0LklvxwYsnXpRdqUY5gaVS0D4raKtbznPz71UJGnPTHEFo0GDxqLOLdMkkmVZjSpET1hFw==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    "@typescript-eslint/visitor-keys" "5.18.0"
-    debug "^4.3.2"
-    globby "^11.0.4"
+    "@typescript-eslint/types" "5.27.1"
+    "@typescript-eslint/visitor-keys" "5.27.1"
+    debug "^4.3.4"
+    globby "^11.1.0"
     is-glob "^4.0.3"
-    semver "^7.3.5"
+    semver "^7.3.7"
     tsutils "^3.21.0"
 
-"@typescript-eslint/visitor-keys@5.18.0":
-  version "5.18.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60"
-  integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg==
+"@typescript-eslint/visitor-keys@5.27.1":
+  version "5.27.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.27.1.tgz#05a62666f2a89769dac2e6baa48f74e8472983af"
+  integrity sha512-xYs6ffo01nhdJgPieyk7HAOpjhTsx7r/oB9LWEhwAXgwn33tkr+W8DI2ChboqhZlC4q3TC6geDYPoiX8ROqyOQ==
   dependencies:
-    "@typescript-eslint/types" "5.18.0"
-    eslint-visitor-keys "^3.0.0"
+    "@typescript-eslint/types" "5.27.1"
+    eslint-visitor-keys "^3.3.0"
 
 aggregate-error@^3.0.0:
   version "3.1.0"
@@ -382,7 +382,7 @@ arr-union@^3.1.0:
 array-each@^1.0.0, array-each@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f"
-  integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8=
+  integrity sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==
 
 array-initial@^1.0.0:
   version "1.1.0"
@@ -468,9 +468,9 @@ async-settle@^1.0.0:
     async-done "^1.2.2"
 
 async@^3.2.0:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8"
-  integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==
+  version "3.2.3"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
+  integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
 
 asynckit@^0.4.0:
   version "0.4.0"
@@ -1005,12 +1005,12 @@ copy-descriptor@^0.1.0:
   integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
 
 copy-props@^2.0.1:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.4.tgz#93bb1cadfafd31da5bb8a9d4b41f471ec3a72dfe"
-  integrity sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/copy-props/-/copy-props-2.0.5.tgz#03cf9ae328d4ebb36f8f1d804448a6af9ee3f2d2"
+  integrity sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==
   dependencies:
-    each-props "^1.3.0"
-    is-plain-object "^2.0.1"
+    each-props "^1.3.2"
+    is-plain-object "^5.0.0"
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
@@ -1084,10 +1084,10 @@ csso@~2.3.1:
     clap "^1.0.9"
     source-map "^0.5.3"
 
-cypress@9.5.3:
-  version "9.5.3"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.5.3.tgz#7c56b50fc1f1aa69ef10b271d895aeb4a1d7999e"
-  integrity sha512-ItelIVmqMTnKYbo1JrErhsGgQGjWOxCpHT1TfMvwnIXKXN/OSlPjEK7rbCLYDZhejQL99PmUqul7XORI24Ik0A==
+cypress@10.0.3:
+  version "10.0.3"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.0.3.tgz#889b4bef863b7d1ef1b608b85b964394ad350c5f"
+  integrity sha512-8C82XTybsEmJC9POYSNITGUhMLCRwB9LadP0x33H+52QVoBjhsWvIzrI+ybCe0+TyxaF0D5/9IL2kSTgjqCB9A==
   dependencies:
     "@cypress/request" "^2.88.10"
     "@cypress/xvfb" "^1.2.4"
@@ -1173,6 +1173,13 @@ debug@^3.1.0:
   dependencies:
     ms "^2.1.1"
 
+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"
+
 decamelize@^1.1.1, decamelize@^1.1.2:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -1261,7 +1268,7 @@ duplexify@^3.6.0:
     readable-stream "^2.0.0"
     stream-shift "^1.0.0"
 
-each-props@^1.3.0:
+each-props@^1.3.2:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/each-props/-/each-props-1.3.2.tgz#ea45a414d16dd5cfa419b1a81720d5ca06892333"
   integrity sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==
@@ -1349,10 +1356,10 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
-eslint-visitor-keys@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2"
-  integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==
+eslint-visitor-keys@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
+  integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
 
 esprima@^2.6.0:
   version "2.7.3"
@@ -1506,10 +1513,10 @@ fancy-log@^1.3.2:
     parse-node-version "^1.0.0"
     time-stamp "^1.0.0"
 
-fast-glob@^3.1.1:
-  version "3.2.7"
-  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1"
-  integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==
+fast-glob@^3.2.9:
+  version "3.2.11"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
+  integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
   dependencies:
     "@nodelib/fs.stat" "^2.0.2"
     "@nodelib/fs.walk" "^1.2.3"
@@ -1826,16 +1833,16 @@ global-prefix@^1.0.1:
     is-windows "^1.0.1"
     which "^1.2.14"
 
-globby@^11.0.4:
-  version "11.0.4"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
-  integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
+globby@^11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+  integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
   dependencies:
     array-union "^2.1.0"
     dir-glob "^3.0.1"
-    fast-glob "^3.1.1"
-    ignore "^5.1.4"
-    merge2 "^1.3.0"
+    fast-glob "^3.2.9"
+    ignore "^5.2.0"
+    merge2 "^1.4.1"
     slash "^3.0.0"
 
 glogg@^1.0.0:
@@ -2001,9 +2008,9 @@ homedir-polyfill@^1.0.1:
     parse-passwd "^1.0.0"
 
 hosted-git-info@^2.1.4:
-  version "2.8.8"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
-  integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
+  version "2.8.9"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
 
 html-comment-regex@^1.1.0:
   version "1.1.2"
@@ -2034,10 +2041,10 @@ ieee754@^1.1.13:
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
 
-ignore@^5.1.4:
-  version "5.1.9"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.9.tgz#9ec1a5cbe8e1446ec60d4420060d43aa6e7382fb"
-  integrity sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==
+ignore@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
+  integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
 
 indent-string@^4.0.0:
   version "4.0.0"
@@ -2276,6 +2283,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
   dependencies:
     isobject "^3.0.1"
 
+is-plain-object@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
+  integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
+
 is-relative@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
@@ -2622,7 +2634,7 @@ merge-stream@^2.0.0:
   resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
   integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
 
-merge2@^1.3.0:
+merge2@^1.3.0, merge2@^1.4.1:
   version "1.4.1"
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
@@ -2969,9 +2981,9 @@ path-key@^3.0.0, path-key@^3.1.0:
   integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
 
 path-parse@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
-  integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
 
 path-root-regex@^0.1.0:
   version "0.1.2"
@@ -3658,13 +3670,20 @@ semver-greatest-satisfied-range@^1.1.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
   integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 
-semver@^7.3.2, semver@^7.3.5:
+semver@^7.3.2:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
   dependencies:
     lru-cache "^6.0.0"
 
+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"
+
 set-blocking@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -4182,10 +4201,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@4.6.3:
-  version "4.6.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
-  integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
+typescript@4.7.3:
+  version "4.7.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.3.tgz#8364b502d5257b540f9de4c40be84c98e23a129d"
+  integrity sha512-WOkT3XYvrpXx4vMMqlD+8R8R37fZkjyLGlxavMc4iB8lrl8L0DeTcHbYgw/v0N/z9wAFsgBhcsF0ruoySS22mA==
 
 unc-path-regex@^0.1.2:
   version "0.1.2"