diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml
index e8da5f5e276a..3907615f73a4 100644
--- a/.config/cypress-devcontainer.yml
+++ b/.config/cypress-devcontainer.yml
@@ -2,6 +2,19 @@
 # Misskey configuration
 #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
+#   ┌────────────────────────┐
+#───┘ Initial Setup Password └─────────────────────────────────────────────────────
+
+# Password to initiate setting up admin account.
+# It will not be used after the initial setup is complete.
+#
+# Be sure to change this when you set up Misskey via the Internet.
+#
+# The provider of the service who sets up Misskey on behalf of the customer should
+# set this value to something unique when generating the Misskey config file,
+# and provide it to the customer.
+setupPassword: example_password_please_change_this_or_you_will_get_hacked
+
 #   ┌─────┐
 #───┘ URL └─────────────────────────────────────────────────────
 
@@ -103,6 +116,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForReactions:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index d347882d1a91..3f8e5734ce87 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -106,6 +106,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForReactions:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.config/example.yml b/.config/example.yml
index b11cbd137328..60a6a0aa71b7 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -59,6 +59,20 @@
 #
 # publishTarballInsteadOfProvideRepositoryUrl: true
 
+#   ┌────────────────────────┐
+#───┘ Initial Setup Password └─────────────────────────────────────────────────────
+
+# Password to initiate setting up admin account.
+# It will not be used after the initial setup is complete.
+#
+# Be sure to change this when you set up Misskey via the Internet.
+#
+# The provider of the service who sets up Misskey on behalf of the customer should
+# set this value to something unique when generating the Misskey config file,
+# and provide it to the customer.
+#
+# setupPassword: example_password_please_change_this_or_you_will_get_hacked
+
 #   ┌─────┐
 #───┘ URL └─────────────────────────────────────────────────────
 
@@ -172,6 +186,16 @@ redis:
 #  # You can specify more ioredis options...
 #  #username: example-username
 
+#redisForReactions:
+#  host: localhost
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+#  # You can specify more ioredis options...
+#  #username: example-username
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml
index beefcfd0a2d5..3eb4fc28794b 100644
--- a/.devcontainer/devcontainer.yml
+++ b/.devcontainer/devcontainer.yml
@@ -103,6 +103,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForReactions:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.github/labeler.yml b/.github/labeler.yml
index a77f73706b9e..b64d726d659a 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -6,7 +6,7 @@
 'packages/backend:test':
 - any:
   - changed-files:
-    - any-glob-to-any-file: ['packages/backend/test/**/*']
+    - any-glob-to-any-file: ['packages/backend/test/**/*', 'packages/backend/test-federation/**/*']
 
 'packages/frontend':
 - any:
diff --git a/.github/misskey/test.yml b/.github/misskey/test.yml
index 7a4aa4ae6cce..3c807e8b9ea2 100644
--- a/.github/misskey/test.yml
+++ b/.github/misskey/test.yml
@@ -1,5 +1,7 @@
 url: 'http://misskey.local'
 
+setupPassword: example_password_please_change_this_or_you_will_get_hacked
+
 # ローカルでテストするときにポートを被らないようにするためデフォルトのものとは変える(以下同じ)
 port: 61812
 
diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml
index e7db18316cb3..8380a3bb238a 100644
--- a/.github/workflows/api-misskey-js.yml
+++ b/.github/workflows/api-misskey-js.yml
@@ -21,7 +21,7 @@ jobs:
       - run: corepack enable
 
       - name: Setup Node.js
-        uses: actions/setup-node@v4.0.3
+        uses: actions/setup-node@v4.0.4
         with:
           node-version-file: '.node-version'
           cache: 'pnpm'
diff --git a/.github/workflows/changelog-check.yml b/.github/workflows/changelog-check.yml
index d4e99f966ef0..44cc1a04f2c1 100644
--- a/.github/workflows/changelog-check.yml
+++ b/.github/workflows/changelog-check.yml
@@ -14,7 +14,7 @@ jobs:
       - name: Checkout head
         uses: actions/checkout@v4.1.1
       - name: Setup Node.js
-        uses: actions/setup-node@v4.0.3
+        uses: actions/setup-node@v4.0.4
         with:
           node-version-file: '.node-version'
 
diff --git a/.github/workflows/check-misskey-js-autogen.yml b/.github/workflows/check-misskey-js-autogen.yml
index 3a2a2d5f8dd7..f26c9a4d45b8 100644
--- a/.github/workflows/check-misskey-js-autogen.yml
+++ b/.github/workflows/check-misskey-js-autogen.yml
@@ -21,6 +21,7 @@ jobs:
         uses: actions/checkout@v4.1.1
         with:
           submodules: true
+          persist-credentials: false
           ref: refs/pull/${{ github.event.pull_request.number }}/merge
 
       - name: setup pnpm
@@ -28,7 +29,7 @@ jobs:
 
       - name: setup node
         id: setup-node
-        uses: actions/setup-node@v4.0.3
+        uses: actions/setup-node@v4.0.4
         with:
           node-version-file: '.node-version'
           cache: pnpm
@@ -57,7 +58,7 @@ jobs:
           name: generated-misskey-js
           path: packages/misskey-js/generator/built/autogen
 
-  # pull_request_target safety: permissions: read-all, and there are no secrets used in this job
+  # pull_request_target safety: permissions: read-all, and no user codes are executed
   get-actual-misskey-js:
     runs-on: ubuntu-latest
     permissions:
@@ -68,6 +69,7 @@ jobs:
         uses: actions/checkout@v4.1.1
         with:
           submodules: true
+          persist-credentials: false
           ref: refs/pull/${{ github.event.pull_request.number }}/merge
 
       - name: Upload From Merged
@@ -131,3 +133,7 @@ jobs:
           mode: delete
           message: "Thank you!"
           create_if_not_exists: false
+
+      - name: Make failure if changes are detected
+        if: steps.check-changes.outputs.changes == 'true'
+        run: exit 1
diff --git a/.github/workflows/check-spdx-license-id.yml b/.github/workflows/check-spdx-license-id.yml
index 2579beb53a8e..05582008b5f2 100644
--- a/.github/workflows/check-spdx-license-id.yml
+++ b/.github/workflows/check-spdx-license-id.yml
@@ -48,13 +48,15 @@ jobs:
             "packages/backend/migration"
             "packages/backend/src"
             "packages/backend/test"
-            "packages/frontend-shared/src"
+            "packages/frontend-shared/@types"
+            "packages/frontend-shared/js"
             "packages/frontend/.storybook"
             "packages/frontend/@types"
             "packages/frontend/lib"
             "packages/frontend/public"
             "packages/frontend/src"
             "packages/frontend/test"
+            "packages/frontend-embed/@types"
             "packages/frontend-embed/src"
             "packages/misskey-bubble-game/src"
             "packages/misskey-reversi/src"
diff --git a/.github/workflows/get-api-diff.yml b/.github/workflows/get-api-diff.yml
index 81e8134fb741..1bcaa0d9c487 100644
--- a/.github/workflows/get-api-diff.yml
+++ b/.github/workflows/get-api-diff.yml
@@ -33,7 +33,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v4
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'pnpm'
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 11903e3ec248..90eb268ddab8 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -12,6 +12,8 @@ on:
       - packages/frontend-embed/**
       - packages/sw/**
       - packages/misskey-js/**
+      - packages/misskey-bubble-game/**
+      - packages/misskey-reversi/**
       - packages/shared/eslint.config.js
       - .github/workflows/lint.yml
   pull_request:
@@ -22,6 +24,8 @@ on:
       - packages/frontend-embed/**
       - packages/sw/**
       - packages/misskey-js/**
+      - packages/misskey-bubble-game/**
+      - packages/misskey-reversi/**
       - packages/shared/eslint.config.js
       - .github/workflows/lint.yml
 jobs:
@@ -33,7 +37,7 @@ jobs:
         fetch-depth: 0
         submodules: true
     - uses: pnpm/action-setup@v4
-    - uses: actions/setup-node@v4.0.3
+    - uses: actions/setup-node@v4.0.4
       with:
         node-version-file: '.node-version'
         cache: 'pnpm'
@@ -53,6 +57,8 @@ jobs:
         - frontend-embed
         - sw
         - misskey-js
+        - misskey-bubble-game
+        - misskey-reversi
     env:
       eslint-cache-version: v1
       eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
@@ -62,14 +68,14 @@ jobs:
         fetch-depth: 0
         submodules: true
     - uses: pnpm/action-setup@v4
-    - uses: actions/setup-node@v4.0.3
+    - uses: actions/setup-node@v4.0.4
       with:
         node-version-file: '.node-version'
         cache: 'pnpm'
     - run: corepack enable
     - run: pnpm i --frozen-lockfile
     - name: Restore eslint cache
-      uses: actions/cache@v4.0.2
+      uses: actions/cache@v4.1.0
       with:
         path: ${{ env.eslint-cache-path }}
         key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
@@ -92,7 +98,7 @@ jobs:
         fetch-depth: 0
         submodules: true
     - uses: pnpm/action-setup@v4
-    - uses: actions/setup-node@v4.0.3
+    - uses: actions/setup-node@v4.0.4
       with:
         node-version-file: '.node-version'
         cache: 'pnpm'
diff --git a/.github/workflows/locale.yml b/.github/workflows/locale.yml
index 95251bfe31e0..6bc8860a11eb 100644
--- a/.github/workflows/locale.yml
+++ b/.github/workflows/locale.yml
@@ -19,7 +19,7 @@ jobs:
         fetch-depth: 0
         submodules: true
     - uses: pnpm/action-setup@v4
-    - uses: actions/setup-node@v4.0.3
+    - uses: actions/setup-node@v4.0.4
       with:
         node-version-file: '.node-version'
         cache: 'pnpm'
diff --git a/.github/workflows/on-release-created.yml b/.github/workflows/on-release-created.yml
index 8dd9ed2513f5..ffaf7bc03833 100644
--- a/.github/workflows/on-release-created.yml
+++ b/.github/workflows/on-release-created.yml
@@ -26,7 +26,7 @@ jobs:
       - name: Install pnpm
         uses: pnpm/action-setup@v4
       - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v4.0.3
+        uses: actions/setup-node@v4.0.4
         with:
           node-version: ${{ matrix.node-version }}
           cache: 'pnpm'
diff --git a/.github/workflows/report-api-diff.yml b/.github/workflows/report-api-diff.yml
index df9cc279e866..1170f898ced9 100644
--- a/.github/workflows/report-api-diff.yml
+++ b/.github/workflows/report-api-diff.yml
@@ -70,18 +70,25 @@ jobs:
       - id: out-diff
         name: Build diff Comment
         run: |
-          cat <<- EOF > ./output.md
-          このPRによるapi.jsonの差分
-          <details>
-          <summary>差分はこちら</summary>
-
-          \`\`\`diff
-          $(cat ./api.json.diff)
-          \`\`\`
-          </details>
-
-          [Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})
-          EOF
+          HEADER="このPRによるapi.jsonの差分"
+          FOOTER="[Get diff files from Workflow Page](https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})"
+          DIFF_BYTES="$(stat ./api.json.diff -c '%s' | tr -d '\n')"
+          
+          echo "$HEADER" > ./output.md
+          
+          if (( "$DIFF_BYTES" <= 1 )); then
+            echo '差分はありません。' >> ./output.md
+          else
+            echo '<details>' >> ./output.md
+            echo '<summary>差分はこちら</summary>' >> ./output.md
+            echo >> ./output.md
+            echo '```diff' >> ./output.md
+            cat ./api.json.diff >> ./output.md
+            echo '```' >> ./output.md
+            echo '</details>' >> .output.md
+          fi
+          
+          echo "$FOOTER" >> ./output.md
       - uses: thollander/actions-comment-pull-request@v2
         with:
           pr_number: ${{ steps.load-pr-num.outputs.pr-number }}
diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index bea93f145612..c02f38ee0b61 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -41,7 +41,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v4
     - name: Use Node.js 20.x
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version-file: '.node-version'
         cache: 'pnpm'
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
index 026550025c95..d95d6676f9a0 100644
--- a/.github/workflows/test-backend.yml
+++ b/.github/workflows/test-backend.yml
@@ -46,7 +46,7 @@ jobs:
     - name: Install FFmpeg
       uses: FedericoCarboni/setup-ffmpeg@v3
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'pnpm'
@@ -93,7 +93,7 @@ jobs:
       - name: Install pnpm
         uses: pnpm/action-setup@v4
       - name: Use Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v4.0.3
+        uses: actions/setup-node@v4.0.4
         with:
           node-version: ${{ matrix.node-version }}
           cache: 'pnpm'
diff --git a/.github/workflows/test-federation.yml b/.github/workflows/test-federation.yml
new file mode 100644
index 000000000000..183ddb6f3414
--- /dev/null
+++ b/.github/workflows/test-federation.yml
@@ -0,0 +1,59 @@
+name: Test (federation)
+
+on:
+  push:
+    branches:
+      - master
+      - develop
+    paths:
+      - packages/backend/**
+      - packages/misskey-js/**
+      - .github/workflows/test-federation.yml
+  pull_request:
+    paths:
+      - packages/backend/**
+      - packages/misskey-js/**
+      - .github/workflows/test-federation.yml
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [20.16.0]
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          submodules: true
+      - name: Install pnpm
+        uses: pnpm/action-setup@v4
+      - name: Install FFmpeg
+        uses: FedericoCarboni/setup-ffmpeg@v3
+      - name: Use Node.js ${{ matrix.node-version }}
+        uses: actions/setup-node@v4.0.3
+        with:
+          node-version: ${{ matrix.node-version }}
+          cache: 'pnpm'
+      - name: Build Misskey
+        run: |
+          corepack enable && corepack prepare
+          pnpm i --frozen-lockfile
+          pnpm build
+      - name: Setup
+        run: |
+          cd packages/backend/test-federation
+          bash ./setup.sh
+          sudo chmod 644 ./certificates/*.test.key
+      - name: Start servers
+        # https://github.com/docker/compose/issues/1294#issuecomment-374847206
+        run: |
+          cd packages/backend/test-federation
+          docker compose up -d --scale tester=0
+      - name: Test
+        run: |
+          cd packages/backend/test-federation
+          docker compose run --no-deps tester
+      - name: Stop servers
+        run: |
+          cd packages/backend/test-federation
+          docker compose down
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index fcaef529695f..c68e1a8ef1d6 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -35,7 +35,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v4
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'pnpm'
@@ -90,7 +90,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v4
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'pnpm'
diff --git a/.github/workflows/test-misskey-js.yml b/.github/workflows/test-misskey-js.yml
index 9ad71919df15..63e81f8c92d1 100644
--- a/.github/workflows/test-misskey-js.yml
+++ b/.github/workflows/test-misskey-js.yml
@@ -31,7 +31,7 @@ jobs:
       - run: corepack enable
 
       - name: Setup Node.js ${{ matrix.node-version }}
-        uses: actions/setup-node@v4.0.3
+        uses: actions/setup-node@v4.0.4
         with:
           node-version: ${{ matrix.node-version }}
           cache: 'pnpm'
diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml
index 8ad8a6476696..0abc09c5a603 100644
--- a/.github/workflows/test-production.yml
+++ b/.github/workflows/test-production.yml
@@ -25,7 +25,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v4
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'pnpm'
diff --git a/.github/workflows/validate-api-json.yml b/.github/workflows/validate-api-json.yml
index 06e987f27e4f..f809af1063d1 100644
--- a/.github/workflows/validate-api-json.yml
+++ b/.github/workflows/validate-api-json.yml
@@ -27,7 +27,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v4
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v4.0.3
+      uses: actions/setup-node@v4.0.4
       with:
         node-version: ${{ matrix.node-version }}
         cache: 'pnpm'
diff --git a/.gitignore b/.gitignore
index 4d5bd1ce0819..5b8a798ba64f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,7 +37,7 @@ coverage
 !/.config/docker_example.env
 !/.config/cypress-devcontainer.yml
 docker-compose.yml
-compose.yml
+./compose.yml
 .devcontainer/compose.yml
 !/.devcontainer/compose.yml
 
@@ -65,6 +65,10 @@ temp
 tsdoc-metadata.json
 misskey-assets
 
+# Vite temporary files
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
+
 # blender backups
 *.blend1
 *.blend2
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9d7425d4638d..e5bbda36fa33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,18 +1,161 @@
-## Unreleased
+## 2024.11.0
 
 ### General
--
+- Feat: コンテンツの表示にログインを必須にできるように
+- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
+- Enhance: 依存関係の更新
+- Enhance: l10nの更新
+
+### Client
+- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように  
+  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/751)
+- Enhance: ドライブでソートができるように
+- Enhance: アイコンデコレーション管理画面の改善
+- Enhance: 「単なるラッキー」の取得条件を変更
+- Enhance: 投稿フォームでEscキーを押したときIME入力中ならフォームを閉じないように( #10866 )  
+- Enhance: MiAuth, OAuthの認可画面の改善
+  - どのアカウントで認証しようとしているのかがわかるように
+  - 認証するアカウントを切り替えられるように
+- Enhance: Self-XSS防止用の警告を追加
+- Enhance: カタルーニャ語 (ca-ES) に対応
+- Enhance: 個別お知らせページではMetaタグを出力するように
+- Fix: 通知の範囲指定の設定項目が必要ない通知設定でも範囲指定の設定がでている問題を修正
+- Fix: Turnstileが失敗・期限切れした際にも成功扱いとなってしまう問題を修正  
+  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768)
+- Fix: デッキのタイムラインカラムで「センシティブなファイルを含むノートを表示」設定が使用できなかった問題を修正
+- Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used
+- Fix: リンク切れを修正
+= Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正  
+  (Cherry-picked from https://github.com/taiyme/misskey/pull/305)
+- Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正
+
+### Server
+- Enhance: 起動前の疎通チェックで、DBとメイン以外のRedisの疎通確認も行うように  
+  (Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588)  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715)
+- Enhance: リモートユーザーの照会をオリジナルにリダイレクトするように
+- Fix: フォロワーへのメッセージの絵文字をemojisに含めるように
+- Fix: Nested proxy requestsを検出した際にブロックするように
+  [ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236)
+- Fix: 招待コードの発行可能な残り数算出に使用すべきロールポリシーの値が違う問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/706)
+- Fix: 連合への配信時に、acctの大小文字が区別されてしまい正しくメンションが処理されないことがある問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/711)
+- Fix: ローカルユーザーへのメンションを含むノートが連合される際に正しいURLに変換されないことがある問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712)
+- Fix: FTT無効時にユーザーリストタイムラインが使用できない問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709)
+
+### Misskey.js
+- Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正
+
+## 2024.10.1
+
+### Note
+- スパム対策として、モデレータ権限を持つユーザのアクティビティが7日以上確認できない場合は自動的に招待制へと切り替え(コントロールパネル -> モデレーション -> "誰でも新規登録できるようにする"をオフに変更)るようになりました。 ( #13437 )
+	- 切り替わった際はモデレーターへお知らせとして通知されます。登録をオープンな状態で継続したい場合は、コントロールパネルから再度設定を行ってください。
+
+### General
+- Feat: ユーザーの名前に禁止ワードを設定できるように
+
+### Client
+- Enhance: タイムライン表示時のパフォーマンスを向上
+- Enhance: アーカイブした個人宛のお知らせを表示・編集できるように
+- Enhance: l10nの更新
+- Fix: メールアドレス不要でCaptchaが有効な場合にアカウント登録完了後自動でのログインに失敗する問題を修正
+
+### Server
+- Feat: モデレータ権限を持つユーザが全員7日間活動しなかった場合は自動的に招待制へと切り替えるように ( #13437 )
+- Enhance: 個人宛のお知らせは「わかった」を押すと自動的にアーカイブされるように
+- Fix: `admin/emoji/update`エンドポイントのidのみ指定した時不正なエラーが発生するバグを修正
+- Fix: RBT有効時、リノートのリアクションが反映されない問題を修正
+- Fix: キューのエラーログを簡略化するように  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/649)
+
+## 2024.10.0
+
+### Note
+- セキュリティ向上のため、サーバー初期設定時に使用する初期パスワードを設定できるようになりました。今後Misskeyサーバーを新たに設置する際には、初回の起動前にコンフィグファイルの`setupPassword`をコメントアウトし、初期パスワードを設定することをおすすめします。(すでに初期設定を完了しているサーバーについては、この変更に伴い対応する必要はありません)  
+  - ホスティングサービスを運営している場合は、コンフィグファイルを構築する際に`setupPassword`をランダムな値に設定し、ユーザーに通知するようにシステムを更新することをおすすめします。
+  - なお、初期パスワードが設定されていない場合でも初期設定を行うことが可能です(UI上で初期パスワードの入力欄を空欄にすると続行できます)。
+- ユーザーデータを読み込む際の型が一部変更されました。
+	- `twoFactorEnabled`, `usePasswordLessLogin`, `securityKeys`: 自分とモデレーター以外のユーザーからは取得できなくなりました
+
+### General
+- Feat: サーバー初期設定時に初期パスワードを設定できるように
+- Feat: 通報にモデレーションノートを残せるように
+- Feat: 通報の解決種別を設定できるように
+- Enhance: 通報の解決と転送を個別に行えるように
+- Enhance: セキュリティ向上のため、サインイン時もCAPTCHAを求めるようになりました
+- Enhance: 依存関係の更新
+- Enhance: l10nの更新
+- Enhance: Playの「人気」タブで10件以上表示可能に #14399
+- Fix: 連合のホワイトリストが正常に登録されない問題を修正
 
 ### Client
+- Enhance: デザインの調整
+- Enhance: ログイン画面の認証フローを改善
+- Fix: クライアント上での時間ベースの実績獲得動作が実績獲得後も発動していた問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/657)
+
+### Server
+- Enhance: セキュリティ向上のため、ログイン時にメール通知を行うように
+- Enhance: 自分とモデレーター以外のユーザーから二要素認証関連のデータが取得できないように
+- Enhance: 通報および通報解決時に送出されるSystemWebhookにユーザ情報を含めるように ( #14697 )
+- Fix: `admin/abuse-user-reports`エンドポイントのスキーマが間違っていた問題を修正
+
+## 2024.9.0
+
+### General
 - Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
-  - 埋め込みコードやウェブサイトへの実装方法の詳細はMisskey Hubに掲載予定です
+  - 埋め込みコードやウェブサイトへの実装方法の詳細は https://misskey-hub.net/docs/for-users/features/embed/ をご覧ください
+- Feat: パスキーでログインボタンを実装 (#14574)
+- Feat: フォローされた際のメッセージを設定できるように
+- Feat: 連合をホワイトリスト制にできるように
+- Feat: UserWebhookとSystemWebhookのテスト送信機能を追加 (#14445)
+- Feat: モデレーターはユーザーにかかわらずファイルが添付されているノートを検索できるように  
+  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/680)
+- Feat: データエクスポートが完了した際に通知を発行するように
+- Enhance: ユーザーによるコンテンツインポートの可否をロールポリシーで制御できるように
+- Enhance: 依存関係の更新
+- Enhance: l10nの更新
+
+### Client
 - Enhance: サイズ制限を超過するファイルをアップロードしようとした際にエラーを出すように
 - Enhance: アイコンデコレーション管理画面にプレビューを追加
+- Enhance: コントロールパネル内のファイル一覧でセンシティブなファイルを区別しやすく
+- Enhance: ScratchpadにUIインスペクターを追加
+- Enhance: Play編集画面の項目の並びを少しリデザイン
+- Enhance: 各種メニューをドロワー表示するかどうか設定可能に
+- Enhance: AiScriptのMk:C:containerのオプションに`borderStyle`と`borderRadius`を追加
+- Enhance: CWでも絵文字をクリックしてメニューを表示できるように
 - Fix: サーバーメトリクスが2つ以上あるとリロード直後の表示がおかしくなる問題を修正
+- Fix: コントロールパネル内のAp requests内のチャートの表示がおかしかった問題を修正
+- Fix: 月の違う同じ日はセパレータが表示されないのを修正
+- Fix: タッチ画面でレンジスライダーを操作するとツールチップが複数表示される問題を修正  
+  (Cherry-picked from https://github.com/taiyme/misskey/pull/265)
+- Fix: 縦横比が極端なカスタム絵文字を表示する際にレイアウトが崩れる箇所があるのを修正  
+  (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/725)
+- Fix: 設定変更時のリロード確認ダイアログが複数個表示されることがある問題を修正
+- Fix: ファイルの詳細ページのファイルの説明で改行が正しく表示されない問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/bde6bb0bd2e8b0d027e724d2acdb8ae0585a8110)
+- Fix: 一部画面のページネーションが動作しにくくなっていたのを修正 ( #12766 , #11449 )
 
 ### Server
+- Feat: Misskey® Reactions Boost Technology™ (RBT)により、リアクションの作成負荷を低減することが可能に
+- Fix: アンテナの書き込み時にキーワードが与えられなかった場合のエラーをApiErrorとして投げるように
+  - この変更により、公式フロントエンドでは入力の不備が内部エラーとして報告される代わりに一般的なエラーダイアログで報告されます
 - Fix: ファイルがサイズの制限を超えてアップロードされた際にエラーを返さなかった問題を修正
-
+- Fix: 外部ページを解析する際に、ページに紐づけられた関連リソースも読み込まれてしまう問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/26e0412fbb91447c37e8fb06ffb0487346063bb8)
+- Fix: Continue importing from file if single emoji import fails
+- Fix: `Retry-After`ヘッダーが送信されなかった問題を修正  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/commit/8a982c61c01909e7540ff1be9f019df07c3f0624)
+- Fix: サーバーサイドのDOM解析完了時にリソースを開放するように  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/634)
+- Fix: `<link rel="alternate">`を追って照会するのはOKレスポンスが返却された場合のみに  
+  (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/633)
+- Fix: メールにスタイルが適用されていなかった問題を修正
 
 ## 2024.8.0
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 72c84c2f1845..f8af6b3df0a4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -64,9 +64,29 @@ Thank you for your PR! Before creating a PR, please check the following:
 
 Thanks for your cooperation 🤗
 
+### Additional things for ActivityPub payload changes
+*This section is specific to misskey-dev implementation. Other fork or implementation may take different way. A significant difference is that non-"misskey-dev" extension is not described in the misskey-hub's document.*
+
+If PR includes changes to ActivityPub payload, please reflect it in [misskey-hub's document](https://github.com/misskey-dev/misskey-hub-next/blob/master/content/ns.md) by sending PR.
+
+The name of purporsed extension property (referred as "extended property" in later) to ActivityPub shall be prefixed by `_misskey_`. (i.e. `_misskey_quote`)
+
+The extended property in `packages/backend/src/core/activitypub/type.ts` **must** be declared as optional because ActivityPub payloads that comes from older Misskey or other implementation may not contain it.
+
+The extended property must be included in the context definition. Context is defined in `packages/backend/src/core/activitypub/misc/contexts.ts`.
+The key shall be same as the name of extended property, and the value shall be same as "short IRI".
+
+"Short IRI" is defined in misskey-hub's document, but usually takes form of `misskey:<name of extended property>`. (i.e. `misskey:_misskey_quote`)
+
+One should not add property that has defined before by other implementation, or add custom variant value to "well-known" property.
+
 ## Reviewers guide
 Be willing to comment on the good points and not just the things you want fixed 💯
 
+読んでおくといいやつ
+- https://blog.lacolaco.net/posts/1e2cf439b3c2/
+- https://konifar-zatsu.hatenadiary.jp/entry/2024/11/05/192421
+
 ### Review perspective
 - Scope
 	- Are the goals of the PR clear?
@@ -116,7 +136,8 @@ You can improve our translations with your Crowdin account.
 Your changes in Crowdin are automatically submitted as a PR (with the title "New Crowdin translations") to the repository.
 The owner [@syuilo](https://github.com/syuilo) merges the PR into the develop branch before the next release.
 
-If your language is not listed in Crowdin, please open an issue.
+If your language is not listed in Crowdin, please open an issue. We will add it to Crowdin.
+For newly added languages, once the translation progress per language exceeds 70%, it will be officially introduced into Misskey and made available to users.
 
 ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
 
@@ -181,31 +202,45 @@ MK_DEV_PREFER=backend pnpm dev
 - HMR may not work in some environments such as Windows.
 
 ## Testing
-- Test codes are located in [`/packages/backend/test`](/packages/backend/test).
-
-### Run test
-Create a config file.
+You can run non-backend tests by executing following commands:
+```sh
+pnpm --filter frontend test
+pnpm --filter misskey-js test
 ```
+
+Backend tests require manual preparation of servers. See the next section for more on this.
+
+### Backend
+There are three types of test codes for the backend:
+- Unit tests: [`/packages/backend/test/unit`](/packages/backend/test/unit)
+- Single-server E2E tests: [`/packages/backend/test/e2e`](/packages/backend/test/e2e)
+- Multiple-server E2E tests: [`/packages/backend/test-federation`](/packages/backend/test-federation)
+
+#### Running Unit Tests or Single-server E2E Tests
+1. Create a config file:
+```sh
 cp .github/misskey/test.yml .config/
 ```
-Prepare DB/Redis for testing.
-```
-docker compose -f packages/backend/test/compose.yml up
-```
-Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`.
 
-Run all test.
-```
-pnpm test
+2. Start DB and Redis servers for testing:
+```sh
+docker compose -f packages/backend/test/compose.yml up
 ```
+Instead, you can prepare an empty (data can be erased) DB and edit `.config/test.yml` appropriately.
 
-#### Run specify test
+3. Run all tests:
+```sh
+pnpm --filter backend test     # unit tests
+pnpm --filter backend test:e2e # single-server E2E tests
 ```
-pnpm jest -- foo.ts
+If you want to run a specific test, run as a following command:
+```sh
+pnpm --filter backend test -- packages/backend/test/unit/activitypub.ts
+pnpm --filter backend test:e2e -- packages/backend/test/e2e/nodeinfo.ts
 ```
 
-### e2e tests
-TODO
+#### Running Multiple-server E2E Tests
+See [`/packages/backend/test-federation/README.md`](/packages/backend/test-federation/README.md).
 
 ## Environment Variable
 
@@ -572,3 +607,24 @@ marginはそのコンポーネントを使う側が設定する
 
 ### indexというファイル名を使うな
 ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
+
+## CSS Recipe
+
+### Lighten CSS vars
+
+``` css
+color: hsl(from var(--MI_THEME-accent) h s calc(l + 10));
+```
+
+### Darken CSS vars
+
+``` css
+color: hsl(from var(--MI_THEME-accent) h s calc(l - 10));
+```
+
+### Add alpha to CSS vars
+
+``` css
+color: color(from var(--MI_THEME-accent) srgb r g b / 0.5);
+```
+
diff --git a/chart/files/default.yml b/chart/files/default.yml
index f98b8ebfee04..4d17131c2546 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -124,6 +124,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForReactions:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts
index d2525e0a7d05..d2efbf709c88 100644
--- a/cypress/e2e/basic.cy.ts
+++ b/cypress/e2e/basic.cy.ts
@@ -23,6 +23,7 @@ describe('Before setup instance', () => {
 
 		cy.intercept('POST', '/api/admin/accounts/create').as('signup');
 
+		cy.get('[data-cy-admin-initial-password] input').type('example_password_please_change_this_or_you_will_get_hacked');
 		cy.get('[data-cy-admin-username] input').type('admin');
 		cy.get('[data-cy-admin-password] input').type('admin1234');
 		cy.get('[data-cy-admin-ok]').click();
@@ -119,11 +120,16 @@ describe('After user signup', () => {
 	it('signin', () => {
 		cy.visitHome();
 
-		cy.intercept('POST', '/api/signin').as('signin');
+		cy.intercept('POST', '/api/signin-flow').as('signin');
 
 		cy.get('[data-cy-signin]').click();
-		cy.get('[data-cy-signin-username] input').type('alice');
-		// Enterキーでサインインできるかの確認も兼ねる
+
+		cy.get('[data-cy-signin-page-input]').should('be.visible', { timeout: 1000 });
+		// Enterキーで続行できるかの確認も兼ねる
+		cy.get('[data-cy-signin-username] input').type('alice{enter}');
+
+		cy.get('[data-cy-signin-page-password]').should('be.visible', { timeout: 10000 });
+		// Enterキーで続行できるかの確認も兼ねる
 		cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
 
 		cy.wait('@signin');
@@ -138,8 +144,9 @@ describe('After user signup', () => {
 		cy.visitHome();
 
 		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.get('[data-cy-signin-page-input]').should('be.visible', { timeout: 1000 });
+		cy.get('[data-cy-signin-username] input').type('alice{enter}');
 
 		// TODO: cypressにブラウザの言語指定できる機能が実装され次第英語のみテストするようにする
 		cy.contains(/アカウントが凍結されています|This account has been suspended due to/gi);
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 281f2e6ccdd8..197ff963acff 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -48,16 +48,19 @@ Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
 	cy.request('POST', route, {
 		username: username,
 		password: password,
+		...(isAdmin ? { setupPassword: 'example_password_please_change_this_or_you_will_get_hacked' } : {}),
 	}).its('body').as(username);
 });
 
 Cypress.Commands.add('login', (username, password) => {
 	cy.visitHome();
 
-	cy.intercept('POST', '/api/signin').as('signin');
+	cy.intercept('POST', '/api/signin-flow').as('signin');
 
 	cy.get('[data-cy-signin]').click();
-	cy.get('[data-cy-signin-username] input').type(username);
+	cy.get('[data-cy-signin-page-input]').should('be.visible', { timeout: 1000 });
+	cy.get('[data-cy-signin-username] input').type(`${username}{enter}`);
+	cy.get('[data-cy-signin-page-password]').should('be.visible', { timeout: 10000 });
 	cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
 
 	cy.wait('@signin').as('signedIn');
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/idea/MkAbuseReport.stories.impl.ts
similarity index 88%
rename from packages/frontend/src/components/MkAbuseReport.stories.impl.ts
rename to idea/MkAbuseReport.stories.impl.ts
index cf09c96fd410..717bceb23d6b 100644
--- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
+++ b/idea/MkAbuseReport.stories.impl.ts
@@ -7,8 +7,8 @@
 import { action } from '@storybook/addon-actions';
 import { StoryObj } from '@storybook/vue3';
 import { HttpResponse, http } from 'msw';
-import { abuseUserReport } from '../../.storybook/fakes.js';
-import { commonHandlers } from '../../.storybook/mocks.js';
+import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js';
+import { commonHandlers } from '../packages/frontend/.storybook/mocks.js';
 import MkAbuseReport from './MkAbuseReport.vue';
 export const Default = {
 	render(args) {
diff --git a/idea/MkDisableSection.vue b/idea/MkDisableSection.vue
new file mode 100644
index 000000000000..360705071b4f
--- /dev/null
+++ b/idea/MkDisableSection.vue
@@ -0,0 +1,41 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root]">
+	<div :inert="disabled" :class="[{ [$style.disabled]: disabled }]">
+		<slot></slot>
+	</div>
+	<div v-if="disabled" :class="[$style.cover]"></div>
+</div>
+</template>
+
+<script lang="ts" setup>
+defineProps<{
+	disabled?: boolean;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+}
+
+.disabled {
+	opacity: 0.7;
+}
+
+.cover {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	cursor: not-allowed;
+	--color: color(from var(--MI_THEME-error) srgb r g b / 0.25);
+	background-size: auto auto;
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px);
+}
+</style>
diff --git a/idea/README.md b/idea/README.md
new file mode 100644
index 000000000000..f64d16800a98
--- /dev/null
+++ b/idea/README.md
@@ -0,0 +1 @@
+使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index b6bfbfa68281..de24ad4bb963 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -626,10 +626,7 @@ abuseReported: "أُرسل البلاغ، شكرًا لك"
 reporter: "المُبلّغ"
 reporteeOrigin: "أصل البلاغ"
 reporterOrigin: "أصل المُبلّغ"
-forwardReport: "وجّه البلاغ إلى المثيل البعيد"
-forwardReportIsAnonymous: "في المثيل البعيد سيظهر المبلّغ كحساب مجهول."
 send: "أرسل"
-abuseMarkAsResolved: "علّم البلاغ كمحلول"
 openInNewTab: "افتح في لسان جديد"
 defaultNavigationBehaviour: "سلوك الملاحة الافتراضي"
 editTheseSettingsMayBreakAccount: "تعديل هذه الإعدادات قد يسبب عطبًا لحسابك"
@@ -1255,7 +1252,6 @@ _theme:
     buttonBg: "خلفية الأزرار"
     buttonHoverBg: "خلفية الأزرار (عند التمرير فوقها)"
     inputBorder: "حواف حقل الإدخال"
-    listItemHoverBg: "خلفية عناصر القائمة (عند التمرير فوقها)"
     driveFolderBg: "خلفية مجلد قرص التخزين"
     messageBg: "خلفية المحادثة"
 _sfx:
@@ -1533,6 +1529,7 @@ _notification:
     reaction: "التفاعل"
     receiveFollowRequest: "طلبات المتابعة"
     followRequestAccepted: "طلبات المتابعة المقبولة"
+    login: "لِج"
     app: "إشعارات التطبيقات المرتبطة"
   _actions:
     followBack: "تابعك بالمثل"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 6fb51ea5d860..0e761b074393 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -451,7 +451,6 @@ or: "অথবা"
 language: "ভাষা"
 uiLanguage: "UI এর ভাষা"
 aboutX: "{x} সম্পর্কে"
-disableDrawer: "ড্রয়ার মেনু প্রদর্শন করবেন না"
 noHistory: "কোনো ইতিহাস নেই"
 signinHistory: "প্রবেশ করার ইতিহাস"
 doing: "প্রক্রিয়া করছে..."
@@ -625,10 +624,7 @@ abuseReported: "আপনার অভিযোগটি দাখিল কর
 reporter: "অভিযোগকারী"
 reporteeOrigin: "অভিযোগটির উৎস"
 reporterOrigin: "অভিযোগকারীর উৎস"
-forwardReport: "রিমোট ইন্সত্যান্সে অভিযোগটি পাঠান"
-forwardReportIsAnonymous: "আপনার তথ্য রিমোট ইন্সত্যান্সে পাঠানো হবে না এবং একটি বেনামী সিস্টেম অ্যাকাউন্ট হিসাবে প্রদর্শিত হবে।"
 send: "পাঠান"
-abuseMarkAsResolved: "অভিযোগটিকে সমাধাকৃত হিসাবে চিহ্নিত করুন"
 openInNewTab: "নতুন ট্যাবে খুলুন"
 openInSideView: "সাইড ভিউতে খুলুন"
 defaultNavigationBehaviour: "ডিফল্ট নেভিগেশন"
@@ -1021,7 +1017,6 @@ _theme:
     buttonBg: "বাটনের পটভূমি"
     buttonHoverBg: "বাটনের পটভূমি (হভার)"
     inputBorder: "ইনপুট ফিল্ডের বর্ডার"
-    listItemHoverBg: "লিস্ট আইটেমের পটভূমি (হোভার)"
     driveFolderBg: "ড্রাইভ ফোল্ডারের পটভূমি"
     wallpaperOverlay: "ওয়ালপেপার ওভারলে"
     badge: "ব্যাজ"
@@ -1314,6 +1309,7 @@ _notification:
     pollEnded: "পোল শেষ"
     receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
     followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
+    login: "প্রবেশ করুন"
     app: "লিঙ্ক করা অ্যাপ থেকে বিজ্ঞপ্তি"
   _actions:
     followBack: "ফলো ব্যাক করেছে"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 7bd9a1bb32de..748f6f03c053 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -2,12 +2,15 @@
 _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! 🚀"
-poweredByMisskeyDescription: "{name} És un del serveis (anomenats instàncies de Misskey) que utilitzen la plataforma de codi obert <b>Misskey</b>."
+poweredByMisskeyDescription: "{name} És un dels serveis (anomenats instàncies de Misskey) que utilitzen la plataforma de codi obert <b>Misskey</b>."
 monthAndDay: "{day}/{month}"
 search: "Cercar"
 notifications: "Notificacions"
 username: "Nom d'usuari"
 password: "Contrasenya"
+initialPasswordForSetup: "Contrasenya inicial per la configuració inicial"
+initialPasswordIsIncorrect: "La contrasenya no és correcta."
+initialPasswordForSetupDescription: "Fes servir la contrasenya que has fet servir al fitxer de configuració, si tu mateix has instal·lat Misskey.\nSi fas servir una empresa d'allotjament de Misskey, fes servir la contrasenya que t'han donat.\nSi no has posat cap contrasenya deixar l'espai en blanc."
 forgotPassword: "Contrasenya oblidada"
 fetchingAsApObject: "Cercant en el Fediverse..."
 ok: "OK"
@@ -15,7 +18,7 @@ gotIt: "Ho he entès!"
 cancel: "Cancel·lar"
 noThankYou: "No, gràcies"
 enterUsername: "Introdueix el teu nom d'usuari"
-renotedBy: "Impulsat per {usuari}"
+renotedBy: "Impulsat per {user}"
 noNotes: "Cap nota"
 noNotifications: "Cap notificació"
 instance: "Servidor"
@@ -60,6 +63,7 @@ copyFileId: "Copiar ID d'arxiu"
 copyFolderId: "Copiar ID de carpeta"
 copyProfileUrl: "Copiar URL del perfil"
 searchUser: "Cercar un usuari"
+searchThisUsersNotes: "Cerca les publicacions de l'usuari"
 reply: "Respondre"
 loadMore: "Carregar més"
 showMore: "Veure més"
@@ -108,11 +112,14 @@ enterEmoji: "Introduir un emoji"
 renote: "Impulsa"
 unrenote: "Anul·la l'impuls"
 renoted: "S'ha impulsat"
+renotedToX: "Impulsat per {name}."
 cantRenote: "No es pot impulsar aquesta publicació"
 cantReRenote: "No es pot impulsar l'impuls."
 quote: "Cita"
 inChannelRenote: "Renotar només al Canal"
 inChannelQuote: "Citar només al Canal"
+renoteToChannel: "Impulsa a un canal"
+renoteToOtherChannel: "Impulsa a un altre canal"
 pinnedNote: "Nota fixada"
 pinned: "Fixar al perfil"
 you: "Tu"
@@ -151,6 +158,7 @@ editList: "Editar llista"
 selectChannel: "Selecciona un canal"
 selectAntenna: "Tria una antena"
 editAntenna: "Modificar antena"
+createAntenna: "Crea una antena"
 selectWidget: "Triar un giny"
 editWidgets: "Editar ginys"
 editWidgetsExit: "Fet"
@@ -177,6 +185,10 @@ addAccount: "Afegeix un compte"
 reloadAccountsList: "Recarregar la llista de contactes"
 loginFailed: "S'ha produït un error al accedir."
 showOnRemote: "Navega més en el perfil original"
+continueOnRemote: "Veure perfil original"
+chooseServerOnMisskeyHub: "Escull un servidor des del Hub de Misskey"
+specifyServerHost: "Especifica un servidor directament"
+inputHostName: "Introdueix el domini"
 general: "General"
 wallpaper: "Fons de Pantalla"
 setWallpaper: "Defineix el fons de pantalla"
@@ -187,6 +199,7 @@ followConfirm: "Estàs segur que vols deixar de seguir {name}?"
 proxyAccount: "Compte de proxy"
 proxyAccountDescription: "Un compte proxy és un compte que actua com a seguidor remot per als usuaris en determinades condicions. Per exemple, quan un usuari afegeix un usuari remot a la llista, l'activitat de l'usuari remot no es lliurarà al servidor si cap usuari local segueix aquest usuari, de manera que el compte proxy el seguirà."
 host: "Amfitrió"
+selectSelf: "Escollir manualment"
 selectUser: "Selecciona usuari/a"
 recipient: "Destinatari"
 annotation: "Comentaris"
@@ -202,6 +215,7 @@ perDay: "Per dia"
 stopActivityDelivery: "Deixa d'enviar activitats"
 blockThisInstance: "Deixa d'enviar activitats"
 silenceThisInstance: "Silencia aquesta instància "
+mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instància "
 operations: "Accions"
 software: "Programari"
 version: "Versió"
@@ -223,6 +237,10 @@ blockedInstances: "Instàncies bloquejades"
 blockedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols bloquejar separades per un salt de pàgina. Les instàncies llistades no podran comunicar-se amb aquesta instància."
 silencedInstances: "Instàncies silenciades"
 silencedInstancesDescription: "Llista els enllaços d'amfitrió de les instàncies que vols silenciar. Tots els comptes de les instàncies llistades s'establiran com silenciades i només podran fer sol·licitacions de seguiment, i no podran mencionar als comptes locals si no els segueixen. Això no afectarà les instàncies bloquejades."
+mediaSilencedInstances: "Instàncies amb els arxius silenciats"
+mediaSilencedInstancesDescription: "Llista els noms dels servidors que vulguis silenciar els arxius, un servidor per línia. Tots els comptes que pertanyin als servidors llistats seran tractats com sensibles i no podran fer servir emojis personalitzats. Això no tindrà efecte sobre els servidors blocats."
+federationAllowedHosts: "Llista de servidors federats"
+federationAllowedHostsDescription: "Llista dels servidors amb els quals es federa."
 muteAndBlock: "Silencia i bloca"
 mutedUsers: "Usuaris silenciats"
 blockedUsers: "Usuaris bloquejats"
@@ -313,6 +331,7 @@ selectFile: "Selecciona fitxers"
 selectFiles: "Selecciona fitxers"
 selectFolder: "Selecció de carpeta"
 selectFolders: "Selecció de carpeta"
+fileNotSelected: "Cap fitxer seleccionat"
 renameFile: "Canvia el nom del fitxer"
 folderName: "Nom de la carpeta"
 createFolder: "Crea una carpeta"
@@ -320,6 +339,7 @@ renameFolder: "Canvia el nom de la carpeta"
 deleteFolder: "Elimina la carpeta"
 folder: "Carpeta "
 addFile: "Afegeix un fitxer"
+showFile: "Mostrar fitxer"
 emptyDrive: "La teva unitat és buida"
 emptyFolder: "La carpeta està buida"
 unableToDelete: "No es pot eliminar"
@@ -434,6 +454,7 @@ totpDescription: "Escriu una contrasenya d'un sol us fent servir l'aplicació d'
 moderator: "Moderador/a"
 moderation: "Moderació"
 moderationNote: "Nota de moderació "
+moderationNoteDescription: "Pots escriure notes que es compartiran entre els moderadors."
 addModerationNote: "Afegir una nota de moderació "
 moderationLogs: "Registre de moderació "
 nUsersMentioned: "{n} usuaris mencionats"
@@ -468,10 +489,12 @@ retype: "Torneu a introduir-la"
 noteOf: "Publicació de: {user}"
 quoteAttached: "Frase adjunta"
 quoteQuestion: "Vols annexar-la com a cita?"
+attachAsFileQuestion: "El text copiat és massa llarg. Vols adjuntar-lo com un fitxer de text?"
 noMessagesYet: "Encara no hi ha missatges"
 newMessageExists: "Has rebut un nou missatge"
 onlyOneFileCanBeAttached: "Només pots adjuntar un fitxer a un missatge"
 signinRequired: "Si us plau, Registra't o inicia la sessió abans de continuar"
+signinOrContinueOnRemote: "Per continuar necessites moure el teu servidor o registrar-te / iniciar sessió en aquest servidor."
 invitations: "Convida"
 invitationCode: "Codi d'invitació"
 checking: "Comprovació en curs..."
@@ -493,7 +516,10 @@ uiLanguage: "Idioma de l'interfície"
 aboutX: "Respecte a {x}"
 emojiStyle: "Estil d'emoji"
 native: "Nadiu"
-disableDrawer: "No mostrar els menús en calaixos"
+menuStyle: "Estil de menú"
+style: "Estil"
+drawer: "Calaix"
+popup: "Emergent"
 showNoteActionsOnlyHover: "Només mostra accions de la nota en passar amb el cursor"
 showReactionsCount: "Mostra el nombre de reaccions a les publicacions"
 noHistory: "No hi ha un registre previ"
@@ -543,7 +569,7 @@ objectStorageUseSSLDesc: "Desactiva'l si no tens pensat fer servir HTTPS per les
 objectStorageUseProxy: "Connectar-se  mitjançant un Proxy"
 objectStorageUseProxyDesc: "Desactiva'l si no faràs servir un Proxy per les connexions de l'API"
 objectStorageSetPublicRead: "Configurar les pujades com públiques "
-s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del dipòsit s'ha d'incloure a l'adreça URL en comtes del nom del host. Potser que necessitis activar-ho quan facis servir, per exemple, Minio a un servidor propi."
+s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del cubell s'haurà d'especificar com a part de l'adreça URL en comptes del nom del servidor. Podria ser que necessitis activar aquesta opció quan facis servir serveis com ara l'allotjament a un servidor propi."
 serverLogs: "Registres del servidor"
 deleteAll: "Elimina-ho tot"
 showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps"
@@ -576,6 +602,8 @@ ascendingOrder: "Ascendent"
 descendingOrder: "Descendent"
 scratchpad: "Bloc de proves"
 scratchpadDescription: "El bloc de proves proporciona un entorn experimental per AiScript. Pot escriure i verificar els resultats que interactuen amb Misskey."
+uiInspector: "Inspector de la interfície"
+uiInspectorDescription: "Podeu visualitzar una llista d'elements UI presents en la memòria. Els components de la interfície d'usuari són generats per les funcions Ui:C:."
 output: "Sortida"
 script: "Script"
 disablePagesScript: "Desactivar AiScript a les pàgines "
@@ -692,10 +720,7 @@ abuseReported: "La teva denúncia s'ha enviat. Moltes gràcies."
 reporter: "Denunciant "
 reporteeOrigin: "Origen de la denúncia "
 reporterOrigin: "Origen del denunciant"
-forwardReport: "Transferir la denúncia a una instància remota"
-forwardReportIsAnonymous: "En lloc del teu compte, es farà servir un compte anònim com a denunciant al servidor remot."
 send: "Envia"
-abuseMarkAsResolved: "Marca la denúncia com a resolta"
 openInNewTab: "Obre a una pestanya nova"
 openInSideView: "Obre a una vista lateral"
 defaultNavigationBehaviour: "Navegació per defecte"
@@ -832,6 +857,7 @@ administration: "Administració"
 accounts: "Comptes"
 switch: "Canvia"
 noMaintainerInformationWarning: "La informació de l'administrador no s'ha configurat"
+noInquiryUrlWarning: "No s'ha desat l'URL de consulta."
 noBotProtectionWarning: "La protecció contra bots no s'ha configurat."
 configure: "Configurar"
 postToGallery: "Crear una nova publicació a la galeria"
@@ -896,6 +922,7 @@ followersVisibility: "Visibilitat dels seguidors"
 continueThread: "Veure la continuació del fil"
 deleteAccountConfirm: "Això eliminarà el teu compte irreversiblement. Procedir?"
 incorrectPassword: "Contrasenya incorrecta."
+incorrectTotp: "La contrasenya no és correcta, o ha caducat."
 voteConfirm: "Confirma el teu vot \"{choice}\""
 hide: "Amagar"
 useDrawerReactionPickerForMobile: "Mostrar el selector de reaccions com un calaix al mòbil "
@@ -920,6 +947,9 @@ oneHour: "1 hora"
 oneDay: "Un dia"
 oneWeek: "Una setmana"
 oneMonth: "Un mes"
+threeMonths: "3 mesos"
+oneYear: "1 any"
+threeDays: "3 dies"
 reflectMayTakeTime: "Això pot trigar una estona a tenir efecte"
 failedToFetchAccountInformation: "No es pot obtenir la informació del compte"
 rateLimitExceeded: "S'ha arribat al màxim de peticions"
@@ -1021,6 +1051,7 @@ thisPostMayBeAnnoyingHome: "Publicar a la línia de temps d'Inici"
 thisPostMayBeAnnoyingCancel: "Cancel·lar "
 thisPostMayBeAnnoyingIgnore: "Publicar de totes maneres"
 collapseRenotes: "Col·lapsar les renotes que ja has vist"
+collapseRenotesDescription: "Col·lapse les notes a les quals ja has reaccionat o que ja has renotat"
 internalServerError: "Error intern del servidor"
 internalServerErrorDescription: "El servidor ha fallat de manera inexplicable."
 copyErrorInfo: "Copiar la informació de l'error "
@@ -1059,6 +1090,7 @@ retryAllQueuesConfirmTitle: "Tornar a intentar-ho tot?"
 retryAllQueuesConfirmText: "Això farà que la càrrega del servidor augmenti temporalment."
 enableChartsForRemoteUser: "Generar gràfiques d'usuaris remots"
 enableChartsForFederatedInstances: "Generar gràfiques d'instàncies remotes"
+enableStatsForFederatedInstances: "Activa les estadístiques de les instàncies remotes federades"
 showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota"
 reactionsDisplaySize: "Mida de les reaccions"
 limitWidthOfReaction: "Limitar l'amplada màxima de la reacció i mostrar-les en una mida reduïda "
@@ -1094,6 +1126,8 @@ preservedUsernames: "Noms d'usuaris reservats"
 preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats."
 createNoteFromTheFile: "Compon una nota des d'aquest fitxer"
 archive: "Arxiu"
+archived: "Arxivat"
+unarchive: "Desarxivar"
 channelArchiveConfirmTitle: "Vols arxivar {name}?"
 channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista de canals o als resultats de cerca. Tampoc es poden afegir noves entrades."
 thisChannelArchived: "Aquest Canal ha sigut arxivat."
@@ -1104,6 +1138,9 @@ preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
 preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
 options: "Opcions"
 specifyUser: "Especificar usuari"
+lookupConfirm: "Vols fer una cerca?"
+openTagPageConfirm: "Vols obrir una pàgina d'etiquetes?"
+specifyHost: "Especifica un servidor"
 failedToPreviewUrl: "Vista prèvia no disponible"
 update: "Actualitzar"
 rolesThatCanBeUsedThisEmojiAsReaction: "Rols que poden fer servir aquest emoji com a reacció "
@@ -1172,7 +1209,10 @@ confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les tev
 confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?"
 externalServices: "Serveis externs"
 sourceCode: "Codi font"
+sourceCodeIsNotYetProvided: "El codi font encara no es troba disponible. Contacta amb l'administrador per solucionar aquest problema."
 repositoryUrl: "URL del repositori"
+repositoryUrlDescription: "Si estàs fent servir Misskey tal com és (sense cap canvi al codi font), introdueix https://github.com/misskey-dev/misskey"
+repositoryUrlOrTarballRequired: "Si no ofereixes cap repositori, publica un fitxer tarball. Dona una ullada a .config/example.yml per a més informació."
 feedback: "Opinió"
 feedbackUrl: "URL per a opinar"
 impressum: "Impressum"
@@ -1211,6 +1251,7 @@ showReplay: "Veure reproducció"
 replay: "Reproduir"
 replaying: "Reproduint"
 endReplay: "Tanca la redifusió"
+copyReplayData: "Copia les dades de la resposta"
 ranking: "Classificació"
 lastNDays: "Últims {n} dies"
 backToTitle: "Torna al títol"
@@ -1224,12 +1265,79 @@ gameRetry: "Torna a provar"
 notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc"
 useTotp: "Usa una contrasenya d'un sol ús"
 useBackupCode: "Usa un codi de recuperació"
+launchApp: "Inicia l'aplicació "
+useNativeUIForVideoAudioPlayer: "Fes servir la UI del navegador quan reprodueixis vídeo i àudio "
+keepOriginalFilename: "Desa el nom del fitxer original"
+keepOriginalFilenameDescription: "Si desactives aquesta opció els noms dels fitxers se substituiran per una cadena aleatòria quan carreguis nous fitxers de forma automàtica."
+noDescription: "No hi ha una descripció "
+alwaysConfirmFollow: "Confirma sempre els seguiments"
+inquiry: "Contacte"
+tryAgain: "Intenta-ho més tard."
+confirmWhenRevealingSensitiveMedia: "Confirmació quan revelis contingut sensible "
+sensitiveMediaRevealConfirm: "Aquest contingut potser sensible. Segur que ho vols revelar?"
+createdLists: "Llistes creades "
+createdAntennas: "Antenes creades"
+fromX: "De {x}"
+genEmbedCode: "Obtenir el codi per incrustar"
+noteOfThisUser: "Notes d'aquest usuari"
+clipNoteLimitExceeded: "No es poden afegir més notes a aquest clip."
+performance: "Rendiment"
+modified: "Modificat"
+discard: "Descarta"
+thereAreNChanges: "Hi ha(n) {n} canvi(s)"
+signinWithPasskey: "Inicia sessió amb Passkey"
+unknownWebAuthnKey: "Passkey desconeguda"
+passkeyVerificationFailed: "La verificació a fallat"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificació de la passkey a estat correcta, però s'ha deshabilitat l'inici de sessió sense contrasenya."
+messageToFollower: "Missatge als meus seguidors"
+target: "Assumpte "
+testCaptchaWarning: "És una característica dissenyada per a la prova de CAPTCHA. <strong>No l'utilitzes en l'entorn real.</strong>"
+prohibitedWordsForNameOfUser: "Noms prohibits per escollir noms d'usuari "
+prohibitedWordsForNameOfUserDescription: "Si qualsevol d'aquestes paraules es troben a un nom d'usuari la creació de l'usuari no es durà a terme. Als moderadors no els afecta aquesta restricció."
+yourNameContainsProhibitedWords: "El nom conté paraules prohibides "
+yourNameContainsProhibitedWordsDescription: "Si de veritat vols fer servir aquest nom posat en contacte amb l'administrador."
+thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autor requereix l'inici de sessió per poder veure"
+lockdown: "Bloquejat"
+pleaseSelectAccount: "Seleccionar un compte"
+_accountSettings:
+  requireSigninToViewContents: "És obligatori l'inici de sessió per poder veure el contingut"
+  requireSigninToViewContentsDescription1: "Es requereix l'inici de sessió per poder veure totes les notes i el contingut que has creat. Amb això esperem evitar que els rastrejadors recopilin informació."
+  requireSigninToViewContentsDescription2: "També es desactivaran les vistes prèvies d'URLS (OGP), la incrustació a pàgines web i la visualització des de servidors que no admetin la citació de notes."
+  requireSigninToViewContentsDescription3: "Aquestes restriccions pot ser que no s'apliquin als continguts federats en servidors remots."
+  makeNotesFollowersOnlyBefore: "Permetre que les notes antigues només es mostrin als seguidors."
+  makeNotesFollowersOnlyBeforeDescription: "Mentre aquesta funció estigui activada, les notes que hagin passat la data i hora fixada o hagi passat els temps establert seran visibles només per als teus seguidors. Quan es desactivi, també es restableix l'estat públic de la nota."
+  makeNotesHiddenBefore: "Fes que les notes antigues siguin privades"
+  makeNotesHiddenBeforeDescription: "Mentres aquesta funció estigui activada les notes que hagin superat una data i hora fixada o hagi passat el temps establert només seran visibles per a tu. Si la desactives es restablirà també l'estat públic de les notes."
+  mayNotEffectForFederatedNotes: "Això pot ser que no afecti les notes federades."
+  notesHavePassedSpecifiedPeriod: "Notes publicades durant un període de temps especificat."
+  notesOlderThanSpecifiedDateAndTime: "Notes més antigues de la data i temps especificat "
+_abuseUserReport:
+  forward: "Reenviar "
+  forwardDescription: "Reenvia l'informe a una altra instància com un compte del sistema anònima."
+  resolve: "Solució "
+  accept: "Acceptar "
+  reject: "Rebutjar"
+  resolveTutorial: "Si l'informe és legítim selecciona \"Acceptar\" per resoldre'l positivament. Però si l'informe no és legítim selecciona \"Rebutjar\" per resoldre'l negativament."
 _delivery:
+  status: "Estat d'entrega "
   stop: "Suspés"
+  resume: "Torna a enviar"
   _type:
     none: "S'està publicant"
+    manuallySuspended: "Suspendre manualment"
+    goneSuspended: "Servidor suspès perquè el servidor s'ha esborrat"
+    autoSuspendedForNotResponding: "Servidor suspès perquè el servidor no respon"
 _bubbleGame:
   howToPlay: "Com es juga"
+  hold: "Mantenir"
+  _score:
+    score: "Puntuació "
+    scoreYen: "Diners guanyats"
+    highScore: "Millor puntuació "
+    maxChain: "Nombre màxim de combos"
+    yen: "{yen}Ien"
+    estimatedQty: "{qty}peces"
+    scoreSweets: "{onigiriQtyWithUnit}ongiris"
   _howToPlay:
     section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa."
     section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts."
@@ -1344,6 +1452,10 @@ _serverSettings:
   fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
   fanoutTimelineDbFallback: "Carregar de la base de dades"
   fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir."
+  reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les línies de temps reduint la càrrega de la base. Com a contrapunt, augmentarà  l'ús de memòria de Redís. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat."
+  inquiryUrl: "URL de consulta "
+  inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pàgina web amb el contacte d'informació."
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Si no es detecta activitat per part del moderador durant un període de temps, aquesta opció es desactiva automàticament per evitar el correu brossa."
 _accountMigration:
   moveFrom: "Migrar un altre compte a aquest"
   moveFromSub: "Crear un àlies per un altre compte"
@@ -1651,6 +1763,7 @@ _role:
     gtlAvailable: "Pot veure la línia de temps global"
     ltlAvailable: "Pot veure la línia de temps local"
     canPublicNote: "Pot enviar notes públiques"
+    mentionMax: "Nombre màxim de mencions a una nota"
     canInvite: "Pot crear invitacions a la instància "
     inviteLimit: "Límit d'invitacions "
     inviteLimitCycle: "Temps de refresc de les invitacions"
@@ -1659,6 +1772,7 @@ _role:
     canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
     driveCapacity: "Capacitat del disc"
     alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
+    canUpdateBioMedia: "Permet l'edició d'una icona o un bàner"
     pinMax: "Nombre màxim de notes fixades"
     antennaMax: "Nombre màxim d'antenes"
     wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades"
@@ -1673,9 +1787,20 @@ _role:
     canSearchNotes: "Pot cercar notes"
     canUseTranslator: "Pot fer servir el traductor"
     avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
+    canImportAntennas: "Autoritza la importació d'antenes "
+    canImportBlocking: "Autoritza la importació de bloquejats"
+    canImportFollowing: "Autoritza la importació de seguidors"
+    canImportMuting: "Autoritza la importació de silenciats"
+    canImportUserLists: "Autoritza la importació de llistes d'usuaris "
   _condition:
+    roleAssignedTo: "Assignat a rols manuals"
     isLocal: "Usuari local"
     isRemote: "Usuari remot"
+    isCat: "Usuaris gats"
+    isBot: "Usuaris bots"
+    isSuspended: "Usuari suspès"
+    isLocked: "Comptes privats"
+    isExplorable: "Fes que el compte aparegui a les cerques"
     createdLessThan: "Han passat menys de X a passat des de la creació del compte"
     createdMoreThan: "Han passat més de X des de la creació del compte"
     followersLessThanOrEq: "Té menys de X seguidors"
@@ -1745,6 +1870,7 @@ _plugin:
   installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança."
   manage: "Gestionar els afegits"
   viewSource: "Veure l'origen "
+  viewLog: "Mostra el registre"
 _preferencesBackups:
   list: "Llista de còpies de seguretat"
   saveNew: "Fer una còpia de seguretat nova"
@@ -1774,6 +1900,8 @@ _aboutMisskey:
   contributors: "Col·laboradors principals"
   allContributors: "Tots els col·laboradors "
   source: "Codi font"
+  original: "Original"
+  thisIsModifiedVersion: "En {name} fa servir una versió modificada de Misskey."
   translation: "Tradueix Misskey"
   donate: "Fes un donatiu a Misskey"
   morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰"
@@ -1881,7 +2009,6 @@ _theme:
     buttonBg: "Fons botó "
     buttonHoverBg: "Fons botó (en passar-hi per sobre)"
     inputBorder: "Contorn del cap d'introducció "
-    listItemHoverBg: "Fons dels elements d'una llista"
     driveFolderBg: "Fons de la carpeta Disc"
     wallpaperOverlay: "Superposició del fons de pantalla "
     badge: "Insígnia "
@@ -1901,6 +2028,7 @@ _soundSettings:
   driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio "
   driveFileDurationWarn: "L'àudio és massa llarg"
   driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?"
+  driveFileError: "El so no es pot carregar. Canvia la configuració"
 _ago:
   future: "Futur "
   justNow: "Ara mateix"
@@ -1953,6 +2081,7 @@ _2fa:
   backupCodesDescription: "Si l'aplicació d'autenticació no es pot utilitzar, es pot accedir al compte utilitzant els següents codis de còpia de seguretat. Assegura't de mantenir aquests codis en un lloc segur. Cada codi es pot utilitzar només una vegada."
   backupCodeUsedWarning: "Es va utilitzar un codi de còpia de seguretat. Si l'aplicació de certificació està disponible, reconfigura l'aplicació d'autenticació tan aviat com sigui possible."
   backupCodesExhaustedWarning: "Es van utilitzar tots els codis de còpia de seguretat. Si no es pot utilitzar l'aplicació d'autenticació, ja no es pot accedir al compte. Torna a registrar l'aplicació d'autenticació."
+  moreDetailedGuideHere: "Aquí tens una guia al detall"
 _permissions:
   "read:account": "Veure la informació del compte."
   "write:account": "Editar la informació del compte."
@@ -2026,22 +2155,76 @@ _permissions:
   "read:admin:emoji": "Veure emojis"
   "write:admin:queue": "Gestionar la cua de feines"
   "read:admin:queue": "Veure la cua de feines"
+  "write:admin:promo": "Gestiona les notes promocionals"
+  "write:admin:drive": "Gestiona el disc de l'usuari"
+  "read:admin:drive": "Veure la informació del disc de l'usuari"
+  "read:admin:stream": "Fes servir l'API sobre Websocket per l'administració"
+  "write:admin:ad": "Gestiona la publicitat"
+  "read:admin:ad": "Veure anuncis"
+  "write:invite-codes": "Crear codis d'invitació"
+  "read:invite-codes": "Obtenir codis d'invitació"
+  "write:clip-favorite": "Gestionar els clips favorits"
+  "read:clip-favorite": "Veure clips favorits"
+  "read:federation": "Veure dades de federació"
+  "write:report-abuse": "Informar d'un abús"
+_auth:
+  shareAccessTitle: "Concedeix permisos a l'aplicació"
+  shareAccess: "Vols que {name} pugui accedir al vostre compte?"
+  shareAccessAsk: "Segur que vols que aquesta aplicació pugui accedir al vostre compte?"
+  permission: "{name} demana els següents permisos"
+  permissionAsk: "Aquesta aplicació demana els següents permisos"
+  pleaseGoBack: "Si us plau, torna a l'aplicació"
+  callback: "Tornant a l'aplicació"
+  accepted: "Accés garantit"
+  denied: "Accés denegat"
+  scopeUser: "Opera com si fossis aquest usuari"
+  pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació."
+  byClickingYouWillBeRedirectedToThisUrl: "Si es garanteix l'accés, seràs redirigit automàticament a la següent adreça URL"
 _antennaSources:
   all: "Totes les publicacions"
   homeTimeline: "Publicacions dels usuaris seguits"
   users: "Publicacions d'usuaris específics"
   userList: "Publicacions d'una llista d'usuaris"
+  userBlacklist: "Totes les notes excepte les d'un o alguns usuaris especificats"
+_weekday:
+  sunday: "Diumenge"
+  monday: "Dilluns"
+  tuesday: "Dimarts"
+  wednesday: "Dimecres"
+  thursday: "Dijous"
+  friday: "Divendres"
+  saturday: "Dissabte"
 _widgets:
   profile: "Perfil"
   instanceInfo: "Informació del fitxer d'instal·lació"
+  memo: "Notes adhesives"
   notifications: "Notificacions"
   timeline: "Línia de temps"
+  calendar: "Calendari"
+  trends: "Tendència"
+  clock: "Rellotge"
+  rss: "Lector RSS"
+  rssTicker: "RSS ticker"
   activity: "Activitat"
+  photos: "Fotografies"
+  digitalClock: "Rellotge digital"
+  unixClock: "Rellotge UNIX"
   federation: "Federació"
+  instanceCloud: "Núvol d'instàncies"
+  postForm: "Formulari de publicació"
+  slideshow: "Presentació"
   button: "Botó "
+  onlineUsers: "Usuaris actius"
   jobQueue: "Cua de tasques"
+  serverMetric: "Mètriques del servidor"
+  aiscript: "Consola AiScript"
+  aiscriptApp: "Aplicació AiScript"
+  aichan: "Ai"
+  userList: "Llistat d'usuaris"
   _userList:
     chooseList: "Tria una llista"
+  clicker: "Clicker"
+  birthdayFollowings: "Usuaris que fan l'aniversari avui"
 _cw:
   hide: "Amagar"
   show: "Carregar més"
@@ -2105,27 +2288,79 @@ _profile:
   changeBanner: "Canviar el bàner "
   verifiedLinkDescription: "Escrivint una adreça URL que enllaci a aquest perfil, una icona de propietat verificada es mostrarà al costat del camp."
   avatarDecorationMax: "Pot afegir un màxim de {max} decoracions."
+  followedMessage: "Missatge als nous seguidors"
+  followedMessageDescription: "Es pot configurar un missatge curt que es mostra a l'altra persona quan comença a seguir-te."
+  followedMessageDescriptionForLockedAccount: "Si comencen a seguir-te es mostra un missatge de quan es permet aquesta sol·licitud. "
 _exportOrImport:
   allNotes: "Totes les publicacions"
+  favoritedNotes: "Notes preferides"
   clips: "Retalls"
   followingList: "Seguint"
   muteList: "Silencia"
   blockingList: "Bloqueja"
   userLists: "Llistes"
+  excludeMutingUsers: "Exclou usuaris silenciats"
+  excludeInactiveUsers: "Exclou usuaris inactius"
+  withReplies: "Inclou a la línia de temps les respostes d'usuaris importats"
 _charts:
   federation: "Federació"
+  apRequest: "Peticions"
+  usersIncDec: "Diferència entre el nombre d'usuaris"
+  usersTotal: "Nombre total d'usuaris"
+  activeUsers: "Usuaris actius"
+  notesIncDec: "Diferència entre el nombre de notes"
+  localNotesIncDec: "Diferencia en el nombre de notes locals"
+  remoteNotesIncDec: "Diferencia en el nombre de notes remotes"
+  notesTotal: "Nombre total de notes"
+  filesIncDec: "Diferencia en el nombre de fitxers"
+  filesTotal: "Nombre total de fitxers"
+  storageUsageIncDec: "Diferencia en l'emmagatzematge usat"
+  storageUsageTotal: "Emmagatzematge usat"
+_instanceCharts:
+  requests: "Peticions"
+  users: "Diferència entre el nombre d'usuaris"
+  usersTotal: "Usuaris totals acumulats"
+  notes: "Diferència entre el nombre de notes"
+  notesTotal: "Notes totals acumulades"
+  ff: "Diferència en nombre d'usuaris seguits / seguidors"
+  ffTotal: "Nombre total acumulat d'usuaris seguits / seguidors"
+  cacheSize: "Diferència a la mida de la memòria cau"
+  cacheSizeTotal: "Total acumulat de la mida de la memòria cau"
+  files: "Diferència al nombre d'arxius"
+  filesTotal: "Nombre acumulatiu de fitxers"
 _timelines:
   home: "Inici"
   local: "Local"
   social: "Social"
   global: "Global"
 _play:
+  new: "Crear un guió"
+  edit: "Editar guió"
+  created: "Guió creat"
+  updated: "Guió editat"
+  deleted: "Guió esborrat"
+  pageSetting: "Configuració del guió"
+  editThisPage: "Edita aquest guió"
   viewSource: "Veure l'origen "
+  my: "Els meus guions"
+  liked: "Guions que m'han agradat"
   featured: "Popular"
   title: "Títol "
   script: "Script"
   summary: "Descripció"
+  visibilityDescription: ""
 _pages:
+  newPage: "pa"
+  editPage: "Editar la pàgina"
+  readPage: "Veure el codi font d'aquesta pàgina"
+  created: "La pàgina ha sigut creada correctament"
+  updated: "La pàgina s'ha editat correctament"
+  deleted: "La pàgina s'ha esborrat sense problemes"
+  pageSetting: "Configuració de la pàgina"
+  nameAlreadyExists: "L'adreça URL de la pàgina ja existeix"
+  invalidNameTitle: "L'adreça URL de la pàgina no és vàlida"
+  invalidNameText: "Assegurat que el títol de la pàgina no és buit"
+  editThisPage: "Editar la pàgina"
   viewSource: "Veure l'origen "
   viewPage: "Veure les teves pàgines "
   like: "M'agrada "
@@ -2148,6 +2383,7 @@ _pages:
   eyeCatchingImageSet: "Escull una miniatura"
   eyeCatchingImageRemove: "Esborrar la miniatura"
   chooseBlock: "Afegeix un bloc"
+  enterSectionTitle: "Escriu el títol de la secció"
   selectType: "Seleccionar tipus"
   contentBlocks: "Contingut"
   inputBlocks: "Entrada "
@@ -2158,6 +2394,8 @@ _pages:
     section: "Secció "
     image: "Imatges"
     button: "Botó "
+    dynamic: "Blocs dinàmics"
+    dynamicDescription: "Aquest bloc és antic. Ara en endavant fes servir {play}"
     note: "Incorporar una Nota"
     _note:
       id: "ID de la publicació"
@@ -2187,29 +2425,55 @@ _notification:
   sendTestNotification: "Enviar notificació de prova"
   notificationWillBeDisplayedLikeThis: "Les notificacions és veure'n així "
   reactedBySomeUsers: "Han reaccionat {n} usuaris"
+  likedBySomeUsers: "A {n} usuaris els hi agrada la teva nota"
   renotedBySomeUsers: "L'han impulsat {n} usuaris"
+  followedBySomeUsers: "Et segueixen {n} usuaris"
+  flushNotification: "Netejar notificacions"
+  exportOfXCompleted: "Completada l'exportació de {x}"
+  login: "Algú ha iniciat sessió "
   _types:
     all: "Tots"
+    note: "Notes noves"
     follow: "Seguint"
     mention: "Menció"
+    reply: "Respostes"
     renote: "Renotar"
     quote: "Citar"
     reaction: "Reaccions"
+    pollEnded: "Enquesta terminada"
+    receiveFollowRequest: "Rebuda una petició de seguiment"
+    followRequestAccepted: "Petició de seguiment acceptada"
+    roleAssigned: "Rol donat"
+    achievementEarned: "Assoliment desbloquejat"
+    exportCompleted: "Exportació completada"
+    login: "Iniciar sessió"
+    test: "Prova la notificació"
+    app: "Notificacions d'aplicacions"
   _actions:
     followBack: "t'ha seguit també"
     reply: "Respondre"
     renote: "Renotar"
 _deck:
+  alwaysShowMainColumn: "Mostrar sempre la columna principal"
   columnAlign: "Alinea les columnes"
   addColumn: "Afig una columna"
+  newNoteNotificationSettings: "Configuració de notificacions per a notes noves"
+  configureColumn: "Configuració de columnes"
   swapLeft: "Mou a l’esquerra"
   swapRight: "Mou a la dreta"
   swapUp: "Mou cap amunt"
   swapDown: "Mou cap avall"
+  stackLeft: "Pila a la columna esquerra"
   popRight: "Col·loca a la dreta"
   profile: "Perfil"
   newProfile: "Perfil nou"
   deleteProfile: "Elimina el perfil"
+  introduction: "Crea la interfície perfecta posant les columnes allà on vulguis!"
+  introduction2: "Fes clic al botó + de la dreta per afegir noves columnes sempre que vulguis."
+  widgetsIntroduction: "Selecciona \"Editar ginys\" a la columna del menú i afegeix un."
+  useSimpleUiForNonRootPages: "Usa una interfície senzilla per a les pàgines navegades"
+  usedAsMinWidthWhenFlexible: "L'amplada mínima es farà servir quan \"Ajust automàtic de l'amplada\" estigui activat"
+  flexible: "Ajust automàtic de l'amplada"
   _columns:
     main: "Principal"
     widgets: "Ginys"
@@ -2220,21 +2484,84 @@ _deck:
     channel: "Canals"
     mentions: "Mencions"
     direct: "Publicacions directes"
+    roleTimeline: "Línia de temps dels rols"
+_dialog:
+  charactersExceeded: "Has arribat al màxim de caràcters! Actualment és {current} de {max}"
+  charactersBelow: "Ets per sota del mínim de caràcters! Actualment és {current} de {min}"
+_disabledTimeline:
+  title: "Línia de tems desactivada"
+  description: "No pots fer servir aquesta línia de temps amb els teus rols actuals."
+_drivecleaner:
+  orderBySizeDesc: "Mida del fitxer descendent"
+  orderByCreatedAtAsc: "Data ascendent"
 _webhookSettings:
+  createWebhook: "Crear un Webhook"
+  modifyWebhook: "Modificar un Webhook"
   name: "Nom"
+  secret: "Secret"
+  trigger: "Activador"
   active: "Activat"
+  _events:
+    follow: "Quan se segueix a un usuari"
+    followed: "Quan et segueixen"
+    note: "Quan es publica una nota"
+    reply: "Quan es rep una resposta"
+    renote: "Quan es renoti"
+    reaction: "Quan es rep una reacció "
+    mention: "Quan et mencionen"
+  _systemEvents:
+    abuseReport: "Quan reps un nou informe de moderació "
+    abuseReportResolved: "Quan resols un informe de moderació "
+    userCreated: "Quan es crea un usuari"
+    inactiveModeratorsWarning: "Quan el compte d'un moderador no té activitat durant un temps"
+    inactiveModeratorsInvitationOnlyChanged: "Quan el compte d'un moderador no té activitat durant un temps, i el servidor es canvia a registre per invitacions"
+  deleteConfirm: "Segur que vols esborrar el webhook?"
+  testRemarks: "Si feu clic al botó a la dreta de l'interruptor, podeu enviar un webhook de prova amb dades dummy."
 _abuseReport:
   _notificationRecipient:
+    createRecipient: "Afegeix un destinatari a l'informe de moderació "
+    modifyRecipient: "Editar un destinatari en l'informe de moderació "
+    recipientType: "Tipus de notificació "
     _recipientType:
       mail: "Correu electrònic"
+      webhook: "Webhook"
+      _captions:
+        mail: "Enviar un correu electrònic a tots els moderadors quan es rep un informe de moderació "
+        webhook: "Enviar una notificació al SystemWebhook quan es rebi o es resolgui un informe de moderació "
+    keywords: "Paraules clau"
+    notifiedUser: "Usuaris que s'han de notificar "
+    notifiedWebhook: "Webhook que s'ha de fer servir"
+    deleteConfirm: "Segur que vols esborrar el destinatari de l'informe de moderació?"
 _moderationLogTypes:
+  createRole: "Rol creat"
+  deleteRole: "Rol esborrat"
+  updateRole: "Rol actualitzat"
+  assignRole: "Assignat al rol"
+  unassignRole: "Esborrat del rol"
   suspend: "Suspèn"
+  unsuspend: "Suspensió treta"
+  addCustomEmoji: "Afegit emoji personalitzat"
+  updateCustomEmoji: "Actualitzat emoji personalitzat"
+  deleteCustomEmoji: "Esborrat emoji personalitzat"
+  updateServerSettings: "Configuració del servidor actualitzada"
+  updateUserNote: "Nota de moderació actualitzada"
+  deleteDriveFile: "Fitxer esborrat"
+  deleteNote: "Nota esborrada"
+  createGlobalAnnouncement: "Anunci global creat"
+  createUserAnnouncement: "Anunci individual creat"
+  updateGlobalAnnouncement: "Anunci global actualitzat"
+  updateUserAnnouncement: "Anunci individual actualitzat "
+  deleteGlobalAnnouncement: "Anunci global esborrat"
+  deleteUserAnnouncement: "Anunci individual esborrat "
   resetPassword: "Restableix la contrasenya"
   suspendRemoteInstance: "Servidor remot suspès "
   unsuspendRemoteInstance: "S'ha tret la suspensió del servidor remot"
+  updateRemoteInstanceNote: "Nota de moderació de la instància remota actualitzada"
   markSensitiveDriveFile: "Fitxer marcat com a sensible"
   unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer"
   resolveAbuseReport: "Informe resolt"
+  forwardAbuseReport: "Informe reenviat"
+  updateAbuseReportNote: "Nota de moderació d'un informe actualitzat"
   createInvitation: "Crear codi d'invitació "
   createAd: "Anunci creat"
   deleteAd: "Anunci esborrat"
@@ -2244,6 +2571,16 @@ _moderationLogTypes:
   deleteAvatarDecoration: "S'ha esborrat la decoració de l'avatar "
   unsetUserAvatar: "Esborrar l'avatar d'aquest usuari"
   unsetUserBanner: "Esborrar el bàner d'aquest usuari"
+  createSystemWebhook: "Crear un SystemWebhook"
+  updateSystemWebhook: "Actualitzar SystemWebhook"
+  deleteSystemWebhook: "Esborrar SystemWebhook"
+  createAbuseReportNotificationRecipient: "Crear un destinatari per l'informe de moderació "
+  updateAbuseReportNotificationRecipient: "Actualitzar destinatari per l'informe de moderació "
+  deleteAbuseReportNotificationRecipient: "Esborrar destinatari de l'informe de moderació "
+  deleteAccount: "Esborrar el compte "
+  deletePage: "Esborrar la pàgina"
+  deleteFlash: "Esborrar el guió"
+  deleteGalleryPost: "Esborrar la publicació de la galeria"
 _fileViewer:
   title: "Detall del fitxer"
   type: "Tipus de fitxer"
@@ -2270,5 +2607,133 @@ _externalResourceInstaller:
   _errors:
     _invalidParams:
       title: "Paràmetres no vàlids "
+      description: "No hi ha suficient informació per carregar les dades del lloc extern. Confirma l'URL que hi ha escrita."
+    _resourceTypeNotSupported:
+      title: "El recurs extern no està suportat."
+      description: "Aquesta mena de recurs no està suportat. Contacta amb l'administrador."
+    _failedToFetch:
+      title: "Ha fallat l'obtenció de dades"
+      fetchErrorDescription: "Ha aparegut un error comunicant-se amb el lloc extern. Si després d'intentar-ho un altre cop no es resol, contacta amb l'administrador."
+      parseErrorDescription: "Ha aparegut un error processant les dades carregades del lloc extern. Contacta amb l'administrador."
+    _hashUnmatched:
+      title: "Ha fallat la verificació de les dades"
+      description: "Ha aparegut un error verificant les dades obtingudes. Com a mesura de seguretat la instal·lació no pot continuar. Contacta amb l'administrador."
+    _pluginParseFailed:
+      title: "Error d'AiScript"
+      description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament d'AiScript. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript."
+    _pluginInstallFailed:
+      title: "La instal·lació de l'afegit a fallat"
+      description: "Ha aparegut un error durant la instal·lació de l'afegit. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript."
+    _themeParseFailed:
+      title: "Ha fallat el processament del tema"
+      description: "Les dades sol·licitades s'han obtingut correctament, però hem trobat un error durant el processament del tema. Contacta amb l'autor de l'afegit. Detalls de l'error es pot veure a la consola JavaScript."
+    _themeInstallFailed:
+      title: "La instal·lació del tema a fallat"
+      description: "Ha aparegut un error durant la instal·lació del tema. Intenta-ho una altra vegada. El detall de l'error es pot veure a la consola JavaScript."
+_dataSaver:
+  _media:
+    title: "Carregant multimèdia "
+    description: "Desactiva la càrrega automàtica d'imatges i vídeos. Les imatges i els vídeos amagats es carregaran quan es faci clic a sobre."
+  _avatar:
+    title: "Avatars animats"
+    description: "Detenir l'animació dels avatars animats. Les imatges animades solen tenir un pes més gran que les imatges normals, reduint el tràfic disponible."
+  _urlPreview:
+    title: "Miniatures vista prèvia de l'URL"
+    description: "Les imatges en miniatura que serveixen com a vista prèvia de les URLs no es tornaran a carregar."
+  _code:
+    title: "Ressaltat del codi "
+    description: "Quan s'utilitza codi MFM, no es llegeix fins que es copiï. En els punts destacats del codi s'han de llegir els fitxers definits per a cada llengua que resulti alt, però no es poden llegir automàticament, per la qual cosa es poden reduir les quantitats de comunicació."
+_hemisphere:
+  N: "Hemisferi Nord "
+  S: "Hemisferi Sud"
+  caption: "El fan servir alguns clients per determinar l'estació de l'any."
 _reversi:
+  reversi: "Reversi"
+  gameSettings: "Opcions del joc"
+  chooseBoard: "Escull un taulell"
+  blackOrWhite: "Negres/Blanques"
+  blackIs: "{name} juga amb negres "
+  rules: "Regles"
+  thisGameIsStartedSoon: "El joc començarà en breu"
+  waitingForOther: "Esperant la tirada de l'oponent "
+  waitingForMe: "Esperant el teu torn"
+  waitingBoth: "Prepara't "
+  ready: "Preparat "
+  cancelReady: " No preparat "
+  opponentTurn: "Torn de l'oponent "
+  myTurn: "El teu torn"
+  turnOf: "Li toca a {name}"
+  pastTurnOf: "Torn de {name}"
+  surrender: "Rendeix-te"
+  surrendered: "T'has rendit"
+  timeout: "Temps esgotat"
+  drawn: "Empat"
+  won: "{name} ha guanyat"
+  black: "Negres"
+  white: "Blanques"
   total: "Total"
+  turnCount: "Torn {count}"
+  myGames: "Jugades"
+  allGames: "Totes les jugades"
+  ended: "Acabat"
+  playing: "Jugant"
+  isLlotheo: "Qui tingui menys pedres guanya (Llotheo)"
+  loopedMap: "Mapa de recursiu"
+  canPutEverywhere: "Les fitxes es poden posar a qualsevol lloc"
+  timeLimitForEachTurn: "Temps límit per jugada"
+  freeMatch: "Partida lliure"
+  lookingForPlayer: "Buscant contrincant..."
+  gameCanceled: "La partida s'ha cancel·lat "
+  shareToTlTheGameWhenStart: "Compartir la partida a la línia de temps quan comenci"
+  iStartedAGame: "La partida ha començat! #MisskeyReversi"
+  opponentHasSettingsChanged: "L'oponent h canviat la seva configuració "
+  allowIrregularRules: "Regles irregulars (totalment lliure)"
+  disallowIrregularRules: "Sense regles irregulars"
+  showBoardLabels: "Mostrar el número de línia i columna al tauler de joc"
+  useAvatarAsStone: "Fer servir els avatars dels usuaris com a fitxes"
+_offlineScreen:
+  title: "Fora de línia - No es pot connectar amb el servidor"
+  header: "Impossible connectar amb el servidor"
+_urlPreviewSetting:
+  title: "Configuració per a la previsualització de l'URL"
+  enable: "Activa la previsualització de l'URL"
+  timeout: "Temps màxim per carregar la previsualització de l'URL (ms)"
+  timeoutDescription: "Si l'obtenció de la previsualització triga més que el temps establert, no es generarà la vista prèvia."
+  maximumContentLength: "Longitud màxima del contingut (bytes)"
+  maximumContentLengthDescription: "Si la màxima longitud és més gran que aquest valor, la previsualització no es generarà."
+  requireContentLength: "Generar la previsualització només si es pot obtenir la longitud màxima "
+  requireContentLengthDescription: "Si l'altre servidor no proporciona la longitud màxima, la previsualització no es generarà."
+  userAgent: "User-Agent"
+  userAgentDescription: "Estableix l'User-Agent que és farà servir per a la recuperació de la vista prèvia. Si és deixa en blanc es farà servir l'User-Agent per defecte."
+  summaryProxy: "Proxy endpoints per generar vistes prèvies"
+  summaryProxyDescription: "La vista prèvia es genera fent servir Summaly proxy, no la genera el mateix Misskey."
+  summaryProxyDescription2: "Els següents paràmetres són passats al proxy com cadenes de consulta. Si el proxy no els admet, s'ignoren els valors configurats."
+_mediaControls:
+  pip: "Imatge sobre impressionada "
+  playbackRate: "Velocitat de reproducció "
+  loop: "Reproducció en bucle"
+_contextMenu:
+  title: "Menú contextual"
+  app: "Aplicació "
+  appWithShift: "Aplicació amb la tecla shift"
+  native: "Interfície del navegador"
+_embedCodeGen:
+  title: "Personalitza el codi per incrustar"
+  header: "Mostrar la capçalera"
+  autoload: "Carregar automàticament (no recomanat)"
+  maxHeight: "Alçada màxima"
+  maxHeightDescription: "0 anul·la la configuració màxima. Per evitar que continuï creixent verticalment, especifiqui qualsevol valor."
+  maxHeightWarn: "El límit màxim d'alçada és nul (0). Si això no és un canvi previst, estableix el màxim d'alçada a un cert valor."
+  previewIsNotActual: "La visualització és diferent de la que es mostra quan s'implanta."
+  rounded: "Angle recte"
+  border: "Afegeix un marc al contenidor"
+  applyToPreview: "Aplica a la vista prèvia"
+  generateCode: "Crea el codi per incrustar"
+  codeGenerated: "Codi generat"
+  codeGeneratedDescription: "Si us plau, enganxeu el codi generat al lloc web."
+_selfXssPrevention:
+  warning: "Advertència "
+  title: "\"Enganxa qualsevol cosa en aquesta finestra\"  És tot un engany."
+  description1: "Si posa alguna cosa al seu compte, un usuari malintencionat podria segrestar-la o robar-li les dades."
+  description2: "Si no entens que estàs fent %cpara ara mateix i tanca la finestra."
+  description3: "Per obtenir més informació. {link}"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 7db742476292..caf6d6e16349 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -471,7 +471,6 @@ uiLanguage: "Jazyk uživatelského rozhraní"
 aboutX: "O {x}"
 emojiStyle: "Styl emoji"
 native: "Výchozí"
-disableDrawer: "Nepoužívat šuplíkové menu"
 showNoteActionsOnlyHover: "Zobrazit akce poznámky jenom při naběhnutí myši"
 noHistory: "Žádná historie"
 signinHistory: "Historie přihlášení"
@@ -658,10 +657,7 @@ abuseReported: "Nahlášení bylo odesláno. Děkujeme převelice."
 reporter: "Nahlásil"
 reporteeOrigin: "Původ nahlášení"
 reporterOrigin: "Původ nahlasovače"
-forwardReport: "Přeposlat nahlášení do vzdálené instance"
-forwardReportIsAnonymous: "Místo vašeho účtu se ve vzdálené instanci zobrazí anonymní systémový účet jako nahlašovač."
 send: "Odeslat"
-abuseMarkAsResolved: "Označit nahlášení jako vyřešené"
 openInNewTab: "Otevřít v nové kartě"
 openInSideView: "Otevřít v bočním panelu"
 defaultNavigationBehaviour: "Výchozí chování navigace"
@@ -1633,7 +1629,6 @@ _theme:
     buttonBg: "Pozadí tlačítka"
     buttonHoverBg: "Pozadí tlačítka (Hover)"
     inputBorder: "Ohraničení vstupního pole"
-    listItemHoverBg: "Pozadí položky seznamu (Hover)"
     driveFolderBg: "Pozadí složky disku"
     wallpaperOverlay: "Překrytí tapety"
     badge: "Odznak"
@@ -1963,6 +1958,7 @@ _notification:
     receiveFollowRequest: "Obdržené žádosti o sledování"
     followRequestAccepted: "Přijaté žádosti o sledování"
     achievementEarned: "Úspěch odemčen"
+    login: "Přihlásit se"
     app: "Oznámení z propojených aplikací"
   _actions:
     followBack: "vás začal sledovat zpět"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 8e44a3bbd4fc..4e2bd069340a 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -491,7 +491,6 @@ uiLanguage: "Sprache der Benutzeroberfläche"
 aboutX: "Über {x}"
 emojiStyle: "Emoji-Stil"
 native: "Nativ"
-disableDrawer: "Keine ausfahrbaren Menüs verwenden"
 showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen"
 noHistory: "Kein Verlauf gefunden"
 signinHistory: "Anmeldungsverlauf"
@@ -687,10 +686,7 @@ abuseReported: "Deine Meldung wurde versendet. Vielen Dank."
 reporter: "Melder"
 reporteeOrigin: "Herkunft des Gemeldeten"
 reporterOrigin: "Herkunft des Meldenden"
-forwardReport: "Meldung an fremde Instanz weiterleiten"
-forwardReportIsAnonymous: "Anstatt deines Benutzerkontos wird bei der fremden Instanz ein anonymes Systemkonto als Melder angezeigt."
 send: "Senden"
-abuseMarkAsResolved: "Meldung als gelöst markieren"
 openInNewTab: "In neuem Tab öffnen"
 openInSideView: "In Seitenansicht öffnen"
 defaultNavigationBehaviour: "Standardnavigationsverhalten"
@@ -1788,7 +1784,6 @@ _theme:
     buttonBg: "Hintergrund von Schaltflächen"
     buttonHoverBg: "Hintergrund von Schaltflächen (Mouseover)"
     inputBorder: "Rahmen von Eingabefeldern"
-    listItemHoverBg: "Hintergrund von Listeneinträgen (Mouseover)"
     driveFolderBg: "Hintergrund von Drive-Ordnern"
     wallpaperOverlay: "Hintergrundbild-Overlay"
     badge: "Wappen"
@@ -2142,6 +2137,7 @@ _notification:
     receiveFollowRequest: "Erhaltene Follow-Anfragen"
     followRequestAccepted: "Akzeptierte Follow-Anfragen"
     achievementEarned: "Errungenschaft freigeschaltet"
+    login: "Anmelden"
     app: "Benachrichtigungen von Apps"
   _actions:
     followBack: "folgt dir nun auch"
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 5eca348e186f..4657842ca5bf 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -378,6 +378,7 @@ _notification:
     renote: "Κοινοποίηση σημειώματος"
     quote: "Παράθεση"
     reaction: "Αντιδράσεις"
+    login: "Σύνδεση"
   _actions:
     reply: "Απάντηση"
     renote: "Κοινοποίηση σημειώματος"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index c82ea3c9a27c..8570addfa2c4 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -8,6 +8,9 @@ search: "Search"
 notifications: "Notifications"
 username: "Username"
 password: "Password"
+initialPasswordForSetup: "Initial password for setup"
+initialPasswordIsIncorrect: "Initial password for setup is incorrect"
+initialPasswordForSetupDescription: "Use the password you entered in the configuration file if you installed Misskey yourself.\n If you are using a Misskey hosting service, use the password provided.\n If you have not set a password, leave it blank to continue."
 forgotPassword: "Forgot password"
 fetchingAsApObject: "Fetching from the Fediverse..."
 ok: "OK"
@@ -109,7 +112,7 @@ enterEmoji: "Enter an emoji"
 renote: "Renote"
 unrenote: "Remove renote"
 renoted: "Renoted."
-renotedToX: "Renote to {name}."
+renotedToX: "Renoted to {name}."
 cantRenote: "This post can't be renoted."
 cantReRenote: "A renote can't be renoted."
 quote: "Quote"
@@ -236,6 +239,8 @@ silencedInstances: "Silenced instances"
 silencedInstancesDescription: "List the host names of the servers that you want to silence, separated by a new line. All accounts belonging to the listed servers will be treated as silenced, and can only make follow requests, and cannot mention local accounts if not followed. This will not affect the blocked servers."
 mediaSilencedInstances: "Media-silenced servers"
 mediaSilencedInstancesDescription: "List the host names of the servers that you want to media-silence, separated by a new line. All accounts belonging to the listed servers will be treated as sensitive, and can't use custom emojis. This will not affect the blocked servers."
+federationAllowedHosts: "Federation allowed servers"
+federationAllowedHostsDescription: "Specify the hostnames of the servers you want to allow federation separated by line breaks."
 muteAndBlock: "Mutes and Blocks"
 mutedUsers: "Muted users"
 blockedUsers: "Blocked users"
@@ -326,7 +331,6 @@ selectFile: "Select a file"
 selectFiles: "Select files"
 selectFolder: "Select a folder"
 selectFolders: "Select folders"
-fileNotSelected: ""
 renameFile: "Rename file"
 folderName: "Folder name"
 createFolder: "Create a folder"
@@ -334,6 +338,7 @@ renameFolder: "Rename this folder"
 deleteFolder: "Delete this folder"
 folder: "Folder"
 addFile: "Add a file"
+showFile: "Show files"
 emptyDrive: "Your Drive is empty"
 emptyFolder: "This folder is empty"
 unableToDelete: "Unable to delete"
@@ -448,6 +453,7 @@ totpDescription: "Use an authenticator app to enter one-time passwords"
 moderator: "Moderator"
 moderation: "Moderation"
 moderationNote: "Moderation note"
+moderationNoteDescription: "You can fill in notes that will be shared only among moderators."
 addModerationNote: "Add moderation note"
 moderationLogs: "Moderation logs"
 nUsersMentioned: "Mentioned by {n} users"
@@ -509,7 +515,10 @@ uiLanguage: "User interface language"
 aboutX: "About {x}"
 emojiStyle: "Emoji style"
 native: "Native"
-disableDrawer: "Don't use drawer-style menus"
+menuStyle: "Menu style"
+style: "Style"
+drawer: "Drawer"
+popup: "Pop up"
 showNoteActionsOnlyHover: "Only show note actions on hover"
 showReactionsCount: "See the number of reactions in notes"
 noHistory: "No history available"
@@ -592,6 +601,8 @@ ascendingOrder: "Ascending"
 descendingOrder: "Descending"
 scratchpad: "Scratchpad"
 scratchpadDescription: "The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Misskey in it."
+uiInspector: "UI inspector"
+uiInspectorDescription: "You can see the UI component server list on memory. UI component will be generated by Ui:C: function."
 output: "Output"
 script: "Script"
 disablePagesScript: "Disable AiScript on Pages"
@@ -708,10 +719,7 @@ abuseReported: "Your report has been sent. Thank you very much."
 reporter: "Reporter"
 reporteeOrigin: "Reportee Origin"
 reporterOrigin: "Reporter Origin"
-forwardReport: "Forward report to remote instance"
-forwardReportIsAnonymous: "Instead of your account, an anonymous system account will be displayed as reporter at the remote instance."
 send: "Send"
-abuseMarkAsResolved: "Mark report as resolved"
 openInNewTab: "Open in new tab"
 openInSideView: "Open in side view"
 defaultNavigationBehaviour: "Default navigation behavior"
@@ -913,6 +921,7 @@ followersVisibility: "Visibility of followers"
 continueThread: "View thread continuation"
 deleteAccountConfirm: "This will irreversibly delete your account. Proceed?"
 incorrectPassword: "Incorrect password."
+incorrectTotp: "The one-time password is incorrect or has expired."
 voteConfirm: "Confirm your vote for \"{choice}\"?"
 hide: "Hide"
 useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile"
@@ -937,6 +946,9 @@ oneHour: "One hour"
 oneDay: "One day"
 oneWeek: "One week"
 oneMonth: "One month"
+threeMonths: "3 months"
+oneYear: "1 year"
+threeDays: "3 days"
 reflectMayTakeTime: "It may take some time for this to be reflected."
 failedToFetchAccountInformation: "Could not fetch account information"
 rateLimitExceeded: "Rate limit exceeded"
@@ -1077,9 +1089,10 @@ retryAllQueuesConfirmTitle: "Really retry all?"
 retryAllQueuesConfirmText: "This will temporarily increase the server load."
 enableChartsForRemoteUser: "Generate remote user data charts"
 enableChartsForFederatedInstances: "Generate remote instance data charts"
+enableStatsForFederatedInstances: "Receive remote server stats"
 showClipButtonInNoteFooter: "Add \"Clip\" to note action menu"
 reactionsDisplaySize: "Reaction display size"
-limitWidthOfReaction: "Limits the maximum width of reactions and display them in reduced size."
+limitWidthOfReaction: "Limit the maximum width of reactions and display them in reduced size."
 noteIdOrUrl: "Note ID or URL"
 video: "Video"
 videos: "Videos"
@@ -1126,7 +1139,7 @@ options: "Options"
 specifyUser: "Specific user"
 lookupConfirm: "Do you want to look up?"
 openTagPageConfirm: "Do you want to open a hashtag page?"
-specifyHost: "Specify a host"
+specifyHost: "Specific host"
 failedToPreviewUrl: "Could not preview"
 update: "Update"
 rolesThatCanBeUsedThisEmojiAsReaction: "Roles that can use this emoji as reaction"
@@ -1263,6 +1276,47 @@ confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media"
 sensitiveMediaRevealConfirm: "This might be a sensitive media. Are you sure to reveal?"
 createdLists: "Created lists"
 createdAntennas: "Created antennas"
+fromX: "From {x}"
+genEmbedCode: "Generate embed code"
+noteOfThisUser: "Notes by this user"
+clipNoteLimitExceeded: "No more notes can be added to this clip."
+performance: "Performance"
+modified: "Modified"
+discard: "Discard"
+thereAreNChanges: "There are {n} change(s)"
+signinWithPasskey: "Sign in with Passkey"
+unknownWebAuthnKey: "Unknown Passkey"
+passkeyVerificationFailed: "Passkey verification has failed."
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification has succeeded but password-less login is disabled."
+messageToFollower: "Message to followers"
+target: "Target"
+testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\n<strong>Do not use in a production environment.</strong>"
+prohibitedWordsForNameOfUser: "Prohibited words for user names"
+prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction."
+yourNameContainsProhibitedWords: "Your name contains prohibited words"
+yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator."
+thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require login to view"
+lockdown: "Lockdown"
+pleaseSelectAccount: "Select an account"
+_accountSettings:
+  requireSigninToViewContents: "Require sign-in to view contents"
+  requireSigninToViewContentsDescription1: "Require login to view all notes and other content you have created. This will have the effect of preventing crawlers from collecting your information."
+  requireSigninToViewContentsDescription2: "Content will not be displayed in URL previews (OGP), embedded in web pages, or on servers that don't support note quotes."
+  requireSigninToViewContentsDescription3: "These restrictions may not apply to federated content from other remote servers."
+  makeNotesFollowersOnlyBefore: "Make past notes to be displayed only to followers"
+  makeNotesFollowersOnlyBeforeDescription: "While this feature is enabled, only followers can see notes past the set date and time or have been visible for a set time. When it is deactivated, the note publication status will also be restored."
+  makeNotesHiddenBefore: "Make past notes private"
+  makeNotesHiddenBeforeDescription: "While this feature is enabled, notes that are past the set date and time or have been visible only to you. When it is deactivated, the note publication status will also be restored."
+  mayNotEffectForFederatedNotes: "Notes federated to a remote server may not be effective."
+  notesHavePassedSpecifiedPeriod: "Note that the specified time has passed"
+  notesOlderThanSpecifiedDateAndTime: "Notes before the specified date and time"
+_abuseUserReport:
+  forward: "Forward"
+  forwardDescription: "Forward the report to a remote server as an anonymous system account."
+  resolve: "Resolve"
+  accept: "Accept"
+  reject: "Reject"
+  resolveTutorial: "If the report is legitimate in content, select \"Accept\" to mark the case as resolved in the affirmative.\nIf the content of the report is not legitimate, select \"Reject\" to mark the case as resolved in the negative."
 _delivery:
   status: "Delivery status"
   stop: "Suspended"
@@ -1397,8 +1451,10 @@ _serverSettings:
   fanoutTimelineDescription: "Greatly increases performance of timeline retrieval and reduces load on the database when enabled. In exchange, memory usage of Redis will increase. Consider disabling this in case of low server memory or server instability."
   fanoutTimelineDbFallback: "Fallback to database"
   fanoutTimelineDbFallbackDescription: "When enabled, the timeline will fall back to the database for additional queries if the timeline is not cached. Disabling it further reduces the server load by eliminating the fallback process, but limits the range of timelines that can be retrieved."
+  reactionsBufferingDescription: "When enabled, performance during reaction creation will be greatly improved, reducing the load on the database. However, Redis memory usage will increase."
   inquiryUrl: "Inquiry URL"
   inquiryUrlDescription: "Specify a URL for the inquiry form to the server maintainer or a web page for the contact information."
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "If no moderator activity is detected for a while, this setting will be automatically turned off to prevent spam."
 _accountMigration:
   moveFrom: "Migrate another account to this one"
   moveFromSub: "Create alias to another account"
@@ -1715,7 +1771,7 @@ _role:
     canManageAvatarDecorations: "Manage avatar decorations"
     driveCapacity: "Drive capacity"
     alwaysMarkNsfw: "Always mark files as NSFW"
-    canUpdateBioMedia: "Allow to edit an icon or a banner image"
+    canUpdateBioMedia: "Can edit an icon or a banner image"
     pinMax: "Maximum number of pinned notes"
     antennaMax: "Maximum number of antennas"
     wordMuteMax: "Maximum number of characters allowed in word mutes"
@@ -1730,6 +1786,11 @@ _role:
     canSearchNotes: "Usage of note search"
     canUseTranslator: "Translator usage"
     avatarDecorationLimit: "Maximum number of avatar decorations that can be applied"
+    canImportAntennas: "Allow importing antennas"
+    canImportBlocking: "Allow importing blocking"
+    canImportFollowing: "Allow importing following"
+    canImportMuting: "Allow importing muting"
+    canImportUserLists: "Allow importing lists"
   _condition:
     roleAssignedTo: "Assigned to manual roles"
     isLocal: "Local user"
@@ -1947,7 +2008,6 @@ _theme:
     buttonBg: "Button background"
     buttonHoverBg: "Button background (Hover)"
     inputBorder: "Input field border"
-    listItemHoverBg: "List item background (Hover)"
     driveFolderBg: "Drive folder background"
     wallpaperOverlay: "Wallpaper overlay"
     badge: "Badge"
@@ -2114,8 +2174,11 @@ _auth:
   permissionAsk: "This application requests the following permissions"
   pleaseGoBack: "Please go back to the application"
   callback: "Returning to the application"
+  accepted: "Access granted"
   denied: "Access denied"
+  scopeUser: "Operate as the following user"
   pleaseLogin: "Please log in to authorize applications."
+  byClickingYouWillBeRedirectedToThisUrl: "When access is granted, you will automatically be redirected to the following URL"
 _antennaSources:
   all: "All notes"
   homeTimeline: "Notes from followed users"
@@ -2224,6 +2287,9 @@ _profile:
   changeBanner: "Change banner"
   verifiedLinkDescription: "By entering an URL that contains a link to your profile here, an ownership verification icon can be displayed next to the field."
   avatarDecorationMax: "You can add up to {max} decorations."
+  followedMessage: "Message when you are followed"
+  followedMessageDescription: "You can set a short message to be displayed to the recipient when they follow you."
+  followedMessageDescriptionForLockedAccount: "If you have set up that follow requests require approval, this will be displayed when you grant a follow request."
 _exportOrImport:
   allNotes: "All notes"
   favoritedNotes: "Favorite notes"
@@ -2362,6 +2428,8 @@ _notification:
   renotedBySomeUsers: "Renote from {n} users"
   followedBySomeUsers: "Followed by {n} users"
   flushNotification: "Clear notifications"
+  exportOfXCompleted: "Export of {x} has been completed"
+  login: "Someone logged in"
   _types:
     all: "All"
     note: "New notes"
@@ -2376,6 +2444,9 @@ _notification:
     followRequestAccepted: "Accepted follow requests"
     roleAssigned: "Role given"
     achievementEarned: "Achievement unlocked"
+    exportCompleted: "The export has been completed"
+    login: "Sign In"
+    test: "Notification test"
     app: "Notifications from linked apps"
   _actions:
     followBack: "followed you back"
@@ -2438,21 +2509,24 @@ _webhookSettings:
     reaction: "When receiving a reaction"
     mention: "When being mentioned"
   _systemEvents:
-    abuseReport: "When received a new abuse report"
-    abuseReportResolved: "When resolved abuse reports"
+    abuseReport: "When received a new report"
+    abuseReportResolved: "When resolved report"
     userCreated: "When user is created"
+    inactiveModeratorsWarning: "When moderators have been inactive for a while"
+    inactiveModeratorsInvitationOnlyChanged: "When a moderator has been inactive for a while, and the server is changed to invitation-only"
   deleteConfirm: "Are you sure you want to delete the Webhook?"
+  testRemarks: "Click the button to the right of the switch to send a test Webhook with dummy data."
 _abuseReport:
   _notificationRecipient:
-    createRecipient: "Add a recipient for abuse reports"
-    modifyRecipient: "Edit a recipient for abuse reports"
+    createRecipient: "Add a recipient for reports"
+    modifyRecipient: "Edit a recipient for reports"
     recipientType: "Notification type"
     _recipientType:
       mail: "Email"
       webhook: "Webhook"
       _captions:
-        mail: "Send the email to moderators' email addresses when you receive abuse."
-        webhook: "Send a notification to SystemWebhook when you receive or resolve abuse."
+        mail: "Send the email to moderators' email addresses when you receive reports."
+        webhook: "Send a notification to System Webhook when you receive or resolve reports."
     keywords: "Keywords"
     notifiedUser: "Users to notify"
     notifiedWebhook: "Webhook to use"
@@ -2485,6 +2559,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "File marked as sensitive"
   unmarkSensitiveDriveFile: "File unmarked as sensitive"
   resolveAbuseReport: "Report resolved"
+  forwardAbuseReport: "Report forwarded"
+  updateAbuseReportNote: "Moderation note of a report updated"
   createInvitation: "Invite generated"
   createAd: "Ad created"
   deleteAd: "Ad deleted"
@@ -2492,18 +2568,18 @@ _moderationLogTypes:
   createAvatarDecoration: "Avatar decoration created"
   updateAvatarDecoration: "Avatar decoration updated"
   deleteAvatarDecoration: "Avatar decoration deleted"
-  unsetUserAvatar: "Unset this user's avatar"
-  unsetUserBanner: "Unset this user's banner"
-  createSystemWebhook: "Create SystemWebhook"
-  updateSystemWebhook: "Update SystemWebhook"
-  deleteSystemWebhook: "Delete SystemWebhook"
-  createAbuseReportNotificationRecipient: "Create a recipient for abuse reports"
-  updateAbuseReportNotificationRecipient: "Update recipients for abuse reports"
-  deleteAbuseReportNotificationRecipient: "Delete a recipient for abuse reports"
-  deleteAccount: "Delete the account"
-  deletePage: "Delete the page"
-  deleteFlash: "Delete Play"
-  deleteGalleryPost: "Delete the gallery post"
+  unsetUserAvatar: "User avatar unset"
+  unsetUserBanner: "User banner unset"
+  createSystemWebhook: "System Webhook created"
+  updateSystemWebhook: "System Webhook updated"
+  deleteSystemWebhook: "System Webhook deleted"
+  createAbuseReportNotificationRecipient: "Recipient for reports created"
+  updateAbuseReportNotificationRecipient: "Recipient for reports updated"
+  deleteAbuseReportNotificationRecipient: "Recipient for reports deleted"
+  deleteAccount: "Account deleted"
+  deletePage: "Page deleted"
+  deleteFlash: "Play deleted"
+  deleteGalleryPost: "Gallery post deleted"
 _fileViewer:
   title: "File details"
   type: "File type"
@@ -2640,3 +2716,17 @@ _contextMenu:
   app: "Application"
   appWithShift: "Application with shift key"
   native: "Native"
+_embedCodeGen:
+  title: "Customize embed code"
+  header: "Show header"
+  autoload: "Automatically load more (deprecated)"
+  maxHeight: "Max height"
+  maxHeightDescription: "Setting it to 0 disables the max height setting. Specify some value to prevent the widget from continuing to expand vertically."
+  maxHeightWarn: "The max height limit is disabled (0). If this was not intended, set the max height to some value."
+  previewIsNotActual: "The display differs from the actual embedding because it exceeds the range displayed on the preview screen."
+  rounded: "Make it rounded"
+  border: "Add a border to the outer frame"
+  applyToPreview: "Apply to the preview"
+  generateCode: "Generate embed code"
+  codeGenerated: "The code has been generated"
+  codeGeneratedDescription: "Paste the generated code into your website to embed the content."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 2621965d1bf5..773159815222 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -8,6 +8,8 @@ search: "Buscar"
 notifications: "Notificaciones"
 username: "Nombre de usuario"
 password: "Contraseña"
+initialPasswordForSetup: "Contraseña para iniciar la inicialización"
+initialPasswordIsIncorrect: "La contraseña para iniciar la configuración inicial es incorrecta."
 forgotPassword: "Olvidé mi contraseña"
 fetchingAsApObject: "Buscando en el fediverso"
 ok: "OK"
@@ -109,11 +111,14 @@ enterEmoji: "Ingresar emojis"
 renote: "Renotar"
 unrenote: "Quitar renota"
 renoted: "Renotado"
+renotedToX: "{name} usuarios han renotado。"
 cantRenote: "No se puede renotar este post"
 cantReRenote: "No se puede renotar una renota"
 quote: "Citar"
 inChannelRenote: "Renota sólo del canal"
 inChannelQuote: "Cita sólo del canal"
+renoteToChannel: "Renotar a otro canal"
+renoteToOtherChannel: "Renotar a otro canal"
 pinnedNote: "Nota fijada"
 pinned: "Fijar al perfil"
 you: "Tú"
@@ -152,6 +157,7 @@ editList: "Editar lista"
 selectChannel: "Seleccionar canal"
 selectAntenna: "Seleccionar antena"
 editAntenna: "Editar antena"
+createAntenna: "Crear una antena"
 selectWidget: "Seleccionar widget"
 editWidgets: "Editar widgets"
 editWidgetsExit: "Terminar edición"
@@ -178,6 +184,10 @@ addAccount: "Agregar Cuenta"
 reloadAccountsList: "Recargar lista de cuentas"
 loginFailed: "Error al iniciar sesión."
 showOnRemote: "Ver en una instancia remota"
+continueOnRemote: "Ver en una instancia remota"
+chooseServerOnMisskeyHub: "Elegir un servidor en Misskey Hub"
+specifyServerHost: "Especifica una instancia directamente"
+inputHostName: "Introduzca el dominio"
 general: "General"
 wallpaper: "Fondo de pantalla"
 setWallpaper: "Establecer fondo de pantalla"
@@ -494,7 +504,8 @@ uiLanguage: "Idioma de visualización de la interfaz"
 aboutX: "Acerca de {x}"
 emojiStyle: "Estilo de emoji"
 native: "Nativo"
-disableDrawer: "No mostrar los menús en cajones"
+menuStyle: "Diseño del menú"
+style: "Diseño"
 showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor"
 showReactionsCount: "Mostrar el número de reacciones en las notas"
 noHistory: "No hay datos en el historial"
@@ -693,10 +704,7 @@ abuseReported: "Se ha enviado el reporte. Muchas gracias."
 reporter: "Reportador"
 reporteeOrigin: "Reportar a"
 reporterOrigin: "Origen del reporte"
-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"
 openInSideView: "Abrir en una vista al costado"
 defaultNavigationBehaviour: "Navegación por defecto"
@@ -921,6 +929,9 @@ oneHour: "1 hora"
 oneDay: "1 día"
 oneWeek: "1 semana"
 oneMonth: "1 mes"
+threeMonths: "Tres meses"
+oneYear: "Un año"
+threeDays: "Tres días"
 reflectMayTakeTime: "Puede pasar un tiempo hasta que se reflejen los cambios"
 failedToFetchAccountInformation: "No se pudo obtener información de la cuenta"
 rateLimitExceeded: "Se excedió el límite de peticiones"
@@ -1095,6 +1106,8 @@ preservedUsernames: "Nombre de usuario reservado"
 preservedUsernamesDescription: "La lista de nombres de usuario para reservar tienen que separarse con saltos de línea.\nEstos estarán indisponibles durante la creación de cuentas, pero pueden ser usados para que los administradores puedan crear esas cuentas manualmente. Las cuentas existentes con esos nombres de usuario no se verán afectadas."
 createNoteFromTheFile: "Componer una nota desde éste archivo"
 archive: "Archivo"
+archived: "Archivado"
+unarchive: "Desarchivar"
 channelArchiveConfirmTitle: "¿Seguro de archivar {name}?"
 channelArchiveConfirmDescription: "Un canal archivado no aparecerá en la lista de canales ni en los resultados. Las nuevas publicaciones tampoco serán añadidas."
 thisChannelArchived: "El canal ha sido archivado."
@@ -1234,6 +1247,14 @@ useNativeUIForVideoAudioPlayer: "Usar la interfaz del navegador cuando se reprod
 keepOriginalFilename: "Mantener el nombre original del archivo"
 noDescription: "No hay descripción"
 alwaysConfirmFollow: "Confirmar siempre cuando se sigue a alguien"
+inquiry: "Contacto"
+tryAgain: "Por favor , inténtalo de nuevo"
+performance: "Rendimiento"
+unknownWebAuthnKey: "Esto no se ha registrado llave maestra."
+messageToFollower: "Mensaje a seguidores"
+_abuseUserReport:
+  accept: "Acepte"
+  reject: "repudio"
 _delivery:
   stop: "Suspendido"
   _type:
@@ -1909,7 +1930,6 @@ _theme:
     buttonBg: "Fondo de botón"
     buttonHoverBg: "Fondo de botón (hover)"
     inputBorder: "Borde de los campos de entrada"
-    listItemHoverBg: "Fondo de elemento de listas (hover)"
     driveFolderBg: "Fondo de capeta del drive"
     wallpaperOverlay: "Transparencia del fondo de pantalla"
     badge: "Medalla"
@@ -2334,6 +2354,8 @@ _notification:
     followRequestAccepted: "El seguimiento fue aceptado"
     roleAssigned: "Rol asignado"
     achievementEarned: "Logro desbloqueado"
+    login: "Iniciar sesión"
+    test: "Pruebas de nofiticaciones"
     app: "Notificaciones desde aplicaciones"
   _actions:
     followBack: "Te sigue de vuelta"
@@ -2392,10 +2414,14 @@ _webhookSettings:
     renote: "Cuando reciba un \"re-note\""
     reaction: "Cuando se recibe una reacción"
     mention: "Cuando hay una mención"
+  _systemEvents:
+    userCreated: "Cuando se crea el usuario."
 _abuseReport:
   _notificationRecipient:
     _recipientType:
       mail: "Correo"
+      webhook: "Webhook"
+    keywords: "Palabras Clave"
 _moderationLogTypes:
   createRole: "Rol creado"
   deleteRole: "Rol eliminado"
@@ -2499,6 +2525,7 @@ _hemisphere:
   S: "Hemisferio sur"
 _reversi:
   reversi: "Reversi"
+  rules: "Reglas"
   won: "{name} ha ganado"
   total: "Total"
 _urlPreviewSetting:
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index c1e2555d0d10..a7060c06fc81 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -493,7 +493,6 @@ uiLanguage: "Langue d’affichage de l’interface"
 aboutX: "À propos de {x}"
 emojiStyle: "Style des émojis"
 native: "Natif"
-disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
 showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
 showReactionsCount: "Afficher le nombre de réactions des notes"
 noHistory: "Pas d'historique"
@@ -692,10 +691,7 @@ abuseReported: "Le rapport est envoyé. Merci."
 reporter: "Signalé par"
 reporteeOrigin: "Origine du signalement"
 reporterOrigin: "Signalé par"
-forwardReport: "Transférer le signalement à l’instance distante"
-forwardReportIsAnonymous: "L'instance distante ne sera pas en mesure de voir vos informations et apparaîtra comme un compte anonyme du système."
 send: "Envoyer"
-abuseMarkAsResolved: "Marquer le signalement comme résolu"
 openInNewTab: "Ouvrir dans un nouvel onglet"
 openInSideView: "Ouvrir en vue latérale"
 defaultNavigationBehaviour: "Navigation par défaut"
@@ -1705,7 +1701,6 @@ _theme:
     buttonBg: "Arrière-plan du bouton"
     buttonHoverBg: "Arrière-plan du bouton (survolé)"
     inputBorder: "Cadre de la zone de texte"
-    listItemHoverBg: "Arrière-plan d'item de liste (survolé)"
     driveFolderBg: "Arrière-plan du dossier de disque"
     wallpaperOverlay: "Superposition de fond d'écran"
     badge: "Badge"
@@ -2038,6 +2033,7 @@ _notification:
     followRequestAccepted: "Demande d'abonnement acceptée"
     roleAssigned: "Rôle reçu"
     achievementEarned: "Déverrouillage d'accomplissement"
+    login: "Se connecter"
     app: "Notifications provenant des apps"
   _actions:
     followBack: "Suivre"
diff --git a/locales/hu-HU.yml b/locales/hu-HU.yml
index 023a91494d02..d0fdc027e991 100644
--- a/locales/hu-HU.yml
+++ b/locales/hu-HU.yml
@@ -1,5 +1,5 @@
 ---
-_lang_: "Japán"
+_lang_: "Magyar"
 monthAndDay: "{month}.{day}."
 search: "Keresés"
 notifications: "Értesítések"
@@ -96,6 +96,7 @@ _notification:
     renote: "Renote"
     quote: "Idézet"
     reaction: "Reakciók"
+    login: "Bejelentkezés"
   _actions:
     renote: "Renote"
 _deck:
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 24f7482fcaea..5d51d2dc7826 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -60,6 +60,7 @@ copyFileId: "Salin Berkas"
 copyFolderId: "Salin Folder"
 copyProfileUrl: "Salin Alamat Web Profil"
 searchUser: "Cari pengguna"
+searchThisUsersNotes: "Mencari catatan pengguna"
 reply: "Balas"
 loadMore: "Selebihnya"
 showMore: "Selebihnya"
@@ -154,6 +155,7 @@ editList: "Sunting daftar"
 selectChannel: "Pilih kanal"
 selectAntenna: "Pilih Antena"
 editAntenna: "Sunting antena"
+createAntenna: "Membuat antena."
 selectWidget: "Pilih gawit"
 editWidgets: "Sunting gawit"
 editWidgetsExit: "Selesai"
@@ -194,6 +196,7 @@ followConfirm: "Apakah kamu yakin ingin mengikuti {name}?"
 proxyAccount: "Akun proksi"
 proxyAccountDescription: "Akun proksi merupakan sebuah akun yang bertindak sebagai pengikut instansi luar untuk pengguna dalam kondisi tertentu. Sebagai contoh, ketika pengguna menambahkan seorang pengguna instansi luar ke dalam daftar, aktivitas dari pengguna instansi luar tidak akan disampaikan ke instansi apabila tidak ada pengguna lokal yang mengikuti pengguna tersebut, dengan begitu akun proksilah yang akan mengikutinya."
 host: "Host"
+selectSelf: "Pilih diri sendiri"
 selectUser: "Pilih pengguna"
 recipient: "Penerima"
 annotation: "Keterangan konten"
@@ -230,6 +233,7 @@ blockedInstances: "Instansi terblokir"
 blockedInstancesDescription: "Daftar nama host dari instansi yang diperlukan untuk diblokir. Instansi yang didaftarkan tidak akan dapat berkomunikasi dengan instansi ini."
 silencedInstances: "Instansi yang disenyapkan"
 silencedInstancesDescription: "Daftar nama host dari instansi yang ingin kamu senyapkan. Semua akun dari instansi yang terdaftar akan diperlakukan sebagai disenyapkan. Hal ini membuat akun hanya dapat membuat permintaan mengikuti, dan tidak dapat menyebutkan akun lokal apabila tidak mengikuti. Hal ini tidak akan mempengaruhi instansi yang diblokir."
+federationAllowedHosts: "Server yang membolehkan federasi"
 muteAndBlock: "Bisukan / Blokir"
 mutedUsers: "Pengguna yang dibisukan"
 blockedUsers: "Pengguna yang diblokir"
@@ -328,6 +332,7 @@ renameFolder: "Ubah nama folder"
 deleteFolder: "Hapus folder"
 folder: "Folder"
 addFile: "Tambahkan berkas"
+showFile: "Tampilkan berkas"
 emptyDrive: "Drive kosong"
 emptyFolder: "Folder kosong"
 unableToDelete: "Tidak dapat menghapus"
@@ -502,7 +507,8 @@ uiLanguage: "Bahasa antarmuka pengguna"
 aboutX: "Tentang {x}"
 emojiStyle: "Gaya emoji"
 native: "Native"
-disableDrawer: "Jangan gunakan menu bergaya laci"
+menuStyle: "Gaya menu"
+style: "Gaya"
 showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
 showReactionsCount: "Lihat jumlah reaksi dalam catatan"
 noHistory: "Tidak ada riwayat"
@@ -701,10 +707,7 @@ abuseReported: "Laporan kamu telah dikirimkan. Terima kasih."
 reporter: "Pelapor"
 reporteeOrigin: "Yang dilaporkan"
 reporterOrigin: "Pelapor"
-forwardReport: "Teruskan laporan ke instansi luar"
-forwardReportIsAnonymous: "Untuk melindungi privasi akun kamu, akun anonim dari sistem akan digunakan sebagai pelapor pada instansi luar."
 send: "Kirim"
-abuseMarkAsResolved: "Tandai laporan sebagai selesai"
 openInNewTab: "Buka di tab baru"
 openInSideView: "Buka di tampilan samping"
 defaultNavigationBehaviour: "Navigasi bawaan"
@@ -929,6 +932,9 @@ oneHour: "1 Jam"
 oneDay: "1 Hari"
 oneWeek: "1 Bulan"
 oneMonth: "satu bulan"
+threeMonths: "3 bulan"
+oneYear: "1 tahun"
+threeDays: "3 hari"
 reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan."
 failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun"
 rateLimitExceeded: "Batas sudah terlampaui"
@@ -1103,6 +1109,7 @@ preservedUsernames: "Nama pengguna tercadangkan"
 preservedUsernamesDescription: "Daftar nama pengguna yang dicadangkan dipisah dengan baris baru. Nama pengguna berikut akan tidak dapat dipakai pada pembuatan akun normal, namun dapat digunakan oleh admin untuk membuat akun baru. Akun yang sudah ada dengan menggunakan nama pengguna ini tidak akan terpengaruh."
 createNoteFromTheFile: "Buat catatan dari berkas ini"
 archive: "Arsipkan"
+archived: "Diarsipkan"
 channelArchiveConfirmTitle: "Yakin untuk mengarsipkan {name}?"
 channelArchiveConfirmDescription: "Kanal yang diarsipkan tidak akan muncul pada daftar kanal atau hasil pencarian. Postingan baru juga tidak dapat ditambahkan lagi."
 thisChannelArchived: "Kanal ini telah diarsipkan."
@@ -1113,6 +1120,7 @@ preventAiLearning: "Tolak penggunaan Pembelajaran Mesin (AI Generatif)"
 preventAiLearningDescription: "Minta perayap web untuk tidak menggunakan materi teks atau gambar yang telah diposting ke dalam set data Pembelajaran Mesin (Prediktif / Generatif). Hal ini dicapai dengan menambahkan flag HTML-Response \"noai\" ke masing-masing konten. Pencegahan penuh mungkin tidak dapat dicapai dengan flag ini, karena juga dapat diabaikan begitu saja."
 options: "Opsi peran"
 specifyUser: "Pengguna spesifik"
+openTagPageConfirm: "Apakah ingin membuka laman tagar?"
 failedToPreviewUrl: "Tidak dapat dipratinjau"
 update: "Perbarui"
 rolesThatCanBeUsedThisEmojiAsReaction: "Peran yang dapat menggunakan emoji ini sebagai reaksi"
@@ -1245,6 +1253,18 @@ noDescription: "Tidak ada deskripsi"
 alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti"
 inquiry: "Hubungi kami"
 tryAgain: "Silahkan coba lagi."
+createdLists: "Senarai yang dibuat"
+createdAntennas: "Antena yang dibuat"
+fromX: "Dari {x}"
+noteOfThisUser: "Catatan oleh pengguna ini"
+clipNoteLimitExceeded: "Klip ini tak bisa ditambahi lagi catatan."
+performance: "Kinerja"
+modified: "Diubah"
+thereAreNChanges: "Ada {n} perubahan"
+prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
+_abuseUserReport:
+  accept: "Setuju"
+  reject: "Tolak"
 _delivery:
   status: "Status pengiriman"
   stop: "Ditangguhkan"
@@ -1709,6 +1729,8 @@ _role:
     canSearchNotes: "Penggunaan pencarian catatan"
     canUseTranslator: "Penggunaan penerjemah"
     avatarDecorationLimit: "Jumlah maksimum dekorasi avatar yang dapat diterapkan"
+    canImportAntennas: "Izinkan mengimpor antena"
+    canImportUserLists: "Izinkan mengimpor senarai"
   _condition:
     roleAssignedTo: "Ditugaskan ke peran manual"
     isLocal: "Pengguna lokal"
@@ -1926,7 +1948,6 @@ _theme:
     buttonBg: "Latar belakang tombol"
     buttonHoverBg: "Latar belakang tombol (Mengambang)"
     inputBorder: "Batas bidang masukan"
-    listItemHoverBg: "Latar belakang daftar item (Mengambang)"
     driveFolderBg: "Latar belakang folder drive"
     wallpaperOverlay: "Lapisan wallpaper"
     badge: "Lencana"
@@ -1946,6 +1967,7 @@ _soundSettings:
   driveFileTypeWarnDescription: "Pilih berkas audio"
   driveFileDurationWarn: "Audio ini terlalu panjang"
   driveFileDurationWarnDescription: "Audio panjang dapat mengganggu penggunaan Misskey. Masih ingin melanjutkan?"
+  driveFileError: "Tak bisa memuat audio. Mohon ubah pengaturan"
 _ago:
   future: "Masa depan"
   justNow: "Baru saja"
@@ -2353,6 +2375,7 @@ _notification:
     followRequestAccepted: "Permintaan mengikuti disetujui"
     roleAssigned: "Peran Diberikan"
     achievementEarned: "Pencapaian didapatkan"
+    login: "Masuk"
     app: "Notifikasi dari aplikasi tertaut"
   _actions:
     followBack: "Ikuti Kembali"
@@ -2417,6 +2440,8 @@ _abuseReport:
   _notificationRecipient:
     _recipientType:
       mail: "Surel"
+      webhook: "Webhook"
+    keywords: "Kata kunci"
 _moderationLogTypes:
   createRole: "Peran telah dibuat"
   deleteRole: "Peran telah dihapus"
@@ -2454,6 +2479,7 @@ _moderationLogTypes:
   deleteAvatarDecoration: "Hapus dekorasi avatar"
   unsetUserAvatar: "Hapus avatar pengguna"
   unsetUserBanner: "Hapus banner pengguna"
+  deleteAccount: "Akun dihapus"
 _fileViewer:
   title: "Rincian berkas"
   type: "Jenis berkas"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index fecc5703950d..440f24ac8472 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -48,6 +48,20 @@ export interface Locale extends ILocale {
      * パスワード
      */
     "password": string;
+    /**
+     * 初期設定開始用パスワード
+     */
+    "initialPasswordForSetup": string;
+    /**
+     * 初期設定開始用のパスワードが違います。
+     */
+    "initialPasswordIsIncorrect": string;
+    /**
+     * Misskeyを自分でインストールした場合は、設定ファイルに入力したパスワードを使用してください。
+     * Misskeyのホスティングサービスなどを使用している場合は、提供されたパスワードを使用してください。
+     * パスワードを設定していない場合は、空欄にしたまま続行してください。
+     */
+    "initialPasswordForSetupDescription": string;
     /**
      * パスワードを忘れた
      */
@@ -960,6 +974,14 @@ export interface Locale extends ILocale {
      * メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。
      */
     "mediaSilencedInstancesDescription": string;
+    /**
+     * 連合を許可するサーバー
+     */
+    "federationAllowedHosts": string;
+    /**
+     * 連合を許可するサーバーのホストを改行で区切って設定します。
+     */
+    "federationAllowedHostsDescription": string;
     /**
      * ミュートとブロック
      */
@@ -1352,6 +1374,10 @@ export interface Locale extends ILocale {
      * ファイルを追加
      */
     "addFile": string;
+    /**
+     * ファイルを表示
+     */
+    "showFile": string;
     /**
      * ドライブは空です
      */
@@ -1808,6 +1834,10 @@ export interface Locale extends ILocale {
      * モデレーションノート
      */
     "moderationNote": string;
+    /**
+     * モデレーター間でだけ共有されるメモを記入することができます。
+     */
+    "moderationNoteDescription": string;
     /**
      * モデレーションノートを追加する
      */
@@ -2053,9 +2083,21 @@ export interface Locale extends ILocale {
      */
     "native": string;
     /**
-     * メニューをドロワーで表示しない
+     * メニューのスタイル
+     */
+    "menuStyle": string;
+    /**
+     * スタイル
+     */
+    "style": string;
+    /**
+     * ドロワー
      */
-    "disableDrawer": string;
+    "drawer": string;
+    /**
+     * ポップアップ
+     */
+    "popup": string;
     /**
      * ノートのアクションをホバー時のみ表示する
      */
@@ -2384,6 +2426,14 @@ export interface Locale extends ILocale {
      * スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。
      */
     "scratchpadDescription": string;
+    /**
+     * UIインスペクター
+     */
+    "uiInspector": string;
+    /**
+     * メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。
+     */
+    "uiInspectorDescription": string;
     /**
      * 出力
      */
@@ -2848,22 +2898,10 @@ export interface Locale extends ILocale {
      * 通報元
      */
     "reporterOrigin": string;
-    /**
-     * リモートサーバーに通報を転送する
-     */
-    "forwardReport": string;
-    /**
-     * リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。
-     */
-    "forwardReportIsAnonymous": string;
     /**
      * 送信
      */
     "send": string;
-    /**
-     * 対応済みにする
-     */
-    "abuseMarkAsResolved": string;
     /**
      * 新しいタブで開く
      */
@@ -3121,7 +3159,7 @@ export interface Locale extends ILocale {
      */
     "narrow": string;
     /**
-     * 設定はページリロード後に反映されます。今すぐリロードしますか?
+     * 設定はページリロード後に反映されます。
      */
     "reloadToApplySetting": string;
     /**
@@ -3668,6 +3706,10 @@ export interface Locale extends ILocale {
      * パスワードが間違っています。
      */
     "incorrectPassword": string;
+    /**
+     * ワンタイムパスワードが間違っているか、期限切れになっています。
+     */
+    "incorrectTotp": string;
     /**
      * 「{choice}」に投票しますか?
      */
@@ -3764,6 +3806,18 @@ export interface Locale extends ILocale {
      * 1ヶ月
      */
     "oneMonth": string;
+    /**
+     * 3ヶ月
+     */
+    "threeMonths": string;
+    /**
+     * 1年
+     */
+    "oneYear": string;
+    /**
+     * 3日
+     */
+    "threeDays": string;
     /**
      * 反映されるまで時間がかかる場合があります。
      */
@@ -4324,6 +4378,10 @@ export interface Locale extends ILocale {
      * リモートサーバーのチャートを生成
      */
     "enableChartsForFederatedInstances": string;
+    /**
+     * リモートサーバーの情報を取得
+     */
+    "enableStatsForFederatedInstances": string;
     /**
      * ノートのアクションにクリップを追加
      */
@@ -5084,6 +5142,155 @@ export interface Locale extends ILocale {
      * これ以上このクリップにノートを追加できません。
      */
     "clipNoteLimitExceeded": string;
+    /**
+     * パフォーマンス
+     */
+    "performance": string;
+    /**
+     * 変更あり
+     */
+    "modified": string;
+    /**
+     * 破棄
+     */
+    "discard": string;
+    /**
+     * {n}件の変更があります
+     */
+    "thereAreNChanges": ParameterizedString<"n">;
+    /**
+     * パスキーでログイン
+     */
+    "signinWithPasskey": string;
+    /**
+     * 登録されていないパスキーです。
+     */
+    "unknownWebAuthnKey": string;
+    /**
+     * パスキーの検証に失敗しました。
+     */
+    "passkeyVerificationFailed": string;
+    /**
+     * パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。
+     */
+    "passkeyVerificationSucceededButPasswordlessLoginDisabled": string;
+    /**
+     * フォロワーへのメッセージ
+     */
+    "messageToFollower": string;
+    /**
+     * 対象
+     */
+    "target": string;
+    /**
+     * CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>
+     */
+    "testCaptchaWarning": string;
+    /**
+     * 禁止ワード(ユーザーの名前)
+     */
+    "prohibitedWordsForNameOfUser": string;
+    /**
+     * このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。
+     */
+    "prohibitedWordsForNameOfUserDescription": string;
+    /**
+     * 変更しようとした名前に禁止された文字列が含まれています
+     */
+    "yourNameContainsProhibitedWords": string;
+    /**
+     * 名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。
+     */
+    "yourNameContainsProhibitedWordsDescription": string;
+    /**
+     * 投稿者により、表示にはログインが必要と設定されています
+     */
+    "thisContentsAreMarkedAsSigninRequiredByAuthor": string;
+    /**
+     * ロックダウン
+     */
+    "lockdown": string;
+    /**
+     * アカウントを選択してください
+     */
+    "pleaseSelectAccount": string;
+    /**
+     * 利用可能なロール
+     */
+    "availableRoles": string;
+    "_accountSettings": {
+        /**
+         * コンテンツの表示にログインを必須にする
+         */
+        "requireSigninToViewContents": string;
+        /**
+         * あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーに情報が収集されるのを防ぐ効果が期待できます。
+         */
+        "requireSigninToViewContentsDescription1": string;
+        /**
+         * URLプレビュー(OGP)、Webページへの埋め込み、ノートの引用に対応していないサーバーからの表示も不可になります。
+         */
+        "requireSigninToViewContentsDescription2": string;
+        /**
+         * リモートサーバーに連合されたコンテンツでは、これらの制限が適用されない場合があります。
+         */
+        "requireSigninToViewContentsDescription3": string;
+        /**
+         * 過去のノートをフォロワーのみ表示可能にする
+         */
+        "makeNotesFollowersOnlyBefore": string;
+        /**
+         * この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートがフォロワーのみ表示可能になります。無効に戻すと、ノートの公開状態も元に戻ります。
+         */
+        "makeNotesFollowersOnlyBeforeDescription": string;
+        /**
+         * 過去のノートを非公開化する
+         */
+        "makeNotesHiddenBefore": string;
+        /**
+         * この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。
+         */
+        "makeNotesHiddenBeforeDescription": string;
+        /**
+         * リモートサーバーに連合されたノートには効果が及ばない場合があります。
+         */
+        "mayNotEffectForFederatedNotes": string;
+        /**
+         * 指定した時間を経過しているノート
+         */
+        "notesHavePassedSpecifiedPeriod": string;
+        /**
+         * 指定した日時より前のノート
+         */
+        "notesOlderThanSpecifiedDateAndTime": string;
+    };
+    "_abuseUserReport": {
+        /**
+         * 転送
+         */
+        "forward": string;
+        /**
+         * 匿名のシステムアカウントとして、リモートサーバーに通報を転送します。
+         */
+        "forwardDescription": string;
+        /**
+         * 解決
+         */
+        "resolve": string;
+        /**
+         * 是認
+         */
+        "accept": string;
+        /**
+         * 否認
+         */
+        "reject": string;
+        /**
+         * 内容が正当である通報に対応した場合は「是認」を選択し、肯定的にケースが解決されたことをマークします。
+         * 内容が正当でない通報の場合は「否認」を選択し、否定的にケースが解決されたことをマークします。
+         */
+        "resolveTutorial": string;
+    };
     "_delivery": {
         /**
          * 配信状態
@@ -5575,6 +5782,10 @@ export interface Locale extends ILocale {
          * 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
          */
         "fanoutTimelineDbFallbackDescription": string;
+        /**
+         * 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。
+         */
+        "reactionsBufferingDescription": string;
         /**
          * 問い合わせ先URL
          */
@@ -5583,6 +5794,10 @@ export interface Locale extends ILocale {
          * サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。
          */
         "inquiryUrlDescription": string;
+        /**
+         * 一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。
+         */
+        "thisSettingWillAutomaticallyOffWhenModeratorsInactive": string;
     };
     "_accountMigration": {
         /**
@@ -6754,6 +6969,26 @@ export interface Locale extends ILocale {
              * アイコンデコレーションの最大取付個数
              */
             "avatarDecorationLimit": string;
+            /**
+             * アンテナのインポートを許可
+             */
+            "canImportAntennas": string;
+            /**
+             * ブロックのインポートを許可
+             */
+            "canImportBlocking": string;
+            /**
+             * フォローのインポートを許可
+             */
+            "canImportFollowing": string;
+            /**
+             * ミュートのインポートを許可
+             */
+            "canImportMuting": string;
+            /**
+             * リストのインポートを許可
+             */
+            "canImportUserLists": string;
         };
         "_condition": {
             /**
@@ -7572,10 +7807,6 @@ export interface Locale extends ILocale {
              * 入力ボックスの縁取り
              */
             "inputBorder": string;
-            /**
-             * リスト項目の背景 (ホバー)
-             */
-            "listItemHoverBg": string;
             /**
              * ドライブフォルダーの背景
              */
@@ -8225,14 +8456,26 @@ export interface Locale extends ILocale {
          * アプリケーションに戻っています
          */
         "callback": string;
+        /**
+         * アクセスを許可しました
+         */
+        "accepted": string;
         /**
          * アクセスを拒否しました
          */
         "denied": string;
+        /**
+         * 以下のユーザーとして操作しています
+         */
+        "scopeUser": string;
         /**
          * アプリケーションにアクセス許可を与えるには、ログインが必要です。
          */
         "pleaseLogin": string;
+        /**
+         * アクセスを許可すると、自動で以下のURLに遷移します
+         */
+        "byClickingYouWillBeRedirectedToThisUrl": string;
     };
     "_antennaSources": {
         /**
@@ -8645,6 +8888,18 @@ export interface Locale extends ILocale {
          * 最大{max}つまでデコレーションを付けられます。
          */
         "avatarDecorationMax": ParameterizedString<"max">;
+        /**
+         * フォローされた時のメッセージ
+         */
+        "followedMessage": string;
+        /**
+         * フォローされた時に相手に表示する短いメッセージを設定できます。
+         */
+        "followedMessageDescription": string;
+        /**
+         * フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。
+         */
+        "followedMessageDescriptionForLockedAccount": string;
     };
     "_exportOrImport": {
         /**
@@ -9102,7 +9357,7 @@ export interface Locale extends ILocale {
          */
         "youGotQuote": ParameterizedString<"name">;
         /**
-         * {name}がRenoteしました
+         * {name}がリノートしました
          */
         "youRenoted": ParameterizedString<"name">;
         /**
@@ -9177,6 +9432,14 @@ export interface Locale extends ILocale {
          * 通知の履歴をリセットする
          */
         "flushNotification": string;
+        /**
+         * {x}のエクスポートが完了しました
+         */
+        "exportOfXCompleted": ParameterizedString<"x">;
+        /**
+         * ログインがありました
+         */
+        "login": string;
         "_types": {
             /**
              * すべて
@@ -9199,7 +9462,7 @@ export interface Locale extends ILocale {
              */
             "reply": string;
             /**
-             * Renote
+             * リノート
              */
             "renote": string;
             /**
@@ -9230,6 +9493,18 @@ export interface Locale extends ILocale {
              * 実績の獲得
              */
             "achievementEarned": string;
+            /**
+             * エクスポートが完了した
+             */
+            "exportCompleted": string;
+            /**
+             * ログイン
+             */
+            "login": string;
+            /**
+             * 通知のテスト
+             */
+            "test": string;
             /**
              * 連携アプリからの通知
              */
@@ -9245,7 +9520,7 @@ export interface Locale extends ILocale {
              */
             "reply": string;
             /**
-             * Renote
+             * リノート
              */
             "renote": string;
         };
@@ -9472,11 +9747,23 @@ export interface Locale extends ILocale {
              * ユーザーが作成されたとき
              */
             "userCreated": string;
+            /**
+             * モデレーターが一定期間非アクティブになったとき
+             */
+            "inactiveModeratorsWarning": string;
+            /**
+             * モデレーターが一定期間非アクティブだったため、システムにより招待制へと変更されたとき
+             */
+            "inactiveModeratorsInvitationOnlyChanged": string;
         };
         /**
          * Webhookを削除しますか?
          */
         "deleteConfirm": string;
+        /**
+         * スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。
+         */
+        "testRemarks": string;
     };
     "_abuseReport": {
         "_notificationRecipient": {
@@ -9639,6 +9926,14 @@ export interface Locale extends ILocale {
          * 通報を解決
          */
         "resolveAbuseReport": string;
+        /**
+         * 通報を転送
+         */
+        "forwardAbuseReport": string;
+        /**
+         * 通報のモデレーションノート更新
+         */
+        "updateAbuseReportNote": string;
         /**
          * 招待コードを作成
          */
@@ -10262,6 +10557,28 @@ export interface Locale extends ILocale {
          */
         "codeGeneratedDescription": string;
     };
+    "_selfXssPrevention": {
+        /**
+         * 警告
+         */
+        "warning": string;
+        /**
+         * 「この画面に何か貼り付けろ」はすべて詐欺です。
+         */
+        "title": string;
+        /**
+         * ここに何かを貼り付けると、悪意のあるユーザーにアカウントを乗っ取られたり、個人情報を盗まれたりする可能性があります。
+         */
+        "description1": string;
+        /**
+         * 貼り付けようとしているものが何なのかを正確に理解していない場合は、%c今すぐ作業を中止してこのウィンドウを閉じてください。
+         */
+        "description2": string;
+        /**
+         * 詳しくはこちらをご確認ください。 {link}
+         */
+        "description3": ParameterizedString<"link">;
+    };
 }
 declare const locales: {
     [lang: string]: Locale;
diff --git a/locales/index.js b/locales/index.js
index c2738884eb34..091d216dee4e 100644
--- a/locales/index.js
+++ b/locales/index.js
@@ -15,6 +15,7 @@ const merge = (...args) => args.reduce((a, c) => ({
 
 const languages = [
 	'ar-SA',
+	'ca-ES',
 	'cs-CZ',
 	'da-DK',
 	'de-DE',
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 2b4b1e425e84..8fb6dcd6f203 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -1,6 +1,6 @@
 ---
 _lang_: "Italiano"
-headlineMisskey: "Rete collegata tramite note"
+headlineMisskey: "Rete collegata tramite Note"
 introMisskey: "Eccoci! Misskey è un servizio di microblogging decentralizzato, libero e aperto. \n\n📡 Puoi pubblicare «Note» per condividere ciò che sta succedendo o per dire a tutti qualcosa su di te. \n\n👍 Puoi reagire inviando emoji rapidi alle «Note» provenienti da altri profili nel Fediverso.\n\n🚀 Esplora un nuovo mondo insieme a noi!"
 poweredByMisskeyDescription: "{name} è uno dei servizi (chiamati istanze) che utilizzano la piattaforma open source <b>Misskey</b>."
 monthAndDay: "{day}/{month}"
@@ -8,6 +8,9 @@ search: "Cerca"
 notifications: "Notifiche"
 username: "Nome utente"
 password: "Password"
+initialPasswordForSetup: "Password iniziale, per avviare le impostazioni"
+initialPasswordIsIncorrect: "Password iniziale, sbagliata."
+initialPasswordForSetupDescription: "Se hai installato Misskey di persona, usa la password che hai indicato nel file di configurazione.\nSe stai utilizzando un servizio di hosting Misskey, usa la password fornita dal gestore.\nSe non hai una password preimpostata, lascia il campo vuoto e continua."
 forgotPassword: "Hai dimenticato la password?"
 fetchingAsApObject: "Recuperando dal Fediverso..."
 ok: "OK"
@@ -60,11 +63,12 @@ copyFileId: "Copia ID del file"
 copyFolderId: "Copia ID della cartella"
 copyProfileUrl: "Copia URL del profilo"
 searchUser: "Cerca profilo"
+searchThisUsersNotes: "Cerca le sue Note"
 reply: "Rispondi"
 loadMore: "Mostra di più"
 showMore: "Espandi"
 showLess: "Comprimi"
-youGotNewFollower: "Adesso ti segue"
+youGotNewFollower: "Hai un nuovo Follower"
 receiveFollowRequest: "Hai ricevuto una richiesta di follow"
 followRequestAccepted: "Ha accettato la tua richiesta di follow"
 mention: "Menzioni"
@@ -76,14 +80,14 @@ export: "Esporta"
 files: "Allegati"
 download: "Scarica"
 driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?"
-unfollowConfirm: "Vuoi davvero smettere di seguire {name}?"
+unfollowConfirm: "Vuoi davvero togliere il Following a {name}?"
 exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive."
 importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo."
 lists: "Liste"
 noLists: "Nessuna lista"
 note: "Nota"
 notes: "Note"
-following: "Follow"
+following: "Following"
 followers: "Follower"
 followsYou: "Follower"
 createList: "Aggiungi una nuova lista"
@@ -102,7 +106,7 @@ defaultNoteVisibility: "Privacy predefinita delle note"
 follow: "Segui"
 followRequest: "Richiesta di follow"
 followRequests: "Richieste di follow"
-unfollow: "Smetti di seguire"
+unfollow: "Togli Following"
 followRequestPending: "Richiesta in approvazione"
 enterEmoji: "Inserisci emoji"
 renote: "Rinota"
@@ -154,6 +158,7 @@ editList: "Modifica Lista"
 selectChannel: "Seleziona canale"
 selectAntenna: "Scegli un'antenna"
 editAntenna: "Modifica Antenna"
+createAntenna: "Crea Antenna"
 selectWidget: "Seleziona il riquadro"
 editWidgets: "Modifica i riquadri"
 editWidgetsExit: "Conferma le modifiche"
@@ -190,10 +195,11 @@ setWallpaper: "Imposta sfondo"
 removeWallpaper: "Elimina lo sfondo"
 searchWith: "Cerca: {q}"
 youHaveNoLists: "Non hai ancora creato nessuna lista"
-followConfirm: "Vuoi seguire {name}?"
+followConfirm: "Confermi il Following a {name}?"
 proxyAccount: "Profilo proxy"
 proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti."
 host: "Host"
+selectSelf: "Segli me"
 selectUser: "Seleziona profilo"
 recipient: "Destinatario"
 annotation: "Annotazione preventiva"
@@ -209,6 +215,7 @@ perDay: "giornaliero"
 stopActivityDelivery: "Interrompi la distribuzione di attività"
 blockThisInstance: "Bloccare l'istanza"
 silenceThisInstance: "Silenziare l'istanza"
+mediaSilenceThisInstance: "Silenzia i media dell'istanza"
 operations: "Operazioni"
 software: "Software"
 version: "Versione"
@@ -230,6 +237,10 @@ blockedInstances: "Istanze bloccate"
 blockedInstancesDescription: "Elenca le istanze che vuoi bloccare, una per riga. Esse non potranno più interagire con la tua istanza."
 silencedInstances: "Istanze silenziate"
 silencedInstancesDescription: "Elenca i nomi host delle istanze che vuoi silenziare. Tutti i profili nelle istanze silenziate vengono trattati come tali. Possono solo inviare richieste di follow e menzionare soltanto i profili locali che seguono. Le istanze bloccate non sono interessate."
+mediaSilencedInstances: "Istanze coi media silenziati"
+mediaSilencedInstancesDescription: "Elenca i nomi host delle istanze di cui vuoi silenziare i media, uno per riga. Tutti gli allegati dei profili nelle istanze silenziate per via degli allegati espliciti, verranno impostati come tali, le emoji personalizzate non saranno disponibili. Le istanze bloccate sono escluse."
+federationAllowedHosts: "Server a cui consentire la federazione"
+federationAllowedHostsDescription: "Indica gli host dei server a cui è consentita la federazione, uno per ogni linea."
 muteAndBlock: "Silenziare e bloccare"
 mutedUsers: "Profili silenziati"
 blockedUsers: "Profili bloccati"
@@ -252,7 +263,7 @@ all: "Tutte"
 subscribing: "Iscrizione"
 publishing: "Pubblicazione"
 notResponding: "Nessuna risposta"
-instanceFollowing: "Seguiti dall'istanza"
+instanceFollowing: "Istanza Following"
 instanceFollowers: "Follower dell'istanza"
 instanceUsers: "Profili nell'istanza"
 changePassword: "Aggiorna Password"
@@ -328,6 +339,7 @@ renameFolder: "Rinomina cartella"
 deleteFolder: "Elimina cartella"
 folder: "Cartella"
 addFile: "Allega"
+showFile: "Visualizza file"
 emptyDrive: "Il Drive è vuoto"
 emptyFolder: "La cartella è vuota"
 unableToDelete: "Eliminazione impossibile"
@@ -442,6 +454,7 @@ totpDescription: "Puoi autenticarti inserendo un codice OTP tramite la tua App d
 moderator: "Moderatore"
 moderation: "moderazione"
 moderationNote: "Promemoria di moderazione"
+moderationNoteDescription: "Puoi scrivere promemoria condivisi solo tra moderatori."
 addModerationNote: "Aggiungi promemoria di moderazione"
 moderationLogs: "Cronologia di moderazione"
 nUsersMentioned: "{n} profili ne parlano"
@@ -449,7 +462,7 @@ securityKeyAndPasskey: "Chiave di sicurezza e accesso"
 securityKey: "Chiave di sicurezza"
 lastUsed: "Ultima attività"
 lastUsedAt: "Uso più recente: {t}"
-unregister: "Annulla l'iscrizione"
+unregister: "Rimuovi autenticazione a due fattori (2FA/MFA)"
 passwordLessLogin: "Accedi senza password"
 passwordLessLoginDescription: "Accedi senza password, usando la chiave di sicurezza"
 resetPassword: "Ripristina la password"
@@ -503,7 +516,10 @@ uiLanguage: "Lingua di visualizzazione dell'interfaccia"
 aboutX: "Informazioni su {x}"
 emojiStyle: "Stile emoji"
 native: "Nativo"
-disableDrawer: "Non mostrare il menù sul drawer"
+menuStyle: "Stile menu"
+style: "Stile"
+drawer: "Drawer"
+popup: "Popup"
 showNoteActionsOnlyHover: "Mostra le azioni delle Note solo al passaggio del mouse"
 showReactionsCount: "Visualizza il numero di reazioni su una nota"
 noHistory: "Nessuna cronologia"
@@ -559,7 +575,7 @@ deleteAll: "Cancella cronologia"
 showFixedPostForm: "Visualizzare la finestra di pubblicazione in cima alla timeline"
 showFixedPostFormInChannel: "Per i canali, mostra il modulo di pubblicazione in cima alla timeline"
 withRepliesByDefaultForNewlyFollowed: "Quando segui nuovi profili, includi le risposte in TL come impostazione predefinita"
-newNoteRecived: "Nuove note da leggere"
+newNoteRecived: "Nuove Note da leggere"
 sounds: "Impostazioni suoni"
 sound: "Suono"
 listen: "Ascolta"
@@ -586,6 +602,8 @@ ascendingOrder: "Aumenta"
 descendingOrder: "Diminuisce"
 scratchpad: "ScratchPad"
 scratchpadDescription: "Lo Scratchpad offre un ambiente per esperimenti di AiScript. È possibile scrivere, eseguire e confermare i risultati dell'interazione del codice con Misskey."
+uiInspector: "UI Inspector"
+uiInspectorDescription: "Puoi visualizzare un elenco di elementi UI presenti in memoria. I componenti dell'interfaccia utente vengono generati dalle funzioni Ui:C:."
 output: "Uscita"
 script: "Script"
 disablePagesScript: "Disabilita AiScript nelle pagine"
@@ -597,7 +615,7 @@ unsetUserBannerConfirm: "Vuoi davvero rimuovere l'intestazione dal profilo?"
 deleteAllFiles: "Elimina tutti i file"
 deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?"
 removeAllFollowing: "Annulla tutti i follow"
-removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più."
+removeAllFollowingDescription: "Togli il Following a tutti i profili su {host}. Utile, ad esempio, quando l'istanza non esiste più."
 userSuspended: "L'utente è in sospensione"
 userSilenced: "Profilo silenziato"
 yourAccountSuspendedTitle: "Questo profilo è sospeso"
@@ -670,7 +688,7 @@ hardWordMute: "Filtro parole forte"
 regexpError: "errore regex"
 regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:"
 instanceMute: "Silenziare l'istanza"
-userSaysSomething: "{name} ha parlato"
+userSaysSomething: "{name} ha detto qualcosa"
 makeActive: "Attiva"
 display: "Visualizza"
 copy: "Copia"
@@ -685,7 +703,7 @@ notificationSetting: "Impostazioni notifiche"
 notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare."
 useGlobalSetting: "Usa impostazioni generali"
 useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifiche del profilo. Altrimenti si possono segliere impostazioni personalizzate."
-other: "Ulteriori"
+other: "Eccetera"
 regenerateLoginToken: "Genera di nuovo un token di connessione"
 regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi."
 theKeywordWhenSearchingForCustomEmoji: "Questa sarà la parola chiave durante la ricerca di emoji personalizzate"
@@ -702,10 +720,7 @@ abuseReported: "La segnalazione è stata inviata. Grazie."
 reporter: "il corrispondente"
 reporteeOrigin: "Segnalazione a"
 reporterOrigin: "Segnalazione da"
-forwardReport: "Inoltro di un report a un'istanza remota."
-forwardReportIsAnonymous: "L'istanza remota non vedrà le tue informazioni, apparirai come profilo di sistema, anonimo."
 send: "Inviare"
-abuseMarkAsResolved: "Risolvi segnalazione"
 openInNewTab: "Apri in una nuova scheda"
 openInSideView: "Apri in vista laterale"
 defaultNavigationBehaviour: "Navigazione preimpostata"
@@ -732,7 +747,7 @@ repliesCount: "Numero di risposte inviate"
 renotesCount: "Numero di note che hai ricondiviso"
 repliedCount: "Numero di risposte ricevute"
 renotedCount: "Numero delle tue note ricondivise"
-followingCount: "Numero di profili seguiti"
+followingCount: "Numero di Following"
 followersCount: "Numero di profili che ti seguono"
 sentReactionsCount: "Numero di reazioni inviate"
 receivedReactionsCount: "Numero di reazioni ricevute"
@@ -827,7 +842,7 @@ onlineStatus: "Stato di connessione"
 hideOnlineStatus: "Modalità invisibile"
 hideOnlineStatusDescription: "Attivando questa opzione potresti ridurre l'usabilità di alcune funzioni, come la ricerca."
 online: "Online"
-active: "Attività"
+active: "Attivo"
 offline: "Offline"
 notRecommended: "Sconsigliato"
 botProtection: "Protezione contro i bot"
@@ -886,8 +901,8 @@ pubSub: "Publish/Subscribe del profilo"
 lastCommunication: "La comunicazione più recente"
 resolved: "Risolto"
 unresolved: "Non risolto"
-breakFollow: "Impedire di seguirmi"
-breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?"
+breakFollow: "Rimuovi Follower"
+breakFollowConfirm: "Vuoi davvero togliere questo Follower?"
 itsOn: "Abilitato"
 itsOff: "Disabilitato"
 on: "Acceso"
@@ -902,11 +917,12 @@ makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a di
 classic: "Classico"
 muteThread: "Silenziare conversazione"
 unmuteThread: "Riattiva la conversazione"
-followingVisibility: "Visibilità dei profili seguiti"
+followingVisibility: "Visibilità dei Following"
 followersVisibility: "Visibilità dei profili che ti seguono"
 continueThread: "Altre conversazioni"
 deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?"
 incorrectPassword: "La password è errata."
+incorrectTotp: "Il codice OTP è sbagliato, oppure scaduto."
 voteConfirm: "Votare per「{choice}」?"
 hide: "Nascondere"
 useDrawerReactionPickerForMobile: "Mostra sul drawer da dispositivo mobile"
@@ -931,6 +947,9 @@ oneHour: "1 ora"
 oneDay: "1 giorno"
 oneWeek: "1 settimana"
 oneMonth: "Un mese"
+threeMonths: "3 mesi"
+oneYear: "1 anno"
+threeDays: "3 giorni"
 reflectMayTakeTime: "Potrebbe essere necessario un po' di tempo perché ciò abbia effetto."
 failedToFetchAccountInformation: "Impossibile recuperare le informazioni sul profilo"
 rateLimitExceeded: "Superato il limite di richieste."
@@ -949,7 +968,7 @@ driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
 driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
 requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore."
 isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
-typeToConfirm: "Per eseguire questa operazione, digitare {x}"
+typeToConfirm: "Digita {x} per continuare"
 deleteAccount: "Eliminazione profilo"
 document: "Documento"
 numberOfPageCache: "Numero di pagine cache"
@@ -1004,7 +1023,7 @@ neverShow: "Non mostrare più"
 remindMeLater: "Rimanda"
 didYouLikeMisskey: "Ti piace Misskey?"
 pleaseDonate: "Misskey è il software libero utilizzato su {host}. Offrendo una donazione è più facile continuare a svilupparlo!"
-correspondingSourceIsAvailable: ""
+correspondingSourceIsAvailable: "Il codice sorgente corrispondente è disponibile su {anchor}."
 roles: "Ruoli"
 role: "Ruolo"
 noRole: "Ruolo non trovato"
@@ -1071,6 +1090,7 @@ retryAllQueuesConfirmTitle: "Vuoi ritentare adesso?"
 retryAllQueuesConfirmText: "Potrebbe sovraccaricare il server temporaneamente."
 enableChartsForRemoteUser: "Abilita i grafici per i profili remoti"
 enableChartsForFederatedInstances: "Abilita i grafici per le istanze federate"
+enableStatsForFederatedInstances: "Informazioni statistiche sui server federati"
 showClipButtonInNoteFooter: "Aggiungi il bottone Clip tra le azioni delle Note"
 reactionsDisplaySize: "Grandezza delle reazioni"
 limitWidthOfReaction: "Limita la larghezza delle reazioni e ridimensionale"
@@ -1106,16 +1126,21 @@ preservedUsernames: "Nomi utente riservati"
 preservedUsernamesDescription: "Elenca, uno per linea, i nomi utente che non possono essere registrati durante la creazione del profilo. La restrizione non si applica agli amministratori. Inoltre, i profili già registrati sono esenti."
 createNoteFromTheFile: "Crea Nota da questo file"
 archive: "Archivio"
+archived: "Archiviato"
+unarchive: "Annulla archiviazione"
 channelArchiveConfirmTitle: "Vuoi davvero archiviare {name}?"
 channelArchiveConfirmDescription: "Un canale archiviato non compare nell'elenco canali, nemmeno nei risultati di ricerca. Non può ricevere nemmeno nuove Note."
 thisChannelArchived: "Questo canale è stato archiviato."
 displayOfNote: "Visualizzazione delle Note"
 initialAccountSetting: "Impostazioni iniziali del profilo"
-youFollowing: "Seguiti"
+youFollowing: "Following"
 preventAiLearning: "Impedisci l'apprendimento della IA"
 preventAiLearningDescription: "Aggiungendo il campo \"noai\" alla risposta HTML, si indica ai Robot esterni di non usare testi e allegati per addestrare sistemi di Machine Learning (IA predittiva/generativa). Anche se è impossibile sapere se la richiesta venga onorata o semplicemente ignorata."
 options: "Opzioni del ruolo"
 specifyUser: "Profilo specifico"
+lookupConfirm: "Vuoi davvero richiedere informazioni?"
+openTagPageConfirm: "Vuoi davvero aprire la pagina dell'hashtag?"
+specifyHost: "Specifica l'host"
 failedToPreviewUrl: "Anteprima non disponibile"
 update: "Aggiorna"
 rolesThatCanBeUsedThisEmojiAsReaction: "Ruoli che possono usare questa emoji come reazione"
@@ -1250,6 +1275,49 @@ inquiry: "Contattaci"
 tryAgain: "Per favore riprova"
 confirmWhenRevealingSensitiveMedia: "Richiedi conferma prima di mostrare gli allegati espliciti"
 sensitiveMediaRevealConfirm: "Questo allegato è esplicito, vuoi vederlo?"
+createdLists: "Liste create"
+createdAntennas: "Antenne create"
+fromX: "Da {x}"
+genEmbedCode: "Ottieni il codice di incorporamento"
+noteOfThisUser: "Elenco di Note di questo profilo"
+clipNoteLimitExceeded: "Non è possibile aggiungere ulteriori Note a questa Clip."
+performance: "Prestazioni"
+modified: "Modificato"
+discard: "Scarta"
+thereAreNChanges: "Ci sono {n} cambiamenti"
+signinWithPasskey: "Accedi con passkey"
+unknownWebAuthnKey: "Questa è una passkey sconosciuta."
+passkeyVerificationFailed: "La verifica della passkey non è riuscita."
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verifica della passkey è riuscita, ma l'accesso senza password è disabilitato."
+messageToFollower: "Messaggio ai follower"
+target: "Riferimento"
+testCaptchaWarning: "Questa funzione è destinata al test CAPTCHA. <strong>Da non utilizzare in ambiente di produzione.</strong>"
+prohibitedWordsForNameOfUser: "Parole proibite (nome utente)"
+prohibitedWordsForNameOfUserDescription: "Il sistema rifiuta di rinominare un utente, se il nome contiene qualsiasi parola nell'elenco. Sono esenti i profili con privilegi di moderazione."
+yourNameContainsProhibitedWords: "Il nome che hai scelto contiene una o più parole vietate"
+yourNameContainsProhibitedWordsDescription: "Se desideri comunque utilizzare questo nome, contatta l''amministrazione."
+thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autore richiede di iscriversi per vedere il contenuto"
+lockdown: "Isolamento"
+pleaseSelectAccount: "Per favore, seleziona un profilo"
+_accountSettings:
+  requireSigninToViewContents: "Per vedere il contenuto, è necessaria l'iscrizione"
+  requireSigninToViewContentsDescription1: "Richiedere l'iscrizione per visualizzare tutte le Note e gli altri contenuti che hai creato. Probabilmente l'effetto è impedire la raccolta di informazioni da parte dei bot crawler."
+  requireSigninToViewContentsDescription2: "La visualizzazione verrà disabilitata a server che non supportano l'anteprima URL (OGP), all'incorporamento nelle pagine Web e alla citazione delle Note."
+  requireSigninToViewContentsDescription3: "Queste restrizioni potrebbero non applicarsi al contenuto federato su server remoti."
+  makeNotesFollowersOnlyBefore: "Rendi visibili solo ai Follower le Note pubblicate in precedenza"
+  makeNotesFollowersOnlyBeforeDescription: "Mentre questa funzione è abilitata, le Note antecedenti al momento impostato, saranno visibili solo ai profili Follower. Disabilitandola nuovamente, verrà ripristinata anche la visibilità pubblica della Nota."
+  makeNotesHiddenBefore: "Nascondi le Note pubblicate in precedenza"
+  makeNotesHiddenBeforeDescription: "Mentre questa funzione è abilitata, le Note antecedenti al momento impostato, saranno visibili soltanto a te (private). Disabilitandola nuovamente, verrà ripristinata anche la visibilità pubblica della Nota."
+  mayNotEffectForFederatedNotes: "Le Note già federate su server remoti potrebbero non essere modificate."
+  notesHavePassedSpecifiedPeriod: "Note antecedenti al periodo specificato"
+  notesOlderThanSpecifiedDateAndTime: "Note antecedenti al momento specificato"
+_abuseUserReport:
+  forward: "Inoltra"
+  forwardDescription: "Inoltra il report al server remoto, per mezzo di account di sistema, anonimo."
+  resolve: "Risolvi"
+  accept: "Approva"
+  reject: "Rifiuta"
+  resolveTutorial: "Se moderi una segnalazione legittima, scegli \"Approva\" per risolvere positivamente.\nSe la segnalazione non è legittima, seleziona \"Rifiuta\" per risolvere negativamente."
 _delivery:
   status: "Stato della consegna"
   stop: "Sospensione"
@@ -1277,16 +1345,16 @@ _bubbleGame:
 _announcement:
   forExistingUsers: "Solo ai profili attuali"
   forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
-  needConfirmationToRead: "Richiede la conferma di lettura"
-  needConfirmationToReadDescription: "Sarà visualizzata una finestra di dialogo che richiede la conferma di lettura. Inoltre, non è soggetto a conferme di lettura massicce."
+  needConfirmationToRead: "Conferma di lettura obbligatoria"
+  needConfirmationToReadDescription: "I profili riceveranno una finestra di dialogo che richiede di accettare obbligatoriamente per procedere. Tale richiesta è esente da  \"conferma tutte\"."
   end: "Archivia l'annuncio"
   tooManyActiveAnnouncementDescription: "L'esperienza delle persone può peggiorare se ci sono troppi annunci attivi. Considera anche l'archiviazione degli annunci conclusi."
   readConfirmTitle: "Segnare come già letto?"
   readConfirmText: "Hai già letto \"{title}˝?"
   shouldNotBeUsedToPresentPermanentInfo: "Ti consigliamo di utilizzare gli annunci per pubblicare informazioni tempestive e limitate nel tempo, anziché informazioni importanti a lungo andare nel tempo, poiché potrebbero risultare difficili da ritrovare e peggiorare la fruibilità del servizio, specialmente alle nuove persone iscritte."
   dialogAnnouncementUxWarn: "Ti consigliamo di usarli con cautela, poiché è molto probabile che avere più di un annuncio in stile \"finestra di dialogo\" peggiori sensibilmente la fruibilità del servizio, specialmente alle nuove persone iscritte."
-  silence: "Silenziare gli annunci"
-  silenceDescription: "Se attivi questa opzione, non riceverai notifiche sugli annunci, evitando di contrassegnarle come già lette."
+  silence: "Annuncio silenzioso"
+  silenceDescription: "Attivando questa opzione, non invierai la notifica, evitando che debba essere contrassegnata come già letta."
 _initialAccountSetting:
   accountCreated: "Il tuo profilo è stato creato!"
   letsStartAccountSetup: "Per iniziare, impostiamo il tuo profilo."
@@ -1304,7 +1372,7 @@ _initialAccountSetting:
   skipAreYouSure: "Vuoi davvero saltare la configurazione iniziale?"
   laterAreYouSure: "Vuoi davvero rimandare la configurazione iniziale?"
 _initialTutorial:
-  launchTutorial: "Guarda il tutorial"
+  launchTutorial: "Inizia il tutorial"
   title: "Tutorial"
   wellDone: "Ottimo lavoro!"
   skipAreYouSure: "Vuoi davvero interrompere il tutorial?"
@@ -1314,13 +1382,13 @@ _initialTutorial:
   _note:
     title: "Cosa sono le Note?"
     description: "Gli status su Misskey sono chiamati \"Note\". Le Note sono elencate in ordine cronologico nelle timeline e vengono aggiornate in tempo reale."
-    reply: "Puoi rispondere alle Note. Puoi anche rispondere alle risposte e continuare i dialoghi come un conversazioni."
-    renote: "Puoi ri-condividere le Note, facendole rifluire sulla Timeline. Puoi anche aggiungere testo e citare altri profili."
-    reaction: "Puoi aggiungere una reazione. Nella pagina successiva spiegheremo i dettagli."
-    menu: "Puoi svolgere varie attività, come visualizzare i dettagli delle Note o copiare i collegamenti."
+    reply: "Puoi rispondere alle Note, alle altre risposte e dialogare in conversazioni."
+    renote: "Puoi ri-condividere le Note, ritorneranno sulla Timeline. Aggiungendo del testo, scriverai una Citazione."
+    reaction: "Puoi aggiungere una reazione. Nella pagina successiva ti spiego come."
+    menu: "Per altre attività, ad esempio, vedere i dettagli delle Note o copiare i collegamenti."
   _reaction:
     title: "Cosa sono le Reazioni?"
-    description: "Puoi reagire alle Note. Le sensazioni che non si riescono a trasmettere con i \"Mi piace\" si possono esprimere facilmente inviando una reazione."
+    description: "Reazioni alle Note. Le sensazioni che non si possono descrivere con \"Mi piace\" si esprimono facilmente con le reazioni."
     letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"+\" (più) della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!"
     reactToContinue: "Aggiungere la Reazione ti consentirà di procedere col tutorial."
     reactNotification: "Quando qualcuno reagisce alle tue Note, ricevi una notifica in tempo reale."
@@ -1328,12 +1396,12 @@ _initialTutorial:
   _timeline:
     title: "Come funziona la Timeline"
     description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori."
-    home: "le Note provenienti dai profili che segui (follow)."
+    home: "le Note provenienti dai profili che segui (Following)."
     local: "tutte le Note pubblicate dai profili di questa istanza."
     social: "sia le Note della Timeline Home che quelle della Timeline Locale, insieme!"
     global: "le Note da pubblicate da tutte le altre istanze federate con la nostra."
     description2: "Nella parte superiore dello schermo, puoi scegliere una Timeline o l'altra in qualsiasi momento."
-    description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare il {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, visita {link}."
+    description3: "Ci sono anche sequenze temporali di elenchi, sequenze temporali di canali, ecc. Per ulteriori dettagli, consultare la {link}.\nPuoi vedere anche Timeline delle liste di profili (se ne hai create), canali, ecc... Per i dettagli, c'è la {link}."
   _postNote:
     title: "La Nota e le sue impostazioni"
     description1: "Quando scrivi una Nota su Misskey, hai a disposizione varie opzioni. Il modulo di invio è simile a questo."
@@ -1366,7 +1434,7 @@ _initialTutorial:
     title: "Il tutorial è finito! 🎉"
     description: "Queste sono solamente alcune delle funzionalità principali di Misskey. Per ulteriori informazioni, {link}."
 _timelineDescription:
-  home: "Nella Timeline Home, la tua cronologia principale, puoi vedere le Note provenienti dai profili che segui (follow)."
+  home: "Nella Timeline Home, la tua cronologia principale, puoi vedere le Note provenienti dai profili che segui (Following)."
   local: "La Timeline Locale, è una cronologia di Note pubblicate da tutti i profili iscritti su questo server."
   social: "La Timeline Sociale, unisce in ordine cronologico l'elenco di Note presenti nella Timeline Home e quella Locale."
   global: "La Timeline Federata ti consente di vedere le Note pubblicate dai profili di tutti gli altri server federati a questo."
@@ -1384,13 +1452,15 @@ _serverSettings:
   fanoutTimelineDescription: "Attivando questa funzionalità migliori notevolmente la capacità delle Timeline di collezionare Note, riducendo il carico sul database. Tuttavia, aumenterà l'impiego di memoria RAM per Redis. Disattiva se il tuo server ha poca RAM o la funzionalità è irregolare."
   fanoutTimelineDbFallback: "Elaborazione dati alternativa"
   fanoutTimelineDbFallbackDescription: "Attivando l'elaborazione alternativa, verrà interrogato ulteriormente il database se la timeline non è nella cache. \nDisattivando, si può ridurre ulteriormente il carico del server, evitando l'elaborazione alternativa, ma limitando l'intervallo recuperabile delle timeline."
+  reactionsBufferingDescription: "Attivando questa opzione, puoi migliorare significativamente le prestazioni durante la creazione delle reazioni e ridurre il carico sul database. Tuttavia, aumenterà l'impiego di memoria Redis."
   inquiryUrl: "URL di contatto"
   inquiryUrlDescription: "Specificare l'URL al modulo di contatto, oppure le informazioni con i dati di contatto dell'amministrazione."
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Per prevenire SPAM, questa impostazione verrà disattivata automaticamente, se non si rileva alcuna attività di moderazione durante un certo periodo di tempo."
 _accountMigration:
   moveFrom: "Migra un altro profilo dentro a questo"
   moveFromSub: "Crea un alias verso un altro profilo remoto"
   moveFromLabel: "Profilo da cui migrare #{n}"
-  moveFromDescription: "Se desideri spostare i profili follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@istanza.it"
+  moveFromDescription: "Se desideri spostare i Follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@istanza.it"
   moveTo: "Migrare questo profilo verso un un altro"
   moveToLabel: "Profilo verso cui migrare"
   moveCannotBeUndone: "La migrazione è irreversibile, non può essere interrotta o annullata."
@@ -1399,7 +1469,7 @@ _accountMigration:
   startMigration: "Avvia la migrazione"
   migrationConfirm: "Vuoi davvero migrare questo profilo su {account}? L'azione è irreversibile e non potrai più utilizzare questo profilo nel suo stato originale.\nInoltre, assicurati di aver già creato un alias sull'account a cui ti stai trasferendo."
   movedAndCannotBeUndone: "Il tuo profilo è stato migrato.\nLa migrazione non può essere annullata."
-  postMigrationNote: "Questo profilo smetterà di seguire gli altri profili remoti a 24 ore dal termine della migrazione.\nSia i Follow che i Follower scenderanno a zero. I tuoi follower saranno comunque in grado di vedere le Note per soli follower, poiché non smetteranno di seguirti."
+  postMigrationNote: "Questo profilo smetterà di seguire gli altri profili remoti a 24 ore dal termine della migrazione.\nSia i Following che i Follower scenderanno a zero. I tuoi Follower saranno comunque in grado di vedere le Note per soli Follower, poiché non smetteranno di seguirti."
   movedTo: "Profilo verso cui migrare"
 _achievements:
   earnedAt: "Data di conseguimento"
@@ -1717,6 +1787,11 @@ _role:
     canSearchNotes: "Ricercare nelle Note"
     canUseTranslator: "Tradurre le Note"
     avatarDecorationLimit: "Numero massimo di decorazioni foto profilo installabili"
+    canImportAntennas: "Può importare Antenne"
+    canImportBlocking: "Può importare Blocchi"
+    canImportFollowing: "Può importare Following"
+    canImportMuting: "Può importare Silenziati"
+    canImportUserLists: "Può importare liste di Profili"
   _condition:
     roleAssignedTo: "Assegnato a ruoli manualmente"
     isLocal: "Profilo locale"
@@ -1787,7 +1862,7 @@ _gallery:
   unlike: "Non mi piace più"
 _email:
   _follow:
-    title: "Adesso ti segue"
+    title: "Follower aggiuntivo"
   _receiveFollowRequest:
     title: "Hai ricevuto una richiesta di follow"
 _plugin:
@@ -1851,7 +1926,7 @@ _channel:
   removeBanner: "Rimuovi intestazione"
   featured: "Di tendenza"
   owned: "I miei canali"
-  following: "Seguiti"
+  following: "Following"
   usersCount: "{n} partecipanti"
   notesCount: "{n} note"
   nameAndDescription: "Nome e descrizione"
@@ -1934,7 +2009,6 @@ _theme:
     buttonBg: "Sfondo del pulsante"
     buttonHoverBg: "Sfondo del pulsante (sorvolato)"
     inputBorder: "Inquadra casella di testo"
-    listItemHoverBg: "Sfondo della voce di elenco (sorvolato)"
     driveFolderBg: "Sfondo della cartella di disco"
     wallpaperOverlay: "Sovrapposizione dello sfondo"
     badge: "Distintivo"
@@ -1954,6 +2028,7 @@ _soundSettings:
   driveFileTypeWarnDescription: "Per favore, scegli un file di tipo audio"
   driveFileDurationWarn: "La durata dell'audio è troppo lunga"
   driveFileDurationWarnDescription: "Scegliere un audio lungo potrebbe interferire con l'uso di Misskey. Vuoi continuare lo stesso?"
+  driveFileError: "Impossibile caricare l'audio. Si prega di modificare le impostazioni"
 _ago:
   future: "Futuro"
   justNow: "Adesso"
@@ -2017,7 +2092,7 @@ _permissions:
   "read:favorites": "Visualizza i tuoi preferiti"
   "write:favorites": "Gestisci i tuoi preferiti"
   "read:following": "Vedi le informazioni di follow"
-  "write:following": "Following di altri profili"
+  "write:following": "Aggiungere e togliere Following"
   "read:messaging": "Visualizzare la chat"
   "write:messaging": "Gestire la chat"
   "read:mutes": "Vedi i profili silenziati"
@@ -2100,11 +2175,14 @@ _auth:
   permissionAsk: "Questa app richiede le seguenti autorizzazioni:"
   pleaseGoBack: "Si prega di ritornare sulla app"
   callback: "Ritornando sulla app"
+  accepted: "Accesso concesso"
   denied: "Accesso negato"
+  scopeUser: "Sto funzionando per il seguente profilo"
   pleaseLogin: "Per favore accedi al tuo account per cambiare i permessi dell'applicazione"
+  byClickingYouWillBeRedirectedToThisUrl: "Consentendo l'accesso, si verrà reindirizzati presso questo indirizzo URL"
 _antennaSources:
   all: "Tutte le note"
-  homeTimeline: "Note dagli utenti che segui"
+  homeTimeline: "Note dai tuoi Following"
   users: "Note dagli utenti selezionati"
   userList: "Note dagli utenti della lista selezionata"
   userBlacklist: "Tutte le Note tranne quelle di uno o più profili specificati"
@@ -2146,7 +2224,7 @@ _widgets:
   _userList:
     chooseList: "Seleziona una lista"
   clicker: "Cliccaggio"
-  birthdayFollowings: "Chi nacque oggi"
+  birthdayFollowings: "Compleanni del giorno"
 _cw:
   hide: "Nascondere"
   show: "Continua la lettura..."
@@ -2210,11 +2288,14 @@ _profile:
   changeBanner: "Cambia intestazione"
   verifiedLinkDescription: "Puoi verificare il tuo profilo mostrando una icona. Devi inserire la URL alla pagina che contiene un link al tuo profilo."
   avatarDecorationMax: "Puoi aggiungere fino a {max} decorazioni."
+  followedMessage: "Messaggio, quando qualcuno ti segue"
+  followedMessageDescription: "Puoi impostare un breve messaggio da mostrare agli altri profili quando ti seguono."
+  followedMessageDescriptionForLockedAccount: "Quando approvi una richiesta di follow, verrà visualizzato questo testo."
 _exportOrImport:
   allNotes: "Tutte le note"
   favoritedNotes: "Note preferite"
   clips: "Clip"
-  followingList: "Follow"
+  followingList: "Following"
   muteList: "Elenco profili silenziati"
   blockingList: "Elenco profili bloccati"
   userLists: "Liste"
@@ -2302,6 +2383,7 @@ _pages:
   eyeCatchingImageSet: "Imposta un'immagine attraente"
   eyeCatchingImageRemove: "Elimina immagine attraente"
   chooseBlock: "Aggiungi blocco"
+  enterSectionTitle: "Inserisci il titolo della sezione"
   selectType: "Seleziona tipo"
   contentBlocks: "Contenuto"
   inputBlocks: "Blocchi di input"
@@ -2329,7 +2411,7 @@ _notification:
   youGotReply: "{name} ti ha risposto"
   youGotQuote: "{name} ha citato la tua Nota e ha detto"
   youRenoted: "{name} ha rinotato"
-  youWereFollowed: "Adesso ti segue"
+  youWereFollowed: "Follower aggiuntivo"
   youReceivedFollowRequest: "Hai ricevuto una richiesta di follow"
   yourFollowRequestAccepted: "La tua richiesta di follow è stata accettata"
   pollEnded: "Risultati del sondaggio."
@@ -2347,10 +2429,12 @@ _notification:
   renotedBySomeUsers: "{n} Rinota"
   followedBySomeUsers: "{n} follower"
   flushNotification: "Azzera le notifiche"
+  exportOfXCompleted: "Abbiamo completato l'esportazione di {x}"
+  login: "Autenticazione avvenuta"
   _types:
     all: "Tutto"
     note: "Nuove Note"
-    follow: "Nuovi profili follower"
+    follow: "Follower"
     mention: "Menzioni"
     reply: "Risposte"
     renote: "Rinota"
@@ -2361,9 +2445,12 @@ _notification:
     followRequestAccepted: "Richiesta di follow accettata"
     roleAssigned: "Ruolo concesso"
     achievementEarned: "Risultato raggiunto"
+    exportCompleted: "Esportazione completata"
+    login: "Accedi"
+    test: "Prova la notifica"
     app: "Notifiche da applicazioni"
   _actions:
-    followBack: "Segui"
+    followBack: "Following ricambiato"
     reply: "Rispondi"
     renote: "Rinota"
 _deck:
@@ -2412,9 +2499,10 @@ _webhookSettings:
   modifyWebhook: "Modifica Webhook"
   name: "Nome"
   secret: "Segreto"
+  trigger: "Trigger"
   active: "Attivo"
   _events:
-    follow: "Quando segui un profilo"
+    follow: "Quando aggiungi Following"
     followed: "Quando ti segue un profilo"
     note: "Quando pubblichi una Nota"
     reply: "Quando rispondono ad una Nota"
@@ -2424,7 +2512,11 @@ _webhookSettings:
   _systemEvents:
     abuseReport: "Quando arriva una segnalazione"
     abuseReportResolved: "Quando una segnalazione è risolta"
+    userCreated: "Quando viene creato un profilo"
+    inactiveModeratorsWarning: "Quando un profilo moderatore rimane inattivo per un determinato periodo"
+    inactiveModeratorsInvitationOnlyChanged: "Quando la moderazione è rimasta inattiva per un determinato periodo e il sistema è cambiato in modalità \"solo inviti\""
   deleteConfirm: "Vuoi davvero eliminare il Webhook?"
+  testRemarks: "Clicca il bottone a destra dell'interruttore, per provare l'invio di un webhook con dati fittizi."
 _abuseReport:
   _notificationRecipient:
     createRecipient: "Aggiungi destinatario della segnalazione"
@@ -2468,6 +2560,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "File nel Drive segnato come esplicito"
   unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito"
   resolveAbuseReport: "Segnalazione risolta"
+  forwardAbuseReport: "Segnalazione inoltrata"
+  updateAbuseReportNote: "Ha aggiornato la segnalazione"
   createInvitation: "Genera codice di invito"
   createAd: "Banner creato"
   deleteAd: "Banner eliminato"
@@ -2483,6 +2577,10 @@ _moderationLogTypes:
   createAbuseReportNotificationRecipient: "Crea destinatario per le notifiche di segnalazioni"
   updateAbuseReportNotificationRecipient: "Aggiorna destinatario notifiche di segnalazioni"
   deleteAbuseReportNotificationRecipient: "Elimina destinatario notifiche di segnalazioni"
+  deleteAccount: "Quando viene eliminato un profilo"
+  deletePage: "Pagina eliminata"
+  deleteFlash: "Play eliminato"
+  deleteGalleryPost: "Eliminazione pubblicazione nella Galleria"
 _fileViewer:
   title: "Dettagli del file"
   type: "Tipo di file"
@@ -2614,3 +2712,28 @@ _mediaControls:
   pip: "Sovraimpressione"
   playbackRate: "Velocità di riproduzione"
   loop: "Ripetizione infinita"
+_contextMenu:
+  title: "Menu contestuale"
+  app: "Applicazione"
+  appWithShift: "Applicazione Shift+Tasto"
+  native: "Interfaccia utente del browser"
+_embedCodeGen:
+  title: "Personalizza il codice di incorporamento"
+  header: "Mostra la testata"
+  autoload: "Carica automaticamente di più (sconsigliato)"
+  maxHeight: "Altezza massima"
+  maxHeightDescription: "Specifica un valore per evitare che continui a crescere verticalmente. Il valore 0 disabilita il limite d'altezza."
+  maxHeightWarn: "L'altezza massima è disabilitata (0). Se l'effetto è indesiderato, prova a impostare l'altezza massima a un valore specifico."
+  previewIsNotActual: "Poiché supera l'intervallo che può essere visualizzato in anteprima, la visualizzazione vera e propria sarà diversa quando effettivamente incorporata."
+  rounded: "Bordo arrotondato"
+  border: "Aggiungi un bordo al contenitore"
+  applyToPreview: "Applica all'anteprima"
+  generateCode: "Crea il codice di incorporamento"
+  codeGenerated: "Codice generato"
+  codeGeneratedDescription: "Incolla il codice appena generato sul tuo sito web."
+_selfXssPrevention:
+  warning: "Avviso"
+  title: "\"Incolla qualcosa su questa schermata\" è tutta una truffa."
+  description1: "Incollando qualcosa qui, malintenzionati potrebbero prendere il controllo del tuo profilo o rubare i tuoi dati personali."
+  description2: "Se non sai esattamente cosa stai facendo, %c smetti subito e chiudi questa finestra."
+  description3: "Per favore, controlla questo collegamento per avere maggiori dettagli. {link}"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index a1210bad2948..5d8e1a5e7256 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -8,6 +8,9 @@ search: "検索"
 notifications: "通知"
 username: "ユーザー名"
 password: "パスワード"
+initialPasswordForSetup: "初期設定開始用パスワード"
+initialPasswordIsIncorrect: "初期設定開始用のパスワードが違います。"
+initialPasswordForSetupDescription: "Misskeyを自分でインストールした場合は、設定ファイルに入力したパスワードを使用してください。\nMisskeyのホスティングサービスなどを使用している場合は、提供されたパスワードを使用してください。\nパスワードを設定していない場合は、空欄にしたまま続行してください。"
 forgotPassword: "パスワードを忘れた"
 fetchingAsApObject: "連合に照会中"
 ok: "OK"
@@ -236,6 +239,8 @@ silencedInstances: "サイレンスしたサーバー"
 silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになります。ブロックしたインスタンスには影響しません。"
 mediaSilencedInstances: "メディアサイレンスしたサーバー"
 mediaSilencedInstancesDescription: "メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。"
+federationAllowedHosts: "連合を許可するサーバー"
+federationAllowedHostsDescription: "連合を許可するサーバーのホストを改行で区切って設定します。"
 muteAndBlock: "ミュートとブロック"
 mutedUsers: "ミュートしたユーザー"
 blockedUsers: "ブロックしたユーザー"
@@ -334,6 +339,7 @@ renameFolder: "フォルダー名を変更"
 deleteFolder: "フォルダーを削除"
 folder: "フォルダー"
 addFile: "ファイルを追加"
+showFile: "ファイルを表示"
 emptyDrive: "ドライブは空です"
 emptyFolder: "フォルダーは空です"
 unableToDelete: "削除できません"
@@ -448,6 +454,7 @@ totpDescription: "認証アプリを使ってワンタイムパスワードを
 moderator: "モデレーター"
 moderation: "モデレーション"
 moderationNote: "モデレーションノート"
+moderationNoteDescription: "モデレーター間でだけ共有されるメモを記入することができます。"
 addModerationNote: "モデレーションノートを追加する"
 moderationLogs: "モデログ"
 nUsersMentioned: "{n}人が投稿"
@@ -509,7 +516,10 @@ uiLanguage: "UIの表示言語"
 aboutX: "{x}について"
 emojiStyle: "絵文字のスタイル"
 native: "ネイティブ"
-disableDrawer: "メニューをドロワーで表示しない"
+menuStyle: "メニューのスタイル"
+style: "スタイル"
+drawer: "ドロワー"
+popup: "ポップアップ"
 showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する"
 showReactionsCount: "ノートのリアクション数を表示する"
 noHistory: "履歴はありません"
@@ -592,6 +602,8 @@ ascendingOrder: "昇順"
 descendingOrder: "降順"
 scratchpad: "スクラッチパッド"
 scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。"
+uiInspector: "UIインスペクター"
+uiInspectorDescription: "メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。"
 output: "出力"
 script: "スクリプト"
 disablePagesScript: "Pagesのスクリプトを無効にする"
@@ -708,10 +720,7 @@ abuseReported: "内容が送信されました。ご報告ありがとうござ
 reporter: "通報者"
 reporteeOrigin: "通報先"
 reporterOrigin: "通報元"
-forwardReport: "リモートサーバーに通報を転送する"
-forwardReportIsAnonymous: "リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。"
 send: "送信"
-abuseMarkAsResolved: "対応済みにする"
 openInNewTab: "新しいタブで開く"
 openInSideView: "サイドビューで開く"
 defaultNavigationBehaviour: "デフォルトのナビゲーション"
@@ -776,7 +785,7 @@ left: "左"
 center: "中央"
 wide: "広い"
 narrow: "狭い"
-reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?"
+reloadToApplySetting: "設定はページリロード後に反映されます。"
 needReloadToApply: "反映には再起動が必要です。"
 showTitlebar: "タイトルバーを表示する"
 clearCache: "キャッシュをクリア"
@@ -913,6 +922,7 @@ followersVisibility: "フォロワーの公開範囲"
 continueThread: "さらにスレッドを見る"
 deleteAccountConfirm: "アカウントが削除されます。よろしいですか?"
 incorrectPassword: "パスワードが間違っています。"
+incorrectTotp: "ワンタイムパスワードが間違っているか、期限切れになっています。"
 voteConfirm: "「{choice}」に投票しますか?"
 hide: "隠す"
 useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
@@ -937,6 +947,9 @@ oneHour: "1時間"
 oneDay: "1日"
 oneWeek: "1週間"
 oneMonth: "1ヶ月"
+threeMonths: "3ヶ月"
+oneYear: "1年"
+threeDays: "3日"
 reflectMayTakeTime: "反映されるまで時間がかかる場合があります。"
 failedToFetchAccountInformation: "アカウント情報の取得に失敗しました"
 rateLimitExceeded: "レート制限を超えました"
@@ -1077,6 +1090,7 @@ retryAllQueuesConfirmTitle: "今すぐ再試行しますか?"
 retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大することがあります。"
 enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
 enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
+enableStatsForFederatedInstances: "リモートサーバーの情報を取得"
 showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 reactionsDisplaySize: "リアクションの表示サイズ"
 limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
@@ -1267,6 +1281,46 @@ fromX: "{x}から"
 genEmbedCode: "埋め込みコードを生成"
 noteOfThisUser: "このユーザーのノート一覧"
 clipNoteLimitExceeded: "これ以上このクリップにノートを追加できません。"
+performance: "パフォーマンス"
+modified: "変更あり"
+discard: "破棄"
+thereAreNChanges: "{n}件の変更があります"
+signinWithPasskey: "パスキーでログイン"
+unknownWebAuthnKey: "登録されていないパスキーです。"
+passkeyVerificationFailed: "パスキーの検証に失敗しました。"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。"
+messageToFollower: "フォロワーへのメッセージ"
+target: "対象"
+testCaptchaWarning: "CAPTCHAのテストを目的とした機能です。<strong>本番環境で使用しないでください。</strong>"
+prohibitedWordsForNameOfUser: "禁止ワード(ユーザーの名前)"
+prohibitedWordsForNameOfUserDescription: "このリストに含まれる文字列がユーザーの名前に含まれる場合、ユーザーの名前の変更を拒否します。モデレーター権限を持つユーザーはこの制限の影響を受けません。"
+yourNameContainsProhibitedWords: "変更しようとした名前に禁止された文字列が含まれています"
+yourNameContainsProhibitedWordsDescription: "名前に禁止されている文字列が含まれています。この名前を使用したい場合は、サーバー管理者にお問い合わせください。"
+thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者により、表示にはログインが必要と設定されています"
+lockdown: "ロックダウン"
+pleaseSelectAccount: "アカウントを選択してください"
+availableRoles: "利用可能なロール"
+
+_accountSettings:
+  requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
+  requireSigninToViewContentsDescription1: "あなたが作成した全てのノートなどのコンテンツを表示するのにログインを必須にします。クローラーに情報が収集されるのを防ぐ効果が期待できます。"
+  requireSigninToViewContentsDescription2: "URLプレビュー(OGP)、Webページへの埋め込み、ノートの引用に対応していないサーバーからの表示も不可になります。"
+  requireSigninToViewContentsDescription3: "リモートサーバーに連合されたコンテンツでは、これらの制限が適用されない場合があります。"
+  makeNotesFollowersOnlyBefore: "過去のノートをフォロワーのみ表示可能にする"
+  makeNotesFollowersOnlyBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートがフォロワーのみ表示可能になります。無効に戻すと、ノートの公開状態も元に戻ります。"
+  makeNotesHiddenBefore: "過去のノートを非公開化する"
+  makeNotesHiddenBeforeDescription: "この機能が有効になっている間、設定された日時より過去、または設定された時間を経過しているノートが自分のみ表示可能(非公開化)になります。無効に戻すと、ノートの公開状態も元に戻ります。"
+  mayNotEffectForFederatedNotes: "リモートサーバーに連合されたノートには効果が及ばない場合があります。"
+  notesHavePassedSpecifiedPeriod: "指定した時間を経過しているノート"
+  notesOlderThanSpecifiedDateAndTime: "指定した日時より前のノート"
+
+_abuseUserReport:
+  forward: "転送"
+  forwardDescription: "匿名のシステムアカウントとして、リモートサーバーに通報を転送します。"
+  resolve: "解決"
+  accept: "是認"
+  reject: "否認"
+  resolveTutorial: "内容が正当である通報に対応した場合は「是認」を選択し、肯定的にケースが解決されたことをマークします。\n内容が正当でない通報の場合は「否認」を選択し、否定的にケースが解決されたことをマークします。"
 
 _delivery:
   status: "配信状態"
@@ -1409,8 +1463,10 @@ _serverSettings:
   fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
   fanoutTimelineDbFallback: "データベースへのフォールバック"
   fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
+  reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。"
   inquiryUrl: "問い合わせ先URL"
   inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。"
 
 _accountMigration:
   moveFrom: "別のアカウントからこのアカウントに移行"
@@ -1745,6 +1801,11 @@ _role:
     canSearchNotes: "ノート検索の利用"
     canUseTranslator: "翻訳機能の利用"
     avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
+    canImportAntennas: "アンテナのインポートを許可"
+    canImportBlocking: "ブロックのインポートを許可"
+    canImportFollowing: "フォローのインポートを許可"
+    canImportMuting: "ミュートのインポートを許可"
+    canImportUserLists: "リストのインポートを許可"
   _condition:
     roleAssignedTo: "マニュアルロールにアサイン済み"
     isLocal: "ローカルユーザー"
@@ -1984,7 +2045,6 @@ _theme:
     buttonBg: "ボタンの背景"
     buttonHoverBg: "ボタンの背景 (ホバー)"
     inputBorder: "入力ボックスの縁取り"
-    listItemHoverBg: "リスト項目の背景 (ホバー)"
     driveFolderBg: "ドライブフォルダーの背景"
     wallpaperOverlay: "壁紙のオーバーレイ"
     badge: "バッジ"
@@ -2159,8 +2219,11 @@ _auth:
   permissionAsk: "このアプリは次の権限を要求しています"
   pleaseGoBack: "アプリケーションに戻ってやっていってください"
   callback: "アプリケーションに戻っています"
+  accepted: "アクセスを許可しました"
   denied: "アクセスを拒否しました"
+  scopeUser: "以下のユーザーとして操作しています"
   pleaseLogin: "アプリケーションにアクセス許可を与えるには、ログインが必要です。"
+  byClickingYouWillBeRedirectedToThisUrl: "アクセスを許可すると、自動で以下のURLに遷移します"
 
 _antennaSources:
   all: "全てのノート"
@@ -2277,6 +2340,9 @@ _profile:
   changeBanner: "バナー画像を変更"
   verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
   avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
+  followedMessage: "フォローされた時のメッセージ"
+  followedMessageDescription: "フォローされた時に相手に表示する短いメッセージを設定できます。"
+  followedMessageDescriptionForLockedAccount: "フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。"
 
 _exportOrImport:
   allNotes: "全てのノート"
@@ -2405,7 +2471,7 @@ _notification:
   youGotMention: "{name}からのメンション"
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
-  youRenoted: "{name}がRenoteしました"
+  youRenoted: "{name}がリノートしました"
   youWereFollowed: "フォローされました"
   youReceivedFollowRequest: "フォローリクエストが来ました"
   yourFollowRequestAccepted: "フォローリクエストが承認されました"
@@ -2424,6 +2490,8 @@ _notification:
   renotedBySomeUsers: "{n}人がリノートしました"
   followedBySomeUsers: "{n}人にフォローされました"
   flushNotification: "通知の履歴をリセットする"
+  exportOfXCompleted: "{x}のエクスポートが完了しました"
+  login: "ログインがありました"
 
   _types:
     all: "すべて"
@@ -2431,7 +2499,7 @@ _notification:
     follow: "フォロー"
     mention: "メンション"
     reply: "リプライ"
-    renote: "Renote"
+    renote: "リノート"
     quote: "引用"
     reaction: "リアクション"
     pollEnded: "アンケートが終了"
@@ -2439,12 +2507,15 @@ _notification:
     followRequestAccepted: "フォローが受理された"
     roleAssigned: "ロールが付与された"
     achievementEarned: "実績の獲得"
+    exportCompleted: "エクスポートが完了した"
+    login: "ログイン"
+    test: "通知のテスト"
     app: "連携アプリからの通知"
 
   _actions:
     followBack: "フォローバック"
     reply: "返信"
-    renote: "Renote"
+    renote: "リノート"
 
 _deck:
   alwaysShowMainColumn: "常にメインカラムを表示"
@@ -2511,7 +2582,10 @@ _webhookSettings:
     abuseReport: "ユーザーから通報があったとき"
     abuseReportResolved: "ユーザーからの通報を処理したとき"
     userCreated: "ユーザーが作成されたとき"
+    inactiveModeratorsWarning: "モデレーターが一定期間非アクティブになったとき"
+    inactiveModeratorsInvitationOnlyChanged: "モデレーターが一定期間非アクティブだったため、システムにより招待制へと変更されたとき"
   deleteConfirm: "Webhookを削除しますか?"
+  testRemarks: "スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。"
 
 _abuseReport:
   _notificationRecipient:
@@ -2557,6 +2631,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "ファイルをセンシティブ付与"
   unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
   resolveAbuseReport: "通報を解決"
+  forwardAbuseReport: "通報を転送"
+  updateAbuseReportNote: "通報のモデレーションノート更新"
   createInvitation: "招待コードを作成"
   createAd: "広告を作成"
   deleteAd: "広告を削除"
@@ -2736,3 +2812,10 @@ _embedCodeGen:
   generateCode: "埋め込みコードを作成"
   codeGenerated: "コードが生成されました"
   codeGeneratedDescription: "生成されたコードをウェブサイトに貼り付けてご利用ください。"
+
+_selfXssPrevention:
+  warning: "警告"
+  title: "「この画面に何か貼り付けろ」はすべて詐欺です。"
+  description1: "ここに何かを貼り付けると、悪意のあるユーザーにアカウントを乗っ取られたり、個人情報を盗まれたりする可能性があります。"
+  description2: "貼り付けようとしているものが何なのかを正確に理解していない場合は、%c今すぐ作業を中止してこのウィンドウを閉じてください。"
+  description3: "詳しくはこちらをご確認ください。 {link}"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 98045b43aca5..50132c064569 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -8,6 +8,9 @@ search: "探す"
 notifications: "通知"
 username: "ユーザー名"
 password: "パスワード"
+initialPasswordForSetup: "初期設定開始用パスワード"
+initialPasswordIsIncorrect: "初期設定開始用のパスワードがちゃうで。"
+initialPasswordForSetupDescription: "Miskkeyを自分でインストールしたんやったら、設定ファイルに入れたパスワードを使ってや。\nホスティングサービスを使っとるんやったら、サービスから言われたやつを使うんやで。\n別に何も設定しとらんのやったら、何も入れずに空けといてな。"
 forgotPassword: "パスワード忘れたん?"
 fetchingAsApObject: "今ちと連合に照会しとるで"
 ok: "ええで"
@@ -236,6 +239,8 @@ silencedInstances: "サーバーサイレンスされてんねん"
 silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定すんで。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなんねん。ブロックしたインスタンスには影響せーへんで。"
 mediaSilencedInstances: "メディアサイレンスしたサーバー"
 mediaSilencedInstancesDescription: "メディアサイレンスしたいサーバーのホストを改行で区切って設定するで。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われてな、カスタム絵文字が使えへんようになるで。ブロックしたインスタンスには影響せえへんで。"
+federationAllowedHosts: "連合を許すサーバー"
+federationAllowedHostsDescription: "連合してもいいサーバーのホストを行ごとに区切って設定してや。"
 muteAndBlock: "ミュートとブロック"
 mutedUsers: "ミュートしとるユーザー"
 blockedUsers: "ブロックしとるユーザー"
@@ -334,6 +339,7 @@ renameFolder: "フォルダー名を変える"
 deleteFolder: "フォルダーをほかす"
 folder: "フォルダー"
 addFile: "ファイルを追加"
+showFile: "ファイル出す"
 emptyDrive: "ドライブは空っぽや"
 emptyFolder: "このフォルダーは空や"
 unableToDelete: "消せんかったわ"
@@ -448,6 +454,7 @@ totpDescription: "認証アプリ使うてワンタイムパスワードを入
 moderator: "モデレーター"
 moderation: "モデレーション"
 moderationNote: "モデレーションノート"
+moderationNoteDescription: "モデレーターの中だけで共有するメモを入れれるで。"
 addModerationNote: "モデレーションノートを追加するで"
 moderationLogs: "モデログ"
 nUsersMentioned: "{n}人が投稿"
@@ -509,7 +516,10 @@ uiLanguage: "UIの表示言語"
 aboutX: "{x}について"
 emojiStyle: "絵文字のスタイル"
 native: "ネイティブ"
-disableDrawer: "メニューをドロワーで表示せえへん"
+menuStyle: "メニューのスタイル"
+style: "スタイル"
+drawer: "ドロワー"
+popup: "ポップアップ"
 showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで"
 showReactionsCount: "ノートのリアクション数を表示する"
 noHistory: "履歴はないわ。"
@@ -592,6 +602,8 @@ ascendingOrder: "小さい順"
 descendingOrder: "大きい順"
 scratchpad: "スクラッチパッド"
 scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Misskeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
+uiInspector: "UIインスペクター"
+uiInspectorDescription: "メモリ上にあるUIコンポーネントのインスタンス一覧を見れるで。UIコンポーネントはUi:C:系関数で生成されるで。"
 output: "出力"
 script: "スクリプト"
 disablePagesScript: "Pagesのスクリプトを無効にしてや"
@@ -708,10 +720,7 @@ abuseReported: "無事内容が送信されたみたいやで。おおきに〜
 reporter: "通報者"
 reporteeOrigin: "通報先"
 reporterOrigin: "通報元"
-forwardReport: "リモートサーバーに通報を転送するで"
-forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見えんなって、匿名のシステムアカウントとして表示されるで。"
 send: "送信"
-abuseMarkAsResolved: "対応したで"
 openInNewTab: "新しいタブで開く"
 openInSideView: "サイドビューで開く"
 defaultNavigationBehaviour: "デフォルトのナビゲーション"
@@ -913,6 +922,7 @@ followersVisibility: "フォロワーの公開範囲"
 continueThread: "さらにスレッドを見るで"
 deleteAccountConfirm: "アカウントを消すで?ええんか?"
 incorrectPassword: "パスワードがちゃうわ。"
+incorrectTotp: "ワンタイムパスワードが間違っとるか、期限が切れとるみたいやな。"
 voteConfirm: "「{choice}」に投票するんか?"
 hide: "隠す"
 useDrawerReactionPickerForMobile: "ケータイとかのときドロワーで表示するで"
@@ -1077,6 +1087,7 @@ retryAllQueuesConfirmTitle: "もっかいやってみるか?"
 retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへんで。"
 enableChartsForRemoteUser: "リモートユーザーのチャートを作る"
 enableChartsForFederatedInstances: "リモートサーバーのチャートを作る"
+enableStatsForFederatedInstances: "リモートサーバの情報を取得"
 showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 reactionsDisplaySize: "ツッコミの表示のでかさ"
 limitWidthOfReaction: "ツッコミの最大横幅を制限して、ちっさく表示するで"
@@ -1263,6 +1274,32 @@ confirmWhenRevealingSensitiveMedia: "センシティブなメディアを表示
 sensitiveMediaRevealConfirm: "センシティブなメディアやで。表示するんか?"
 createdLists: "作成したリスト"
 createdAntennas: "作成したアンテナ"
+fromX: "{x}から"
+genEmbedCode: "埋め込みコードを作る"
+noteOfThisUser: "このユーザーのノート全部"
+clipNoteLimitExceeded: "これ以上このクリップにノート追加でけへんわ。"
+performance: "パフォーマンス"
+modified: "変更あり"
+discard: "やめる"
+thereAreNChanges: "{n}個の変更があるみたいや"
+signinWithPasskey: "パスキーでログイン"
+unknownWebAuthnKey: "登録されてへんパスキーやな。"
+passkeyVerificationFailed: "パスキーの検証に失敗したで。"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証は成功したんやけど、パスワードレスログインが無効になっとるわ。"
+messageToFollower: "フォロワーへのメッセージ"
+target: "対象"
+testCaptchaWarning: "CAPTCHAのテストを目的としてるで。<strong>絶対に本番環境で使わんといてな。絶対やで。</strong>"
+prohibitedWordsForNameOfUser: "禁止ワード(ユーザー名)"
+prohibitedWordsForNameOfUserDescription: "このリストの中にある文字列がユーザー名に入っとったら、その名前に変更できひんようになるで。モデレーター権限があるユーザーは除外や。"
+yourNameContainsProhibitedWords: "その名前は禁止した文字列が含まれとるで"
+yourNameContainsProhibitedWordsDescription: "その名前は禁止した文字列が含まれとるわ。どうしてもって言うなら、サーバー管理者に言うしかないで。"
+_abuseUserReport:
+  forward: "転送"
+  forwardDescription: "匿名のシステムアカウントってことにして、リモートサーバーに通報を転送するで。"
+  resolve: "解決"
+  accept: "ええよ"
+  reject: "あかんよ"
+  resolveTutorial: "内容がええなら「ええよ」を選ぶんや。肯定的に解決されたことにして記録するで。\n逆に、内容がだめなら「あかんよ」を選びいや。否定的に解決されたって記録しとくで。"
 _delivery:
   status: "配信状態"
   stop: "配信せぇへん"
@@ -1397,8 +1434,10 @@ _serverSettings:
   fanoutTimelineDescription: "入れると、おのおのタイムラインを取得するときにめちゃめちゃ動きが良うなって、データベースが軽くなるわ。でも、Redisのメモリ使う量が増えるから注意な。サーバーのメモリが足りんときとか、動きが変なときは切れるで。"
   fanoutTimelineDbFallback: "データベースにフォールバックする"
   fanoutTimelineDbFallbackDescription: "有効にしたら、タイムラインがキャッシュん中に入ってないときにDBにもっかい問い合わせるフォールバック処理ってのをやっとくで。切ったらフォールバック処理をやらんからサーバーはもっと軽くなんねんけど、タイムラインの取得範囲がちょっと減るで。"
+  reactionsBufferingDescription: "有効にしたら、リアクション作るときのパフォーマンスがすっごい上がって、データベースへの負荷が減るで。代わりに、Redisのメモリ使用は増えるで。"
   inquiryUrl: "問い合わせ先URL"
   inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定するで。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターがおらんかったら、スパムを防ぐためにこの設定は勝手に切られるで。"
 _accountMigration:
   moveFrom: "別のアカウントからこのアカウントに引っ越す"
   moveFromSub: "別のアカウントへエイリアスを作る"
@@ -1730,6 +1769,11 @@ _role:
     canSearchNotes: "ノート探せるかどうか"
     canUseTranslator: "翻訳使えるかどうか"
     avatarDecorationLimit: "アイコンデコのいっちばんつけれる数"
+    canImportAntennas: "アンテナのインポートを許す"
+    canImportBlocking: "ブロックのインポートを許す"
+    canImportFollowing: "フォローのインポートを許す"
+    canImportMuting: "ミュートのインポートを許す"
+    canImportUserLists: "リストのインポートを許す"
   _condition:
     roleAssignedTo: "マニュアルロールにアサイン済み"
     isLocal: "ローカルユーザー"
@@ -1947,7 +1991,6 @@ _theme:
     buttonBg: "ボタンの背景"
     buttonHoverBg: "ボタンの背景 (ホバー)"
     inputBorder: "入力ボックスの縁取り"
-    listItemHoverBg: "リスト項目の背景 (ホバー)"
     driveFolderBg: "ドライブフォルダーの背景"
     wallpaperOverlay: "壁紙のオーバーレイ"
     badge: "バッジ"
@@ -2224,6 +2267,9 @@ _profile:
   changeBanner: "バナー画像を変更するで"
   verifiedLinkDescription: "内容をURLに設定すると、リンク先のwebサイトに自分のプロフのリンクが含まれてる場合に所有者確認済みアイコンを表示させることができるで。"
   avatarDecorationMax: "最大{max}つまでデコつけれんで"
+  followedMessage: "フォローされたら返すメッセージ"
+  followedMessageDescription: "フォローされたときに相手に返す短めのメッセージを決めれるで。"
+  followedMessageDescriptionForLockedAccount: "フォローが承認制なら、フォローリクエストをOKしたときに見せるで。"
 _exportOrImport:
   allNotes: "全てのノート"
   favoritedNotes: "お気に入りにしたノート"
@@ -2316,6 +2362,7 @@ _pages:
   eyeCatchingImageSet: "アイキャッチ画像を設定"
   eyeCatchingImageRemove: "アイキャッチ画像を削除"
   chooseBlock: "ブロックを追加"
+  enterSectionTitle: "セクションタイトルを入れる"
   selectType: "種類を選択"
   contentBlocks: "コンテンツ"
   inputBlocks: "入力"
@@ -2361,13 +2408,15 @@ _notification:
   renotedBySomeUsers: "{n}人がリノートしたで"
   followedBySomeUsers: "{n}人にフォローされたで"
   flushNotification: "通知の履歴をリセットする"
+  exportOfXCompleted: "{x}のエクスポートが終わったわ"
+  login: "ログインしとったで"
   _types:
     all: "すべて"
     note: "あんたらの新規投稿"
     follow: "フォロー"
     mention: "メンション"
     reply: "リプライ"
-    renote: "Renote"
+    renote: "リノート"
     quote: "引用"
     reaction: "ツッコミ"
     pollEnded: "アンケートが終了したで"
@@ -2375,11 +2424,14 @@ _notification:
     followRequestAccepted: "フォローが受理されたで"
     roleAssigned: "ロールが付与された"
     achievementEarned: "実績の獲得"
+    exportCompleted: "エクスポート終わった"
+    login: "ログイン"
+    test: "通知テスト"
     app: "連携アプリからの通知や"
   _actions:
     followBack: "フォローバック"
     reply: "返事"
-    renote: "Renote"
+    renote: "リノート"
 _deck:
   alwaysShowMainColumn: "いつもメインカラムを表示"
   columnAlign: "カラムの寄せ"
@@ -2440,7 +2492,10 @@ _webhookSettings:
     abuseReport: "ユーザーから通報があったとき"
     abuseReportResolved: "ユーザーからの通報を処理したとき"
     userCreated: "ユーザーが作成されたとき"
+    inactiveModeratorsWarning: "モデレーターがしばらくおらんかったとき"
+    inactiveModeratorsInvitationOnlyChanged: "モデレーターがしばらくおらんかったから、システムが招待制に変えたとき"
   deleteConfirm: "ほんまにWebhookをほかしてもええんか?"
+  testRemarks: "スイッチ右のボタンを押すとダミーデータを使ったテスト用Webhookを送れるで。"
 _abuseReport:
   _notificationRecipient:
     createRecipient: "通報の通知先を追加"
@@ -2484,6 +2539,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "ファイルをセンシティブ付与"
   unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
   resolveAbuseReport: "苦情を解決"
+  forwardAbuseReport: "通報を転送"
+  updateAbuseReportNote: "通報のモデレーションノート更新"
   createInvitation: "招待コード作る"
   createAd: "広告を作んで"
   deleteAd: "広告ほかす"
@@ -2495,6 +2552,14 @@ _moderationLogTypes:
   unsetUserBanner: "この子のバナー元に戻す"
   createSystemWebhook: "SystemWebhookを作成"
   updateSystemWebhook: "SystemWebhookを更新"
+  deleteSystemWebhook: "SystemWebhookを削除"
+  createAbuseReportNotificationRecipient: "通報の通知先作る"
+  updateAbuseReportNotificationRecipient: "通報の通知先更新"
+  deleteAbuseReportNotificationRecipient: "通報の通知先消す"
+  deleteAccount: "アカウント消す"
+  deletePage: "ページ消す"
+  deleteFlash: "Playをほかす"
+  deleteGalleryPost: "ギャラリーの投稿をほかす"
 _fileViewer:
   title: "ファイルの詳しい情報"
   type: "ファイルの種類"
@@ -2626,3 +2691,22 @@ _mediaControls:
   pip: "ピクチャインピクチャ"
   playbackRate: "再生速度"
   loop: "ループ再生"
+_contextMenu:
+  title: "コンテキストメニュー"
+  app: "アプリ"
+  appWithShift: "Shiftキーでアプリ"
+  native: "ブラウザのUI"
+_embedCodeGen:
+  title: "埋め込みコードをカスタム"
+  header: "ヘッダー出す"
+  autoload: "勝手に続きを読み込む(非推奨)"
+  maxHeight: "高さの最大値"
+  maxHeightDescription: "0は最大値を指定せえへんけど、ウィジェットが伸び続けるから絶対1以上にしといてや。"
+  maxHeightWarn: "高さの最大値が無効になっとるで。意図してへん変更なら、普通の値に戻してや。"
+  previewIsNotActual: "プレビュー画面で出せる範囲をはみ出したから、ホンマの表示とはちゃうとおもうで。"
+  rounded: "角丸める"
+  border: "外枠に枠線つける"
+  applyToPreview: "プレビューに反映"
+  generateCode: "埋め込みコード作る"
+  codeGenerated: "コード作ったで"
+  codeGeneratedDescription: "作ったコードはウェブサイトに貼っつけて使ってや。"
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index b3ad46f2b16a..222599572a61 100644
--- a/locales/kn-IN.yml
+++ b/locales/kn-IN.yml
@@ -77,6 +77,8 @@ _profile:
   username: "ಬಳಕೆಹೆಸರು"
 _notification:
   youWereFollowed: "ಹಿಂಬಾಲಿಸಿದರು"
+  _types:
+    login: "ಪ್ರವೇಶ"
   _actions:
     reply: "ಉತ್ತರಿಸು"
 _deck:
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 9323ed2a26ae..1f7faba23a4c 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -468,7 +468,7 @@ tooShort: "억수로 짜립니다"
 tooLong: "억수로 집니다"
 passwordMatched: "맞십니다"
 passwordNotMatched: "안 맞십니다"
-signinWith: "{n}서 로그인"
+signinWith: "{x} 서 로그인"
 signinFailed: "로그인 몬 했십니다. 고 이름이랑 비밀번호 제대로 썼는가 확인해 주이소."
 or: "아니면"
 language: "언어"
@@ -476,7 +476,6 @@ uiLanguage: "UI 표시 언어"
 aboutX: "{x}에 대해서"
 emojiStyle: "이모지 모양"
 native: "기본"
-disableDrawer: "드로어 메뉴 쓰지 않기"
 showNoteActionsOnlyHover: "마우스 올맀을 때만 노트 액션 버턴 보이기"
 noHistory: "기록이 없십니다"
 signinHistory: "로그인 기록"
@@ -583,6 +582,9 @@ describeFile: "캡션 옇기"
 enterFileDescription: "캡션 서기"
 author: "맨던 사람"
 manage: "간리"
+large: "커게"
+medium: "엔갆게"
+small: "쪼맪게"
 emailServer: "전자우펜 서버"
 email: "전자우펜"
 emailAddress: "전자우펜 주소"
@@ -599,7 +601,6 @@ reportAbuseOf: "{name}님얼 신고하기"
 reporter: "신고한 사람"
 reporteeOrigin: "신고덴 사람"
 reporterOrigin: "신고한 곳"
-forwardReport: "웬겍 서버에 신고 보내기"
 waitingFor: "{x}(얼)럴 지달리고 잇십니다"
 random: "무작이"
 system: "시스템"
@@ -613,12 +614,14 @@ followersCount: "팔로워 수"
 noteFavoritesCount: "질겨찾기한 노트 수"
 clips: "클립 맨걸기"
 clearCache: "캐시 비우기"
+nUsers: "{n} 사용자"
 typingUsers: "{users} 님이 서고 잇어예"
 unlikeConfirm: "좋네예럴 무룹니꺼?"
 info: "정보"
 selectAccount: "계정 개리기"
 user: "사용자"
 administration: "간리"
+middle: "엔갆게"
 translatedFrom: "{x}서 번옉"
 on: "킴"
 off: "껌"
@@ -633,6 +636,7 @@ oneMonth: "한 달"
 file: "파일"
 typeToConfirm: "게속할라먼 {x}럴 누질라 주이소"
 pleaseSelect: "개리 주이소"
+remoteOnly: "웬겍만"
 tools: "도구"
 like: "좋네예!"
 unlike: "좋네예 무루기"
@@ -643,7 +647,10 @@ role: "옉할"
 noRole: "옉할이 어ᇝ십니다"
 thisPostMayBeAnnoyingCancel: "아이예"
 likeOnly: "좋네예마"
+hiddenTags: "수ᇚ훈 해시태그"
 myClips: "내 클립"
+preservedUsernames: "예약 사용자 이럼"
+specifyUser: "사용자 지정"
 icon: "아바타"
 replies: "답하기"
 renotes: "리노트"
@@ -709,6 +716,16 @@ _achievements:
       description: "0분 0초에 노트를 섰어예"
     _tutorialCompleted:
       description: "길라잡이럴 껕냇십니다"
+_role:
+  displayOrder: "보기 순서"
+  _priority:
+    middle: "엔갆게"
+  _options:
+    canHideAds: "강고 수ᇚ후기"
+  _condition:
+    isRemote: "웬겍 사용자"
+    isCat: "갱이 사용자"
+    isBot: "자동 사용자"
 _gallery:
   my: "내 걸"
   liked: "좋네예한 걸"
@@ -792,10 +809,13 @@ _notification:
   _types:
     follow: "팔로잉"
     mention: "멘션"
+    renote: "리노트"
     quote: "따오기"
     reaction: "반엉"
+    login: "로그인"
   _actions:
     reply: "답하기"
+    renote: "리노트"
 _deck:
   _columns:
     notifications: "알림"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 34c1cc3ebfb5..414202adab50 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -8,6 +8,9 @@ search: "검색"
 notifications: "알림"
 username: "유저명"
 password: "비밀번호"
+initialPasswordForSetup: "초기 설정용 비밀번호"
+initialPasswordIsIncorrect: "초기 설정용 비밀번호가 올바르지 않습니다."
+initialPasswordForSetupDescription: "Misskey를 직접 설치하는 경우, 설정 파일에 입력해둔 비밀번호를 사용하세요.\nMisskey 설치를 도와주는 호스팅 서비스 등을 사용하는 경우, 서비스 제공자로부터 받은 비밀번호를 사용하세요.\n비밀번호를 따로 설정하지 않은 경우, 아무것도 입력하지 않아도 됩니다."
 forgotPassword: "비밀번호 재설정"
 fetchingAsApObject: "연합에서 찾아보는 중"
 ok: "확인"
@@ -52,14 +55,15 @@ deleteAndEditConfirm: "이 노트를 삭제한 뒤 다시 편집하시겠습니
 addToList: "리스트에 추가"
 addToAntenna: "안테나에 추가"
 sendMessage: "메시지 보내기"
-copyRSS: "RSS 주소 복사"
+copyRSS: "RSS 복사"
 copyUsername: "유저명 복사"
 copyUserId: "유저 ID 복사"
 copyNoteId: "노트 ID 복사"
 copyFileId: "파일 ID 복사"
 copyFolderId: "폴더 ID 복사"
 copyProfileUrl: "프로필 URL 복사"
-searchUser: "유저 검색"
+searchUser: "사용자 검색"
+searchThisUsersNotes: "사용자의 노트 검색"
 reply: "답글"
 loadMore: "더 보기"
 showMore: "더 보기"
@@ -154,6 +158,7 @@ editList: "리스트 편집"
 selectChannel: "채널 선택"
 selectAntenna: "안테나 선택"
 editAntenna: "안테나 편집"
+createAntenna: "안테나 만들기"
 selectWidget: "위젯 선택"
 editWidgets: "위젯 편집"
 editWidgetsExit: "편집 종료"
@@ -194,6 +199,7 @@ followConfirm: "{name}님을 팔로우 하시겠습니까?"
 proxyAccount: "프록시 계정"
 proxyAccountDescription: "프록시 계정은 특정 조건 하에서 유저의 리모트 팔로우를 대행하는 계정입니다. 예를 들면, 유저가 리모트 유저를 리스트에 넣었을 때, 리스트에 들어간 유저를 아무도 팔로우한 적이 없다면 액티비티가 서버로 배달되지 않기 때문에, 대신 프록시 계정이 해당 유저를 팔로우하도록 합니다."
 host: "호스트"
+selectSelf: "본인을 선택"
 selectUser: "유저 선택"
 recipient: "수신인"
 annotation: "내용에 대한 주석"
@@ -209,6 +215,7 @@ perDay: "1일마다"
 stopActivityDelivery: "액티비티 보내지 않기"
 blockThisInstance: "이 서버를 차단"
 silenceThisInstance: "서버를 사일런스"
+mediaSilenceThisInstance: "서버의 미디어를 사일런스"
 operations: "작업"
 software: "소프트웨어"
 version: "버전"
@@ -230,6 +237,10 @@ blockedInstances: "차단된 서버"
 blockedInstancesDescription: "차단하려는 서버의 호스트 이름을 줄바꿈으로 구분하여 설정합니다. 차단된 인스턴스는 이 인스턴스와 통신할 수 없게 됩니다."
 silencedInstances: "사일런스한 서버"
 silencedInstancesDescription: "사일런스하려는 서버의 호스트명을 한 줄에 하나씩 입력합니다. 사일런스된 서버에 소속된 유저는 모두 '사일런스'된 상태로 취급되며, 이 서버로부터의 팔로우가 프로필 설정과 무관하게 승인제로 변경되고, 팔로워가 아닌 로컬 유저에게는 멘션할 수 없게 됩니다. 정지된 서버에는 적용되지 않습니다."
+mediaSilencedInstances: "미디어를 사일런스한 서버"
+mediaSilencedInstancesDescription: "미디어를 사일런스 하려는 서버의 호스트를 한 줄에 하나씩 입력합니다. 미디어가 사일런스된 서버의 유저가 업로드한 파일은 모두 민감한 미디어로 처리되며, 커스텀 이모지를 사용할 수 없게 됩니다. 또한, 차단한 인스턴스에는 적용되지 않습니다."
+federationAllowedHosts: "연합을 허가하는 서버"
+federationAllowedHostsDescription: "연합을 허가하는 서버의 호스트를 엔터로 구분해서 설정합니다."
 muteAndBlock: "뮤트 및 차단"
 mutedUsers: "뮤트한 유저"
 blockedUsers: "차단한 유저"
@@ -328,6 +339,7 @@ renameFolder: "폴더 이름 바꾸기"
 deleteFolder: "폴더 삭제"
 folder: "폴더"
 addFile: "파일 추가"
+showFile: "파일 표시하기"
 emptyDrive: "드라이브가 비어 있습니다"
 emptyFolder: "폴더가 비어 있습니다"
 unableToDelete: "삭제할 수 없습니다"
@@ -373,7 +385,7 @@ registration: "등록"
 enableRegistration: "신규 회원가입을 활성화"
 invite: "초대"
 driveCapacityPerLocalAccount: "로컬 유저 한 명당 드라이브 용량"
-driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량"
+driveCapacityPerRemoteAccount: "원격 사용자별 드라이브 용량"
 inMb: "메가바이트 단위"
 bannerUrl: "배너 이미지 URL"
 backgroundImageUrl: "배경 이미지 URL"
@@ -442,6 +454,7 @@ totpDescription: "인증 앱을 사용하여 일회성 비밀번호 입력"
 moderator: "모더레이터"
 moderation: "조정"
 moderationNote: "조정 기록"
+moderationNoteDescription: "모더레이터 역할을 가진 유저만 보이는 메모를 적을 수 있습니다."
 addModerationNote: "조정 기록 추가하기"
 moderationLogs: "모더레이션 로그"
 nUsersMentioned: "{n}명이 언급함"
@@ -503,7 +516,10 @@ uiLanguage: "UI 표시 언어"
 aboutX: "{x}에 대하여"
 emojiStyle: "이모지 스타일"
 native: "기본"
-disableDrawer: "드로어 메뉴를 사용하지 않기"
+menuStyle: "메뉴 스타일"
+style: "스타일"
+drawer: "서랍"
+popup: "팝업"
 showNoteActionsOnlyHover: "마우스가 올라간 때에만 노트 동작 버튼을 표시하기"
 showReactionsCount: "노트의 반응 수를 표시하기"
 noHistory: "기록이 없습니다"
@@ -586,6 +602,8 @@ ascendingOrder: "오름차순"
 descendingOrder: "내림차순"
 scratchpad: "스크래치 패드"
 scratchpadDescription: "스크래치 패드는 AiScript 의 테스트 환경을 제공합니다. Misskey 와 상호 작용하는 코드를 작성, 실행 및 결과를 확인할 수 있습니다."
+uiInspector: "UI 인스펙터"
+uiInspectorDescription: "메모리에 있는 UI 컴포넌트의 인스턴트 목록을 볼 수 있습니다. UI 컴포넌트는 Ui:C: 계열 함수로 만들어집니다."
 output: "출력"
 script: "스크립트"
 disablePagesScript: "Pages 에서 AiScript 를 사용하지 않음"
@@ -702,10 +720,7 @@ abuseReported: "신고를 보냈습니다. 신고해 주셔서 감사합니다."
 reporter: "신고자"
 reporteeOrigin: "피신고자"
 reporterOrigin: "신고자"
-forwardReport: "리모트 서버에도 신고 내용 보내기"
-forwardReportIsAnonymous: "리모트 서버에서는 나의 정보를 볼 수 없으며, 익명의 시스템 계정으로 표시됩니다."
 send: "전송"
-abuseMarkAsResolved: "해결됨으로 표시"
 openInNewTab: "새 탭에서 열기"
 openInSideView: "사이드뷰로 열기"
 defaultNavigationBehaviour: "기본 탐색 동작"
@@ -907,6 +922,7 @@ followersVisibility: "팔로워의 공개 범위"
 continueThread: "글타래 더 보기"
 deleteAccountConfirm: "계정이 삭제되고 되돌릴 수 없게 됩니다. 계속하시겠습니까? "
 incorrectPassword: "비밀번호가 올바르지 않습니다."
+incorrectTotp: "OTP 번호가 틀렸거나 유효기간이 만료되어 있을 수 있습니다."
 voteConfirm: "\"{choice}\"에 투표하시겠습니까?"
 hide: "숨기기"
 useDrawerReactionPickerForMobile: "모바일에서 드로어 메뉴로 표시"
@@ -1071,6 +1087,7 @@ retryAllQueuesConfirmTitle: "지금 다시 시도하시겠습니까?"
 retryAllQueuesConfirmText: "일시적으로 서버의 부하가 증가할 수 있습니다."
 enableChartsForRemoteUser: "리모트 유저의 차트를 생성"
 enableChartsForFederatedInstances: "리모트 서버의 차트를 생성"
+enableStatsForFederatedInstances: "리모트 서버 정보 받아오기"
 showClipButtonInNoteFooter: "노트 동작에 클립을 추가"
 reactionsDisplaySize: "리액션 표시 크기"
 limitWidthOfReaction: "리액션의 최대 폭을 제한하고 작게 표시하기"
@@ -1106,6 +1123,8 @@ preservedUsernames: "예약한 사용자 이름"
 preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다."
 createNoteFromTheFile: "이 파일로 노트를 작성"
 archive: "아카이브"
+archived: "아카이브 됨"
+unarchive: "보관 취소"
 channelArchiveConfirmTitle: "{name} 채널을 보존하시겠습니까?"
 channelArchiveConfirmDescription: "보존한 채널은 채널 목록과 검색 결과에 표시되지 않으며 새로운 노트도 작성할 수 없습니다."
 thisChannelArchived: "이 채널은 보존되었습니다."
@@ -1116,6 +1135,9 @@ preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부"
 preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다."
 options: "옵션"
 specifyUser: "사용자 지정"
+lookupConfirm: "조회 할까요?"
+openTagPageConfirm: "해시태그의 페이지를 열까요?"
+specifyHost: "호스트 지정"
 failedToPreviewUrl: "미리 볼 수 없음"
 update: "업데이트"
 rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할"
@@ -1249,6 +1271,35 @@ alwaysConfirmFollow: "팔로우일 때 항상 확인하기"
 inquiry: "문의하기"
 tryAgain: "다시 시도해 주세요."
 confirmWhenRevealingSensitiveMedia: "민감한 미디어를 열 때 두 번 확인"
+sensitiveMediaRevealConfirm: "민감한 미디어입니다. 표시할까요?"
+createdLists: "만든 리스트"
+createdAntennas: "만든 안테나"
+fromX: "{x}부터"
+genEmbedCode: "임베디드 코드 만들기"
+noteOfThisUser: "이 유저의 노트 목록"
+clipNoteLimitExceeded: "더 이상 이 클립에 노트를 추가 할 수 없습니다."
+performance: "퍼포먼스"
+modified: "변경 있음"
+discard: "파기"
+thereAreNChanges: "{n}건 변경이 있습니다."
+signinWithPasskey: "패스키로 로그인"
+unknownWebAuthnKey: "등록되지 않은 패스키입니다."
+passkeyVerificationFailed: "패스키 검증을 실패했습니다."
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증했으나, 비밀번호 없이 로그인하기가 꺼져 있습니다."
+messageToFollower: "팔로워에 보낼 메시지"
+target: "대상"
+testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
+prohibitedWordsForNameOfUser: "금지 단어 (사용자 이름)"
+prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 사용자 이름에 있는 경우, 일반 사용자는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 사용자는 제한 대상에서 제외됩니다."
+yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다."
+yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요."
+_abuseUserReport:
+  forward: "전달"
+  forwardDescription: "익명 시스템 계정을 사용하여 리모트 서버에 신고 내용을 전달할 수 있습니다."
+  resolve: "해결됨"
+  accept: "인용"
+  reject: "기각"
+  resolveTutorial: "적절한 신고 내용에 대응한 경우, \"인용\"을 선택하여 \"해결됨\"으로 기록합니다.\n적절하지 않은 신고를 받은 경우, \"기각\"을 선택하여 \"기각\"으로 기록합니다."
 _delivery:
   status: "전송 상태"
   stop: "정지됨"
@@ -1383,8 +1434,10 @@ _serverSettings:
   fanoutTimelineDescription: "활성화하면 각종 타임라인을 가져올 때의 성능을 대폭 향상하며, 데이터베이스의 부하를 줄일 수 있습니다. 단, Redis의 메모리 사용량이 증가합니다. 서버의 메모리 용량이 작거나, 서비스가 불안정해지는 경우 비활성화할 수 있습니다."
   fanoutTimelineDbFallback: "데이터베이스를 예비로 사용하기"
   fanoutTimelineDbFallbackDescription: "활성화하면 타임라인의 캐시되어 있지 않은 부분에 대해 DB에 질의하여 정보를 가져옵니다. 비활성화하면 이를 실행하지 않음으로써 서버의 부하를 줄일 수 있지만, 타임라인에서 가져올 수 있는 게시물 범위가 한정됩니다."
+  reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 퍼포먼스가 대폭 향상되어 DB의 부하를 줄일 수 있으나, Redis의 메모리 사용량이 많아집니다."
   inquiryUrl: "문의처 URL"
   inquiryUrlDescription: "서버 운영자에게 보내는 문의 양식의 URL이나 운영자의 연락처 등이 적힌 웹 페이지의 URL을 설정합니다."
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "일정 기간동안 모더레이터의 활동이 감지되지 않는 경우, 스팸 방지를 위해 이 설정은 자동으로 꺼집니다."
 _accountMigration:
   moveFrom: "다른 계정에서 이 계정으로 이사"
   moveFromSub: "다른 계정에 대한 별칭을 생성"
@@ -1716,10 +1769,15 @@ _role:
     canSearchNotes: "노트 검색 이용 가능 여부"
     canUseTranslator: "번역 기능의 사용"
     avatarDecorationLimit: "아바타 장식의 최대 붙임 개수"
+    canImportAntennas: "안테나 가져오기 허용"
+    canImportBlocking: "차단 목록 가져오기 허용"
+    canImportFollowing: "팔로우 가져오기 허용"
+    canImportMuting: "뮤트 목록 가져오기 허용"
+    canImportUserLists: "리스트 목록 가져오기 허용"
   _condition:
     roleAssignedTo: "수동 역할에 이미 할당됨"
     isLocal: "로컬 사용자"
-    isRemote: "리모트 사용자"
+    isRemote: "원격 사용자"
     isCat: "고양이 사용자"
     isBot: "봇 사용자"
     isSuspended: "정지된 사용자"
@@ -1933,7 +1991,6 @@ _theme:
     buttonBg: "버튼 배경"
     buttonHoverBg: "버튼 배경 (호버)"
     inputBorder: "입력 필드 테두리"
-    listItemHoverBg: "리스트 항목 배경 (호버)"
     driveFolderBg: "드라이브 폴더 배경"
     wallpaperOverlay: "배경화면 오버레이"
     badge: "배지"
@@ -1949,10 +2006,11 @@ _sfx:
 _soundSettings:
   driveFile: "드라이브에 있는 오디오를 사용"
   driveFileWarn: "드라이브에 있는 파일을 선택하세요."
-  driveFileTypeWarn: "이 파일은 지원되지 않습니다."
+  driveFileTypeWarn: "이 파이"
   driveFileTypeWarnDescription: "오디오 파일을 선택하세요."
   driveFileDurationWarn: "오디오가 너무 깁니다"
   driveFileDurationWarnDescription: "긴 오디오로 설정할 경우 미스키 사용에 지장이 갈 수도 있습니다. 그래도 괜찮습니까?"
+  driveFileError: "오디오를 불러올 수 없습니다. 설정을 바꿔주세요."
 _ago:
   future: "미래"
   justNow: "방금 전"
@@ -2209,6 +2267,9 @@ _profile:
   changeBanner: "배너 이미지 변경"
   verifiedLinkDescription: "내용에 자신의 프로필로 향하는 링크가 포함된 페이지의 URL을 삽입하면 소유자 인증 마크가 표시됩니다."
   avatarDecorationMax: "최대 {max}개까지 장식을 할 수 있습니다."
+  followedMessage: "팔로우 받았을 때 메시지"
+  followedMessageDescription: "팔로우 받았을 때 상대방에게 보여줄 단문 메시지를 설정할 수 있습니다."
+  followedMessageDescriptionForLockedAccount: "팔로우를 승인제로 한 경우, 팔로우 요청을 수락했을 때 보여줍니다."
 _exportOrImport:
   allNotes: "모든 노트"
   favoritedNotes: "즐겨찾기한 노트"
@@ -2301,6 +2362,7 @@ _pages:
   eyeCatchingImageSet: "아이캐치 이미지를 설정"
   eyeCatchingImageRemove: "아이캐치 이미지를 삭제"
   chooseBlock: "블록 추가"
+  enterSectionTitle: "섹션 타이틀을 입력하기"
   selectType: "종류 선택"
   contentBlocks: "콘텐츠"
   inputBlocks: "입력"
@@ -2346,6 +2408,8 @@ _notification:
   renotedBySomeUsers: "{n}명이 리노트했습니다"
   followedBySomeUsers: "{n}명에게 팔로우됨"
   flushNotification: "알림 이력을 초기화"
+  exportOfXCompleted: "{x} 추출에 성공했습니다."
+  login: "로그인 알림이 있습니다"
   _types:
     all: "전부"
     note: "사용자의 새 글"
@@ -2360,6 +2424,9 @@ _notification:
     followRequestAccepted: "팔로우 요청이 승인되었을 때"
     roleAssigned: "역할이 부여 됨"
     achievementEarned: "도전 과제 획득"
+    exportCompleted: "추출을 성공함"
+    login: "로그인"
+    test: "알림 테스트"
     app: "연동된 앱을 통한 알림"
   _actions:
     followBack: "팔로우"
@@ -2411,6 +2478,7 @@ _webhookSettings:
   modifyWebhook: "Webhook 수정"
   name: "이름"
   secret: "시크릿"
+  trigger: "트리거"
   active: "활성화"
   _events:
     follow: "누군가를 팔로우했을 때"
@@ -2421,15 +2489,18 @@ _webhookSettings:
     reaction: "누군가 내 노트에 리액션했을 때"
     mention: "누군가 나를 멘션했을 때"
   _systemEvents:
-    abuseReport: "유저로부터 신고를 받았을 때"
+    abuseReport: "유저롭"
     abuseReportResolved: "받은 신고를 처리했을 때"
     userCreated: "유저가 생성되었을 때"
+    inactiveModeratorsWarning: "모더레이터가 일정 기간동안 활동하지 않은 경우"
+    inactiveModeratorsInvitationOnlyChanged: "모더레이터가 일정 기간 활동하지 않아 시스템에 의해 초대제로 바뀐 경우"
   deleteConfirm: "Webhook을 삭제할까요?"
+  testRemarks: "스위치 오른쪽에 있는 버튼을 클릭하여 더미 데이터를 사용한 테스트용 웹 훅을 보낼 수 있습니다."
 _abuseReport:
   _notificationRecipient:
     createRecipient: "신고 수신자 추가"
     modifyRecipient: "신고 수신자 편집"
-    recipientType: "알림 수신 유형"
+    recipientType: "알림 종류"
     _recipientType:
       mail: "이메일"
       webhook: "Webhook"
@@ -2437,7 +2508,7 @@ _abuseReport:
         mail: "모더레이터 권한을 가진 사용자의 이메일 주소에 알림을 보냅니다 (신고를 받은 때에만)"
         webhook: "지정한 SystemWebhook에 알림을 보냅니다 (신고를 받은 때와 해결했을 때에 송신)"
     keywords: "키워드"
-    notifiedUser: "신고 알림을 보낼 유저"
+    notifiedUser: "알릴 사용자"
     notifiedWebhook: "사용할 Webhook"
     deleteConfirm: "수신자를 삭제하시겠습니까?"
 _moderationLogTypes:
@@ -2468,6 +2539,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "파일에 열람주의를 설정"
   unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
   resolveAbuseReport: "신고 처리"
+  forwardAbuseReport: "신고 전달"
+  updateAbuseReportNote: "신고 조정 노트 갱신"
   createInvitation: "초대 코드 생성"
   createAd: "광고 생성"
   deleteAd: "광고 삭제"
@@ -2483,6 +2556,10 @@ _moderationLogTypes:
   createAbuseReportNotificationRecipient: "신고 알림 수신자 생성"
   updateAbuseReportNotificationRecipient: "신고 알림 수신자 편집"
   deleteAbuseReportNotificationRecipient: "신고 알림 수신자 삭제"
+  deleteAccount: "계정을 삭제"
+  deletePage: "페이지를 삭제"
+  deleteFlash: "Play를 삭제"
+  deleteGalleryPost: "갤러리 포스트를 삭제"
 _fileViewer:
   title: "파일 상세"
   type: "파일 유형"
@@ -2603,7 +2680,7 @@ _urlPreviewSetting:
   timeoutDescription: "미리보기를 로딩하는데 걸리는 시간이 정한 시간보다 오래 걸리는 경우, 미리보기를 생성하지 않습니다."
   maximumContentLength: "Content-Length의 최대치 (byte)"
   maximumContentLengthDescription: "Content-Length가 이 값을 넘어서면 미리보기를 생성하지 않습니다."
-  requireContentLength: "Content-Length를 얻었을 때만 미리보기 만들기"
+  requireContentLength: "Content-Length를 받아온 경우에만 "
   requireContentLengthDescription: "상대 서버가 Content-Length를 되돌려주지 않는다면 미리보기를 만들지 않습니다."
   userAgent: "User-Agent"
   userAgentDescription: "미리보기를 얻을 때 사용한 User-Agent를 설정합니다. 비어 있다면 기본값의 User-Agent를 사용합니다."
@@ -2614,3 +2691,22 @@ _mediaControls:
   pip: "화면 속 화면"
   playbackRate: "재생 속도"
   loop: "반복 재생"
+_contextMenu:
+  title: "컨텍스트 메뉴"
+  app: "애플리케이션"
+  appWithShift: "Shift 키로 애플리케이션"
+  native: "브라우저의 UI"
+_embedCodeGen:
+  title: "임베디드 코드를 커스터마이즈"
+  header: "해더를 표시"
+  autoload: "자동으로 다음 코드를 실행 (비권장)"
+  maxHeight: "최대 높이"
+  maxHeightDescription: "최대 값을 무시하려면 0을 입력하세요. 위젯이 상하로 길어지는 것을 방지하려면, 임의의 값을 입력해 주세요."
+  maxHeightWarn: "높이 최대 값이 설정되어져 있지 않습니다(0). 의도적으로 설정 하지 않았다면 임의의 값을 설정해주세요."
+  previewIsNotActual: "미리보기로 표시할 수 있는 크기보다 큽니다. 실제로 넣은 코드의 표시가 다른 경우가 있습니다."
+  rounded: "외곽선을 둥글게 하기"
+  border: "외곽선에 테두리를 씌우기"
+  applyToPreview: "미리보기에 반영"
+  generateCode: "임베디드 코드를 만들기"
+  codeGenerated: "코드를 만들었습니다."
+  codeGeneratedDescription: "만들어진 코드를 웹 사이트에 붙여서 사용하세요."
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 1bead5635dfa..b100d0300f26 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -456,6 +456,7 @@ _notification:
     renote: "Renote"
     quote: "ອ້າງອີງ"
     reaction: "Reaction"
+    login: "ເຂົ້າ​ສູ່​ລະ​ບົບ"
   _actions:
     reply: "ຕອບ​ກັບ"
     renote: "Renote"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index eb48cf72da02..dde3035357dd 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -486,6 +486,7 @@ _notification:
     renote: "Herdelen"
     quote: "Quote"
     reaction: "Reacties"
+    login: "Inloggen"
   _actions:
     reply: "Antwoord"
     renote: "Herdelen"
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index cd00ecf9abe6..c5f61db74506 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -701,6 +701,7 @@ _notification:
     renote: "Renotes"
     quote: "Sitater"
     reaction: "Reaksjoner"
+    login: "Logg inn"
   _actions:
     reply: "Svar"
     renote: "Renote"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 73eff0941a06..d7afd577607e 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -492,7 +492,6 @@ uiLanguage: "Język wyświetlania UI"
 aboutX: "O {x}"
 emojiStyle: "Styl emoji"
 native: "Natywny"
-disableDrawer: "Nie używaj menu w stylu szuflady"
 showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszką"
 showReactionsCount: "Wyświetl liczbę reakcji na notatkę"
 noHistory: "Brak historii"
@@ -690,10 +689,7 @@ abuseReported: "Twoje zgłoszenie zostało wysłane. Dziękujemy."
 reporter: "Zgłaszający"
 reporteeOrigin: "Pochodzenie zgłoszonego"
 reporterOrigin: "Pochodzenie zgłaszającego"
-forwardReport: "Przekaż zgłoszenie do innej instancji"
-forwardReportIsAnonymous: "Zamiast twojego konta, anonimowe konto systemowe będzie wyświetlone jako zgłaszający na instancji zdalnej."
 send: "Wyślij"
-abuseMarkAsResolved: "Oznacz zgłoszenie jako rozwiązane"
 openInNewTab: "Otwórz w nowej karcie"
 openInSideView: "Otwórz w bocznym widoku"
 defaultNavigationBehaviour: "Domyślne zachowanie nawigacji"
@@ -1209,7 +1205,6 @@ _theme:
     buttonBg: "Tło przycisku"
     buttonHoverBg: "Tło przycisku (po najechaniu)"
     inputBorder: "Obramowanie pola wejścia"
-    listItemHoverBg: "Tło elementu listy (po najechaniu)"
     driveFolderBg: "Tło folderu na dysku"
     wallpaperOverlay: "Nakładka tapety"
     badge: "Odznaka"
@@ -1510,6 +1505,7 @@ _notification:
     reaction: "Reakcja"
     receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
     followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
+    login: "Zaloguj się"
     app: "Powiadomienia z aplikacji"
   _actions:
     followBack: "zaobserwował cię z powrotem"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 87f934201c19..9039fd21414b 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -9,7 +9,7 @@ notifications: "Notificações"
 username: "Nome de usuário"
 password: "Senha"
 forgotPassword: "Esqueci-me da senha"
-fetchingAsApObject: "Buscando no Fediverso"
+fetchingAsApObject: "Buscando no Fediverso..."
 ok: "OK"
 gotIt: "Entendi"
 cancel: "Cancelar"
@@ -82,7 +82,7 @@ exportRequested: "A sua solicitação de exportação foi enviada. Isso pode lev
 importRequested: "A sua solicitação de importação foi enviada. Isso pode levar algum tempo."
 lists: "Listas"
 noLists: "Não possui nenhuma lista"
-note: "Post"
+note: "Publicar"
 notes: "Posts"
 following: "Seguindo"
 followers: "Seguidores"
@@ -272,7 +272,7 @@ more: "Mais!"
 featured: "Destaques"
 usernameOrUserId: "Nome de usuário ou ID do usuário"
 noSuchUser: "Usuário não encontrado"
-lookup: "Buscando"
+lookup: "Consultar"
 announcements: "Avisos"
 imageUrl: "URL da imagem"
 remove: "Remover"
@@ -296,7 +296,7 @@ explore: "Explorar"
 messageRead: "Lida"
 noMoreHistory: "Não existe histórico anterior"
 startMessaging: "Iniciar conversação"
-nUsersRead: "{n} Pessoas leem"
+nUsersRead: "{n} pessoas leram"
 agreeTo: "Eu concordo com {0}"
 agree: "Concordar"
 agreeBelow: "Eu concordo com o seguinte"
@@ -312,7 +312,7 @@ birthday: "Aniversário"
 yearsOld: "{age} anos"
 registeredDate: "Data de registro"
 location: "Localização"
-theme: "tema"
+theme: "Tema"
 themeForLightMode: "Temas usados ​​no modo de luz"
 themeForDarkMode: "Temas usados ​​no modo escuro"
 light: "Claro"
@@ -509,7 +509,6 @@ uiLanguage: "Idioma de exibição da interface "
 aboutX: "Sobre {x}"
 emojiStyle: "Estilo de emojis"
 native: "Nativo"
-disableDrawer: "Não mostrar o menu em formato de gaveta"
 showNoteActionsOnlyHover: "Exibir as ações da nota somente ao passar o cursor sobre ela"
 showReactionsCount: "Ver o número de reações nas notas"
 noHistory: "Ainda não há histórico"
@@ -700,7 +699,7 @@ fileIdOrUrl: "ID do arquivo ou URL"
 behavior: "Comportamento"
 sample: "Exemplo"
 abuseReports: "Denúncias"
-reportAbuse: "Denúncias"
+reportAbuse: "Denunciar"
 reportAbuseRenote: "Reportar repostagem"
 reportAbuseOf: "Denunciar {name}"
 fillAbuseReportDescription: "Por favor, forneça detalhes sobre o motivo da denúncia. Se houver uma nota específica envolvida, inclua também a URL dela."
@@ -708,10 +707,7 @@ abuseReported: "Denúncia enviada. Obrigado por sua ajuda."
 reporter: "Denunciante"
 reporteeOrigin: "Origem da denúncia"
 reporterOrigin: "Origem do denunciante"
-forwardReport: "Encaminhar a denúncia para o servidor remoto"
-forwardReportIsAnonymous: "No servidor remoto, suas informações não serão visíveis, e você será apresentado como uma conta do sistema anônima."
 send: "Enviar"
-abuseMarkAsResolved: "Marcar denúncia como resolvida"
 openInNewTab: "Abrir em nova aba"
 openInSideView: "Abrir em visão lateral"
 defaultNavigationBehaviour: "Navegação padrão"
@@ -843,7 +839,7 @@ switchAccount: "Trocar conta"
 enabled: "Ativado"
 disabled: "Desativado"
 quickAction: "Ações rápidas"
-user: "Usuários"
+user: "Usuário"
 administration: "Administrar"
 accounts: "Contas"
 switch: "Trocar"
@@ -1062,7 +1058,7 @@ resetPasswordConfirm: "Deseja realmente mudar a sua senha?"
 sensitiveWords: "Palavras sensíveis"
 sensitiveWordsDescription: "A visibilidade de todas as notas contendo as palavras configuradas será colocadas como \"Início\" automaticamente. Você pode listar várias delas separando-as por linha."
 sensitiveWordsDescription2: "Utilizar espaços irá criar expressões aditivas (AND) e cercar palavras-chave com barras irá transformá-las em expressões regulares (RegEx)"
-prohibitedWords: "Palavras proibídas"
+prohibitedWords: "Palavras proibidas"
 prohibitedWordsDescription: "Habilita um erro ao tentar publicar uma nota contendo as palavras escolhidas. Várias palavras podem ser escolhidas, separando-as por linha."
 prohibitedWordsDescription2: "Utilizar espaços irá criar expressões aditivas (AND) e cercar palavras-chave com barras irá transformá-las em expressões regulares (RegEx)"
 hiddenTags: "Hashtags escondidas"
@@ -1263,6 +1259,7 @@ confirmWhenRevealingSensitiveMedia: "Confirmar ao revelar mídia sensível"
 sensitiveMediaRevealConfirm: "Essa mídia pode ser sensível. Deseja revelá-la?"
 createdLists: "Listas criadas"
 createdAntennas: "Antenas criadas"
+clipNoteLimitExceeded: "Não é possível adicionar mais notas ao clipe."
 _delivery:
   status: "Estado de entrega"
   stop: "Suspenso"
@@ -1419,7 +1416,7 @@ _achievements:
   _types:
     _notes1:
       title: "Configurando o meu misskey"
-      description: "Post uma nota pela primeira vez"
+      description: "Poste uma nota pela primeira vez"
       flavor: "Divirta-se com o Misskey!"
     _notes10:
       title: "Algumas notas"
@@ -1947,7 +1944,6 @@ _theme:
     buttonBg: "Plano de fundo de botão"
     buttonHoverBg: "Plano de fundo de botão (Selecionado)"
     inputBorder: "Borda de campo digitável"
-    listItemHoverBg: "Plano de fundo do item de uma lista (Selecionado)"
     driveFolderBg: "Plano de fundo da pasta no Drive"
     wallpaperOverlay: "Sobreposição do papel de parede."
     badge: "Emblema"
@@ -2316,6 +2312,7 @@ _pages:
   eyeCatchingImageSet: "Escolher miniatura"
   eyeCatchingImageRemove: "Excluir miniatura"
   chooseBlock: "Adicionar bloco"
+  enterSectionTitle: "Insira um título à seção"
   selectType: "Selecionar um tipo"
   contentBlocks: "Conteúdo"
   inputBlocks: "Inserir"
@@ -2368,13 +2365,14 @@ _notification:
     mention: "Menção"
     reply: "Respostas"
     renote: "Repostar"
-    quote: "Citar"
+    quote: "Citações"
     reaction: "Reações"
     pollEnded: "Enquetes terminando"
     receiveFollowRequest: "Recebeu pedidos de seguidor"
     followRequestAccepted: "Aceitou pedidos de seguidor"
     roleAssigned: "Cargo dado"
     achievementEarned: "Conquista desbloqueada"
+    login: "Iniciar sessão"
     app: "Notificações de aplicativos conectados"
   _actions:
     followBack: "te seguiu de volta"
@@ -2499,6 +2497,10 @@ _moderationLogTypes:
   createAbuseReportNotificationRecipient: "Criar um destinatário para relatórios de abuso"
   updateAbuseReportNotificationRecipient: "Atualizar destinatários para relatórios de abuso"
   deleteAbuseReportNotificationRecipient: "Remover um destinatário para relatórios de abuso"
+  deleteAccount: "Remover conta"
+  deletePage: "Remover página"
+  deleteFlash: "Remover Play"
+  deleteGalleryPost: "Remover a publicação da galeria"
 _fileViewer:
   title: "Detalhes do arquivo"
   type: "Tipo de arquivo"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index b4c9b90de9c8..3cc09aa5c2d6 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -453,7 +453,6 @@ or: "Sau"
 language: "Limbă"
 uiLanguage: "Limba interfeței"
 aboutX: "Despre {x}"
-disableDrawer: "Nu folosi meniuri în stil sertar"
 noHistory: "Nu există istoric"
 signinHistory: "Istoric autentificări"
 doing: "Se procesează..."
@@ -626,10 +625,7 @@ 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"
@@ -715,6 +711,7 @@ _notification:
     renote: "Re-notează"
     quote: "Citează"
     reaction: "Reacție"
+    login: "Autentifică-te"
   _actions:
     reply: "Răspunde"
     renote: "Re-notează"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 88f59155d656..817467588032 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -2,23 +2,26 @@
 _lang_: "Русский"
 headlineMisskey: "Сеть, сплетённая из заметок"
 introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀"
-poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый инстансом Misskey."
+poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый экземпляром Misskey."
 monthAndDay: "{day}.{month}"
 search: "Поиск"
 notifications: "Уведомления"
 username: "Имя пользователя"
 password: "Пароль"
+initialPasswordForSetup: "Пароль для начала настройки"
+initialPasswordIsIncorrect: "Пароль для запуска настройки неверен"
+initialPasswordForSetupDescription: "Если вы установили Misskey самостоятельно, используйте пароль, который вы указали в файле конфигурации.\nЕсли вы используете что-то вроде хостинга Misskey, используйте предоставленный пароль.\nЕсли вы не установили пароль, оставьте его пустым и продолжайте."
 forgotPassword: "Забыли пароль?"
 fetchingAsApObject: "Приём с других сайтов"
-ok: "Окей"
+ok: "Подтвердить"
 gotIt: "Ясно!"
 cancel: "Отмена"
 noThankYou: "Нет, спасибо"
 enterUsername: "Введите имя пользователя"
-renotedBy: "{user} делится"
+renotedBy: "{user} репостнул(а)"
 noNotes: "Нет ни одной заметки"
 noNotifications: "Нет уведомлений"
-instance: "Инстанс"
+instance: "Экземпляр"
 settings: "Настройки"
 notificationSettings: "Настройки уведомлений"
 basicSettings: "Основные настройки"
@@ -45,22 +48,24 @@ pin: "Закрепить в профиле"
 unpin: "Открепить от профиля"
 copyContent: "Скопировать содержимое"
 copyLink: "Скопировать ссылку"
+copyLinkRenote: "Скопировать ссылку на репост"
 delete: "Удалить"
 deleteAndEdit: "Удалить и отредактировать"
-deleteAndEditConfirm: "Удалить эту заметку и создать отредактированную? Все реакции, ссылки и ответы на существующую будут будут потеряны."
+deleteAndEditConfirm: "Удалить этот пост и отредактировать заново? Все реакции, репосты и ответы на него также будут удалены."
 addToList: "Добавить в список"
 addToAntenna: "Добавить к антенне"
 sendMessage: "Отправить сообщение"
 copyRSS: "Скопировать RSS"
 copyUsername: "Скопировать имя пользователя"
-copyUserId: "Скопировать идентификатор пользователя"
-copyNoteId: "Скопировать идентификатор заметки"
+copyUserId: "Скопировать ID пользователя"
+copyNoteId: "Скопировать ID поста"
 copyFileId: "Скопировать ID файла"
 copyFolderId: "Скопировать ID папки"
-copyProfileUrl: "Скопировать URL профиля "
+copyProfileUrl: "Скопировать ссылку на профиль"
 searchUser: "Поиск людей"
+searchThisUsersNotes: "Искать по заметкам пользователя"
 reply: "Ответ"
-loadMore: "Показать еще"
+loadMore: "Загрузить ещё"
 showMore: "Показать ещё"
 showLess: "Закрыть"
 youGotNewFollower: "Новый подписчик"
@@ -107,11 +112,14 @@ enterEmoji: "Введите эмодзи"
 renote: "Репост"
 unrenote: "Отмена репоста"
 renoted: "Репост совершён."
+renotedToX: "Репостнуть в {name}."
 cantRenote: "Это нельзя репостить."
 cantReRenote: "Невозможно репостить репост."
 quote: "Цитата"
 inChannelRenote: "В канале"
 inChannelQuote: "Заметки в канале"
+renoteToChannel: "Репостнуть в канал"
+renoteToOtherChannel: "Репостнуть в другой канал"
 pinnedNote: "Закреплённая заметка"
 pinned: "Закрепить в профиле"
 you: "Вы"
@@ -150,6 +158,7 @@ editList: "Редактировать список"
 selectChannel: "Выберите канал"
 selectAntenna: "Выберите антенну"
 editAntenna: "Редактировать антенну"
+createAntenna: "Создать антенну"
 selectWidget: "Выберите виджет"
 editWidgets: "Редактировать виджеты"
 editWidgetsExit: "Готово"
@@ -157,11 +166,12 @@ customEmojis: "Собственные эмодзи"
 emoji: "Эмодзи"
 emojis: "Эмодзи"
 emojiName: "Название эмодзи"
-emojiUrl: "URL эмодзи"
+emojiUrl: "Ссылка на эмодзи"
 addEmoji: "Добавить эмодзи"
 settingGuide: "Рекомендуемые настройки"
 cacheRemoteFiles: "Кешировать внешние файлы"
 cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы."
+youCanCleanRemoteFilesCache: "Вы можете очистить кэш, нажав на кнопку 🗑️ в меню управления файлами."
 cacheRemoteSensitiveFiles: "Кэшировать внешние файлы «не для всех»"
 cacheRemoteSensitiveFilesDescription: "Если отключено, файлы «не для всех» загружаются непосредственно с удалённых серверов, не кэшируясь."
 flagAsBot: "Аккаунт бота"
@@ -175,6 +185,10 @@ addAccount: "Добавить учётную запись"
 reloadAccountsList: "Обновить список учётных записей"
 loginFailed: "Неудачная попытка входа"
 showOnRemote: "Перейти к оригиналу на сайт"
+continueOnRemote: "Продолжить на удалённом сервере"
+chooseServerOnMisskeyHub: "Выбрать сервер с Misskey Hub"
+specifyServerHost: "Укажите сервер напрямую"
+inputHostName: "Введите домен"
 general: "Общее"
 wallpaper: "Обои"
 setWallpaper: "Установить обои"
@@ -185,6 +199,7 @@ followConfirm: "Подписаться на {name}?"
 proxyAccount: "Учётная запись прокси"
 proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси."
 host: "Хост"
+selectSelf: "Выбрать себя"
 selectUser: "Выберите пользователя"
 recipient: "Кому"
 annotation: "Описание"
@@ -199,6 +214,7 @@ perHour: "По часам"
 perDay: "По дням"
 stopActivityDelivery: "Остановить отправку обновлений активности"
 blockThisInstance: "Блокировать этот инстанс"
+silenceThisInstance: "Заглушить этот инстанс"
 operations: "Операции"
 software: "Программы"
 version: "Версия"
@@ -218,6 +234,8 @@ clearCachedFiles: "Очистить кэш"
 clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?"
 blockedInstances: "Заблокированные инстансы"
 blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом."
+silencedInstances: "Заглушённые инстансы"
+federationAllowedHosts: "Серверы, поддерживающие федерацию"
 muteAndBlock: "Скрытие и блокировка"
 mutedUsers: "Скрытые пользователи"
 blockedUsers: "Заблокированные пользователи"
@@ -236,7 +254,7 @@ noJobs: "Нет заданий"
 federating: "Федерируется"
 blocked: "Заблокировано"
 suspended: "Заморожено"
-all: "Всё"
+all: "Все"
 subscribing: "Подписка"
 publishing: "Публикация"
 notResponding: "Нет ответа"
@@ -268,7 +286,7 @@ messaging: "Сообщения"
 upload: "Загрузить"
 keepOriginalUploading: "Сохранить исходное изображение"
 keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации."
-fromDrive: "С «диска»"
+fromDrive: "С Диска"
 fromUrl: "По ссылке"
 uploadFromUrl: "Загрузить по ссылке"
 uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
@@ -308,6 +326,7 @@ selectFile: "Выберите файл"
 selectFiles: "Выберите файлы"
 selectFolder: "Выберите папку"
 selectFolders: "Выберите папки"
+fileNotSelected: "Файл не выбран"
 renameFile: "Переименовать файл"
 folderName: "Имя папки"
 createFolder: "Создать папку"
@@ -315,6 +334,7 @@ renameFolder: "Переименовать папку"
 deleteFolder: "Удалить папку"
 folder: "Папка"
 addFile: "Добавить файл"
+showFile: "Посмотреть файл"
 emptyDrive: "Диск пуст"
 emptyFolder: "Папка пуста"
 unableToDelete: "Удаление невозможно"
@@ -359,8 +379,8 @@ disablingTimelinesInfo: "У администраторов и модератор
 registration: "Регистрация"
 enableRegistration: "Разрешить регистрацию"
 invite: "Пригласить"
-driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя"
-driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта"
+driveCapacityPerLocalAccount: "Объём Диска на одного локального пользователя"
+driveCapacityPerRemoteAccount: "Объём Диска на одного пользователя с другого экземпляра"
 inMb: "В мегабайтах"
 bannerUrl: "Ссылка на изображение в шапке"
 backgroundImageUrl: "Ссылка на фоновое изображение"
@@ -379,6 +399,7 @@ mcaptcha: "mCaptcha"
 enableMcaptcha: "Включить mCaptcha"
 mcaptchaSiteKey: "Ключ сайта"
 mcaptchaSecretKey: "Секретный ключ"
+mcaptchaInstanceUrl: "Ссылка на сервер mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Включить reCAPTCHA"
 recaptchaSiteKey: "Ключ сайта"
@@ -393,7 +414,8 @@ manageAntennas: "Настройки антенн"
 name: "Название"
 antennaSource: "Источник антенны"
 antennaKeywords: "Ключевые слова"
-antennaExcludeKeywords: "Исключения"
+antennaExcludeKeywords: "Чёрный список слов"
+antennaExcludeBots: "Исключать ботов"
 antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них."
 notifyAntenna: "Уведомлять о новых заметках"
 withFileAntenna: "Только заметки с вложениями"
@@ -426,6 +448,8 @@ totp: "Приложение-аутентификатор"
 totpDescription: "Описание приложения-аутентификатора"
 moderator: "Модератор"
 moderation: "Модерация"
+moderationNote: "Примечания модератора"
+moderationLogs: "Журнал модерации"
 nUsersMentioned: "Упомянуло пользователей: {n}"
 securityKeyAndPasskey: "Ключ безопасности и парольная фраза"
 securityKey: "Ключ безопасности"
@@ -458,10 +482,12 @@ retype: "Введите ещё раз"
 noteOf: "Что пишет {user}"
 quoteAttached: "Цитата"
 quoteQuestion: "Хотите добавить цитату?"
+attachAsFileQuestion: "Текста в буфере обмена слишком много. Прикрепить как текстовый файл?"
 noMessagesYet: "Пока ни одного сообщения"
 newMessageExists: "Новое сообщение"
 onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл"
 signinRequired: "Пожалуйста, войдите"
+signinOrContinueOnRemote: "Чтобы продолжить, вам необходимо войти в аккаунт на своём сервере или зарегистрироваться / войти в аккаунт на этом."
 invitations: "Приглашения"
 invitationCode: "Код приглашения"
 checking: "Проверка"
@@ -471,7 +497,7 @@ usernameInvalidFormat: "Можно использовать только лат
 tooShort: "Слишком короткий"
 tooLong: "Слишком длинный"
 weakPassword: "Слабый пароль"
-normalPassword: "Годный пароль"
+normalPassword: "Хороший пароль"
 strongPassword: "Надёжный пароль"
 passwordMatched: "Совпали"
 passwordNotMatched: "Не совпадают"
@@ -483,8 +509,10 @@ uiLanguage: "Язык интерфейса"
 aboutX: "Описание {x}"
 emojiStyle: "Стиль эмодзи"
 native: "Системные"
-disableDrawer: "Не использовать выдвижные меню"
+menuStyle: "Стиль меню"
+style: "Стиль"
 showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении"
+showReactionsCount: "Видеть количество реакций на заметках"
 noHistory: "История пока пуста"
 signinHistory: "Журнал посещений"
 enableAdvancedMfm: "Включить расширенный MFM"
@@ -547,7 +575,7 @@ popout: "Развернуть"
 volume: "Громкость"
 masterVolume: "Основная регулировка громкости"
 notUseSound: "Выключить звук"
-useSoundOnlyWhenActive: "Использовать звук, когда Misskey активен."
+useSoundOnlyWhenActive: "Воспроизводить звук только когда Misskey активен."
 details: "Подробнее"
 chooseEmoji: "Выберите эмодзи"
 unableToProcess: "Не удаётся завершить операцию"
@@ -601,7 +629,7 @@ poll: "Опрос"
 useCw: "Скрывать содержимое под предупреждением"
 enablePlayer: "Включить проигрыватель"
 disablePlayer: "Выключить проигрыватель"
-expandTweet: "Развернуть твит"
+expandTweet: "Развернуть заметку"
 themeEditor: "Редактор темы оформления"
 description: "Описание"
 describeFile: "Добавить подпись"
@@ -613,7 +641,7 @@ plugins: "Расширения"
 preferencesBackups: "Резервная копия"
 deck: "Пульт"
 undeck: "Покинуть пульт"
-useBlurEffectForModal: "Размывка под формой поверх всего"
+useBlurEffectForModal: "Размытие за формой ввода заметки"
 useFullReactionPicker: "Полнофункциональный выбор реакций"
 width: "Ширина"
 height: "Высота"
@@ -644,7 +672,7 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений"
 smtpSecureInfo: "Выключите при использовании STARTTLS."
 testEmail: "Проверка доставки электронной почты"
 wordMute: "Скрытие слов"
-hardWordMute: ""
+hardWordMute: "Строгое скрытие слов"
 regexpError: "Ошибка в регулярном выражении"
 regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:"
 instanceMute: "Глушение инстансов"
@@ -680,10 +708,7 @@ abuseReported: "Жалоба отправлена. Большое спасибо
 reporter: "Сообщивший"
 reporteeOrigin: "О ком сообщено"
 reporterOrigin: "Кто сообщил"
-forwardReport: "Отправить жалобу на инстанс автора."
-forwardReportIsAnonymous: "Жалоба на удалённый инстанс будет отправлена анонимно. Вместо ваших данных у получателя будет отображена системная учётная запись."
 send: "Отправить"
-abuseMarkAsResolved: "Отметить жалобу как решённую"
 openInNewTab: "Открыть в новой вкладке"
 openInSideView: "Открывать в боковой колонке"
 defaultNavigationBehaviour: "Поведение навигации по умолчанию"
@@ -726,6 +751,7 @@ lockedAccountInfo: "Даже если вы вручную подтверждае
 alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию"
 loadRawImages: "Сразу показывать изображения в полном размере"
 disableShowingAnimatedImages: "Не проигрывать анимацию"
+highlightSensitiveMedia: "Выделять содержимое не для всех"
 verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста, по ссылке из письма, чтобы завершить проверку."
 notSet: "Не настроено"
 emailVerified: "Адрес электронной почты подтверждён."
@@ -743,7 +769,7 @@ makeExplorable: "Опубликовать профиль в «Обзоре»."
 makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»."
 showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте"
 duplicate: "Дубликат"
-left: "Влево"
+left: "Слева"
 center: "По центру"
 wide: "Толстый"
 narrow: "Тонкий"
@@ -822,7 +848,7 @@ noMaintainerInformationWarning: "Не заполнены сведения об 
 noBotProtectionWarning: "Ботозащита не настроена"
 configure: "Настроить"
 postToGallery: "Опубликовать в галерею"
-postToHashtag: "Написать заметку с этим хэштегом"
+postToHashtag: "Написать заметку с этим хештегом"
 gallery: "Галерея"
 recentPosts: "Недавние публикации"
 popularPosts: "Популярные публикации"
@@ -839,13 +865,13 @@ emailNotConfiguredWarning: "Не указан адрес электронной
 ratio: "Соотношение"
 previewNoteText: "Предварительный просмотр"
 customCss: "Индивидуальный CSS"
-customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас."
+customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что у вас перестанет нормально работать сайт."
 global: "Всеобщая"
 squareAvatars: "Квадратные аватарки"
 sent: "Отправить"
 received: "Получено"
 searchResult: "Результаты поиска"
-hashtags: "Хэштег"
+hashtags: "Хештеги"
 troubleshooting: "Разрешение проблем"
 useBlurEffect: "Размытие в интерфейсе"
 learnMore: "Подробнее"
@@ -857,7 +883,7 @@ accountDeletionInProgress: "В настоящее время выполняет
 usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже."
 aiChanMode: "Режим Ай"
 devMode: "Режим разработчика"
-keepCw: "Сохраняйте Предупреждения о содержимом"
+keepCw: "Сохраняйте предупреждения о содержимом"
 pubSub: "Учётные записи Pub/Sub"
 lastCommunication: "Последнее сообщение"
 resolved: "Решено"
@@ -878,6 +904,8 @@ makeReactionsPublicDescription: "Список сделанных вами реа
 classic: "Классика"
 muteThread: "Скрыть цепочку"
 unmuteThread: "Отменить сокрытие цепочки"
+followingVisibility: "Видимость подписок"
+followersVisibility: "Видимость подписчиков"
 continueThread: "Показать следующие ответы"
 deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?"
 incorrectPassword: "Пароль неверен."
@@ -987,6 +1015,7 @@ assign: "Назначить"
 unassign: "Отменить назначение"
 color: "Цвет"
 manageCustomEmojis: "Управлять пользовательскими эмодзи"
+manageAvatarDecorations: "Управление украшениями аватара"
 youCannotCreateAnymore: "Вы достигли лимита создания."
 cannotPerformTemporary: "Временно недоступен"
 cannotPerformTemporaryDescription: "Это действие временно невозможно выполнить из-за превышения лимита выполнения."
@@ -1003,7 +1032,8 @@ thisPostMayBeAnnoying: "Это сообщение может быть непри
 thisPostMayBeAnnoyingHome: "Этот пост может быть отправлен на главную"
 thisPostMayBeAnnoyingCancel: "Этот пост не может быть отменен."
 thisPostMayBeAnnoyingIgnore: "Этот пост может быть проигнорирован "
-collapseRenotes: "Свернуть репосты"
+collapseRenotes: "Сворачивать увиденные репосты"
+collapseRenotesDescription: "Сворачивать посты с которыми вы взаимодействовали."
 internalServerError: "Внутренняя ошибка сервера"
 internalServerErrorDescription: "Внутри сервера произошла непредвиденная ошибка."
 copyErrorInfo: "Скопировать код ошибки"
@@ -1027,7 +1057,10 @@ resetPasswordConfirm: "Сбросить пароль?"
 sensitiveWords: "Чувствительные слова"
 sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
 sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+prohibitedWords: "Запрещённые слова"
+prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать пост, содержащий указанное слово/набор слов.\nМножество слов может быть указано, разделяемые новой строкой."
 prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+hiddenTags: "Скрытые хештеги"
 notesSearchNotAvailable: "Поиск заметок недоступен"
 license: "Лицензия"
 unfavoriteConfirm: "Удалить избранное?"
@@ -1038,9 +1071,14 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
 retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться"
 enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей"
 enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов"
+showClipButtonInNoteFooter: "Показать кнопку добавления в подборку в меню действий с заметкой"
+reactionsDisplaySize: "Размер реакций"
+limitWidthOfReaction: "Ограничить максимальную ширину реакций и отображать их в уменьшенном размере."
 noteIdOrUrl: "ID или ссылка на заметку"
 video: "Видео"
 videos: "Видео"
+audio: "Звук"
+audioFiles: "Звуковые файлы"
 dataSaver: "Экономия трафика"
 accountMigration: "Перенос учётной записи"
 accountMoved: "Учётная запись перенесена"
@@ -1052,12 +1090,13 @@ editMemo: "Изменить памятку"
 reactionsList: "Список реакций"
 renotesList: "Репосты"
 notificationDisplay: "Отображение уведомлений"
-leftTop: "Влево вверх"
-rightTop: "Вправо вверх"
-leftBottom: "Влево вниз"
-rightBottom: "Вправо вниз"
-vertical: "Вертикальная"
-horizontal: "Сбоку"
+leftTop: "Слева вверху"
+rightTop: "Справа сверху"
+leftBottom: "Слева внизу"
+rightBottom: "Справа внизу"
+stackAxis: "Положение уведомлений"
+vertical: "Вертикально"
+horizontal: "Горизонтально"
 position: "Позиция"
 serverRules: "Правила сервера"
 pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями."
@@ -1069,57 +1108,114 @@ createNoteFromTheFile: "Создать заметку из этого файла
 archive: "Архив"
 channelArchiveConfirmTitle: "Переместить {name} в архив?"
 channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи."
+thisChannelArchived: "Этот канал находится в архиве."
 displayOfNote: "Отображение заметок"
 initialAccountSetting: "Настройка профиля"
 youFollowing: "Подписки"
 preventAiLearning: "Отказаться от использования в машинном обучении (Генеративный ИИ)"
+preventAiLearningDescription: "Запросить краулеров не использовать опубликованный текст или изображения и т.д. для машинного обучения (Прогнозирующий / Генеративный ИИ) датасетов. Это достигается путём добавления \"noai\" HTTP-заголовка в ответ на соответствующий контент. Полного предотвращения через этот заголовок не избежать, так как он может быть просто проигнорирован."
 options: "Настройки ролей"
 specifyUser: "Указанный пользователь"
+openTagPageConfirm: "Открыть страницу этого хештега?"
+specifyHost: "Указать сайт"
 failedToPreviewUrl: "Предварительный просмотр недоступен"
 update: "Обновить"
 rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно использовать эти эмодзи как реакцию"
 rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый."
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Эти роли должны быть общедоступными."
+cancelReactionConfirm: "Вы действительно хотите удалить свою реакцию?"
 later: "Позже"
 goToMisskey: "К Misskey"
 additionalEmojiDictionary: "Дополнительные словари эмодзи"
 installed: "Установлено"
 branding: "Бренд"
+enableServerMachineStats: "Опубликовать характеристики сервера"
 enableIdenticonGeneration: "Включить генерацию иконки пользователя"
 turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность."
+createInviteCode: "Создать код приглашения"
+createCount: "Количество приглашений"
 expirationDate: "Дата истечения"
-unused: "Неиспользуемый"
+noExpirationDate: "Бессрочно"
+unused: "Неиспользованное"
+used: "Использован"
 expired: "Срок действия приглашения истёк"
 doYouAgree: "Согласны?"
 icon: "Аватар"
 replies: "Ответы"
 renotes: "Репост"
 loadReplies: "Показать ответы"
+pinnedList: "Закреплённый список"
+keepScreenOn: "Держать экран включённым"
+showRenotes: "Показывать репосты"
+mutualFollow: "Взаимные подписки"
+followingOrFollower: "Подписки или подписчики"
+fileAttachedOnly: "Только заметки с файлами"
+showRepliesToOthersInTimeline: "Показывать ответы в ленте"
+showRepliesToOthersInTimelineAll: "Показывать в ленте ответы пользователей, на которых вы подписаны"
+hideRepliesToOthersInTimelineAll: "Скрывать в ленте ответы пользователей, на которых вы подписаны"
 sourceCode: "Исходный код"
+sourceCodeIsNotYetProvided: "Исходный код пока не доступен. Свяжитесь с администратором, чтобы исправить эту проблему."
+repositoryUrl: "Ссылка на репозиторий"
+repositoryUrlDescription: "Если вы используете Misskey как есть (без изменений в исходном коде), введите https://github.com/misskey-dev/misskey"
+privacyPolicy: "Политика Конфиденциальности"
+privacyPolicyUrl: "Ссылка на Политику Конфиденциальности"
+attach: "Прикрепить"
+angle: "Угол"
 flip: "Переворот"
+disableStreamingTimeline: "Отключить обновление ленты в режиме реального времени"
+useGroupedNotifications: "Отображать уведомления сгруппировано"
+doReaction: "Добавить реакцию"
 code: "Код"
+remainingN: "Остаётся: {n}"
+seasonalScreenEffect: "Эффект времени года на экране"
+decorate: "Украсить"
+addMfmFunction: "Добавить MFM"
 lastNDays: "Последние {n} сут"
+hemisphere: "Место проживания"
+enableHorizontalSwipe: "Смахните в сторону, чтобы сменить вкладки"
 surrender: "Этот пост не может быть отменен."
+useNativeUIForVideoAudioPlayer: "Использовать интерфейс браузера при проигрывании видео и звука"
+keepOriginalFilename: "Сохранять исходное имя файла"
+keepOriginalFilenameDescription: "Если вы выключите данную настройку, имена файлов будут автоматически заменены случайной строкой при загрузке."
+alwaysConfirmFollow: "Всегда подтверждать подписку"
+inquiry: "Связаться"
 _delivery:
   stop: "Заморожено"
   _type:
     none: "Публикация"
+_announcement:
+  tooManyActiveAnnouncementDescription: "Большое количество оповещений может ухудшить пользовательский опыт. Рассмотрите архивирование неактуальных оповещений. "
 _initialAccountSetting:
   accountCreated: "Аккаунт успешно создан!"
   letsStartAccountSetup: "Давайте настроим вашу учётную запись."
   profileSetting: "Настройки профиля"
   privacySetting: "Настройки конфиденциальности"
   initialAccountSettingCompleted: "Первоначальная настройка успешно завершена!"
+  startTutorial: "Пройти Обучение"
   skipAreYouSure: "Пропустить настройку?"
 _initialTutorial:
+  launchTutorial: "Пройти обучение"
   _note:
     description: "Посты в Misskey называются 'Заметками.' Заметки отсортированы в хронологическом порядке в ленте и обновляются в режиме реального времени."
+  _reaction:
+    reactToContinue: "Добавьте реакцию, чтобы продолжить."
+  _postNote:
+    _visibility:
+      public: "Твоя заметка будет видна всем."
+      doNotSendConfidencialOnDirect2: "Администратор целевого сервера может видеть что вы отправляете. Будьте осторожны с конфиденциальной информацией, когда отправляете личные заметки пользователям с ненадёжных серверов."
 _timelineDescription:
   home: "В персональной ленте располагаются заметки тех, на которых вы подписаны."
-  local: "Местная лента показывает заметки всех пользователей этого сайта."
+  local: "Местная лента показывает заметки всех пользователей этого экземпляра."
   social: "В социальной ленте собирается всё, что есть в персональной и местной лентах."
-  global: "В глобальную ленту попадает вообще всё со связанных инстансов."
+  global: "В глобальную ленту попадает вообще всё со связанных экземпляров."
 _serverSettings:
   iconUrl: "Адрес на иконку роли"
+_accountMigration:
+  moveFrom: "Перенести другую учётную запись сюда"
+  moveTo: "Перенести учётную запись на другой сервер"
+  moveAccountDescription: "Это действие перенесёт ваш аккаунт на другой сервер.\n ・Подписчики с этого аккаунта автоматически подпишутся на новый\n ・Этот аккаунт отпишется от всех пользователей, на которых подписан сейчас\n ・Вы не сможете создавать новые заметки и т.д. на этом аккаунте\n\nТогда как перенос подписчиков происходит автоматически, вы должны будете подготовиться, сделав некоторые шаги, чтобы перенести список пользователей, на которых вы подписаны. Чтобы сделать это, экспортируйте список подписчиков в файл, который затем импортируете на новом аккаунте в меню настроек. То же самое необходимо будет сделать со списками, также как и со скрытыми и заблокированными пользователями.\n\n(Это объяснение применяется к Misskey v13.12.0 и выше. Другое ActivityPub программное обеспечение, такое, как Mastodon, может работать по-другому."
+  startMigration: "Перенести"
+  movedAndCannotBeUndone: "Аккаунт был перемещён. Это действие необратимо."
 _achievements:
   earnedAt: "Разблокировано в"
   _types:
@@ -1395,6 +1491,7 @@ _role:
     canPublicNote: "Может публиковать общедоступные заметки"
     canInvite: "Может создавать пригласительные коды"
     canManageCustomEmojis: "Управлять пользовательскими эмодзи"
+    canManageAvatarDecorations: "Управление украшениями аватара"
     driveCapacity: "Доступное пространство на «диске»"
     alwaysMarkNsfw: "Всегда отмечать файлы как «не для всех»"
     pinMax: "Доступное количество закреплённых заметок"
@@ -1505,6 +1602,11 @@ _aboutMisskey:
   donate: "Пожертвование на Misskey"
   morePatrons: "Большое спасибо и многим другим, кто принял участие в этом проекте! 🥰"
   patrons: "Материальная поддержка"
+  projectMembers: "Участники проекта"
+_displayOfSensitiveMedia:
+  respect: "Скрывать содержимое не для всех"
+  ignore: "Показывать содержимое не для всех"
+  force: "Скрывать всё содержимое"
 _instanceTicker:
   none: "Не показывать"
   remote: "Только для других сайтов"
@@ -1533,7 +1635,7 @@ _wordMute:
   muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках."
   muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)."
 _instanceMute:
-  instanceMuteDescription: "Заметки и репосты с указанных здесь инстансов, а также ответы пользователям оттуда же не будут отображаться."
+  instanceMuteDescription: "Любые активности, затрагивающие инстансы из данного списка, будут скрыты."
   instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке"
   title: "Скрывает заметки с заданных инстансов."
   heading: "Список скрытых инстансов"
@@ -1582,7 +1684,7 @@ _theme:
     navActive: "Текст на боковой панели (активирован)"
     navIndicator: "Индикатор на боковой панели"
     link: "Ссылка"
-    hashtag: "Хэштег"
+    hashtag: "Хештег"
     mention: "Упоминание"
     mentionMe: "Упоминания вас"
     renote: "Репост"
@@ -1600,7 +1702,6 @@ _theme:
     buttonBg: "Фон кнопки"
     buttonHoverBg: "Текст кнопки"
     inputBorder: "Рамка поля ввода"
-    listItemHoverBg: "Фон пункта списка (под указателем)"
     driveFolderBg: "Фон папки «Диска»"
     wallpaperOverlay: "Слой обоев"
     badge: "Значок"
@@ -1612,6 +1713,10 @@ _sfx:
   note: "Заметки"
   noteMy: "Собственные заметки"
   notification: "Уведомления"
+  reaction: "При выборе реакции"
+_soundSettings:
+  driveFile: "Использовать аудиофайл с Диска."
+  driveFileWarn: "Выбрать аудиофайл с Диска."
 _ago:
   future: "Из будущего"
   justNow: "Только что"
@@ -1690,6 +1795,7 @@ _permissions:
   "write:gallery": "Редактирование галереи"
   "read:gallery-likes": "Просмотр списка понравившегося в галерее"
   "write:gallery-likes": "Изменение списка понравившегося в галерее"
+  "write:admin:reset-password": "Сбросить пароль пользователю"
 _auth:
   shareAccessTitle: "Разрешения для приложений"
   shareAccess: "Дать доступ для «{name}» к вашей учётной записи?"
@@ -1743,6 +1849,7 @@ _widgets:
   _userList:
     chooseList: "Выберите список"
   clicker: "Счётчик щелчков"
+  birthdayFollowings: "Пользователи, у которых сегодня день рождения"
 _cw:
   hide: "Спрятать"
   show: "Показать"
@@ -1796,7 +1903,7 @@ _profile:
   name: "Имя"
   username: "Имя пользователя"
   description: "О себе"
-  youCanIncludeHashtags: "Можете использовать здесь хэштеги"
+  youCanIncludeHashtags: "Можете использовать здесь хештеги."
   metadata: "Дополнительные сведения"
   metadataEdit: "Редактировать дополнительные сведения"
   metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль."
@@ -1804,6 +1911,8 @@ _profile:
   metadataContent: "Содержимое"
   changeAvatar: "Поменять аватар"
   changeBanner: "Поменять изображение в шапке"
+  verifiedLinkDescription: "Указывая здесь URL, содержащий ссылку на профиль, иконка владения ресурсом может быть отображена рядом с полем"
+  avatarDecorationMax: "Вы можете добавить до {max} украшений."
 _exportOrImport:
   allNotes: "Все заметки\n"
   favoritedNotes: "Избранное"
@@ -1926,6 +2035,9 @@ _notification:
   unreadAntennaNote: "Антенна {name}"
   emptyPushNotificationMessage: "Обновлены push-уведомления"
   achievementEarned: "Получено достижение"
+  checkNotificationBehavior: "Проверить внешний вид уведомления"
+  sendTestNotification: "Отправить тестовое уведомление"
+  flushNotification: "Очистить уведомления"
   _types:
     all: "Все"
     follow: "Подписки"
@@ -1938,6 +2050,7 @@ _notification:
     receiveFollowRequest: "Получен запрос на подписку"
     followRequestAccepted: "Запрос на подписку одобрен"
     achievementEarned: "Получение достижений"
+    login: "Войти"
     app: "Уведомления из приложений"
   _actions:
     followBack: "отвечает взаимной подпиской"
@@ -1977,19 +2090,57 @@ _dialog:
 _disabledTimeline:
   title: "Лента отключена"
   description: "Ваша текущая роль не позволяет пользоваться этой лентой."
+_drivecleaner:
+  orderBySizeDesc: "Размеры файлов по убыванию"
+  orderByCreatedAtAsc: "По увеличению даты"
 _webhookSettings:
   createWebhook: "Создать вебхук"
+  modifyWebhook: "Изменить Вебхук"
   name: "Название"
+  secret: "Секрет"
+  trigger: "Условие срабатывания"
   active: "Вкл."
+  _events:
+    follow: "Когда подписались на пользователя"
+    followed: "Когда на вас подписались"
+    note: "Когда создали заметку"
+    reply: "Когда получили ответ на заметку"
+    renote: "Когда вас репостнули"
+    reaction: "Когда получили реакцию"
+    mention: "Когда вас упоминают"
+  _systemEvents:
+    abuseReport: "Когда приходит жалоба"
+    abuseReportResolved: "Когда разрешается жалоба"
+    userCreated: "Когда создан пользователь"
+  deleteConfirm: "Вы уверены, что хотите удалить этот Вебхук?"
 _abuseReport:
   _notificationRecipient:
     _recipientType:
       mail: "Электронная почта"
+      webhook: "Вебхук"
+      _captions:
+        webhook: "Отправить уведомление Системному Вебхуку при получении или разрешении жалоб."
+    notifiedWebhook: "Используемый Вебхук"
 _moderationLogTypes:
   suspend: "Заморозить"
   addCustomEmoji: "Добавлено эмодзи"
   updateCustomEmoji: "Изменено эмодзи"
   deleteCustomEmoji: "Удалено эмодзи"
+  deleteDriveFile: "Файл удалён"
   resetPassword: "Сброс пароля:"
+  createInvitation: "Создать код приглашения"
+  createSystemWebhook: "Создать Системный Вебхук"
+  updateSystemWebhook: "Обновить Системый Вебхук"
+  deleteSystemWebhook: "Удалить Системный Вебхук"
+_fileViewer:
+  url: "Ссылка"
+  attachedNotes: "Закреплённые заметки"
+_dataSaver:
+  _code:
+    title: "Подсветка кода"
+_hemisphere:
+  N: "Северное полушарие"
+  S: "Южное полушарие"
+  caption: "Используется для некоторых настроек клиента для определения сезона."
 _reversi:
   total: "Всего"
diff --git a/locales/si-LK.yml b/locales/si-LK.yml
index e130d68ed850..c43f3d860d87 100644
--- a/locales/si-LK.yml
+++ b/locales/si-LK.yml
@@ -17,3 +17,6 @@ _sfx:
   note: "නෝට්"
 _profile:
   username: "පරිශීලක නාමය"
+_notification:
+  _types:
+    login: "පිවිසෙන්න"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 41f894919615..60ce45a6b926 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -454,7 +454,6 @@ uiLanguage: "Jazyk používateľského prostredia"
 aboutX: "O {x}"
 emojiStyle: "Štýl emoji"
 native: "Natívne"
-disableDrawer: "Nepoužívať šuflíkové menu"
 showNoteActionsOnlyHover: "Ovládacie prvky poznámky sa zobrazujú len po nabehnutí myši"
 noHistory: "Žiadna história"
 signinHistory: "História prihlásení"
@@ -632,10 +631,7 @@ abuseReported: "Vaše nahlásenie je odoslané. Veľmi pekne ďakujeme."
 reporter: "Nahlásil"
 reporteeOrigin: "Pôvod nahláseného"
 reporterOrigin: "Pôvod nahlasovača"
-forwardReport: "Preposlať nahlásenie na server"
-forwardReportIsAnonymous: "Namiesto vášho účtu bude zobrazený anonymný systémový účet na vzdialenom serveri ako autor nahlásenia."
 send: "Poslať"
-abuseMarkAsResolved: "Označiť nahlásenia ako vyriešené"
 openInNewTab: "Otvoriť v novom tabe"
 openInSideView: "Otvoriť v bočnom paneli"
 defaultNavigationBehaviour: "Predvolené správanie navigácie"
@@ -1112,7 +1108,6 @@ _theme:
     buttonBg: "Pozadie tlačidla"
     buttonHoverBg: "Pozadie tlačidla (pod kurzorom)"
     inputBorder: "Okraj vstupného poľa"
-    listItemHoverBg: "Pozadie položky zoznamu (pod kurzorom)"
     driveFolderBg: "Pozadie priečinu disku"
     wallpaperOverlay: "Vrstvenie pozadia"
     badge: "Odznak"
@@ -1410,6 +1405,7 @@ _notification:
     pollEnded: "Hlasovanie skončilo"
     receiveFollowRequest: "Doručené žiadosti o sledovanie"
     followRequestAccepted: "Schválené žiadosti o sledovanie"
+    login: "Prihlásiť sa"
     app: "Oznámenia z prepojených aplikácií"
   _actions:
     followBack: "Sledovať späť\n"
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index c1a998b8fb57..5a0de660e896 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -562,6 +562,7 @@ _notification:
     renote: "Omnotera"
     quote: "Citat"
     reaction: "Reaktioner"
+    login: "Logga in"
   _actions:
     reply: "Svara"
     renote: "Omnotera"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index 2fb4a5253a53..58cf8f068c03 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -8,6 +8,9 @@ search: "ค้นหา"
 notifications: "เเจ้งเตือน"
 username: "ชื่อผู้ใช้"
 password: "รหัสผ่าน"
+initialPasswordForSetup: "รหัสผ่านเริ่มต้นสำหรับการตั้งค่า"
+initialPasswordIsIncorrect: "รหัสผ่านเริ่มต้นสำหรับตั้งค่านั้นไม่ถูกต้องค่ะ"
+initialPasswordForSetupDescription: "ถ้าหากคุณติดตั้ง Misskey เอง ให้ใช้รหัสผ่านที่คุณป้อนในไฟล์กำหนดค่า \nถ้าหากคุณกำลังใช้บริการโฮสต์ Misskey ให้ใช้รหัสผ่านที่ได้รับมา\nถ้ายังไม่มีรหัสผ่าน ให้ข้ามช่องรหัสผ่านไป แล้วกดต่อไป"
 forgotPassword: "ลืมรหัสผ่าน"
 fetchingAsApObject: "กำลังดึงข้อมูลจากสหพันธ์..."
 ok: "ตกลง"
@@ -236,6 +239,8 @@ silencedInstances: "ปิดปากเซิร์ฟเวอร์นี้
 silencedInstancesDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่ต้องการปิดปาก คั่นด้วยการขึ้นบรรทัดใหม่, บัญชีทั้งหมดของเซิร์ฟเวอร์ดังกล่าวจะถือว่าถูกปิดปากเช่นกัน ทำได้เฉพาะคำขอติดตามเท่านั้น และไม่สามารถกล่าวถึงบัญชีในเซิร์ฟเวอร์นี้ได้หากไม่ได้ถูกติดตามกลับ | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก"
 mediaSilencedInstances: "เซิร์ฟเวอร์ที่ถูกปิดปากสื่อ"
 mediaSilencedInstancesDescription: "ระบุโฮสต์ของเซิร์ฟเวอร์ที่ต้องการปิดปากสื่อ คั่นด้วยการขึ้นบรรทัดใหม่, ไฟล์ที่ถูกส่งจากบัญชีของเซิร์ฟเวอร์ดังกล่าวจะถือว่าถูกปิดปาก แล้วจะถูกติดเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน และเอโมจิแบบกำหนดเองก็จะใช้ไม่ได้ด้วย | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก"
+federationAllowedHosts: "เซิร์ฟเวอร์ที่เปิดให้บริการแบบเฟเดอเรชั่น"
+federationAllowedHostsDescription: "ระบุชื่อโฮสต์ของเซิร์ฟเวอร์ที่คุณต้องการอนุญาตให้เชื่อมต่อแบบเฟเดอเรชั่น โดยต้องเว้นวรรคแต่ละบรรทัด"
 muteAndBlock: "ปิดเสียงและบล็อก"
 mutedUsers: "ผู้ใช้ที่ถูกปิดเสียง"
 blockedUsers: "ผู้ใช้ที่ถูกบล็อก"
@@ -334,6 +339,7 @@ renameFolder: "เปลี่ยนชื่อโฟลเดอร์"
 deleteFolder: "ลบโฟลเดอร์"
 folder: "โฟลเดอร์"
 addFile: "เพิ่มไฟล์"
+showFile: "แสดงไฟล์"
 emptyDrive: "ไดรฟ์ของคุณว่างเปล่านะ"
 emptyFolder: "โฟลเดอร์นี้ว่างเปล่า"
 unableToDelete: "ไม่สามารถลบออกได้"
@@ -448,6 +454,7 @@ totpDescription: "ใช้แอปยืนยันตัวตนเพื
 moderator: "ผู้ควบคุม"
 moderation: "การกลั่นกรอง"
 moderationNote: "โน้ตการกลั่นกรอง"
+moderationNoteDescription: "คุณสามารถใส่โน้ตส่วนตัวที่เฉพาะผู้ดูแลระบบเท่านั้นที่สามารถเข้าถึงได้"
 addModerationNote: "เพิ่มโน้ตการกลั่นกรอง"
 moderationLogs: "ปูมการควบคุมดูแล"
 nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} ราย"
@@ -509,7 +516,10 @@ uiLanguage: "ภาษาอินเทอร์เฟซผู้ใช้ง
 aboutX: "เกี่ยวกับ {x}"
 emojiStyle: "สไตล์ของเอโมจิ"
 native: "ภาษาแม่"
-disableDrawer: "ไม่แสดงเมนูในรูปแบบลิ้นชัก"
+menuStyle: "สไตล์เมนู"
+style: "สไตล์"
+drawer: "ตัววาด"
+popup: "ป๊อปอัพ"
 showNoteActionsOnlyHover: "แสดงการดำเนินการโน้ตเมื่อโฮเวอร์(วางเมาส์เหนือ)เท่านั้น"
 showReactionsCount: "แสดงจำนวนรีแอกชั่นในโน้ต"
 noHistory: "ไม่มีประวัติ"
@@ -592,6 +602,8 @@ ascendingOrder: "เรียงลำดับขึ้น"
 descendingOrder: "เรียงลำดับลง"
 scratchpad: "Scratchpad"
 scratchpadDescription: "Scratchpad ให้สภาพแวดล้อมสำหรับการทดลอง AiScript คุณสามารถเขียนโค้ด/สั่งดำเนินการ/ตรวจสอบผลลัพธ์ ของการโต้ตอบกับ Misskey ได้"
+uiInspector: "ตัวตรวจสอบ UI"
+uiInspectorDescription: "คุณสามารถตรวจสอบรายชื่อเซิร์ฟเวอร์ที่เกี่ยวข้องกับส่วนประกอบอินเตอร์เฟซผู้ใช้ (UI) บนหน่วยความจำของระบบ ส่วนประกอบ UI เหล่านี้จะถูกสร้างขึ้นโดยฟังก์ชัน Ui:C:"
 output: "เอาท์พุต"
 script: "สคริปต์"
 disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ"
@@ -708,10 +720,7 @@ abuseReported: "เราได้ส่งรายงานของคุณ
 reporter: "ผู้รายงาน"
 reporteeOrigin: "ปลายทางรายงาน"
 reporterOrigin: "แหล่งผู้รายงาน"
-forwardReport: "ส่งต่อรายงานไปยังเซิร์ฟเวอร์ระยะไกล"
-forwardReportIsAnonymous: "ข้อมูลของคุณจะไม่ปรากฏบนเซิร์ฟเวอร์ระยะไกลและปรากฏเป็นบัญชีระบบที่ไม่ระบุชื่อ"
 send: "ส่ง"
-abuseMarkAsResolved: "ทำเครื่องหมายรายงานว่าแก้ไขแล้ว"
 openInNewTab: "เปิดในแท็บใหม่"
 openInSideView: "เปิดในมุมมองด้านข้าง"
 defaultNavigationBehaviour: "พฤติกรรมการนำทางที่เป็นค่าเริ่มต้น"
@@ -913,6 +922,7 @@ followersVisibility: "การมองเห็นผู้ที่กำล
 continueThread: "ดูความต่อเนื่องเธรด"
 deleteAccountConfirm: "การดำเนินการนี้จะลบบัญชีของคุณอย่างถาวรเลยนะ แน่ใจหรอดำเนินการ?"
 incorrectPassword: "รหัสผ่านไม่ถูกต้อง"
+incorrectTotp: "รหัสยืนยันตัวตนแบบใช้ครั้งเดียวที่ท่านได้ระบุมานั้น ไม่ถูกต้องหรือหมดอายุลงแล้วค่ะ"
 voteConfirm: "ต้องการโหวต “{choice}” ใช่ไหม?"
 hide: "ซ่อน"
 useDrawerReactionPickerForMobile: "แสดง ตัวจิ้มรีแอคชั่น เป็นแบบลิ้นชัก เมื่อใช้บนมือถือ"
@@ -1077,6 +1087,7 @@ retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริ
 retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ"
 enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
 enableChartsForFederatedInstances: "สร้างแผนภูมิของเซิร์ฟเวอร์ระยะไกล"
+enableStatsForFederatedInstances: "ดึงข้อมูลสถิติจากเซิร์ฟเวอร์ที่อยู่ห่างไกล"
 showClipButtonInNoteFooter: "เพิ่ม “คลิป” ไปยังเมนูสั่งการของโน้ต"
 reactionsDisplaySize: "ขนาดของรีแอคชั่น"
 limitWidthOfReaction: "จำกัดความกว้างสูงสุดของรีแอคชั่นและแสดงให้เล็กลง"
@@ -1263,6 +1274,32 @@ confirmWhenRevealingSensitiveMedia: "ตรวจสอบก่อนแสด
 sensitiveMediaRevealConfirm: "สื่อนี้มีเนื้อหาละเอียดอ่อน, ต้องการแสดงใช่ไหม?"
 createdLists: "รายชื่อที่ถูกสร้าง"
 createdAntennas: "เสาอากาศที่ถูกสร้าง"
+fromX: "จาก {x}"
+genEmbedCode: "สร้างรหัสฝัง"
+noteOfThisUser: "โน้ตโดยผู้ใช้นี้"
+clipNoteLimitExceeded: "ไม่สามารถเพิ่มโน้ตเพิ่มเติมในคลิปนี้ได้อีกแล้ว"
+performance: "ประสิทธิภาพ​"
+modified: "แก้ไข"
+discard: "ละทิ้ง"
+thereAreNChanges: "มีอยู่ {n} เปลี่ยนแปลง(s)"
+signinWithPasskey: "ลงชื่อเข้าใช้ด้วย Passkey"
+unknownWebAuthnKey: "พาสคีย์ไม่ถูกต้องค่ะ"
+passkeyVerificationFailed: "การยืนยันกุญแจดิจิทัลไม่สำเร็จค่ะ"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "การยืนยันพาสคีย์สำเร็จแล้ว แต่การลงชื่อเข้าใช้แบบไม่ต้องใส่รหัสผ่านถูกปิดใช้งานแล้ว"
+messageToFollower: "ข้อความถึงผู้ติดตาม"
+target: "เป้า"
+testCaptchaWarning: "ฟังก์ชันนี้มีไว้สำหรับทดสอบ CAPTCHA เท่านั้น\n<strong>ห้ามนำไปใช้ในระบบจริงโดยเด็ดขาด</strong>"
+prohibitedWordsForNameOfUser: "คำนี้ไม่สามารถใช้เป็นชื่อผู้ใช้ได้"
+prohibitedWordsForNameOfUserDescription: "หากมีสตริงใดๆ ในรายการนี้ปรากฏอยู่ในชื่อของผู้ใช้ ชื่อนั้นจะถูกปฏิเสธ ผู้ใช้ที่มีสิทธิ์แต่ผู้ดูแลระบบนั้นจะไม่ได้รับผลกระทบใดๆจากข้อจำกัดนี้ค่ะ"
+yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
+yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
+_abuseUserReport:
+  forward: "ส่ง​ต่อ"
+  forwardDescription: "ส่งรายงานไปยังเซิร์ฟเวอร์ระยะไกลโดยใช้บัญชีระบบที่ไม่ระบุตัวตน"
+  resolve: "แก้ไข"
+  accept: "ยอมรับ"
+  reject: "ปฏิเสธ"
+  resolveTutorial: "ถ้าหากรายงานนี้มีเนื้อหาถูกต้อง ให้เลือก \"ยอมรับ\" เพื่อปิดเคสกรณีนี้โดยถือว่าได้รับการแก้ไขแล้ว\nถ้าหากเนื้อหาในรายงานนี้นั้นไม่ถูกต้อง ให้เลือก \"ปฏิเสธ\" เพื่อปิดเคสกรณีนี้โดยถือว่าไม่ได้รับการแก้ไข"
 _delivery:
   status: "สถานะการจัดส่ง"
   stop: "ระงับการส่ง"
@@ -1397,8 +1434,10 @@ _serverSettings:
   fanoutTimelineDescription: "เพิ่มประสิทธิภาพการดึงข้อมูลไทม์ไลน์อย่างมาก และลดภาระในฐานข้อมูลเมื่อเปิดใช้งาน ในทางกลับกัน การใช้หน่วยความจำของ Redis จะเพิ่มขึ้น ลองปิดการใช้งานนี้ในกรณีที่หน่วยความจำเซิร์ฟเวอร์เหลือน้อยหรือเซิร์ฟเวอร์ไม่เสถียร"
   fanoutTimelineDbFallback: "ฟอลแบ๊กกลับฐานข้อมูล"
   fanoutTimelineDbFallbackDescription: "เมื่อเปิดใช้งาน หากไม่ได้แคชไทม์ไลน์ ไทม์ไลน์จะฟอลแบ๊กไปยังฐานข้อมูลสำหรับการ query เพิ่มเติม การปิดใช้งานจะช่วยลดภาระของเซิร์ฟเวอร์ด้วยการกำจัดกระบวนฟอลแบ๊ก แต่มันก็จะจำกัดช่วงเวลาไทม์ไลน์ที่สามารถดึงข้อมูลได้"
+  reactionsBufferingDescription: "เมื่อเปิดใช้งานฟังก์ชันนี้ก็จะช่วยลด latency ในการสร้างปฏิกิริยา แต่อาจจะส่งผลให้ memory footprint ของ Redis เพิ่มขึ้นนะ"
   inquiryUrl: "URL สำหรับการติดต่อสอบถาม"
   inquiryUrlDescription: "ระบุ URL ของหน้าเว็บที่มีแบบฟอร์มสำหรับติดต่อผู้ดูแลเซิร์ฟเวอร์ หรือข้อมูลการติดต่อของผู้ดูแลเซิร์ฟเวอร์"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "ถ้าหากไม่มีการตรวจสอบจากผู้ดูแลระบบหรือไม่มีความเคลื่อนไหวมาเป็นระยะเวลาหนึ่ง ระบบจะทำการปิดใช้งานฟังก์ชันนี้โดยอัตโนมัติ เพื่อลดความเสี่ยงในการถูกโจมตีด้วยสแปมและอื่นๆ"
 _accountMigration:
   moveFrom: "ย้ายจากบัญชีอื่นมาที่บัญชีนี้"
   moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น"
@@ -1730,6 +1769,11 @@ _role:
     canSearchNotes: "การใช้การค้นหาโน้ต"
     canUseTranslator: "การใช้งานแปล"
     avatarDecorationLimit: "จำนวนการตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้"
+    canImportAntennas: "อนุญาตให้นำเข้าเสาอากาศ"
+    canImportBlocking: "อนุญาตให้นำเข้าการบล็อก"
+    canImportFollowing: "อนุญาตให้นำเข้ารายการต่อไปนี้"
+    canImportMuting: "อนุญาตให้นำเข้าการปิดกั้น"
+    canImportUserLists: "อนุญาตให้นำเข้ารายการ"
   _condition:
     roleAssignedTo: "มอบหมายให้มีบทบาทแบบทำมือ"
     isLocal: "ผู้ใช้ท้องถิ่น"
@@ -1947,7 +1991,6 @@ _theme:
     buttonBg: "ปุ่มพื้นหลัง"
     buttonHoverBg: "ปุ่มพื้นหลัง (โฮเวอร์)"
     inputBorder: "เส้นขอบของช่องป้อนข้อมูล"
-    listItemHoverBg: "รายการไอเทมพื้นหลัง (โฮเวอร์)"
     driveFolderBg: "พื้นหลังโฟลเดอร์ไดรฟ์"
     wallpaperOverlay: "วอลล์เปเปอร์ซ้อนทับ"
     badge: "ตรา"
@@ -2224,6 +2267,9 @@ _profile:
   changeBanner: "เปลี่ยนแบนเนอร์"
   verifiedLinkDescription: "หากป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณ ไอคอนการยืนยันความเป็นเจ้าของจะแสดงถัดจากฟิลด์นั้น ๆ"
   avatarDecorationMax: "คุณสามารถเพิ่มการตกแต่งได้สูงสุด {max}"
+  followedMessage: "ส่งข้อความเมื่อมีคนกดติดตาม"
+  followedMessageDescription: "ส่งข้อความเมื่อมีคนกดติดตามแล้ว"
+  followedMessageDescriptionForLockedAccount: "ถ้าหากคุณตั้งค่าให้คนอื่นต้องขออนุญาตก่อนที่จะติดตามคุณ ระบบจะขึ้นข้อความนี้ในตอนที่คุณอนุมัติให้เขาติดตาม"
 _exportOrImport:
   allNotes: "โน้ตทั้งหมด"
   favoritedNotes: "โน้ตที่ถูกใจไว้"
@@ -2316,6 +2362,7 @@ _pages:
   eyeCatchingImageSet: "ตั้งค่าภาพขนาดย่อ"
   eyeCatchingImageRemove: "ลบภาพขนาดย่อ"
   chooseBlock: "เพิ่มบล็อค"
+  enterSectionTitle: "ป้อนชื่อหัวข้อ"
   selectType: "เลือกชนิด"
   contentBlocks: "เนื้อหา"
   inputBlocks: "ป้อนข้อมูล"
@@ -2361,6 +2408,8 @@ _notification:
   renotedBySomeUsers: "รีโน้ตจากผู้ใช้ {n} ราย"
   followedBySomeUsers: "มีผู้ติดตาม {n} ราย"
   flushNotification: "ล้างประวัติการแจ้งเตือน"
+  exportOfXCompleted: "การดำเนินการส่งออก {x} ได้เสร็จสิ้นลงแล้ว"
+  login: "มีคนล็อกอิน"
   _types:
     all: "ทั้งหมด"
     note: "โน้ตใหม่"
@@ -2375,6 +2424,9 @@ _notification:
     followRequestAccepted: "อนุมัติให้ติดตามแล้ว"
     roleAssigned: "ให้บทบาท"
     achievementEarned: "ปลดล็อกความสำเร็จแล้ว"
+    exportCompleted: "กระบวนการส่งออกข้อมูลได้เสร็จสิ้นสมบูรณ์แล้ว"
+    login: "เข้าสู่ระบบ"
+    test: "ทดสอบระบบแจ้งเตือน"
     app: "การแจ้งเตือนจากแอปที่มีลิงก์"
   _actions:
     followBack: "ติดตามกลับด้วย"
@@ -2440,7 +2492,10 @@ _webhookSettings:
     abuseReport: "เมื่อมีการรายงานจากผู้ใช้"
     abuseReportResolved: "เมื่อมีการจัดการกับการรายงานจากผู้ใช้"
     userCreated: "เมื่อผู้ใช้ถูกสร้างขึ้น"
+    inactiveModeratorsWarning: "เมื่อผู้ดูแลระบบไม่ได้ใช้งานมานานระยะหนึ่ง"
+    inactiveModeratorsInvitationOnlyChanged: "เมื่อผู้ดูแลระบบที่ไม่ได้ใช้งานมานาน และเซิร์ฟเวอร์เปลี่ยนเป็นแบบเชิญเข้าร่วมเท่านั้น"
   deleteConfirm: "ต้องการลบ Webhook ใช่ไหม?"
+  testRemarks: "คลิกปุ่มทางด้านขวาของสวิตช์เพื่อส่ง Webhook ทดสอบที่มีข้อมูลจำลอง"
 _abuseReport:
   _notificationRecipient:
     createRecipient: "เพิ่มปลายทางการแจ้งเตือนการรายงาน"
@@ -2484,6 +2539,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "ทำเครื่องหมายไฟล์ว่ามีเนื้อหาละเอียดอ่อน"
   unmarkSensitiveDriveFile: "ยกเลิกทำเครื่องหมายไฟล์ว่ามีเนื้อหาละเอียดอ่อน"
   resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
+  forwardAbuseReport: "ได้ส่งรายงานไปแล้ว"
+  updateAbuseReportNote: "โน้ตการกลั่นกรองที่รายงานไปนั้น ได้รับการอัปเดตแล้ว"
   createInvitation: "สร้างรหัสเชิญ"
   createAd: "สร้างโฆษณาแล้ว"
   deleteAd: "ลบโฆษณาออกแล้ว"
@@ -2499,6 +2556,10 @@ _moderationLogTypes:
   createAbuseReportNotificationRecipient: "สร้างปลายทางการแจ้งเตือนการรายงาน"
   updateAbuseReportNotificationRecipient: "อัปเดตปลายทางการแจ้งเตือนการรายงาน"
   deleteAbuseReportNotificationRecipient: "ลบปลายทางการแจ้งเตือนการรายงาน"
+  deleteAccount: "บัญชีถูกลบไปแล้ว"
+  deletePage: "เพจถูกลบออกไปแล้ว"
+  deleteFlash: "Play ถูกลบออกไปแล้ว"
+  deleteGalleryPost: "โพสต์แกลเลอรี่ถูกลบออกแล้ว"
 _fileViewer:
   title: "รายละเอียดไฟล์"
   type: "ประเภทไฟล์"
@@ -2635,3 +2696,17 @@ _contextMenu:
   app: "แอปพลิเคชัน"
   appWithShift: "แอปฟลิเคชันด้วยปุ่มยกแคร่ (Shift)"
   native: "UI ของเบราว์เซอร์"
+_embedCodeGen:
+  title: "ปรับแต่งโค้ดฝัง"
+  header: "แสดงส่วนหัว"
+  autoload: "โหลดเพิ่มโดยอัตโนมัติ (เลิกใช้แล้ว)"
+  maxHeight: "ความสูงสุด"
+  maxHeightDescription: "หากถ้าตั้งค่าเป็น 0 จะทำให้ไม่มีการจำกัดความสูงของวิดเจ็ต แต่ควรตั้งค่าเป็นตัวเลขอื่นๆ เพื่อไม่ให้วิดเจ็ตยืดตัวลงไปเรื่อยๆ"
+  maxHeightWarn: "การจำกัดความสูงสูงสุดถูกปิดใช้งาน (0) หากไม่ได้ตั้งใจให้เป็นเช่นนี้ โปรดตั้งค่าความสูงสูงสุดให้เป็นค่าอื่นๆแทน"
+  previewIsNotActual: "การแสดงผลนั้นต่างจากการฝังจริงเพราะเกินขอบเขตที่แสดงบนหน้าจอตัวอย่างนะ"
+  rounded: "ทำให้มันกลม"
+  border: "เพิ่มขอบให้กับกรอบด้านนอก"
+  applyToPreview: "นำไปใช้กับการแสดงตัวอย่าง"
+  generateCode: "สร้างโค้ดสำหรับการฝัง"
+  codeGenerated: "รหัสถูกสร้างขึ้นแล้ว"
+  codeGeneratedDescription: "นำโค้ดที่สร้างแล้วไปวางในเว็บไซต์ของคุณเพื่อฝังเนื้อหา"
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index cf6729a81d8e..fe2f158ff687 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -446,6 +446,7 @@ _notification:
     reaction: "Tepkiler"
     receiveFollowRequest: "Takip isteği alındı"
     followRequestAccepted: "Takip isteği kabul edildi"
+    login: "Giriş Yap "
   _actions:
     reply: "yanıt"
     renote: "vazgeçme"
diff --git a/locales/ug-CN.yml b/locales/ug-CN.yml
index e48f64511cf5..fef26040a5b4 100644
--- a/locales/ug-CN.yml
+++ b/locales/ug-CN.yml
@@ -17,3 +17,6 @@ _2fa:
   renewTOTPCancel: "ئۇنى توختىتىڭ"
 _widgets:
   profile: "profile"
+_notification:
+  _types:
+    login: "كىرىش"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 36d741d30e4a..f2262cd71f09 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -452,7 +452,6 @@ language: "Мова"
 uiLanguage: "Мова інтерфейсу"
 aboutX: "Про {x}"
 native: "місцевий"
-disableDrawer: "Не використовувати висувні меню"
 noHistory: "Історія порожня"
 signinHistory: "Історія входів"
 enableAdvancedMfm: "Увімкнути розширений MFM"
@@ -631,10 +630,7 @@ abuseReported: "Дякуємо, вашу скаргу було відправл
 reporter: "Репортер"
 reporteeOrigin: "Про кого повідомлено"
 reporterOrigin: "Хто повідомив"
-forwardReport: "Переслати звіт на віддалений інстанс"
-forwardReportIsAnonymous: "Замість вашого облікового запису анонімний системний обліковий запис буде відображатися як доповідач на віддаленому інстансі"
 send: "Відправити"
-abuseMarkAsResolved: "Позначити скаргу як вирішену"
 openInNewTab: "Відкрити в новій вкладці"
 openInSideView: "Відкрити збоку"
 defaultNavigationBehaviour: "Поведінка навігації за замовчуванням"
@@ -1306,7 +1302,6 @@ _theme:
     buttonBg: "Фон кнопки"
     buttonHoverBg: "Фон кнопки (при наведенні)"
     inputBorder: "Край поля вводу"
-    listItemHoverBg: "Фон елементу в списку (при наведенні)"
     driveFolderBg: "Фон папки на диску"
     wallpaperOverlay: "Накладання шпалер"
     badge: "Значок"
@@ -1588,6 +1583,7 @@ _notification:
     reaction: "Реакції"
     receiveFollowRequest: "Запити на підписку"
     followRequestAccepted: "Прийняті підписки"
+    login: "Увійти"
     app: "Сповіщення від додатків"
   _actions:
     reply: "Відповісти"
diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml
index ee4ab83ce7e1..37a550008ab9 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -471,7 +471,6 @@ uiLanguage: "Interfeys tili"
 aboutX: "{x} haqida"
 emojiStyle: "Emoji ko'rinishi"
 native: "Mahalliy"
-disableDrawer: "Slayd menyusidan foydalanmang"
 showNoteActionsOnlyHover: "Eslatma amallarini faqat sichqonchani olib borganda ko‘rsatish"
 noHistory: "Tarix yo'q"
 signinHistory: "kirish tarixi"
@@ -630,10 +629,7 @@ abuseReported: "Shikoyatingiz yetkazildi. Ma'lumot uchun rahmat."
 reporter: "Shikoyat qiluvchi"
 reporteeOrigin: "Xabarning kelib chiqishi"
 reporterOrigin: "Xabarchining joylashuvi"
-forwardReport: "Xabarni masofadagi serverga yuborish"
-forwardReportIsAnonymous: "Sizning yuborayotgan xabaringiz o'z akkountingiz emas balki anonim tarzda qoladi"
 send: "Yuborish"
-abuseMarkAsResolved: "Yuborilgan xabarni hal qilingan deb belgilash"
 openInNewTab: "Yangi tab da ochish"
 openInSideView: "Yon panelda ochish"
 defaultNavigationBehaviour: "Standart navigatsiya harakati"
@@ -1058,6 +1054,7 @@ _notification:
     quote: "Iqtibos keltirish"
     reaction: "Reaktsiyalar"
     receiveFollowRequest: "Qabul qilingan kuzatuv so'rovlari"
+    login: "Kirish"
   _actions:
     reply: "Javob berish"
     renote: "Qayta qayd qilish"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index aadbf8b16f22..235497d84428 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -486,7 +486,6 @@ uiLanguage: "Ngôn ngữ giao diện"
 aboutX: "Giới thiệu {x}"
 emojiStyle: "Kiểu cách Emoji"
 native: "Bản xứ"
-disableDrawer: "Không dùng menu thanh bên"
 showNoteActionsOnlyHover: "Chỉ hiển thị các hành động ghi chú khi di chuột"
 noHistory: "Không có dữ liệu"
 signinHistory: "Lịch sử đăng nhập"
@@ -676,10 +675,7 @@ 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"
@@ -1550,7 +1546,6 @@ _theme:
     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"
@@ -1879,6 +1874,7 @@ _notification:
     receiveFollowRequest: "Yêu cầu theo dõi"
     followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
     achievementEarned: "Hoàn thành Achievement"
+    login: "Đăng nhập"
     app: "Từ app liên kết"
   _actions:
     followBack: "đã theo dõi lại bạn"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index d422c3afc576..93804608c2e5 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -8,6 +8,9 @@ search: "搜索"
 notifications: "通知"
 username: "用户名"
 password: "密码"
+initialPasswordForSetup: "初始化密码"
+initialPasswordIsIncorrect: "初始化密码不正确"
+initialPasswordForSetupDescription: "如果是自己安装的 Misskey,请输入配置文件里设好的密码。\n如果使用的是 Misskey 的托管服务等,请输入服务商提供的密码。\n如果没有设置密码,请留空并继续。"
 forgotPassword: "忘记密码"
 fetchingAsApObject: "在联邦宇宙查询中..."
 ok: "OK"
@@ -90,7 +93,7 @@ followsYou: "正在关注你"
 createList: "创建列表"
 manageLists: "管理列表"
 error: "错误"
-somethingHappened: "出现了一些问题!"
+somethingHappened: "出错了"
 retry: "重试"
 pageLoadError: "页面加载失败。"
 pageLoadErrorDescription: "这通常是由于网络或浏览器缓存的原因。请清除缓存或等待片刻后重试。"
@@ -167,7 +170,7 @@ emojiUrl: "emoji 地址"
 addEmoji: "添加表情符号"
 settingGuide: "推荐配置"
 cacheRemoteFiles: "缓存远程文件"
-cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。"
+cacheRemoteFilesDescription: "启用此设定时,将在此服务器上缓存远程文件。虽然可以加快图片显示的速度,但是相对的会消耗大量的服务器存储空间。用户角色内的网盘容量决定了这个远程用户能在服务器上保留多少缓存。当超出了这个限制时,旧的文件将从缓存中被删除,成为链接。当禁用此设定时,则是从一开始就将远程文件保留为链接。此时推荐将 default.yml 的 proxyRemoteFiles 设置为 true 以优化缩略图生成及保护用户隐私。"
 youCanCleanRemoteFilesCache: "可以使用文件管理的🗑️按钮来删除所有的缓存。"
 cacheRemoteSensitiveFiles: "缓存远程敏感媒体文件"
 cacheRemoteSensitiveFilesDescription: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。"
@@ -210,8 +213,8 @@ charts: "图表"
 perHour: "每小时"
 perDay: "每天"
 stopActivityDelivery: "停止发送活动"
-blockThisInstance: "阻止此服务器向本服务器推流"
-silenceThisInstance: "使服务器静音"
+blockThisInstance: "封锁此服务器"
+silenceThisInstance: "静音此服务器"
 mediaSilenceThisInstance: "隐藏此服务器的媒体文件"
 operations: "操作"
 software: "软件"
@@ -236,6 +239,8 @@ silencedInstances: "被静音的服务器"
 silencedInstancesDescription: "设置要静音的服务器,以换行分隔。被静音的服务器内所有的账户将默认处于「静音」状态,仅能发送关注请求,并且在未关注状态下无法提及本地账户。被阻止的实例不受影响。"
 mediaSilencedInstances: "已隐藏媒体文件的服务器"
 mediaSilencedInstancesDescription: "设置要隐藏媒体文件的服务器,以换行分隔。被设置为隐藏媒体文件服务器内所有账号的文件均按照「敏感内容」处理,且将无法使用自定义表情符号。被阻止的实例不受影响。"
+federationAllowedHosts: "允许联合的服务器"
+federationAllowedHostsDescription: "设定允许联合的服务器,以换行分隔。"
 muteAndBlock: "静音/拉黑"
 mutedUsers: "已静音用户"
 blockedUsers: "已拉黑的用户"
@@ -253,7 +258,7 @@ noCustomEmojis: "没有自定义表情符号"
 noJobs: "没有任务"
 federating: "联合中"
 blocked: "已拉黑"
-suspended: "停止推流"
+suspended: "停止投递"
 all: "全部"
 subscribing: "已订阅"
 publishing: "投递中"
@@ -334,6 +339,7 @@ renameFolder: "重命名文件夹"
 deleteFolder: "删除文件夹"
 folder: "文件夹"
 addFile: "添加文件"
+showFile: "显示文件"
 emptyDrive: "网盘中无文件"
 emptyFolder: "此文件夹中无文件"
 unableToDelete: "无法删除"
@@ -448,6 +454,7 @@ totpDescription: "使用验证器输入一次性密码"
 moderator: "监察员"
 moderation: "管理"
 moderationNote: "管理笔记"
+moderationNoteDescription: "可以用来记录仅在管理员之间共享的笔记。"
 addModerationNote: "添加管理笔记"
 moderationLogs: "管理日志"
 nUsersMentioned: "{n} 被提到"
@@ -509,7 +516,10 @@ uiLanguage: "显示语言"
 aboutX: "关于 {x}"
 emojiStyle: "表情符号的样式"
 native: "原生"
-disableDrawer: "不显示抽屉菜单"
+menuStyle: "菜单样式"
+style: "样式"
+drawer: "抽屉"
+popup: "弹窗"
 showNoteActionsOnlyHover: "仅在悬停时显示帖子操作"
 showReactionsCount: "显示帖子的回应数"
 noHistory: "没有历史记录"
@@ -592,6 +602,8 @@ ascendingOrder: "升序"
 descendingOrder: "降序"
 scratchpad: "AiScript 控制台"
 scratchpadDescription: "AiScript 控制台为 AiScript 提供了实验环境。您可以编写代码与 Misskey 交互,运行并查看结果。"
+uiInspector: "UI 检查器"
+uiInspectorDescription: "查看所有内存中由 UI 组件生成出的实例。UI 组件由 UI:C 系列函数所生成。"
 output: "输出"
 script: "脚本"
 disablePagesScript: "禁用页面脚本"
@@ -694,7 +706,7 @@ useGlobalSettingDesc: "启用时,将使用账户通知设置。关闭时,则
 other: "其他"
 regenerateLoginToken: "重新生成登录令牌"
 regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。"
-theKeywordWhenSearchingForCustomEmoji: "这将是搜素自定义表情符号时的关键词。"
+theKeywordWhenSearchingForCustomEmoji: "这将是搜索自定义表情符号时的关键词。"
 setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。"
 fileIdOrUrl: "文件 ID 或者 URL"
 behavior: "行为"
@@ -708,10 +720,7 @@ abuseReported: "内容已发送。感谢您提交信息。"
 reporter: "举报者"
 reporteeOrigin: "举报来源"
 reporterOrigin: "举报者来源"
-forwardReport: "将该举报信息转发给远程服务器"
-forwardReportIsAnonymous: "在远程实例上显示的报告者是匿名的系统账号,而不是您的账号。"
 send: "发送"
-abuseMarkAsResolved: "处理完毕"
 openInNewTab: "在新标签页中打开"
 openInSideView: "在侧边栏中打开"
 defaultNavigationBehaviour: "默认导航"
@@ -913,6 +922,7 @@ followersVisibility: "关注者的公开范围"
 continueThread: "查看更多帖子"
 deleteAccountConfirm: "将要删除账户。是否确认?"
 incorrectPassword: "密码错误"
+incorrectTotp: "一次性密码不正确或已过期"
 voteConfirm: "确定投给 “{choice}” ?"
 hide: "隐藏"
 useDrawerReactionPickerForMobile: "在移动设备上使用抽屉显示"
@@ -937,6 +947,9 @@ oneHour: "1 小时"
 oneDay: "1 天"
 oneWeek: "1 周"
 oneMonth: "1 个月"
+threeMonths: "3 个月"
+oneYear: "1 年"
+threeDays: "3 天"
 reflectMayTakeTime: "可能需要一些时间才能体现出效果。"
 failedToFetchAccountInformation: "获取账户信息失败"
 rateLimitExceeded: "已超过速率限制"
@@ -1060,7 +1073,7 @@ nonSensitiveOnlyForLocalLikeOnlyForRemote: "仅限非敏感内容(远程仅点
 rolesAssignedToMe: "指派给自己的角色"
 resetPasswordConfirm: "确定重置密码?"
 sensitiveWords: "敏感词"
-sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
+sensitiveWordsDescription: "包含这些词的帖子将只在首页可见。可用换行来设定多个词。"
 sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
 prohibitedWords: "禁用词"
 prohibitedWordsDescription: "发布包含设定词汇的帖子时将出错。可用换行设定多个关键字"
@@ -1077,6 +1090,7 @@ retryAllQueuesConfirmTitle: "要再尝试一次吗?"
 retryAllQueuesConfirmText: "可能会使服务器负荷在一定时间内增加"
 enableChartsForRemoteUser: "生成远程用户的图表"
 enableChartsForFederatedInstances: "生成远程服务器的图表"
+enableStatsForFederatedInstances: "获取远程服务器的信息"
 showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
 reactionsDisplaySize: "回应显示大小"
 limitWidthOfReaction: "限制回应的最大宽度,并将其缩小显示"
@@ -1189,10 +1203,10 @@ followingOrFollower: "关注中或关注者"
 fileAttachedOnly: "仅限媒体"
 showRepliesToOthersInTimeline: "在时间线中包含给别人的回复"
 hideRepliesToOthersInTimeline: "在时间线中隐藏给别人的回复"
-showRepliesToOthersInTimelineAll: "在时间线中包含现在关注的所有人的回复"
-hideRepliesToOthersInTimelineAll: "在时间线中隐藏现在关注的所有人的回复"
-confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中包含现在关注的所有人的回复吗?"
-confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏现在关注的所有人的回复吗?"
+showRepliesToOthersInTimelineAll: "在时间线中显示所有现在关注的人的回复"
+hideRepliesToOthersInTimelineAll: "在时间线中隐藏所有现在关注的人的回复"
+confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中显示所有现在关注的人的回复吗?"
+confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏所有现在关注的人的回复吗?"
 externalServices: "外部服务"
 sourceCode: "源代码"
 sourceCodeIsNotYetProvided: "还未提供源代码。要解决此问题请联系管理员。"
@@ -1263,6 +1277,47 @@ confirmWhenRevealingSensitiveMedia: "显示敏感内容前需要确认"
 sensitiveMediaRevealConfirm: "这是敏感内容。是否显示?"
 createdLists: "已创建的列表"
 createdAntennas: "已创建的天线"
+fromX: "从 {x}"
+genEmbedCode: "生成嵌入代码"
+noteOfThisUser: "此用户的帖子"
+clipNoteLimitExceeded: "无法再往此便签内添加更多帖子"
+performance: "性能"
+modified: "有变更"
+discard: "取消"
+thereAreNChanges: "有 {n} 处更改"
+signinWithPasskey: "使用通行密钥登录"
+unknownWebAuthnKey: "此通行密钥未注册。"
+passkeyVerificationFailed: "验证通行密钥失败。"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "通行密钥验证成功,但账户未开启无密码登录。"
+messageToFollower: "给关注者的消息"
+target: "对象"
+testCaptchaWarning: "此功能为测试 CAPTCHA 用。<strong>请勿在正式环境中使用。</strong>"
+prohibitedWordsForNameOfUser: "用户名中禁止的词"
+prohibitedWordsForNameOfUserDescription: "更改用户名时,如果用户名中包含此列表里的词汇,用户的改名请求将被拒绝。持有管理员权限的用户不受此限制。"
+yourNameContainsProhibitedWords: "目标用户名包含违禁词"
+yourNameContainsProhibitedWordsDescription: "用户名内含有违禁词。若想使用此用户名,请联系服务器管理员。"
+thisContentsAreMarkedAsSigninRequiredByAuthor: "根据发帖者的设定,需要登录才能显示"
+lockdown: "锁定"
+pleaseSelectAccount: "请选择帐户"
+_accountSettings:
+  requireSigninToViewContents: "需要登录才能显示内容"
+  requireSigninToViewContentsDescription1: "您发布的所有帖子将变成需要登入后才会显示。有望防止爬虫收集各种信息。"
+  requireSigninToViewContentsDescription2: "没有 URL 预览(OGP)、内嵌网页、引用帖子的功能的服务器也将无法显示。"
+  requireSigninToViewContentsDescription3: "这些限制可能不适用于联合到远程服务器的内容。"
+  makeNotesFollowersOnlyBefore: "可将过去的帖子设为仅关注者可见"
+  makeNotesFollowersOnlyBeforeDescription: "开启此设定时,超过设定的时间或日期后,帖子将变为仅关注者可见。关闭后帖子的公开状态将恢复成原本的设定。"
+  makeNotesHiddenBefore: "将过去的帖子设为私密"
+  makeNotesHiddenBeforeDescription: "开启此设定时,超过设定的时间或日期后,帖子将变为仅自己可见。关闭后帖子的公开状态将恢复成原本的设定。"
+  mayNotEffectForFederatedNotes: "与远程服务器联合的帖子在远端可能会没有效果。"
+  notesHavePassedSpecifiedPeriod: "超过指定时间的帖子"
+  notesOlderThanSpecifiedDateAndTime: "指定日期前的帖子"
+_abuseUserReport:
+  forward: "转发"
+  forwardDescription: "目标是匿名系统账户,将把举报转发给远程服务器。"
+  resolve: "解决"
+  accept: "确认"
+  reject: "拒绝"
+  resolveTutorial: "如果举报内容有理且已解决,选择「确认」将案件以肯定的态度标记为已解决。\n如果举报内容站不住脚,选择「拒绝」将案件以否定的态度标记为已解决。"
 _delivery:
   status: "投递状态"
   stop: "停止投递"
@@ -1397,8 +1452,10 @@ _serverSettings:
   fanoutTimelineDescription: "当启用时,可显著提高获取各种时间线时的性能,并减轻数据库的负荷。但是相对的 Redis 的内存使用量将会增加。如果服务器的内存不是很大,又或者运行不稳定的话可以把它关掉。"
   fanoutTimelineDbFallback: "回退到数据库"
   fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。"
+  reactionsBufferingDescription: "开启时可显著提高发送回应时的性能,及减轻数据库负荷。但 Redis 的内存用量会相应增加。"
   inquiryUrl: "联络地址"
   inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "若在一段时间内没有检测到管理活动,为防止垃圾信息,此设定将自动关闭。"
 _accountMigration:
   moveFrom: "从别的账号迁移到此账户"
   moveFromSub: "为另一个账户建立别名"
@@ -1598,7 +1655,7 @@ _achievements:
     _postedAt0min0sec:
       title: "报时"
       description: "在 0 点发布一篇帖子"
-      flavor: "嘣 嘣 嘣 Biu——!"
+      flavor: "嘟 · 嘟 · 嘟 · 哔——"
     _selfQuote:
       title: "自我引用"
       description: "引用了自己的帖子"
@@ -1647,8 +1704,8 @@ _achievements:
       flavor: "今年也请对本服务器多多指教!"
     _cookieClicked:
       title: "点击饼干小游戏"
-      description: "点击了可疑的饼干"
-      flavor: "是不是软件有问题?"
+      description: "点击了饼干"
+      flavor: "用错软件了?"
     _brainDiver:
       title: "Brain Diver"
       description: "发布了包含 Brain Diver 链接的帖子"
@@ -1665,7 +1722,7 @@ _achievements:
     _bubbleGameDoubleExplodingHead:
       title: "两个🤯"
       description: "你合成出了2个游戏里最大的Emoji"
-      flavor: ""
+      flavor: "大约能 装满 这些便当盒 🤯 🤯 (比划)"
 _role:
   new: "创建角色"
   edit: "编辑角色"
@@ -1730,6 +1787,11 @@ _role:
     canSearchNotes: "是否可以搜索帖子"
     canUseTranslator: "使用翻译功能"
     avatarDecorationLimit: "可添加头像挂件的最大个数"
+    canImportAntennas: "允许导入天线"
+    canImportBlocking: "允许导入拉黑列表"
+    canImportFollowing: "允许导入关注列表"
+    canImportMuting: "允许导入屏蔽列表"
+    canImportUserLists: "允许导入用户列表"
   _condition:
     roleAssignedTo: "已分配给手动角色"
     isLocal: "是本地用户"
@@ -1947,7 +2009,6 @@ _theme:
     buttonBg: "按钮背景"
     buttonHoverBg: "按钮背景(悬停)"
     inputBorder: "输入框边框"
-    listItemHoverBg: "下拉列表项目背景(悬停)"
     driveFolderBg: "网盘的文件夹背景"
     wallpaperOverlay: "壁纸叠加层"
     badge: "徽章"
@@ -2114,8 +2175,11 @@ _auth:
   permissionAsk: "这个应用程序需要以下权限"
   pleaseGoBack: "请返回到应用程序"
   callback: "回到应用程序"
+  accepted: "已允许访问"
   denied: "拒绝访问"
+  scopeUser: "以下面的用户进行操作"
   pleaseLogin: "在对应用进行授权许可之前,请先登录"
+  byClickingYouWillBeRedirectedToThisUrl: "允许访问后将会自动重定向到以下 URL"
 _antennaSources:
   all: "所有帖子"
   homeTimeline: "已关注用户的帖子"
@@ -2224,6 +2288,9 @@ _profile:
   changeBanner: "修改横幅"
   verifiedLinkDescription: "如果将内容设置为 URL,当链接所指向的网页内包含自己的个人资料链接时,可以显示一个已验证图标。"
   avatarDecorationMax: "最多可添加 {max} 个挂件"
+  followedMessage: "被关注时显示的消息"
+  followedMessageDescription: "可以设置被关注时向对方显示的短消息。"
+  followedMessageDescriptionForLockedAccount: "需要批准才能关注的情况下,消息是在请求被批准后显示。"
 _exportOrImport:
   allNotes: "所有帖子"
   favoritedNotes: "收藏的帖子"
@@ -2362,6 +2429,8 @@ _notification:
   renotedBySomeUsers: "{n} 人转发了"
   followedBySomeUsers: "被 {n} 人关注"
   flushNotification: "重置通知历史"
+  exportOfXCompleted: "已完成 {x} 个导出"
+  login: "有新的登录"
   _types:
     all: "全部"
     note: "用户的新帖子"
@@ -2376,6 +2445,9 @@ _notification:
     followRequestAccepted: "关注请求已通过"
     roleAssigned: "授予的角色"
     achievementEarned: "取得的成就"
+    exportCompleted: "已完成导出"
+    login: "登录"
+    test: "测试通知"
     app: "关联应用的通知"
   _actions:
     followBack: "回关"
@@ -2441,7 +2513,10 @@ _webhookSettings:
     abuseReport: "当收到举报时"
     abuseReportResolved: "当举报被处理时"
     userCreated: "当用户被创建时"
+    inactiveModeratorsWarning: "当管理员在一段时间内不活跃时"
+    inactiveModeratorsInvitationOnlyChanged: "当因为管理员在一段时间内不活跃,导致服务器变为邀请制时"
   deleteConfirm: "要删除 webhook 吗?"
+  testRemarks: "点击开关右侧的按钮,可以发送使用假数据的测试 Webhook。"
 _abuseReport:
   _notificationRecipient:
     createRecipient: "新建举报通知"
@@ -2485,6 +2560,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "标记网盘文件为敏感媒体"
   unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
   resolveAbuseReport: "处理举报"
+  forwardAbuseReport: "转发举报"
+  updateAbuseReportNote: "更新举报用管理笔记"
   createInvitation: "生成邀请码"
   createAd: "创建了广告"
   deleteAd: "删除了广告"
@@ -2640,3 +2717,23 @@ _contextMenu:
   app: "应用"
   appWithShift: "Shift 键应用"
   native: "浏览器的用户界面"
+_embedCodeGen:
+  title: "自定义嵌入代码"
+  header: "显示标题"
+  autoload: "连续加载(不推荐)"
+  maxHeight: "最大高度"
+  maxHeightDescription: "若将最大值设为 0 则不限制最大高度。为防止小工具无限增高,建议设置一下。"
+  maxHeightWarn: "最大高度限制已禁用(0)。若这不是您想要的效果,请将最大高度设一个值。"
+  previewIsNotActual: "由于超出了预览画面可显示的范围,因此显示内容会与实际嵌入时有所不同。"
+  rounded: "圆角"
+  border: "外边框"
+  applyToPreview: "应用预览"
+  generateCode: "生成嵌入代码"
+  codeGenerated: "已生成代码"
+  codeGeneratedDescription: "将生成的代码贴到网站上来使用。"
+_selfXssPrevention:
+  warning: "警告"
+  title: "「在此处粘贴什么东西」是欺诈行为。"
+  description1: "如果在此处粘贴了什么,恶意用户可能会接管账户或者盗取个人资料。"
+  description2: "如果不能完全理解将要粘贴的内容,%c 请立即停止操作并关闭这个窗口。"
+  description3: "详情请看这里。{link}"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 57da480d2877..16afeed0f8a0 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -8,6 +8,9 @@ search: "搜尋"
 notifications: "通知"
 username: "使用者名稱"
 password: "密碼"
+initialPasswordForSetup: "初始設定用的密碼"
+initialPasswordIsIncorrect: "初始設定用的密碼錯誤。"
+initialPasswordForSetupDescription: "如果您自己安裝了 Misskey,請使用您在設定檔中輸入的密碼。\n如果您使用 Misskey 的託管服務之類的服務,請使用提供的密碼。\n如果您尚未設定密碼,請將其留空並繼續。"
 forgotPassword: "忘記密碼"
 fetchingAsApObject: "從聯邦宇宙取得中..."
 ok: "OK"
@@ -236,6 +239,8 @@ silencedInstances: "被禁言的伺服器"
 silencedInstancesDescription: "設定要禁言的伺服器主機名稱,以換行分隔。隸屬於禁言伺服器的所有帳戶都將被視為「禁言帳戶」,只能發出「追隨請求」,而且無法提及未追隨的本地帳戶。這不會影響已封鎖的實例。"
 mediaSilencedInstances: "媒體被禁言的伺服器"
 mediaSilencedInstancesDescription: "設定您想要對媒體設定禁言的伺服器,以換行符號區隔。來自被媒體禁言的伺服器所屬帳戶的所有檔案都會被視為敏感檔案,且自訂表情符號不能使用。被封鎖的伺服器不受影響。"
+federationAllowedHosts: "允許聯邦通訊的伺服器"
+federationAllowedHostsDescription: "設定允許聯邦通訊的伺服器主機,以換行符號分隔。"
 muteAndBlock: "靜音和封鎖"
 mutedUsers: "被靜音的使用者"
 blockedUsers: "被封鎖的使用者"
@@ -334,6 +339,7 @@ renameFolder: "重新命名資料夾"
 deleteFolder: "刪除資料夾"
 folder: "資料夾"
 addFile: "加入附件"
+showFile: "瀏覽文件"
 emptyDrive: "雲端硬碟為空"
 emptyFolder: "資料夾為空"
 unableToDelete: "無法刪除"
@@ -448,6 +454,7 @@ totpDescription: "以驗證應用程式輸入一次性密碼"
 moderator: "審查員"
 moderation: "審查"
 moderationNote: "管理筆記"
+moderationNoteDescription: "您可以編寫僅在審查員之間共用的註解。"
 addModerationNote: "新增管理筆記"
 moderationLogs: "管理日誌"
 nUsersMentioned: "被 {n} 個人提及"
@@ -509,8 +516,11 @@ uiLanguage: "介面語言"
 aboutX: "關於{x}"
 emojiStyle: "表情符號的風格"
 native: "原生"
-disableDrawer: "不顯示下拉式選單"
-showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的操作選項"
+menuStyle: "選單風格"
+style: "風格"
+drawer: "側邊欄"
+popup: "彈出式視窗"
+showNoteActionsOnlyHover: "僅在游標停留時顯示貼文的"
 showReactionsCount: "顯示貼文的反應數目"
 noHistory: "沒有歷史紀錄"
 signinHistory: "登入歷史"
@@ -592,6 +602,8 @@ ascendingOrder: "昇冪"
 descendingOrder: "降冪"
 scratchpad: "暫存記憶體"
 scratchpadDescription: "AiScript 控制臺為 AiScript 的實驗環境。您可以在此編寫、執行和確認程式碼與 Misskey 互動的結果。"
+uiInspector: "UI 檢查"
+uiInspectorDescription: "您可以看到記憶體中存在的 UI 元件實例的清單。  UI 元件由 Ui:C: 系列函數產生。"
 output: "輸出"
 script: "腳本"
 disablePagesScript: "停用頁面的 AiScript 腳本"
@@ -708,10 +720,7 @@ abuseReported: "檢舉完成。感謝您的報告。"
 reporter: "檢舉者"
 reporteeOrigin: "檢舉來源"
 reporterOrigin: "檢舉者來源"
-forwardReport: "將報告轉送給遠端伺服器"
-forwardReportIsAnonymous: "在遠端實例上看不到您的資訊,顯示的報告者是匿名的系统帳戶。"
 send: "發送"
-abuseMarkAsResolved: "處理完畢"
 openInNewTab: "在新分頁中開啟"
 openInSideView: "在側欄中開啟"
 defaultNavigationBehaviour: "預設導航"
@@ -913,6 +922,7 @@ followersVisibility: "追隨者的可見性"
 continueThread: "查看更多貼文"
 deleteAccountConfirm: "將要刪除帳戶。是否確定?"
 incorrectPassword: "密碼錯誤。"
+incorrectTotp: "一次性密碼錯誤,或者已過期。"
 voteConfirm: "確定投給「{choice}」?"
 hide: "隱藏"
 useDrawerReactionPickerForMobile: "在移動設備上使用抽屜顯示"
@@ -937,6 +947,9 @@ oneHour: "一小時"
 oneDay: "一天"
 oneWeek: "一週"
 oneMonth: "一個月"
+threeMonths: "3 個月"
+oneYear: "1 年"
+threeDays: "3 日"
 reflectMayTakeTime: "可能需要一些時間才會出現效果。"
 failedToFetchAccountInformation: "取得帳戶資訊失敗"
 rateLimitExceeded: "已超過速率限制"
@@ -1009,7 +1022,7 @@ show: "檢視"
 neverShow: "不再顯示"
 remindMeLater: "以後再說"
 didYouLikeMisskey: "您喜歡 Misskey 嗎?"
-pleaseDonate: "Misskey 是由 {host} 使用的免費軟體。請贊助我們,讓開發得以持續!"
+pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發的工作能夠持續!"
 correspondingSourceIsAvailable: "對應的原始碼可以在 {anchor} 處找到。"
 roles: "角色"
 role: "角色"
@@ -1077,6 +1090,7 @@ retryAllQueuesConfirmTitle: "要現在重試嗎?"
 retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
 enableChartsForRemoteUser: "生成遠端使用者的圖表"
 enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
+enableStatsForFederatedInstances: "取得遠端伺服器資訊"
 showClipButtonInNoteFooter: "新增摘錄按鈕至貼文"
 reactionsDisplaySize: "反應的顯示尺寸"
 limitWidthOfReaction: "限制反應的最大寬度,並縮小顯示尺寸。"
@@ -1185,8 +1199,8 @@ showRenotes: "顯示其他人的轉發貼文"
 edited: "已編輯"
 notificationRecieveConfig: "接受通知的設定"
 mutualFollow: "互相追隨"
-followingOrFollower: "追隨中或追隨者"
-fileAttachedOnly: "顯示包含附件的貼文"
+followingOrFollower: "追隨中或者追隨者"
+fileAttachedOnly: "只顯示包含附件的貼文"
 showRepliesToOthersInTimeline: "顯示給其他人的回覆"
 hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
 showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆"
@@ -1256,13 +1270,54 @@ useNativeUIForVideoAudioPlayer: "使用瀏覽器的 UI 播放影片與音訊"
 keepOriginalFilename: "保留原始檔名"
 keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案名稱會自動替換為隨機字串。"
 noDescription: "沒有說明文字"
-alwaysConfirmFollow: "點擊追隨時總是顯示確認訊息"
+alwaysConfirmFollow: "跟隨時總是確認"
 inquiry: "聯絡我們"
 tryAgain: "請再試一次。"
 confirmWhenRevealingSensitiveMedia: "要顯示敏感媒體時需確認"
 sensitiveMediaRevealConfirm: "這是敏感媒體。確定要顯示嗎?"
 createdLists: "已建立的清單"
 createdAntennas: "已建立的天線"
+fromX: "自 {x}"
+genEmbedCode: "產生嵌入程式碼"
+noteOfThisUser: "這個使用者的貼文列表"
+clipNoteLimitExceeded: "沒辦法在這個摘錄中增加更多貼文了。"
+performance: "性能"
+modified: "已變更"
+discard: "取消"
+thereAreNChanges: "有 {n} 處的變更"
+signinWithPasskey: "使用密碼金鑰登入"
+unknownWebAuthnKey: "未註冊的金鑰。"
+passkeyVerificationFailed: "驗證金鑰失敗。"
+passkeyVerificationSucceededButPasswordlessLoginDisabled: "雖然驗證金鑰成功,但是無密碼登入的方式是停用的。"
+messageToFollower: "給追隨者的訊息"
+target: "目標 "
+testCaptchaWarning: "此功能用於 CAPTCHA 的測試。<strong>請勿在正式環境中使用。</strong>"
+prohibitedWordsForNameOfUser: "禁止使用的字詞(使用者名稱)"
+prohibitedWordsForNameOfUserDescription: "如果使用者名稱包含此清單中的任何字串,則拒絕重新命名使用者。 具有審查員權限的使用者不受此限制的影響。"
+yourNameContainsProhibitedWords: "您嘗試更改的名稱包含禁止的字串"
+yourNameContainsProhibitedWordsDescription: "名稱中包含禁止使用的字串。 如果您想使用此名稱,請聯絡您的伺服器管理員。"
+thisContentsAreMarkedAsSigninRequiredByAuthor: "作者將其設定為需要登入才能顯示。"
+lockdown: "鎖定"
+pleaseSelectAccount: "請選擇帳戶"
+_accountSettings:
+  requireSigninToViewContents: "須登入以顯示內容"
+  requireSigninToViewContentsDescription1: "必須登入才會顯示您建立的貼文等內容。可望有效防止資訊被爬蟲蒐集。"
+  requireSigninToViewContentsDescription2: "來自不支援 URL 預覽 (OGP)、 網頁嵌入和引用貼文的伺服器,也將停止顯示。"
+  requireSigninToViewContentsDescription3: "這些限制可能不適用於被聯邦發送至遠端伺服器的內容。"
+  makeNotesFollowersOnlyBefore: "讓過去的貼文僅對追隨者顯示"
+  makeNotesFollowersOnlyBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對追隨者顯示。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
+  makeNotesHiddenBefore: "隱藏過去的貼文"
+  makeNotesHiddenBeforeDescription: "啟用此功能後,超過設定的日期和時間或超過設定時間的貼文將僅對自己顯示(私密化)。 如果您再次停用它,貼文的公開狀態也會恢復原狀。"
+  mayNotEffectForFederatedNotes: "聯邦發送至遠端伺服器的貼文可能會不受影響。"
+  notesHavePassedSpecifiedPeriod: "早於指定時間的貼文"
+  notesOlderThanSpecifiedDateAndTime: "指定時間和日期之前的貼文"
+_abuseUserReport:
+  forward: "轉發"
+  forwardDescription: "以匿名系統帳戶將檢舉轉發至遠端伺服器。"
+  resolve: "解決"
+  accept: "接受"
+  reject: "拒絕"
+  resolveTutorial: "如果您已回覆正當的檢舉,請選擇「接受」以將案件標記為已解決。\n 如果檢舉的內容不正當,請選擇「拒絕」將案件標記為已解決。"
 _delivery:
   status: "傳送狀態"
   stop: "停止發送"
@@ -1397,8 +1452,10 @@ _serverSettings:
   fanoutTimelineDescription: "如果啟用的話,檢索各個時間軸的性能會顯著提昇,資料庫的負荷也會減少。不過,Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
   fanoutTimelineDbFallback: "資料庫的回退"
   fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
+  reactionsBufferingDescription: "啟用時,可以顯著提高建立反應時的效能並減少資料庫的負載。 但是,Redis 記憶體使用量會增加。"
   inquiryUrl: "聯絡表單網址"
   inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "為了防止 spam,如果一段期間內沒有偵測到審查員的活動,此設定將自動關閉。"
 _accountMigration:
   moveFrom: "從其他帳戶遷移到這個帳戶"
   moveFromSub: "為另一個帳戶建立別名"
@@ -1412,7 +1469,7 @@ _accountMigration:
   startMigration: "遷移"
   migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。"
   movedAndCannotBeUndone: "帳戶已遷移。\n遷移無法撤消。"
-  postMigrationNote: "在完成遷移的 24 小時後解除此帳戶的追隨。此帳戶的追隨中、追隨者數量變為 0。由於不會解除追隨者,你的追隨者仍然可以繼續檢視這個帳戶發布給追隨者的貼文。"
+  postMigrationNote: "取消追蹤此帳戶將在遷移操作後 24 小時執行。\n 此帳戶有 0 個關注者/關注者。 您的關注者仍然可以看到此帳戶的關注者帖子,因為您不會被取消關注。"
   movedTo: "要遷移到的帳戶:"
 _achievements:
   earnedAt: "獲得日期"
@@ -1532,7 +1589,7 @@ _achievements:
     _markedAsCat:
       title: "我是貓"
       description: "已將帳戶設定為貓"
-      flavor: "還沒有名字。"
+      flavor: "沒有名字。"
     _following1:
       title: "首次追隨"
       description: "首次追隨了"
@@ -1546,7 +1603,7 @@ _achievements:
       title: "一百位朋友"
       description: "追隨超過100人了"
     _following300:
-      title: "朋友過多"
+      title: "朋友太多"
       description: "追隨超過300人了"
     _followers1:
       title: "第一個追隨者"
@@ -1730,6 +1787,11 @@ _role:
     canSearchNotes: "可否搜尋貼文"
     canUseTranslator: "使用翻譯功能"
     avatarDecorationLimit: "頭像裝飾的最大設置量"
+    canImportAntennas: "允許匯入天線"
+    canImportBlocking: "允許匯入封鎖名單"
+    canImportFollowing: "允許匯入跟隨名單"
+    canImportMuting: "允許匯入靜音名單"
+    canImportUserLists: "允許匯入清單"
   _condition:
     roleAssignedTo: "手動指派角色完成"
     isLocal: "本地使用者"
@@ -1867,7 +1929,7 @@ _channel:
   following: "追隨中"
   usersCount: "有 {n} 人參與"
   notesCount: "有 {n} 篇貼文"
-  nameAndDescription: "名稱與說明"
+  nameAndDescription: "名稱"
   nameOnly: "僅名稱"
   allowRenoteToExternal: "允許在頻道外轉發和引用"
 _menuDisplay:
@@ -1947,7 +2009,6 @@ _theme:
     buttonBg: "按鈕背景"
     buttonHoverBg: "按鈕背景 (漂浮)"
     inputBorder: "輸入框邊框"
-    listItemHoverBg: "列表物品背景 (漂浮)"
     driveFolderBg: "雲端硬碟文件夾背景"
     wallpaperOverlay: "壁紙覆蓋層"
     badge: "徽章"
@@ -2114,8 +2175,11 @@ _auth:
   permissionAsk: "此應用程式需要以下權限"
   pleaseGoBack: "請返回至應用程式"
   callback: "回到應用程式"
+  accepted: "已授予存取權限"
   denied: "拒絕訪問"
+  scopeUser: "以下列使用者身分操作"
   pleaseLogin: "必須登入以提供應用程式的存取權限。"
+  byClickingYouWillBeRedirectedToThisUrl: "如果授予存取權限,就會自動導向到以下的網址"
 _antennaSources:
   all: "全部貼文"
   homeTimeline: "來自已追隨使用者的貼文"
@@ -2224,6 +2288,9 @@ _profile:
   changeBanner: "變更橫幅圖像"
   verifiedLinkDescription: "如果輸入包含您個人資料的網站 URL,欄位旁邊將出現驗證圖示。"
   avatarDecorationMax: "最多可以設置 {max} 個裝飾。"
+  followedMessage: "被追隨時的訊息"
+  followedMessageDescription: "可以設定被追隨時顯示給對方的訊息。"
+  followedMessageDescriptionForLockedAccount: "如果追隨是需要審核的話,在允許追隨請求之後顯示。"
 _exportOrImport:
   allNotes: "所有貼文"
   favoritedNotes: "「我的最愛」貼文"
@@ -2362,13 +2429,15 @@ _notification:
   renotedBySomeUsers: "{n}人做了轉發"
   followedBySomeUsers: "被{n}人追隨了"
   flushNotification: "重置通知歷史紀錄"
+  exportOfXCompleted: "{x} 的匯出已完成。"
+  login: "已登入"
   _types:
     all: "全部 "
     note: "使用者的最新貼文"
     follow: "追隨中"
     mention: "提及"
     reply: "回覆"
-    renote: "轉發貼文"
+    renote: "轉發"
     quote: "引用"
     reaction: "反應"
     pollEnded: "問卷調查結束"
@@ -2376,6 +2445,9 @@ _notification:
     followRequestAccepted: "追隨請求已接受"
     roleAssigned: "已授予角色"
     achievementEarned: "獲得成就"
+    exportCompleted: "已完成匯出。"
+    login: "登入"
+    test: "通知測試"
     app: "應用程式通知"
   _actions:
     followBack: "追隨回去"
@@ -2441,7 +2513,10 @@ _webhookSettings:
     abuseReport: "當使用者檢舉時"
     abuseReportResolved: "當處理了使用者的檢舉時"
     userCreated: "使用者被新增時"
+    inactiveModeratorsWarning: "當審查員在一段時間內沒有活動時"
+    inactiveModeratorsInvitationOnlyChanged: "當審查員在一段時間內不活動時,系統會將模式變更為邀請制"
   deleteConfirm: "請問是否要刪除 Webhook?"
+  testRemarks: "按下切換開關右側的按鈕,就會將假資料發送至 Webhook。"
 _abuseReport:
   _notificationRecipient:
     createRecipient: "新增接收檢舉的通知對象"
@@ -2454,7 +2529,7 @@ _abuseReport:
         mail: "寄送到擁有監察員權限的使用者電子郵件地址(僅在收到檢舉時)"
         webhook: "向指定的 SystemWebhook 發送通知(在收到檢舉和解決檢舉時發送)"
     keywords: "關鍵字"
-    notifiedUser: "被通知的使用者"
+    notifiedUser: "通知的使用者"
     notifiedWebhook: "使用的 Webhook"
     deleteConfirm: "確定要刪除通知對象嗎?"
 _moderationLogTypes:
@@ -2485,6 +2560,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "標記為敏感檔案"
   unmarkSensitiveDriveFile: "撤銷標記為敏感檔案"
   resolveAbuseReport: "解決檢舉"
+  forwardAbuseReport: "轉發檢舉"
+  updateAbuseReportNote: "更新檢舉的審查備註"
   createInvitation: "建立邀請碼"
   createAd: "建立廣告"
   deleteAd: "刪除廣告"
@@ -2640,3 +2717,23 @@ _contextMenu:
   app: "應用程式"
   appWithShift: "Shift 鍵應用程式"
   native: "瀏覽器的使用者介面"
+_embedCodeGen:
+  title: "自訂嵌入程式碼"
+  header: "檢視標頭 "
+  autoload: "自動繼續載入(不建議)"
+  maxHeight: "最大高度"
+  maxHeightDescription: "設定為 0 時代表沒有最大值。請指定某個值以避免小工具持續在縱向延伸。"
+  maxHeightWarn: "最大高度限制已停用(0)。如果這個變更不是您想要的,請將最大高度設定為某個值。"
+  previewIsNotActual: "由於超出了預覽畫面可顯示的範圍,因此顯示內容會與實際嵌入時有所不同。"
+  rounded: "圓角"
+  border: "給外框加上邊框"
+  applyToPreview: "反映在預覽中"
+  generateCode: "建立嵌入程式碼"
+  codeGenerated: "已產生程式碼"
+  codeGeneratedDescription: "請將產生的程式碼貼到您的網站上。"
+_selfXssPrevention:
+  warning: "警告"
+  title: "「在此畫面貼上一些內容」完全是個騙局。"
+  description1: "如果您在此處貼上任何內容,惡意使用者可能會接管您的帳戶或竊取您的個人資訊。"
+  description2: "如果您不確切知道要貼上的內容,%c 請立即停止工作並關閉此視窗。"
+  description3: "細節請看這裡。{link}"
diff --git a/package.json b/package.json
index 85b4f62752bc..6a44eb04f3e9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "2024.8.0",
+	"version": "2024.11.0-alpha.0",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
@@ -56,11 +56,11 @@
 		"fast-glob": "3.3.2",
 		"ignore-walk": "6.0.5",
 		"js-yaml": "4.1.0",
-		"postcss": "8.4.40",
+		"postcss": "8.4.47",
 		"tar": "6.2.1",
-		"terser": "5.31.3",
-		"typescript": "5.5.4",
-		"esbuild": "0.23.0",
+		"terser": "5.33.0",
+		"typescript": "5.6.2",
+		"esbuild": "0.23.1",
 		"glob": "11.0.0"
 	},
 	"devDependencies": {
@@ -69,11 +69,11 @@
 		"@typescript-eslint/eslint-plugin": "7.17.0",
 		"@typescript-eslint/parser": "7.17.0",
 		"cross-env": "7.0.3",
-		"cypress": "13.13.1",
+		"cypress": "13.14.2",
 		"eslint": "9.8.0",
-		"globals": "15.8.0",
+		"globals": "15.9.0",
 		"ncp": "2.0.0",
-		"start-server-and-test": "2.0.4"
+		"start-server-and-test": "2.0.8"
 	},
 	"optionalDependencies": {
 		"@tensorflow/tfjs-core": "4.4.0"
diff --git a/packages/backend/assets/tabler-badges/login-2.png b/packages/backend/assets/tabler-badges/login-2.png
new file mode 100644
index 000000000000..f3ca8de3ddd0
Binary files /dev/null and b/packages/backend/assets/tabler-badges/login-2.png differ
diff --git a/packages/backend/eslint.config.js b/packages/backend/eslint.config.js
index 4fd9f0cd51b6..ae7b2baf4922 100644
--- a/packages/backend/eslint.config.js
+++ b/packages/backend/eslint.config.js
@@ -11,7 +11,7 @@ export default [
 		languageOptions: {
 			parserOptions: {
 				parser: tsParser,
-				project: ['./tsconfig.json', './test/tsconfig.json'],
+				project: ['./tsconfig.json', './test/tsconfig.json', './test-federation/tsconfig.json'],
 				sourceType: 'module',
 				tsconfigRootDir: import.meta.dirname,
 			},
diff --git a/packages/backend/jest.config.fed.cjs b/packages/backend/jest.config.fed.cjs
new file mode 100644
index 000000000000..fae187bc23f3
--- /dev/null
+++ b/packages/backend/jest.config.fed.cjs
@@ -0,0 +1,13 @@
+/*
+ * For a detailed explanation regarding each configuration property and type check, visit:
+ * https://jestjs.io/docs/en/configuration.html
+ */
+
+const base = require('./jest.config.cjs');
+
+module.exports = {
+	...base,
+	testMatch: [
+		'<rootDir>/test-federation/test/**/*.test.ts',
+	],
+};
diff --git a/packages/backend/migration/1723944246767-followedMessage.js b/packages/backend/migration/1723944246767-followedMessage.js
new file mode 100644
index 000000000000..fc9ad1cb8538
--- /dev/null
+++ b/packages/backend/migration/1723944246767-followedMessage.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class FollowedMessage1723944246767 {
+	name = 'FollowedMessage1723944246767';
+
+	async up(queryRunner) {
+		await queryRunner.query('ALTER TABLE "user_profile" ADD "followedMessage" character varying(256)');
+	}
+
+	async down(queryRunner) {
+		await queryRunner.query('ALTER TABLE "user_profile" DROP COLUMN "followedMessage"');
+	}
+}
diff --git a/packages/backend/migration/1726804538569-reactions-buffering.js b/packages/backend/migration/1726804538569-reactions-buffering.js
new file mode 100644
index 000000000000..bc19e9cc8aa6
--- /dev/null
+++ b/packages/backend/migration/1726804538569-reactions-buffering.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class ReactionsBuffering1726804538569 {
+    name = 'ReactionsBuffering1726804538569'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "enableReactionsBuffering" boolean NOT NULL DEFAULT false`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableReactionsBuffering"`);
+    }
+}
diff --git a/packages/backend/migration/1727318020265-enableStatsForFederatedInstances.js b/packages/backend/migration/1727318020265-enableStatsForFederatedInstances.js
new file mode 100644
index 000000000000..4ff520172b8e
--- /dev/null
+++ b/packages/backend/migration/1727318020265-enableStatsForFederatedInstances.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class EnableStatsForFederatedInstances1727318020265 {
+    name = 'EnableStatsForFederatedInstances1727318020265'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "enableStatsForFederatedInstances" boolean NOT NULL DEFAULT true`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableStatsForFederatedInstances"`);
+    }
+}
diff --git a/packages/backend/migration/1727491883993-user-score.js b/packages/backend/migration/1727491883993-user-score.js
new file mode 100644
index 000000000000..7292d5363c53
--- /dev/null
+++ b/packages/backend/migration/1727491883993-user-score.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class UserScore1727491883993 {
+    name = 'UserScore1727491883993'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" ADD "score" integer NOT NULL DEFAULT '0'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "score"`);
+    }
+}
diff --git a/packages/backend/migration/1727512908322-meta-federation.js b/packages/backend/migration/1727512908322-meta-federation.js
new file mode 100644
index 000000000000..52c24df4f76a
--- /dev/null
+++ b/packages/backend/migration/1727512908322-meta-federation.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class MetaFederation1727512908322 {
+    name = 'MetaFederation1727512908322'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "federation" character varying(128) NOT NULL DEFAULT 'all'`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "federationHosts" character varying(1024) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federationHosts"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "federation"`);
+    }
+}
diff --git a/packages/backend/migration/1728085812127-refine-abuse-user-report.js b/packages/backend/migration/1728085812127-refine-abuse-user-report.js
new file mode 100644
index 000000000000..57cbfdcf6d32
--- /dev/null
+++ b/packages/backend/migration/1728085812127-refine-abuse-user-report.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class RefineAbuseUserReport1728085812127 {
+    name = 'RefineAbuseUserReport1728085812127'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "moderationNote" character varying(8192) NOT NULL DEFAULT ''`);
+        await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "resolvedAs" character varying(128)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "resolvedAs"`);
+        await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "moderationNote"`);
+    }
+}
diff --git a/packages/backend/migration/1728550878802-testcaptcha.js b/packages/backend/migration/1728550878802-testcaptcha.js
new file mode 100644
index 000000000000..d8d987c0c1c9
--- /dev/null
+++ b/packages/backend/migration/1728550878802-testcaptcha.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Testcaptcha1728550878802 {
+    name = 'Testcaptcha1728550878802'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "meta" ADD "enableTestcaptcha" boolean NOT NULL DEFAULT false`);
+    }
+
+    async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTestcaptcha"`);
+    }
+}
diff --git a/packages/backend/migration/1728634286056-prohibitedWordsForNameOfUser.js b/packages/backend/migration/1728634286056-prohibitedWordsForNameOfUser.js
new file mode 100644
index 000000000000..36e698d12075
--- /dev/null
+++ b/packages/backend/migration/1728634286056-prohibitedWordsForNameOfUser.js
@@ -0,0 +1,14 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class ProhibitedWordsForNameOfUser1728634286056 {
+		async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWordsForNameOfUser" character varying(1024) array NOT NULL DEFAULT '{}'`);
+		}
+
+		async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWordsForNameOfUser"`);
+		}
+}
diff --git a/packages/backend/migration/1729333924409-signinRequiredForShowContents.js b/packages/backend/migration/1729333924409-signinRequiredForShowContents.js
new file mode 100644
index 000000000000..5d4d1fcce2fd
--- /dev/null
+++ b/packages/backend/migration/1729333924409-signinRequiredForShowContents.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SigninRequiredForShowContents1729333924409 {
+    name = 'SigninRequiredForShowContents1729333924409'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" ADD "requireSigninToViewContents" boolean NOT NULL DEFAULT false`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "requireSigninToViewContents"`);
+    }
+}
diff --git a/packages/backend/migration/1729486255072-makeNotesHiddenBefore.js b/packages/backend/migration/1729486255072-makeNotesHiddenBefore.js
new file mode 100644
index 000000000000..5fe4886b0433
--- /dev/null
+++ b/packages/backend/migration/1729486255072-makeNotesHiddenBefore.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class MakeNotesHiddenBefore1729486255072 {
+    name = 'MakeNotesHiddenBefore1729486255072'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesFollowersOnlyBefore" integer`);
+        await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesHiddenBefore" integer`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesHiddenBefore"`);
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesFollowersOnlyBefore"`);
+    }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index f497610af902..0dd738a1e65e 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -19,16 +19,18 @@
 		"watch": "node ./scripts/watch.mjs",
 		"restart": "pnpm build && pnpm start",
 		"dev": "node ./scripts/dev.mjs",
-		"typecheck": "tsc --noEmit && tsc -p test --noEmit",
-		"eslint": "eslint --quiet \"src/**/*.ts\"",
+		"typecheck": "tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit",
+		"eslint": "eslint --quiet \"{src,test-federation}/**/*.ts\"",
 		"lint": "pnpm typecheck && pnpm eslint",
 		"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
 		"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
+		"jest:fed": "node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.fed.cjs",
 		"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
 		"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
 		"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
 		"test": "pnpm jest",
 		"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
+		"test:fed": "pnpm jest:fed",
 		"test-and-coverage": "pnpm jest-and-coverage",
 		"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
 		"generate-api-json": "node ./scripts/generate_api_json.js"
@@ -67,24 +69,24 @@
 	"dependencies": {
 		"@aws-sdk/client-s3": "3.620.0",
 		"@aws-sdk/lib-storage": "3.620.0",
-		"@bull-board/api": "5.21.1",
-		"@bull-board/fastify": "5.21.1",
-		"@bull-board/ui": "5.21.1",
-		"@discordapp/twemoji": "15.0.3",
-		"@fastify/accepts": "4.3.0",
-		"@fastify/cookie": "9.3.1",
-		"@fastify/cors": "9.0.1",
-		"@fastify/express": "3.0.0",
-		"@fastify/http-proxy": "9.5.0",
-		"@fastify/multipart": "8.3.0",
-		"@fastify/static": "7.0.4",
-		"@fastify/view": "9.1.0",
+		"@bull-board/api": "6.0.0",
+		"@bull-board/fastify": "6.0.0",
+		"@bull-board/ui": "6.0.0",
+		"@discordapp/twemoji": "15.1.0",
+		"@fastify/accepts": "5.0.1",
+		"@fastify/cookie": "10.0.1",
+		"@fastify/cors": "10.0.1",
+		"@fastify/express": "4.0.1",
+		"@fastify/http-proxy": "10.0.0",
+		"@fastify/multipart": "9.0.1",
+		"@fastify/static": "8.0.1",
+		"@fastify/view": "10.0.1",
 		"@misskey-dev/sharp-read-bmp": "1.2.0",
 		"@misskey-dev/summaly": "5.1.0",
-		"@napi-rs/canvas": "^0.1.53",
-		"@nestjs/common": "10.3.10",
-		"@nestjs/core": "10.3.10",
-		"@nestjs/testing": "10.3.10",
+		"@napi-rs/canvas": "0.1.56",
+		"@nestjs/common": "10.4.4",
+		"@nestjs/core": "10.4.4",
+		"@nestjs/testing": "10.4.4",
 		"@peertube/http-signature": "1.7.0",
 		"@sentry/node": "8.20.0",
 		"@sentry/profiling-node": "8.20.0",
@@ -100,8 +102,8 @@
 		"async-mutex": "0.5.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "2.0.5",
-		"body-parser": "1.20.2",
-		"bullmq": "5.10.4",
+		"body-parser": "1.20.3",
+		"bullmq": "5.15.0",
 		"cacheable-lookup": "7.0.0",
 		"cbor": "9.0.2",
 		"chalk": "5.3.0",
@@ -112,27 +114,28 @@
 		"content-disposition": "0.5.4",
 		"date-fns": "2.30.0",
 		"deep-email-validator": "0.1.21",
-		"fastify": "4.28.1",
-		"fastify-raw-body": "4.3.0",
+		"fastify": "5.0.0",
+		"fastify-raw-body": "5.0.0",
 		"feed": "4.2.2",
-		"file-type": "19.3.0",
+		"file-type": "19.5.0",
 		"fluent-ffmpeg": "2.1.3",
 		"form-data": "4.0.0",
 		"got": "14.4.2",
-		"happy-dom": "10.0.3",
+		"happy-dom": "15.7.4",
 		"hpagent": "1.2.0",
 		"htmlescape": "1.1.1",
 		"http-link-header": "1.1.3",
 		"ioredis": "5.4.1",
-		"ip-cidr": "4.0.1",
+		"ip-cidr": "4.0.2",
 		"ipaddr.js": "2.2.0",
-		"is-svg": "5.0.1",
+		"is-svg": "5.1.0",
 		"js-yaml": "4.1.0",
 		"jsdom": "24.1.1",
 		"json5": "2.2.3",
 		"jsonld": "8.3.2",
 		"jsrsasign": "11.1.0",
-		"meilisearch": "0.41.0",
+		"meilisearch": "0.42.0",
+		"juice": "11.0.0",
 		"mfm-js": "0.24.0",
 		"microformats-parser": "2.0.2",
 		"mime-types": "2.1.35",
@@ -142,42 +145,42 @@
 		"nanoid": "5.0.7",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.2",
-		"nodemailer": "6.9.14",
+		"nodemailer": "6.9.15",
 		"nsfwjs": "2.4.2",
 		"oauth": "0.10.0",
 		"oauth2orize": "1.12.0",
 		"oauth2orize-pkce": "0.1.2",
 		"os-utils": "0.0.14",
-		"otpauth": "9.3.1",
+		"otpauth": "9.3.4",
 		"parse5": "7.1.2",
-		"pg": "8.12.0",
+		"pg": "8.13.0",
 		"pkce-challenge": "4.1.0",
 		"probe-image-size": "7.2.3",
 		"promise-limit": "2.7.0",
 		"pug": "3.0.3",
 		"punycode": "2.3.1",
-		"qrcode": "1.5.3",
+		"qrcode": "1.5.4",
 		"random-seed": "0.3.0",
 		"ratelimiter": "3.4.1",
-		"re2": "1.21.3",
+		"re2": "1.21.4",
 		"redis-lock": "0.1.4",
 		"reflect-metadata": "0.2.2",
 		"rename": "1.0.4",
 		"rss-parser": "3.13.0",
 		"rxjs": "7.8.1",
-		"sanitize-html": "2.13.0",
+		"sanitize-html": "2.13.1",
 		"secure-json-parse": "2.7.0",
-		"sharp": "0.33.4",
+		"sharp": "0.33.5",
 		"slacc": "0.0.10",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
-		"systeminformation": "5.22.11",
+		"systeminformation": "5.23.5",
 		"tinycolor2": "1.6.0",
 		"tmp": "0.2.3",
 		"tsc-alias": "1.8.10",
 		"tsconfig-paths": "4.2.0",
 		"typeorm": "0.3.20",
-		"typescript": "5.5.4",
+		"typescript": "5.6.2",
 		"ulid": "2.3.0",
 		"vary": "1.1.2",
 		"web-push": "3.6.7",
@@ -186,19 +189,19 @@
 	},
 	"devDependencies": {
 		"@jest/globals": "29.7.0",
-		"@nestjs/platform-express": "10.3.10",
+		"@nestjs/platform-express": "10.4.4",
 		"@simplewebauthn/types": "10.0.0",
 		"@swc/jest": "0.2.36",
 		"@types/accepts": "1.3.7",
 		"@types/archiver": "6.0.2",
 		"@types/bcryptjs": "2.4.6",
 		"@types/body-parser": "1.19.5",
-		"@types/color-convert": "2.0.3",
+		"@types/color-convert": "2.0.4",
 		"@types/content-disposition": "0.5.8",
-		"@types/fluent-ffmpeg": "2.1.24",
+		"@types/fluent-ffmpeg": "2.1.26",
 		"@types/htmlescape": "1.1.3",
 		"@types/http-link-header": "1.0.7",
-		"@types/jest": "29.5.12",
+		"@types/jest": "29.5.13",
 		"@types/js-yaml": "4.0.9",
 		"@types/jsdom": "21.1.7",
 		"@types/jsonld": "1.5.15",
@@ -206,18 +209,18 @@
 		"@types/mime-types": "2.1.4",
 		"@types/ms": "0.7.34",
 		"@types/node": "20.14.12",
-		"@types/nodemailer": "6.4.15",
+		"@types/nodemailer": "6.4.16",
 		"@types/oauth": "0.9.5",
 		"@types/oauth2orize": "1.11.5",
 		"@types/oauth2orize-pkce": "0.1.2",
-		"@types/pg": "8.11.6",
+		"@types/pg": "8.11.10",
 		"@types/pug": "2.0.10",
 		"@types/punycode": "2.1.4",
 		"@types/qrcode": "1.5.5",
 		"@types/random-seed": "0.3.5",
 		"@types/ratelimiter": "3.4.6",
 		"@types/rename": "1.0.7",
-		"@types/sanitize-html": "2.11.0",
+		"@types/sanitize-html": "2.13.0",
 		"@types/semver": "7.5.8",
 		"@types/simple-oauth2": "5.0.7",
 		"@types/sinonjs__fake-timers": "8.1.5",
@@ -225,17 +228,17 @@
 		"@types/tmp": "0.2.6",
 		"@types/vary": "1.1.3",
 		"@types/web-push": "3.6.3",
-		"@types/ws": "8.5.11",
+		"@types/ws": "8.5.12",
 		"@typescript-eslint/eslint-plugin": "7.17.0",
 		"@typescript-eslint/parser": "7.17.0",
 		"aws-sdk-client-mock": "4.0.1",
 		"cross-env": "7.0.3",
-		"eslint-plugin-import": "2.29.1",
-		"execa": "9.3.0",
+		"eslint-plugin-import": "2.30.0",
+		"execa": "9.4.0",
 		"fkill": "9.0.0",
 		"jest": "29.7.0",
 		"jest-mock": "29.7.0",
-		"nodemon": "3.1.4",
+		"nodemon": "3.1.7",
 		"pid-port": "1.0.0",
 		"simple-oauth2": "5.1.0"
 	}
diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js
index ba25fd416c78..bb149444b555 100644
--- a/packages/backend/scripts/check_connect.js
+++ b/packages/backend/scripts/check_connect.js
@@ -5,11 +5,52 @@
 
 import Redis from 'ioredis';
 import { loadConfig } from '../built/config.js';
+import { createPostgresDataSource } from '../built/postgres.js';
 
 const config = loadConfig();
-const redis = new Redis(config.redis);
 
-redis.on('connect', () => redis.disconnect());
-redis.on('error', (e) => {
-	throw e;
-});
+async function connectToPostgres() {
+	const source = createPostgresDataSource(config);
+	await source.initialize();
+	await source.destroy();
+}
+
+async function connectToRedis(redisOptions) {
+	return await new Promise(async (resolve, reject) => {
+		const redis = new Redis({
+			...redisOptions,
+			lazyConnect: true,
+			reconnectOnError: false,
+			showFriendlyErrorStack: true,
+		});
+		redis.on('error', e => reject(e));
+
+		try {
+			await redis.connect();
+			resolve();
+
+		} catch (e) {
+			reject(e);
+
+		} finally {
+			redis.disconnect(false);
+		}
+	});
+}
+
+// If not all of these are defined, the default one gets reused.
+// so we use a Set to only try connecting once to each **uniq** redis.
+const promises = Array
+	.from(new Set([
+		config.redis,
+		config.redisForPubsub,
+		config.redisForJobQueue,
+		config.redisForTimelines,
+		config.redisForReactions,
+	]))
+	.map(connectToRedis)
+	.concat([
+		connectToPostgres()
+	]);
+
+await Promise.allSettled(promises);
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 09971e8ca022..6ae8ccfbb32c 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -13,6 +13,8 @@ import { createPostgresDataSource } from './postgres.js';
 import { RepositoryModule } from './models/RepositoryModule.js';
 import { allSettled } from './misc/promise-tracker.js';
 import type { Provider, OnApplicationShutdown } from '@nestjs/common';
+import { MiMeta } from '@/models/Meta.js';
+import { GlobalEvents } from './core/GlobalEventService.js';
 
 const $config: Provider = {
 	provide: DI.config,
@@ -78,11 +80,76 @@ const $redisForTimelines: Provider = {
 	inject: [DI.config],
 };
 
+const $redisForReactions: Provider = {
+	provide: DI.redisForReactions,
+	useFactory: (config: Config) => {
+		return new Redis.Redis(config.redisForReactions);
+	},
+	inject: [DI.config],
+};
+
+const $meta: Provider = {
+	provide: DI.meta,
+	useFactory: async (db: DataSource, redisForSub: Redis.Redis) => {
+		const meta = await db.transaction(async transactionalEntityManager => {
+			// 過去のバグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
+			const metas = await transactionalEntityManager.find(MiMeta, {
+				order: {
+					id: 'DESC',
+				},
+			});
+
+			const meta = metas[0];
+
+			if (meta) {
+				return meta;
+			} else {
+				// metaが空のときfetchMetaが同時に呼ばれるとここが同時に呼ばれてしまうことがあるのでフェイルセーフなupsertを使う
+				const saved = await transactionalEntityManager
+					.upsert(
+						MiMeta,
+						{
+							id: 'x',
+						},
+						['id'],
+					)
+					.then((x) => transactionalEntityManager.findOneByOrFail(MiMeta, x.identifiers[0]));
+
+				return saved;
+			}
+		});
+
+		async function onMessage(_: string, data: string): Promise<void> {
+			const obj = JSON.parse(data);
+
+			if (obj.channel === 'internal') {
+				const { type, body } = obj.message as GlobalEvents['internal']['payload'];
+				switch (type) {
+					case 'metaUpdated': {
+						for (const key in body.after) {
+							(meta as any)[key] = (body.after as any)[key];
+						}
+						meta.proxyAccount = null; // joinなカラムは通常取ってこないので
+						break;
+					}
+					default:
+						break;
+				}
+			}
+		}
+
+		redisForSub.on('message', onMessage);
+
+		return meta;
+	},
+	inject: [DI.db, DI.redisForSub],
+};
+
 @Global()
 @Module({
 	imports: [RepositoryModule],
-	providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
-	exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
+	providers: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions],
+	exports: [$config, $db, $meta, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, $redisForReactions, RepositoryModule],
 })
 export class GlobalModule implements OnApplicationShutdown {
 	constructor(
@@ -91,6 +158,7 @@ export class GlobalModule implements OnApplicationShutdown {
 		@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
 		@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
 		@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
+		@Inject(DI.redisForReactions) private redisForReactions: Redis.Redis,
 	) { }
 
 	public async dispose(): Promise<void> {
@@ -103,6 +171,7 @@ export class GlobalModule implements OnApplicationShutdown {
 			this.redisForPub.disconnect(),
 			this.redisForSub.disconnect(),
 			this.redisForTimelines.disconnect(),
+			this.redisForReactions.disconnect(),
 		]);
 	}
 
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index cbd6d1c086dc..42f1033b9d0a 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -49,6 +49,7 @@ type Source = {
 	redisForPubsub?: RedisOptionsSource;
 	redisForJobQueue?: RedisOptionsSource;
 	redisForTimelines?: RedisOptionsSource;
+	redisForReactions?: RedisOptionsSource;
 	meilisearch?: {
 		host: string;
 		port: string;
@@ -62,6 +63,8 @@ type Source = {
 
 	publishTarballInsteadOfProvideRepositoryUrl?: boolean;
 
+	setupPassword?: string;
+
 	proxy?: string;
 	proxySmtp?: string;
 	proxyBypassHosts?: string[];
@@ -151,6 +154,7 @@ export type Config = {
 
 	version: string;
 	publishTarballInsteadOfProvideRepositoryUrl: boolean;
+	setupPassword: string | undefined;
 	host: string;
 	hostname: string;
 	scheme: string;
@@ -171,6 +175,7 @@ export type Config = {
 	redisForPubsub: RedisOptions & RedisOptionsSource;
 	redisForJobQueue: RedisOptions & RedisOptionsSource;
 	redisForTimelines: RedisOptions & RedisOptionsSource;
+	redisForReactions: RedisOptions & RedisOptionsSource;
 	sentryForBackend: { options: Partial<Sentry.NodeOptions>; enableNodeProfiling: boolean; } | undefined;
 	sentryForFrontend: { options: Partial<Sentry.NodeOptions> } | undefined;
 	perChannelMaxNoteCacheCount: number;
@@ -230,6 +235,7 @@ export function loadConfig(): Config {
 	return {
 		version,
 		publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
+		setupPassword: config.setupPassword,
 		url: url.origin,
 		port: config.port ?? parseInt(process.env.PORT ?? '', 10),
 		socket: config.socket,
@@ -251,6 +257,7 @@ export function loadConfig(): Config {
 		redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
 		redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
 		redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
+		redisForReactions: config.redisForReactions ? convertRedisOptions(config.redisForReactions, host) : redis,
 		sentryForBackend: config.sentryForBackend,
 		sentryForFrontend: config.sentryForFrontend,
 		id: config.id,
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index a238f4973a95..e3a61861f425 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -8,6 +8,8 @@ export const MAX_NOTE_TEXT_LENGTH = 3000;
 export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
 export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
 
+export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
+
 //#region hard limits
 // If you change DB_* values, you must also change the DB schema.
 
diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts
index 7be533588564..25e265f2b127 100644
--- a/packages/backend/src/core/AbuseReportNotificationService.ts
+++ b/packages/backend/src/core/AbuseReportNotificationService.ts
@@ -14,30 +14,36 @@ import type {
 	AbuseReportNotificationRecipientRepository,
 	MiAbuseReportNotificationRecipient,
 	MiAbuseUserReport,
+	MiMeta,
 	MiUser,
 } from '@/models/_.js';
 import { EmailService } from '@/core/EmailService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { RoleService } from '@/core/RoleService.js';
 import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
 import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { IdService } from './IdService.js';
 
 @Injectable()
 export class AbuseReportNotificationService implements OnApplicationShutdown {
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.abuseReportNotificationRecipientRepository)
 		private abuseReportNotificationRecipientRepository: AbuseReportNotificationRecipientRepository,
+
 		@Inject(DI.redisForSub)
 		private redisForSub: Redis.Redis,
+
 		private idService: IdService,
 		private roleService: RoleService,
 		private systemWebhookService: SystemWebhookService,
 		private emailService: EmailService,
-		private metaService: MetaService,
 		private moderationLogService: ModerationLogService,
 		private globalEventService: GlobalEventService,
+		private userEntityService: UserEntityService,
 	) {
 		this.redisForSub.on('message', this.onMessage);
 	}
@@ -55,7 +61,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 			return;
 		}
 
-		const moderatorIds = await this.roleService.getModeratorIds(true, true);
+		const moderatorIds = await this.roleService.getModeratorIds({
+			includeAdmins: true,
+			excludeExpire: true,
+		});
 
 		for (const moderatorId of moderatorIds) {
 			for (const abuseReport of abuseReports) {
@@ -93,10 +102,8 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 			.filter(x => x != null),
 		);
 
-		// 送信先の鮮度を保つため、毎回取得する
-		const meta = await this.metaService.fetch(true);
 		recipientEMailAddresses.push(
-			...(meta.email ? [meta.email] : []),
+			...(this.meta.email ? [this.meta.email] : []),
 		);
 
 		if (recipientEMailAddresses.length <= 0) {
@@ -133,6 +140,26 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 			return;
 		}
 
+		const usersMap = await this.userEntityService.packMany(
+			[
+				...new Set([
+					...abuseReports.map(it => it.reporter ?? it.reporterId),
+					...abuseReports.map(it => it.targetUser ?? it.targetUserId),
+					...abuseReports.map(it => it.assignee ?? it.assigneeId),
+				].filter(x => x != null)),
+			],
+			null,
+			{ schema: 'UserLite' },
+		).then(it => new Map(it.map(it => [it.id, it])));
+		const convertedReports = abuseReports.map(it => {
+			return {
+				...it,
+				reporter: usersMap.get(it.reporterId),
+				targetUser: usersMap.get(it.targetUserId),
+				assignee: it.assigneeId ? usersMap.get(it.assigneeId) : null,
+			};
+		});
+
 		const recipientWebhookIds = await this.fetchWebhookRecipients()
 			.then(it => it
 				.filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook')
@@ -140,7 +167,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 				.filter(x => x != null));
 		for (const webhookId of recipientWebhookIds) {
 			await Promise.all(
-				abuseReports.map(it => {
+				convertedReports.map(it => {
 					return this.systemWebhookService.enqueueSystemWebhook(
 						webhookId,
 						type,
@@ -261,8 +288,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 			.log(updater, 'createAbuseReportNotificationRecipient', {
 				recipientId: id,
 				recipient: created,
-			})
-			.then();
+			});
 
 		return created;
 	}
@@ -300,8 +326,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 				recipientId: params.id,
 				before: beforeEntity,
 				after: afterEntity,
-			})
-			.then();
+			});
 
 		return afterEntity;
 	}
@@ -322,8 +347,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 			.log(updater, 'deleteAbuseReportNotificationRecipient', {
 				recipientId: id,
 				recipient: entity,
-			})
-			.then();
+			});
 	}
 
 	/**
@@ -346,7 +370,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
 		}
 
 		// モデレータ権限の有無で通知先設定を振り分ける
-		const authorizedUserIds = await this.roleService.getModeratorIds(true, true);
+		const authorizedUserIds = await this.roleService.getModeratorIds({
+			includeAdmins: true,
+			excludeExpire: true,
+		});
 		const authorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>();
 		const unauthorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>();
 		for (const recipient of userRecipients) {
diff --git a/packages/backend/src/core/AbuseReportService.ts b/packages/backend/src/core/AbuseReportService.ts
index 69c51509ba40..0b022d3b0880 100644
--- a/packages/backend/src/core/AbuseReportService.ts
+++ b/packages/backend/src/core/AbuseReportService.ts
@@ -20,8 +20,10 @@ export class AbuseReportService {
 	constructor(
 		@Inject(DI.abuseUserReportsRepository)
 		private abuseUserReportsRepository: AbuseUserReportsRepository,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
+
 		private idService: IdService,
 		private abuseReportNotificationService: AbuseReportNotificationService,
 		private queueService: QueueService,
@@ -77,16 +79,16 @@ export class AbuseReportService {
 	 * - SystemWebhook
 	 *
 	 * @param params 通報内容. もし複数件の通報に対応した時のために、あらかじめ複数件を処理できる前提で考える
-	 * @param operator 通報を処理したユーザ
+	 * @param moderator 通報を処理したユーザ
 	 * @see AbuseReportNotificationService.notify
 	 */
 	@bindThis
 	public async resolve(
 		params: {
 			reportId: string;
-			forward: boolean;
+			resolvedAs: MiAbuseUserReport['resolvedAs'];
 		}[],
-		operator: MiUser,
+		moderator: MiUser,
 	) {
 		const paramsMap = new Map(params.map(it => [it.reportId, it]));
 		const reports = await this.abuseUserReportsRepository.findBy({
@@ -99,30 +101,76 @@ export class AbuseReportService {
 
 			await this.abuseUserReportsRepository.update(report.id, {
 				resolved: true,
-				assigneeId: operator.id,
-				forwarded: ps.forward && report.targetUserHost !== null,
+				assigneeId: moderator.id,
+				resolvedAs: ps.resolvedAs,
 			});
 
-			if (ps.forward && report.targetUserHost != null) {
-				const actor = await this.instanceActorService.getInstanceActor();
-				const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
-
-				// eslint-disable-next-line
-				const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
-				const contextAssignedFlag = this.apRendererService.addContext(flag);
-				this.queueService.deliver(actor, contextAssignedFlag, targetUser.inbox, false);
-			}
-
 			this.moderationLogService
-				.log(operator, 'resolveAbuseReport', {
+				.log(moderator, 'resolveAbuseReport', {
 					reportId: report.id,
 					report: report,
-					forwarded: ps.forward && report.targetUserHost !== null,
-				})
-				.then();
+					resolvedAs: ps.resolvedAs,
+				});
 		}
 
 		return this.abuseUserReportsRepository.findBy({ id: In(reports.map(it => it.id)) })
 			.then(reports => this.abuseReportNotificationService.notifySystemWebhook(reports, 'abuseReportResolved'));
 	}
+
+	@bindThis
+	public async forward(
+		reportId: MiAbuseUserReport['id'],
+		moderator: MiUser,
+	) {
+		const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId });
+
+		if (report.targetUserHost == null) {
+			throw new Error('The target user host is null.');
+		}
+
+		if (report.forwarded) {
+			throw new Error('The report has already been forwarded.');
+		}
+
+		await this.abuseUserReportsRepository.update(report.id, {
+			forwarded: true,
+		});
+
+		const actor = await this.instanceActorService.getInstanceActor();
+		const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
+
+		const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment);
+		const contextAssignedFlag = this.apRendererService.addContext(flag);
+		this.queueService.deliver(actor, contextAssignedFlag, targetUser.inbox, false);
+
+		this.moderationLogService
+			.log(moderator, 'forwardAbuseReport', {
+				reportId: report.id,
+				report: report,
+			});
+	}
+
+	@bindThis
+	public async update(
+		reportId: MiAbuseUserReport['id'],
+		params: {
+			moderationNote?: MiAbuseUserReport['moderationNote'];
+		},
+		moderator: MiUser,
+	) {
+		const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId });
+
+		await this.abuseUserReportsRepository.update(report.id, {
+			moderationNote: params.moderationNote,
+		});
+
+		if (params.moderationNote != null && report.moderationNote !== params.moderationNote) {
+			this.moderationLogService.log(moderator, 'updateAbuseReportNote', {
+				reportId: report.id,
+				report: report,
+				before: report.moderationNote,
+				after: params.moderationNote,
+			});
+		}
+	}
 }
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index b6b591d24036..24d11f29ff82 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
 import { bindThis } from '@/decorators.js';
 import { DI } from '@/di-symbols.js';
 import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
-import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
+import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MiMeta, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
 import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
 
 import { IdService } from '@/core/IdService.js';
@@ -22,13 +22,15 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ProxyAccountService } from '@/core/ProxyAccountService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
-import { MetaService } from '@/core/MetaService.js';
 import InstanceChart from '@/core/chart/charts/instance.js';
 import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
 
 @Injectable()
 export class AccountMoveService {
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -57,7 +59,6 @@ export class AccountMoveService {
 		private perUserFollowingChart: PerUserFollowingChart,
 		private federatedInstanceService: FederatedInstanceService,
 		private instanceChart: InstanceChart,
-		private metaService: MetaService,
 		private relayService: RelayService,
 		private queueService: QueueService,
 	) {
@@ -273,13 +274,15 @@ export class AccountMoveService {
 		}
 
 		// Update instance stats by decreasing remote followers count by the number of local followers who were following the old account.
-		if (this.userEntityService.isRemoteUser(oldAccount)) {
-			this.federatedInstanceService.fetch(oldAccount.host).then(async i => {
-				this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-					this.instanceChart.updateFollowers(i.host, false);
-				}
-			});
+		if (this.meta.enableStatsForFederatedInstances) {
+			if (this.userEntityService.isRemoteUser(oldAccount)) {
+				this.federatedInstanceService.fetchOrRegister(oldAccount.host).then(async i => {
+					this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
+					if (this.meta.enableChartsForFederatedInstances) {
+						this.instanceChart.updateFollowers(i.host, false);
+					}
+				});
+			}
 		}
 
 		// FIXME: expensive?
diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts
index 40a9db01c057..d4fcf194391d 100644
--- a/packages/backend/src/core/AnnouncementService.ts
+++ b/packages/backend/src/core/AnnouncementService.ts
@@ -209,6 +209,13 @@ export class AnnouncementService {
 			return;
 		}
 
+		const announcement = await this.announcementsRepository.findOneBy({ id: announcementId });
+		if (announcement != null && announcement.userId === user.id) {
+			await this.announcementsRepository.update(announcementId, {
+				isActive: false,
+			});
+		}
+
 		if ((await this.getUnreadAnnouncements(user)).length === 0) {
 			this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements');
 		}
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 793d8974b34b..e827ffa68c0f 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -123,11 +123,14 @@ export class AntennaService implements OnApplicationShutdown {
 		if (antenna.src === 'home') {
 			// TODO
 		} else if (antenna.src === 'list') {
-			const listUsers = (await this.userListMembershipsRepository.findBy({
-				userListId: antenna.userListId!,
-			})).map(x => x.userId);
-
-			if (!listUsers.includes(note.userId)) return false;
+			if (antenna.userListId == null) return false;
+			const exists = await this.userListMembershipsRepository.exists({
+				where: {
+					userListId: antenna.userListId,
+					userId: note.userId,
+				},
+			});
+			if (!exists) return false;
 		} else if (antenna.src === 'users') {
 			const accts = antenna.users.map(x => {
 				const { username, host } = Acct.parse(x);
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index f6b7955cd207..206d0dbe0aaf 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -119,5 +119,18 @@ export class CaptchaService {
 			throw new Error(`turnstile-failed: ${errorCodes}`);
 		}
 	}
+
+	@bindThis
+	public async verifyTestcaptcha(response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw new Error('testcaptcha-failed: no response provided');
+		}
+
+		const success = response === 'testcaptcha-passed';
+
+		if (!success) {
+			throw new Error('testcaptcha-failed');
+		}
+	}
 }
 
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index c9427bbeb7bd..734d135648d5 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -13,6 +13,8 @@ import {
 import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
 import { SystemWebhookService } from '@/core/SystemWebhookService.js';
 import { UserSearchService } from '@/core/UserSearchService.js';
+import { WebhookTestService } from '@/core/WebhookTestService.js';
+import { FlashService } from '@/core/FlashService.js';
 import { AccountMoveService } from './AccountMoveService.js';
 import { AccountUpdateService } from './AccountUpdateService.js';
 import { AiService } from './AiService.js';
@@ -49,6 +51,7 @@ import { PollService } from './PollService.js';
 import { PushNotificationService } from './PushNotificationService.js';
 import { QueryService } from './QueryService.js';
 import { ReactionService } from './ReactionService.js';
+import { ReactionsBufferingService } from './ReactionsBufferingService.js';
 import { RelayService } from './RelayService.js';
 import { RoleService } from './RoleService.js';
 import { S3Service } from './S3Service.js';
@@ -192,6 +195,7 @@ const $ProxyAccountService: Provider = { provide: 'ProxyAccountService', useExis
 const $PushNotificationService: Provider = { provide: 'PushNotificationService', useExisting: PushNotificationService };
 const $QueryService: Provider = { provide: 'QueryService', useExisting: QueryService };
 const $ReactionService: Provider = { provide: 'ReactionService', useExisting: ReactionService };
+const $ReactionsBufferingService: Provider = { provide: 'ReactionsBufferingService', useExisting: ReactionsBufferingService };
 const $RelayService: Provider = { provide: 'RelayService', useExisting: RelayService };
 const $RoleService: Provider = { provide: 'RoleService', useExisting: RoleService };
 const $S3Service: Provider = { provide: 'S3Service', useExisting: S3Service };
@@ -211,8 +215,10 @@ const $UserAuthService: Provider = { provide: 'UserAuthService', useExisting: Us
 const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', useExisting: VideoProcessingService };
 const $UserWebhookService: Provider = { provide: 'UserWebhookService', useExisting: UserWebhookService };
 const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useExisting: SystemWebhookService };
+const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService };
 const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
 const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
+const $FlashService: Provider = { provide: 'FlashService', useExisting: FlashService };
 const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
 const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
 const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
@@ -340,6 +346,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PushNotificationService,
 		QueryService,
 		ReactionService,
+		ReactionsBufferingService,
 		RelayService,
 		RoleService,
 		S3Service,
@@ -359,8 +366,10 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		VideoProcessingService,
 		UserWebhookService,
 		SystemWebhookService,
+		WebhookTestService,
 		UtilityService,
 		FileInfoService,
+		FlashService,
 		SearchService,
 		ClipService,
 		FeaturedService,
@@ -484,6 +493,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PushNotificationService,
 		$QueryService,
 		$ReactionService,
+		$ReactionsBufferingService,
 		$RelayService,
 		$RoleService,
 		$S3Service,
@@ -503,8 +513,10 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$VideoProcessingService,
 		$UserWebhookService,
 		$SystemWebhookService,
+		$WebhookTestService,
 		$UtilityService,
 		$FileInfoService,
+		$FlashService,
 		$SearchService,
 		$ClipService,
 		$FeaturedService,
@@ -629,6 +641,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PushNotificationService,
 		QueryService,
 		ReactionService,
+		ReactionsBufferingService,
 		RelayService,
 		RoleService,
 		S3Service,
@@ -648,8 +661,10 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		VideoProcessingService,
 		UserWebhookService,
 		SystemWebhookService,
+		WebhookTestService,
 		UtilityService,
 		FileInfoService,
+		FlashService,
 		SearchService,
 		ClipService,
 		FeaturedService,
@@ -772,6 +787,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PushNotificationService,
 		$QueryService,
 		$ReactionService,
+		$ReactionsBufferingService,
 		$RelayService,
 		$RoleService,
 		$S3Service,
@@ -791,6 +807,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$VideoProcessingService,
 		$UserWebhookService,
 		$SystemWebhookService,
+		$WebhookTestService,
 		$UtilityService,
 		$FileInfoService,
 		$SearchService,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 5db3c5b98035..45661134491e 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -103,19 +103,33 @@ export class CustomEmojiService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public async update(id: MiEmoji['id'], data: {
+	public async update(data: (
+		{ id: MiEmoji['id'], name?: string; } | { name: string; id?: MiEmoji['id'], }
+	) & {
 		driveFile?: MiDriveFile;
-		name?: string;
 		category?: string | null;
 		aliases?: string[];
 		license?: string | null;
 		isSensitive?: boolean;
 		localOnly?: boolean;
 		roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][];
-	}, moderator?: MiUser): Promise<void> {
-		const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
-		const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
-		if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists');
+	}, moderator?: MiUser): Promise<
+		null
+		| 'NO_SUCH_EMOJI'
+		| 'SAME_NAME_EMOJI_EXISTS'
+	> {
+		const emoji = data.id
+			? await this.getEmojiById(data.id)
+			: await this.getEmojiByName(data.name!);
+		if (emoji === null) return 'NO_SUCH_EMOJI';
+		const id = emoji.id;
+
+		// IDと絵文字名が両方指定されている場合は絵文字名の変更を行うため重複チェックが必要
+		const doNameUpdate = data.id && data.name && (data.name !== emoji.name);
+		if (doNameUpdate) {
+			const isDuplicate = await this.checkDuplicate(data.name!);
+			if (isDuplicate) return 'SAME_NAME_EMOJI_EXISTS';
+		}
 
 		await this.emojisRepository.update(emoji.id, {
 			updatedAt: new Date(),
@@ -135,7 +149,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 
 		const packed = await this.emojiEntityService.packDetailed(emoji.id);
 
-		if (emoji.name === data.name) {
+		if (!doNameUpdate) {
 			this.globalEventService.publishBroadcastStream('emojiUpdated', {
 				emojis: [packed],
 			});
@@ -157,6 +171,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 				after: updated,
 			});
 		}
+		return null;
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index 8aa04b4da733..c332e5a0a8f3 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -11,11 +11,10 @@ import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
 import { IsNull } from 'typeorm';
 import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
 import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/_.js';
+import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
 import type { Config } from '@/config.js';
 import Logger from '@/logger.js';
 import type { MiRemoteUser, MiUser } from '@/models/User.js';
-import { MetaService } from '@/core/MetaService.js';
 import { MiDriveFile } from '@/models/DriveFile.js';
 import { IdService } from '@/core/IdService.js';
 import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
@@ -99,6 +98,9 @@ export class DriveService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -115,7 +117,6 @@ export class DriveService {
 		private userEntityService: UserEntityService,
 		private driveFileEntityService: DriveFileEntityService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private downloadService: DownloadService,
 		private internalStorageService: InternalStorageService,
 		private s3Service: S3Service,
@@ -149,9 +150,7 @@ export class DriveService {
 	// thunbnail, webpublic を必要なら生成
 		const alts = await this.generateAlts(path, type, !file.uri);
 
-		const meta = await this.metaService.fetch();
-
-		if (meta.useObjectStorage) {
+		if (this.meta.useObjectStorage) {
 		//#region ObjectStorage params
 			let [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) ?? ['']);
 
@@ -170,11 +169,11 @@ export class DriveService {
 				ext = '';
 			}
 
-			const baseUrl = meta.objectStorageBaseUrl
-				?? `${ meta.objectStorageUseSSL ? 'https' : 'http' }://${ meta.objectStorageEndpoint }${ meta.objectStoragePort ? `:${meta.objectStoragePort}` : '' }/${ meta.objectStorageBucket }`;
+			const baseUrl = this.meta.objectStorageBaseUrl
+				?? `${ this.meta.objectStorageUseSSL ? 'https' : 'http' }://${ this.meta.objectStorageEndpoint }${ this.meta.objectStoragePort ? `:${this.meta.objectStoragePort}` : '' }/${ this.meta.objectStorageBucket }`;
 
 			// for original
-			const key = `${meta.objectStoragePrefix}/${randomUUID()}${ext}`;
+			const key = `${this.meta.objectStoragePrefix}/${randomUUID()}${ext}`;
 			const url = `${ baseUrl }/${ key }`;
 
 			// for alts
@@ -191,7 +190,7 @@ export class DriveService {
 			];
 
 			if (alts.webpublic) {
-				webpublicKey = `${meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
+				webpublicKey = `${this.meta.objectStoragePrefix}/webpublic-${randomUUID()}.${alts.webpublic.ext}`;
 				webpublicUrl = `${ baseUrl }/${ webpublicKey }`;
 
 				this.registerLogger.info(`uploading webpublic: ${webpublicKey}`);
@@ -199,7 +198,7 @@ export class DriveService {
 			}
 
 			if (alts.thumbnail) {
-				thumbnailKey = `${meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
+				thumbnailKey = `${this.meta.objectStoragePrefix}/thumbnail-${randomUUID()}.${alts.thumbnail.ext}`;
 				thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
 
 				this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
@@ -376,10 +375,8 @@ export class DriveService {
 		if (type === 'image/apng') type = 'image/png';
 		if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
 
-		const meta = await this.metaService.fetch();
-
 		const params = {
-			Bucket: meta.objectStorageBucket,
+			Bucket: this.meta.objectStorageBucket,
 			Key: key,
 			Body: stream,
 			ContentType: type,
@@ -392,9 +389,9 @@ export class DriveService {
 			// 許可されているファイル形式でしか拡張子をつけない
 			ext ? correctFilename(filename, ext) : filename,
 		);
-		if (meta.objectStorageSetPublicRead) params.ACL = 'public-read';
+		if (this.meta.objectStorageSetPublicRead) params.ACL = 'public-read';
 
-		await this.s3Service.upload(meta, params)
+		await this.s3Service.upload(this.meta, params)
 			.then(
 				result => {
 					if ('Bucket' in result) { // CompleteMultipartUploadCommandOutput
@@ -460,32 +457,31 @@ export class DriveService {
 		ext = null,
 	}: AddFileArgs): Promise<MiDriveFile> {
 		let skipNsfwCheck = false;
-		const instance = await this.metaService.fetch();
 		const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
 		if (user == null) {
 			skipNsfwCheck = true;
 		} else if (userRoleNSFW) {
 			skipNsfwCheck = true;
 		}
-		if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true;
-		if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true;
-		if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true;
+		if (this.meta.sensitiveMediaDetection === 'none') skipNsfwCheck = true;
+		if (user && this.meta.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true;
+		if (user && this.meta.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true;
 
 		const info = await this.fileInfoService.getFileInfo(path, {
 			skipSensitiveDetection: skipNsfwCheck,
 			sensitiveThreshold: // 感度が高いほどしきい値は低くすることになる
-			instance.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 :
-			instance.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 :
-			instance.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 :
-			instance.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 :
+			this.meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 0.1 :
+			this.meta.sensitiveMediaDetectionSensitivity === 'high' ? 0.3 :
+			this.meta.sensitiveMediaDetectionSensitivity === 'low' ? 0.7 :
+			this.meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0.9 :
 			0.5,
 			sensitiveThresholdForPorn: 0.75,
-			enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
+			enableSensitiveMediaDetectionForVideos: this.meta.enableSensitiveMediaDetectionForVideos,
 		});
 		this.registerLogger.info(`${JSON.stringify(info)}`);
 
 		// 現状 false positive が多すぎて実用に耐えない
-		//if (info.porn && instance.disallowUploadWhenPredictedAsPorn) {
+		//if (info.porn && this.meta.disallowUploadWhenPredictedAsPorn) {
 		//	throw new IdentifiableError('282f77bf-5816-4f72-9264-aa14d8261a21', 'Detected as porn.');
 		//}
 
@@ -589,9 +585,9 @@ export class DriveService {
 			sensitive ?? false
 			: false;
 
-		if (user && this.utilityService.isMediaSilencedHost(instance.mediaSilencedHosts, user.host)) file.isSensitive = true;
+		if (user && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) file.isSensitive = true;
 		if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;
-		if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true;
+		if (info.sensitive && this.meta.setSensitiveFlagAutomatically) file.isSensitive = true;
 		if (userRoleNSFW) file.isSensitive = true;
 
 		if (url !== null) {
@@ -652,7 +648,7 @@ export class DriveService {
 			// ローカルユーザーのみ
 			this.perUserDriveChart.update(file, true);
 		} else {
-			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+			if (this.meta.enableChartsForFederatedInstances) {
 				this.instanceChart.updateDrive(file, true);
 			}
 		}
@@ -798,7 +794,7 @@ export class DriveService {
 			// ローカルユーザーのみ
 			this.perUserDriveChart.update(file, false);
 		} else {
-			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+			if (this.meta.enableChartsForFederatedInstances) {
 				this.instanceChart.updateDrive(file, false);
 			}
 		}
@@ -820,14 +816,13 @@ export class DriveService {
 
 	@bindThis
 	public async deleteObjectStorageFile(key: string) {
-		const meta = await this.metaService.fetch();
 		try {
 			const param = {
-				Bucket: meta.objectStorageBucket,
+				Bucket: this.meta.objectStorageBucket,
 				Key: key,
 			} as DeleteObjectCommandInput;
 
-			await this.s3Service.delete(meta, param);
+			await this.s3Service.delete(this.meta, param);
 		} catch (err: any) {
 			if (err.name === 'NoSuchKey') {
 				this.deleteLogger.warn(`The object storage had no such key to delete: ${key}. Skipping this.`, err as Error);
diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 435dbbae28a9..a176474b953f 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -5,18 +5,17 @@
 
 import { URLSearchParams } from 'node:url';
 import * as nodemailer from 'nodemailer';
+import juice from 'juice';
 import { Inject, Injectable } from '@nestjs/common';
 import { validate as validateEmail } from 'deep-email-validator';
-import { MetaService } from '@/core/MetaService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
-import type { UserProfilesRepository } from '@/models/_.js';
+import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
 import { LoggerService } from '@/core/LoggerService.js';
 import { bindThis } from '@/decorators.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
-import { QueueService } from '@/core/QueueService.js';
 
 @Injectable()
 export class EmailService {
@@ -26,49 +25,41 @@ export class EmailService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
-		private metaService: MetaService,
 		private loggerService: LoggerService,
 		private utilityService: UtilityService,
 		private httpRequestService: HttpRequestService,
-		private queueService: QueueService,
 	) {
 		this.logger = this.loggerService.getLogger('email');
 	}
 
 	@bindThis
 	public async sendEmail(to: string, subject: string, html: string, text: string) {
-		const meta = await this.metaService.fetch(true);
-
-		if (!meta.enableEmail) return;
+		if (!this.meta.enableEmail) return;
 
 		const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
 		const emailSettingUrl = `${this.config.url}/settings/email`;
 
-		const enableAuth = meta.smtpUser != null && meta.smtpUser !== '';
+		const enableAuth = this.meta.smtpUser != null && this.meta.smtpUser !== '';
 
 		const transporter = nodemailer.createTransport({
-			host: meta.smtpHost,
-			port: meta.smtpPort,
-			secure: meta.smtpSecure,
+			host: this.meta.smtpHost,
+			port: this.meta.smtpPort,
+			secure: this.meta.smtpSecure,
 			ignoreTLS: !enableAuth,
 			proxy: this.config.proxySmtp,
 			auth: enableAuth ? {
-				user: meta.smtpUser,
-				pass: meta.smtpPass,
+				user: this.meta.smtpUser,
+				pass: this.meta.smtpPass,
 			} : undefined,
 		} as any);
 
-		try {
-			// TODO: htmlサニタイズ
-			const info = await transporter.sendMail({
-				from: meta.email!,
-				to: to,
-				subject: subject,
-				text: text,
-				html: `<!doctype html>
+		const htmlContent = `<!doctype html>
 <html>
 	<head>
 		<meta charset="utf-8">
@@ -133,7 +124,7 @@ export class EmailService {
 	<body>
 		<main>
 			<header>
-				<img src="${ meta.logoImageUrl ?? meta.iconUrl ?? iconUrl }"/>
+				<img src="${ this.meta.logoImageUrl ?? this.meta.iconUrl ?? iconUrl }"/>
 			</header>
 			<article>
 				<h1>${ subject }</h1>
@@ -147,7 +138,18 @@ export class EmailService {
 			<a href="${ this.config.url }">${ this.config.host }</a>
 		</nav>
 	</body>
-</html>`,
+</html>`;
+
+		const inlinedHtml = juice(htmlContent);
+
+		try {
+			// TODO: htmlサニタイズ
+			const info = await transporter.sendMail({
+				from: this.meta.email!,
+				to: to,
+				subject: subject,
+				text: text,
+				html: inlinedHtml,
 			});
 
 			this.logger.info(`Message sent: ${info.messageId}`);
@@ -162,8 +164,6 @@ export class EmailService {
 		available: boolean;
 		reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
 	}> {
-		const meta = await this.metaService.fetch();
-
 		const exist = await this.userProfilesRepository.countBy({
 			emailVerified: true,
 			email: emailAddress,
@@ -181,11 +181,11 @@ export class EmailService {
 			reason?: string | null,
 		} = { valid: true, reason: null };
 
-		if (meta.enableActiveEmailValidation) {
-			if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
-				validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
-			} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) {
-				validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey);
+		if (this.meta.enableActiveEmailValidation) {
+			if (this.meta.enableVerifymailApi && this.meta.verifymailAuthKey != null) {
+				validated = await this.verifyMail(emailAddress, this.meta.verifymailAuthKey);
+			} else if (this.meta.enableTruemailApi && this.meta.truemailInstance && this.meta.truemailAuthKey != null) {
+				validated = await this.trueMail(this.meta.truemailInstance, emailAddress, this.meta.truemailAuthKey);
 			} else {
 				validated = await validateEmail({
 					email: emailAddress,
@@ -215,7 +215,7 @@ export class EmailService {
 		}
 
 		const emailDomain: string = emailAddress.split('@')[1];
-		const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
+		const isBanned = this.utilityService.isBlockedHost(this.meta.bannedEmailDomains, emailDomain);
 
 		if (isBanned) {
 			return {
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index 7aeeb781786c..73bbf03b26d1 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -47,7 +47,7 @@ export class FederatedInstanceService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public async fetch(host: string): Promise<MiInstance> {
+	public async fetchOrRegister(host: string): Promise<MiInstance> {
 		host = this.utilityService.toPuny(host);
 
 		const cached = await this.federatedInstanceCache.get(host);
@@ -70,6 +70,24 @@ export class FederatedInstanceService implements OnApplicationShutdown {
 		}
 	}
 
+	@bindThis
+	public async fetch(host: string): Promise<MiInstance | null> {
+		host = this.utilityService.toPuny(host);
+
+		const cached = await this.federatedInstanceCache.get(host);
+		if (cached !== undefined) return cached;
+
+		const index = await this.instancesRepository.findOneBy({ host });
+
+		if (index == null) {
+			this.federatedInstanceCache.set(host, null);
+			return null;
+		} else {
+			this.federatedInstanceCache.set(host, index);
+			return index;
+		}
+	}
+
 	@bindThis
 	public async update(id: MiInstance['id'], data: Partial<MiInstance>): Promise<void> {
 		const result = await this.instancesRepository.createQueryBuilder().update()
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index aa16468ecb65..987999bce7d4 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -82,7 +82,7 @@ export class FetchInstanceMetadataService {
 
 		try {
 			if (!force) {
-				const _instance = await this.federatedInstanceService.fetch(host);
+				const _instance = await this.federatedInstanceService.fetchOrRegister(host);
 				const now = Date.now();
 				if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) {
 					// unlock at the finally caluse
diff --git a/packages/backend/src/core/FlashService.ts b/packages/backend/src/core/FlashService.ts
new file mode 100644
index 000000000000..2a98225382b3
--- /dev/null
+++ b/packages/backend/src/core/FlashService.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import { type FlashsRepository } from '@/models/_.js';
+
+/**
+ * MisskeyPlay関係のService
+ */
+@Injectable()
+export class FlashService {
+	constructor(
+		@Inject(DI.flashsRepository)
+		private flashRepository: FlashsRepository,
+	) {
+	}
+
+	/**
+	 * 人気のあるPlay一覧を取得する.
+	 */
+	public async featured(opts?: { offset?: number, limit: number }) {
+		const builder = this.flashRepository.createQueryBuilder('flash')
+			.andWhere('flash.likedCount > 0')
+			.andWhere('flash.visibility = :visibility', { visibility: 'public' })
+			.addOrderBy('flash.likedCount', 'DESC')
+			.addOrderBy('flash.updatedAt', 'DESC')
+			.addOrderBy('flash.id', 'DESC');
+
+		if (opts?.offset) {
+			builder.skip(opts.offset);
+		}
+
+		builder.take(opts?.limit ?? 10);
+
+		return await builder.getMany();
+	}
+}
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 87aa70713e61..03646ff56680 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -241,7 +241,7 @@ export interface InternalEventTypes {
 	avatarDecorationCreated: MiAvatarDecoration;
 	avatarDecorationDeleted: MiAvatarDecoration;
 	avatarDecorationUpdated: MiAvatarDecoration;
-	metaUpdated: MiMeta;
+	metaUpdated: { before?: MiMeta; after: MiMeta; };
 	followChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
 	unfollowChannel: { userId: MiUser['id']; channelId: MiChannel['id']; };
 	updateUserProfile: MiUserProfile;
diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index eb192ee6dafc..793bbeecb162 100644
--- a/packages/backend/src/core/HashtagService.ts
+++ b/packages/backend/src/core/HashtagService.ts
@@ -10,16 +10,18 @@ import type { MiUser } from '@/models/User.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { IdService } from '@/core/IdService.js';
 import type { MiHashtag } from '@/models/Hashtag.js';
-import type { HashtagsRepository } from '@/models/_.js';
+import type { HashtagsRepository, MiMeta } from '@/models/_.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import { FeaturedService } from '@/core/FeaturedService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 
 @Injectable()
 export class HashtagService {
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.redis)
 		private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
 
@@ -29,7 +31,6 @@ export class HashtagService {
 		private userEntityService: UserEntityService,
 		private featuredService: FeaturedService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private utilityService: UtilityService,
 	) {
 	}
@@ -160,10 +161,9 @@ export class HashtagService {
 
 	@bindThis
 	public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> {
-		const instance = await this.metaService.fetch();
-		const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
+		const hiddenTags = this.meta.hiddenTags.map(t => normalizeForSearch(t));
 		if (hiddenTags.includes(hashtag)) return;
-		if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
+		if (this.utilityService.isKeyWordIncluded(hashtag, this.meta.sensitiveWords)) return;
 
 		// YYYYMMDDHHmm (10分間隔)
 		const now = new Date();
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index ec630f804e7c..3d88d0aefe97 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -52,7 +52,7 @@ export class MetaService implements OnApplicationShutdown {
 			switch (type) {
 				case 'metaUpdated': {
 					this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
-						...body,
+						...(body.after),
 						proxyAccount: null, // joinなカラムは通常取ってこないので
 					};
 					break;
@@ -141,7 +141,7 @@ export class MetaService implements OnApplicationShutdown {
 			});
 		}
 
-		this.globalEventService.publishInternalEvent('metaUpdated', updated);
+		this.globalEventService.publishInternalEvent('metaUpdated', { before, after: updated });
 
 		return updated;
 	}
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 74536c68f515..8061622340b6 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -239,7 +239,7 @@ export class MfmService {
 			return null;
 		}
 
-		const { window } = new Window();
+		const { happyDOM, window } = new Window();
 
 		const doc = window.document;
 
@@ -406,8 +406,10 @@ export class MfmService {
 			mention: (node) => {
 				const a = doc.createElement('a');
 				const { username, host, acct } = node.props;
-				const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
-				a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
+				const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username.toLowerCase() === username.toLowerCase() && remoteUser.host?.toLowerCase() === host?.toLowerCase());
+				a.setAttribute('href', remoteUserInfo
+					? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri)
+					: `${this.config.url}/${acct.endsWith(`@${this.config.url}`) ? acct.substring(0, acct.length - this.config.url.length - 1) : acct}`);
 				a.className = 'u-url mention';
 				a.textContent = acct;
 				return a;
@@ -457,6 +459,10 @@ export class MfmService {
 
 		appendChildren(nodes, body);
 
-		return new XMLSerializer().serializeToString(body);
+		const serialized = new XMLSerializer().serializeToString(body);
+
+		happyDOM.close().catch(err => {});
+
+		return serialized;
 	}
 }
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 1d8d2483228a..3647fa723173 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -8,13 +8,12 @@ import * as mfm from 'mfm-js';
 import { In, DataSource, IsNull, LessThan } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
-import RE2 from 're2';
 import { extractMentions } from '@/misc/extract-mentions.js';
 import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
 import { extractHashtags } from '@/misc/extract-hashtags.js';
 import type { IMentionedRemoteUsers } from '@/models/Note.js';
 import { MiNote } from '@/models/Note.js';
-import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiApp } from '@/models/App.js';
 import { concat } from '@/misc/prelude/array.js';
@@ -23,11 +22,8 @@ import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
 import type { IPoll } from '@/models/Poll.js';
 import { MiPoll } from '@/models/Poll.js';
 import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
-import { checkWordMute } from '@/misc/check-word-mute.js';
 import type { MiChannel } from '@/models/Channel.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
-import { MemorySingleCache } from '@/misc/cache.js';
-import type { MiUserProfile } from '@/models/UserProfile.js';
 import { RelayService } from '@/core/RelayService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { DI } from '@/di-symbols.js';
@@ -51,7 +47,6 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { bindThis } from '@/decorators.js';
 import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { RoleService } from '@/core/RoleService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { FeaturedService } from '@/core/FeaturedService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
@@ -60,6 +55,7 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
 import { isReply } from '@/misc/is-reply.js';
 import { trackPromise } from '@/misc/promise-tracker.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { CollapsedQueue } from '@/misc/collapsed-queue.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -151,11 +147,15 @@ type Option = {
 @Injectable()
 export class NoteCreateService implements OnApplicationShutdown {
 	#shutdownController = new AbortController();
+	private updateNotesCountQueue: CollapsedQueue<MiNote['id'], number>;
 
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.db)
 		private db: DataSource,
 
@@ -210,7 +210,6 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private apDeliverManagerService: ApDeliverManagerService,
 		private apRendererService: ApRendererService,
 		private roleService: RoleService,
-		private metaService: MetaService,
 		private searchService: SearchService,
 		private notesChart: NotesChart,
 		private perUserNotesChart: PerUserNotesChart,
@@ -218,7 +217,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private instanceChart: InstanceChart,
 		private utilityService: UtilityService,
 		private userBlockingService: UserBlockingService,
-	) { }
+	) {
+		this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount);
+	}
 
 	@bindThis
 	public async create(user: {
@@ -251,10 +252,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 		if (data.channel != null) data.visibleUsers = [];
 		if (data.channel != null) data.localOnly = true;
 
-		const meta = await this.metaService.fetch();
-
 		if (data.visibility === 'public' && data.channel == null) {
-			const sensitiveWords = meta.sensitiveWords;
+			const sensitiveWords = this.meta.sensitiveWords;
 			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
@@ -262,17 +261,17 @@ export class NoteCreateService implements OnApplicationShutdown {
 			}
 		}
 
-		const hasProhibitedWords = await this.checkProhibitedWordsContain({
+		const hasProhibitedWords = this.checkProhibitedWordsContain({
 			cw: data.cw,
 			text: data.text,
 			pollChoices: data.poll?.choices,
-		}, meta.prohibitedWords);
+		}, this.meta.prohibitedWords);
 
 		if (hasProhibitedWords) {
 			throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
 		}
 
-		const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
+		const inSilencedInstance = this.utilityService.isSilencedHost(this.meta.silencedHosts, user.host);
 
 		if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
 			data.visibility = 'home';
@@ -365,7 +364,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		}
 
 		// if the host is media-silenced, custom emojis are not allowed
-		if (this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, user.host)) emojis = [];
+		if (this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, user.host)) emojis = [];
 
 		tags = tags.filter(tag => Array.from(tag).length <= 128).splice(0, 32);
 
@@ -506,21 +505,21 @@ export class NoteCreateService implements OnApplicationShutdown {
 		host: MiUser['host'];
 		isBot: MiUser['isBot'];
 	}, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) {
-		const meta = await this.metaService.fetch();
-
 		this.notesChart.update(note, true);
-		if (note.visibility !== 'specified' && (meta.enableChartsForRemoteUser || (user.host == null))) {
+		if (note.visibility !== 'specified' && (this.meta.enableChartsForRemoteUser || (user.host == null))) {
 			this.perUserNotesChart.update(user, note, true);
 		}
 
 		// Register host
-		if (this.userEntityService.isRemoteUser(user)) {
-			this.federatedInstanceService.fetch(user.host).then(async i => {
-				this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-					this.instanceChart.updateNote(i.host, note, true);
-				}
-			});
+		if (this.meta.enableStatsForFederatedInstances) {
+			if (this.userEntityService.isRemoteUser(user)) {
+				this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
+					this.updateNotesCountQueue.enqueue(i.id, 1);
+					if (this.meta.enableChartsForFederatedInstances) {
+						this.instanceChart.updateNote(i.host, note, true);
+					}
+				});
+			}
 		}
 
 		// ハッシュタグ更新
@@ -853,15 +852,14 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 	@bindThis
 	private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
-		const meta = await this.metaService.fetch();
-		if (!meta.enableFanoutTimeline) return;
+		if (!this.meta.enableFanoutTimeline) return;
 
 		const r = this.redisForTimelines.pipeline();
 
 		if (note.channelId) {
 			this.fanoutTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
 
-			this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+			this.fanoutTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
 
 			const channelFollowings = await this.channelFollowingsRepository.find({
 				where: {
@@ -871,9 +869,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 			});
 
 			for (const channelFollowing of channelFollowings) {
-				this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+				this.fanoutTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
 				if (note.fileIds.length > 0) {
-					this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+					this.fanoutTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
 				}
 			}
 		} else {
@@ -911,9 +909,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 					if (!following.withReplies) continue;
 				}
 
-				this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+				this.fanoutTimelineService.push(`homeTimeline:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
 				if (note.fileIds.length > 0) {
-					this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+					this.fanoutTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
 				}
 			}
 
@@ -930,25 +928,25 @@ export class NoteCreateService implements OnApplicationShutdown {
 					if (!userListMembership.withReplies) continue;
 				}
 
-				this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
+				this.fanoutTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax, r);
 				if (note.fileIds.length > 0) {
-					this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
+					this.fanoutTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, this.meta.perUserListTimelineCacheMax / 2, r);
 				}
 			}
 
 			// 自分自身のHTL
 			if (note.userHost == null) {
 				if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) {
-					this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+					this.fanoutTimelineService.push(`homeTimeline:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax, r);
 					if (note.fileIds.length > 0) {
-						this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+						this.fanoutTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, this.meta.perUserHomeTimelineCacheMax / 2, r);
 					}
 				}
 			}
 
 			// 自分自身以外への返信
 			if (isReply(note)) {
-				this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+				this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
 
 				if (note.visibility === 'public' && note.userHost == null) {
 					this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
@@ -957,9 +955,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 					}
 				}
 			} else {
-				this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+				this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax : this.meta.perRemoteUserUserTimelineCacheMax, r);
 				if (note.fileIds.length > 0) {
-					this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
+					this.fanoutTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? this.meta.perLocalUserUserTimelineCacheMax / 2 : this.meta.perRemoteUserUserTimelineCacheMax / 2, r);
 				}
 
 				if (note.visibility === 'public' && note.userHost == null) {
@@ -1018,9 +1016,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 		}
 	}
 
-	public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
+	public checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
 		if (prohibitedWords == null) {
-			prohibitedWords = (await this.metaService.fetch()).prohibitedWords;
+			prohibitedWords = this.meta.prohibitedWords;
 		}
 
 		if (
@@ -1036,12 +1034,23 @@ export class NoteCreateService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public dispose(): void {
+	private collapseNotesCount(oldValue: number, newValue: number) {
+		return oldValue + newValue;
+	}
+
+	@bindThis
+	private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) {
+		await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy);
+	}
+
+	@bindThis
+	public async dispose(): Promise<void> {
 		this.#shutdownController.abort();
+		await this.updateNotesCountQueue.performAllNow();
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined): void {
-		this.dispose();
+	public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
+		await this.dispose();
 	}
 }
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index b7c01c64c854..4ecd2592b2ca 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -7,7 +7,7 @@ import { Brackets, In } from 'typeorm';
 import { Injectable, Inject } from '@nestjs/common';
 import type { MiUser, MiLocalUser, MiRemoteUser } from '@/models/User.js';
 import type { MiNote, IMentionedRemoteUsers } from '@/models/Note.js';
-import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/_.js';
+import type { InstancesRepository, MiMeta, NotesRepository, UsersRepository } from '@/models/_.js';
 import { RelayService } from '@/core/RelayService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { DI } from '@/di-symbols.js';
@@ -19,9 +19,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { bindThis } from '@/decorators.js';
-import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
 import { isQuote, isRenote } from '@/misc/is-renote.js';
@@ -32,6 +30,9 @@ export class NoteDeleteService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -42,13 +43,11 @@ export class NoteDeleteService {
 		private instancesRepository: InstancesRepository,
 
 		private userEntityService: UserEntityService,
-		private noteEntityService: NoteEntityService,
 		private globalEventService: GlobalEventService,
 		private relayService: RelayService,
 		private federatedInstanceService: FederatedInstanceService,
 		private apRendererService: ApRendererService,
 		private apDeliverManagerService: ApDeliverManagerService,
-		private metaService: MetaService,
 		private searchService: SearchService,
 		private moderationLogService: ModerationLogService,
 		private notesChart: NotesChart,
@@ -102,20 +101,20 @@ export class NoteDeleteService {
 			}
 			//#endregion
 
-			const meta = await this.metaService.fetch();
-
 			this.notesChart.update(note, false);
-			if (meta.enableChartsForRemoteUser || (user.host == null)) {
+			if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
 				this.perUserNotesChart.update(user, note, false);
 			}
 
-			if (this.userEntityService.isRemoteUser(user)) {
-				this.federatedInstanceService.fetch(user.host).then(async i => {
-					this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
-					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-						this.instanceChart.updateNote(i.host, note, false);
-					}
-				});
+			if (this.meta.enableStatsForFederatedInstances) {
+				if (this.userEntityService.isRemoteUser(user)) {
+					this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
+						this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
+						if (this.meta.enableChartsForFederatedInstances) {
+							this.instanceChart.updateNote(i.host, note, false);
+						}
+					});
+				}
 			}
 		}
 
diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts
index 71d663bf9060..c3ff2a68d335 100644
--- a/packages/backend/src/core/ProxyAccountService.ts
+++ b/packages/backend/src/core/ProxyAccountService.ts
@@ -4,26 +4,25 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsersRepository } from '@/models/_.js';
 import type { MiLocalUser } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
-import { MetaService } from '@/core/MetaService.js';
 import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class ProxyAccountService {
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
-
-		private metaService: MetaService,
 	) {
 	}
 
 	@bindThis
 	public async fetch(): Promise<MiLocalUser | null> {
-		const meta = await this.metaService.fetch();
-		if (meta.proxyAccountId == null) return null;
-		return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as MiLocalUser;
+		if (this.meta.proxyAccountId == null) return null;
+		return await this.usersRepository.findOneByOrFail({ id: this.meta.proxyAccountId }) as MiLocalUser;
 	}
 }
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 6a845b951de6..1479bb00d9b8 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -10,8 +10,7 @@ import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
-import type { MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
-import { MetaService } from '@/core/MetaService.js';
+import type { MiMeta, MiSwSubscription, SwSubscriptionsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import { RedisKVCache } from '@/misc/cache.js';
 
@@ -54,13 +53,14 @@ export class PushNotificationService implements OnApplicationShutdown {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.redis)
 		private redisClient: Redis.Redis,
 
 		@Inject(DI.swSubscriptionsRepository)
 		private swSubscriptionsRepository: SwSubscriptionsRepository,
-
-		private metaService: MetaService,
 	) {
 		this.subscriptionsCache = new RedisKVCache<MiSwSubscription[]>(this.redisClient, 'userSwSubscriptions', {
 			lifetime: 1000 * 60 * 60 * 1, // 1h
@@ -73,14 +73,12 @@ export class PushNotificationService implements OnApplicationShutdown {
 
 	@bindThis
 	public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) {
-		const meta = await this.metaService.fetch();
-
-		if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
+		if (!this.meta.enableServiceWorker || this.meta.swPublicKey == null || this.meta.swPrivateKey == null) return;
 
 		// アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録
 		push.setVapidDetails(this.config.url,
-			meta.swPublicKey,
-			meta.swPrivateKey);
+			this.meta.swPublicKey,
+			this.meta.swPrivateKey);
 
 		const subscriptions = await this.subscriptionsCache.fetch(userId);
 
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index 80827a500b56..37028026cc43 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -87,6 +87,19 @@ export class QueueService {
 			repeat: { pattern: '*/5 * * * *' },
 			removeOnComplete: true,
 		});
+
+		this.systemQueue.add('bakeBufferedReactions', {
+		}, {
+			repeat: { pattern: '0 0 * * *' },
+			removeOnComplete: true,
+		});
+
+		this.systemQueue.add('checkModeratorsActivity', {
+		}, {
+			// 毎時30分に起動
+			repeat: { pattern: '30 * * * *' },
+			removeOnComplete: true,
+		});
 	}
 
 	@bindThis
@@ -452,10 +465,15 @@ export class QueueService {
 
 	/**
 	 * @see UserWebhookDeliverJobData
-	 * @see WebhookDeliverProcessorService
+	 * @see UserWebhookDeliverProcessorService
 	 */
 	@bindThis
-	public userWebhookDeliver(webhook: MiWebhook, type: typeof webhookEventTypes[number], content: unknown) {
+	public userWebhookDeliver(
+		webhook: MiWebhook,
+		type: typeof webhookEventTypes[number],
+		content: unknown,
+		opts?: { attempts?: number },
+	) {
 		const data: UserWebhookDeliverJobData = {
 			type,
 			content,
@@ -468,7 +486,7 @@ export class QueueService {
 		};
 
 		return this.userWebhookDeliverQueue.add(webhook.id, data, {
-			attempts: 4,
+			attempts: opts?.attempts ?? 4,
 			backoff: {
 				type: 'custom',
 			},
@@ -479,10 +497,15 @@ export class QueueService {
 
 	/**
 	 * @see SystemWebhookDeliverJobData
-	 * @see WebhookDeliverProcessorService
+	 * @see SystemWebhookDeliverProcessorService
 	 */
 	@bindThis
-	public systemWebhookDeliver(webhook: MiSystemWebhook, type: SystemWebhookEventType, content: unknown) {
+	public systemWebhookDeliver(
+		webhook: MiSystemWebhook,
+		type: SystemWebhookEventType,
+		content: unknown,
+		opts?: { attempts?: number },
+	) {
 		const data: SystemWebhookDeliverJobData = {
 			type,
 			content,
@@ -494,7 +517,7 @@ export class QueueService {
 		};
 
 		return this.systemWebhookDeliverQueue.add(webhook.id, data, {
-			attempts: 4,
+			attempts: opts?.attempts ?? 4,
 			backoff: {
 				type: 'custom',
 			},
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 371207c33a7d..6f9fe53937bc 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -4,9 +4,8 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
-import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
+import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, MiMeta } from '@/models/_.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 import type { MiRemoteUser, MiUser } from '@/models/User.js';
 import type { MiNote } from '@/models/Note.js';
@@ -21,7 +20,6 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { bindThis } from '@/decorators.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
@@ -30,9 +28,10 @@ import { RoleService } from '@/core/RoleService.js';
 import { FeaturedService } from '@/core/FeaturedService.js';
 import { trackPromise } from '@/misc/promise-tracker.js';
 import { isQuote, isRenote } from '@/misc/is-renote.js';
+import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
+import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
 
 const FALLBACK = '\u2764';
-const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
 
 const legacies: Record<string, string> = {
 	'like': '👍',
@@ -71,8 +70,8 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
 @Injectable()
 export class ReactionService {
 	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.meta)
+		private meta: MiMeta,
 
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
@@ -87,12 +86,12 @@ export class ReactionService {
 		private emojisRepository: EmojisRepository,
 
 		private utilityService: UtilityService,
-		private metaService: MetaService,
 		private customEmojiService: CustomEmojiService,
 		private roleService: RoleService,
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
 		private userBlockingService: UserBlockingService,
+		private reactionsBufferingService: ReactionsBufferingService,
 		private idService: IdService,
 		private featuredService: FeaturedService,
 		private globalEventService: GlobalEventService,
@@ -105,8 +104,6 @@ export class ReactionService {
 
 	@bindThis
 	public async create(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot'] }, note: MiNote, _reaction?: string | null) {
-		const meta = await this.metaService.fetch();
-
 		// Check blocking
 		if (note.userId !== user.id) {
 			const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
@@ -152,7 +149,7 @@ export class ReactionService {
 						}
 
 						// for media silenced host, custom emoji reactions are not allowed
-						if (reacterHost != null && this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, reacterHost)) {
+						if (reacterHost != null && this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, reacterHost)) {
 							reaction = FALLBACK;
 						}
 					} else {
@@ -174,7 +171,6 @@ export class ReactionService {
 			reaction,
 		};
 
-		// Create reaction
 		try {
 			await this.noteReactionsRepository.insert(record);
 		} catch (e) {
@@ -198,16 +194,20 @@ export class ReactionService {
 		}
 
 		// Increment reactions count
-		const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
-		await this.notesRepository.createQueryBuilder().update()
-			.set({
-				reactions: () => sql,
-				...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
-					reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
-				} : {}),
-			})
-			.where('id = :id', { id: note.id })
-			.execute();
+		if (this.meta.enableReactionsBuffering) {
+			await this.reactionsBufferingService.create(note.id, user.id, reaction, note.reactionAndUserPairCache);
+		} else {
+			const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
+			await this.notesRepository.createQueryBuilder().update()
+				.set({
+					reactions: () => sql,
+					...(note.reactionAndUserPairCache.length < PER_NOTE_REACTION_USER_PAIR_CACHE_MAX ? {
+						reactionAndUserPairCache: () => `array_append("reactionAndUserPairCache", '${user.id}/${reaction}')`,
+					} : {}),
+				})
+				.where('id = :id', { id: note.id })
+				.execute();
+		}
 
 		// 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新
 		if (
@@ -227,7 +227,7 @@ export class ReactionService {
 			}
 		}
 
-		if (meta.enableChartsForRemoteUser || (user.host == null)) {
+		if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
 			this.perUserReactionsChart.update(user, note);
 		}
 
@@ -305,14 +305,18 @@ export class ReactionService {
 		}
 
 		// Decrement reactions count
-		const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
-		await this.notesRepository.createQueryBuilder().update()
-			.set({
-				reactions: () => sql,
-				reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
-			})
-			.where('id = :id', { id: note.id })
-			.execute();
+		if (this.meta.enableReactionsBuffering) {
+			await this.reactionsBufferingService.delete(note.id, user.id, exist.reaction);
+		} else {
+			const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
+			await this.notesRepository.createQueryBuilder().update()
+				.set({
+					reactions: () => sql,
+					reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
+				})
+				.where('id = :id', { id: note.id })
+				.execute();
+		}
 
 		this.globalEventService.publishNoteStream(note.id, 'unreacted', {
 			reaction: this.decodeReaction(exist.reaction).reaction,
@@ -334,8 +338,21 @@ export class ReactionService {
 	}
 
 	/**
-	 * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
-	 * データベース上には存在する「0個のリアクションがついている」という情報を削除する。
+	 * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
+	 * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
+	 */
+	@bindThis
+	public convertLegacyReaction(reaction: string): string {
+		reaction = this.decodeReaction(reaction).reaction;
+		if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
+		return reaction;
+	}
+
+	// TODO: 廃止
+	/**
+	 * - 文字列タイプのレガシーな形式のリアクションを現在の形式に変換する
+	 * - ローカルのリアクションのホストを `@.` にする(`decodeReaction()`の効果)
+	 * - データベース上には存在する「0個のリアクションがついている」という情報を削除する
 	 */
 	@bindThis
 	public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
@@ -348,10 +365,7 @@ export class ReactionService {
 				return count > 0;
 			})
 			.map(([reaction, count]) => {
-				// unchecked indexed access
-				const convertedReaction = legacies[reaction] as string | undefined;
-
-				const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
+				const key = this.convertLegacyReaction(reaction);
 
 				return [key, count] as const;
 			})
@@ -406,11 +420,4 @@ export class ReactionService {
 			host: undefined,
 		};
 	}
-
-	@bindThis
-	public convertLegacyReaction(reaction: string): string {
-		reaction = this.decodeReaction(reaction).reaction;
-		if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
-		return reaction;
-	}
 }
diff --git a/packages/backend/src/core/ReactionsBufferingService.ts b/packages/backend/src/core/ReactionsBufferingService.ts
new file mode 100644
index 000000000000..b4207c51062c
--- /dev/null
+++ b/packages/backend/src/core/ReactionsBufferingService.ts
@@ -0,0 +1,211 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import { DI } from '@/di-symbols.js';
+import type { MiNote } from '@/models/Note.js';
+import { bindThis } from '@/decorators.js';
+import type { MiUser, NotesRepository } from '@/models/_.js';
+import type { Config } from '@/config.js';
+import { PER_NOTE_REACTION_USER_PAIR_CACHE_MAX } from '@/const.js';
+import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import type { OnApplicationShutdown } from '@nestjs/common';
+
+const REDIS_DELTA_PREFIX = 'reactionsBufferDeltas';
+const REDIS_PAIR_PREFIX = 'reactionsBufferPairs';
+
+@Injectable()
+export class ReactionsBufferingService implements OnApplicationShutdown {
+	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
+		@Inject(DI.redisForSub)
+		private redisForSub: Redis.Redis,
+
+		@Inject(DI.redisForReactions)
+		private redisForReactions: Redis.Redis, // TODO: 専用のRedisインスタンスにする
+
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+	) {
+		this.redisForSub.on('message', this.onMessage);
+	}
+
+	@bindThis
+	private async onMessage(_: string, data: string) {
+		const obj = JSON.parse(data);
+
+		if (obj.channel === 'internal') {
+			const { type, body } = obj.message as GlobalEvents['internal']['payload'];
+			switch (type) {
+				case 'metaUpdated': {
+					// リアクションバッファリングが有効→無効になったら即bake
+					if (body.before != null && body.before.enableReactionsBuffering && !body.after.enableReactionsBuffering) {
+						this.bake();
+					}
+					break;
+				}
+				default:
+					break;
+			}
+		}
+	}
+
+	@bindThis
+	public async create(noteId: MiNote['id'], userId: MiUser['id'], reaction: string, currentPairs: string[]): Promise<void> {
+		const pipeline = this.redisForReactions.pipeline();
+		pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, 1);
+		for (let i = 0; i < currentPairs.length; i++) {
+			pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, i, currentPairs[i]);
+		}
+		pipeline.zadd(`${REDIS_PAIR_PREFIX}:${noteId}`, Date.now(), `${userId}/${reaction}`);
+		pipeline.zremrangebyrank(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -(PER_NOTE_REACTION_USER_PAIR_CACHE_MAX + 1));
+		await pipeline.exec();
+	}
+
+	@bindThis
+	public async delete(noteId: MiNote['id'], userId: MiUser['id'], reaction: string): Promise<void> {
+		const pipeline = this.redisForReactions.pipeline();
+		pipeline.hincrby(`${REDIS_DELTA_PREFIX}:${noteId}`, reaction, -1);
+		pipeline.zrem(`${REDIS_PAIR_PREFIX}:${noteId}`, `${userId}/${reaction}`);
+		// TODO: 「消した要素一覧」も持っておかないとcreateされた時に上書きされて復活する
+		await pipeline.exec();
+	}
+
+	@bindThis
+	public async get(noteId: MiNote['id']): Promise<{
+		deltas: Record<string, number>;
+		pairs: ([MiUser['id'], string])[];
+	}> {
+		const pipeline = this.redisForReactions.pipeline();
+		pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`);
+		pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1);
+		const results = await pipeline.exec();
+
+		const resultDeltas = results![0][1] as Record<string, string>;
+		const resultPairs = results![1][1] as string[];
+
+		const deltas = {} as Record<string, number>;
+		for (const [name, count] of Object.entries(resultDeltas)) {
+			deltas[name] = parseInt(count);
+		}
+
+		const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]);
+
+		return {
+			deltas,
+			pairs,
+		};
+	}
+
+	@bindThis
+	public async getMany(noteIds: MiNote['id'][]): Promise<Map<MiNote['id'], {
+		deltas: Record<string, number>;
+		pairs: ([MiUser['id'], string])[];
+	}>> {
+		const map = new Map<MiNote['id'], {
+			deltas: Record<string, number>;
+			pairs: ([MiUser['id'], string])[];
+		}>();
+
+		const pipeline = this.redisForReactions.pipeline();
+		for (const noteId of noteIds) {
+			pipeline.hgetall(`${REDIS_DELTA_PREFIX}:${noteId}`);
+			pipeline.zrange(`${REDIS_PAIR_PREFIX}:${noteId}`, 0, -1);
+		}
+		const results = await pipeline.exec();
+
+		const opsForEachNotes = 2;
+		for (let i = 0; i < noteIds.length; i++) {
+			const noteId = noteIds[i];
+			const resultDeltas = results![i * opsForEachNotes][1] as Record<string, string>;
+			const resultPairs = results![i * opsForEachNotes + 1][1] as string[];
+
+			const deltas = {} as Record<string, number>;
+			for (const [name, count] of Object.entries(resultDeltas)) {
+				deltas[name] = parseInt(count);
+			}
+
+			const pairs = resultPairs.map(x => x.split('/') as [MiUser['id'], string]);
+
+			map.set(noteId, {
+				deltas,
+				pairs,
+			});
+		}
+
+		return map;
+	}
+
+	// TODO: scanは重い可能性があるので、別途 bufferedNoteIds を直接Redis上に持っておいてもいいかもしれない
+	@bindThis
+	public async bake(): Promise<void> {
+		const bufferedNoteIds = [];
+		let cursor = '0';
+		do {
+			// https://github.com/redis/ioredis#transparent-key-prefixing
+			const result = await this.redisForReactions.scan(
+				cursor,
+				'MATCH',
+				`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:*`,
+				'COUNT',
+				'1000');
+
+			cursor = result[0];
+			bufferedNoteIds.push(...result[1].map(x => x.replace(`${this.config.redis.prefix}:${REDIS_DELTA_PREFIX}:`, '')));
+		} while (cursor !== '0');
+
+		const bufferedMap = await this.getMany(bufferedNoteIds);
+
+		// clear
+		const pipeline = this.redisForReactions.pipeline();
+		for (const noteId of bufferedNoteIds) {
+			pipeline.del(`${REDIS_DELTA_PREFIX}:${noteId}`);
+			pipeline.del(`${REDIS_PAIR_PREFIX}:${noteId}`);
+		}
+		await pipeline.exec();
+
+		// TODO: SQL一個にまとめたい
+		for (const [noteId, buffered] of bufferedMap) {
+			const sql = Object.entries(buffered.deltas)
+				.map(([reaction, count]) =>
+					`jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + ${count})::text::jsonb)`)
+				.join(' || ');
+
+			this.notesRepository.createQueryBuilder().update()
+				.set({
+					reactions: () => sql,
+					reactionAndUserPairCache: buffered.pairs.map(x => x.join('/')),
+				})
+				.where('id = :id', { id: noteId })
+				.execute();
+		}
+	}
+
+	@bindThis
+	public mergeReactions(src: MiNote['reactions'], delta: Record<string, number>): MiNote['reactions'] {
+		const reactions = { ...src };
+		for (const [name, count] of Object.entries(delta)) {
+			if (reactions[name] != null) {
+				reactions[name] += count;
+			} else {
+				reactions[name] = count;
+			}
+		}
+		return reactions;
+	}
+
+	@bindThis
+	public dispose(): void {
+		this.redisForSub.off('message', this.onMessage);
+	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
+}
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 0210012a0300..5af6b0594253 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -8,6 +8,7 @@ import * as Redis from 'ioredis';
 import { In } from 'typeorm';
 import { ModuleRef } from '@nestjs/core';
 import type {
+	MiMeta,
 	MiRole,
 	MiRoleAssignment,
 	RoleAssignmentsRepository,
@@ -18,7 +19,6 @@ import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
 import type { MiUser } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
-import { MetaService } from '@/core/MetaService.js';
 import { CacheService } from '@/core/CacheService.js';
 import type { RoleCondFormulaValue } from '@/models/Role.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -58,6 +58,11 @@ export type RolePolicies = {
 	userEachUserListsLimit: number;
 	rateLimitFactor: number;
 	avatarDecorationLimit: number;
+	canImportAntennas: boolean;
+	canImportBlocking: boolean;
+	canImportFollowing: boolean;
+	canImportMuting: boolean;
+	canImportUserLists: boolean;
 };
 
 export const DEFAULT_POLICIES: RolePolicies = {
@@ -87,10 +92,16 @@ export const DEFAULT_POLICIES: RolePolicies = {
 	userEachUserListsLimit: 50,
 	rateLimitFactor: 1,
 	avatarDecorationLimit: 1,
+	canImportAntennas: true,
+	canImportBlocking: true,
+	canImportFollowing: true,
+	canImportMuting: true,
+	canImportUserLists: true,
 };
 
 @Injectable()
 export class RoleService implements OnApplicationShutdown, OnModuleInit {
+	private rootUserIdCache: MemorySingleCache<MiUser['id']>;
 	private rolesCache: MemorySingleCache<MiRole[]>;
 	private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>;
 	private notificationService: NotificationService;
@@ -101,8 +112,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 	constructor(
 		private moduleRef: ModuleRef,
 
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.meta)
+		private meta: MiMeta,
 
 		@Inject(DI.redisForTimelines)
 		private redisForTimelines: Redis.Redis,
@@ -119,7 +130,6 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 		@Inject(DI.roleAssignmentsRepository)
 		private roleAssignmentsRepository: RoleAssignmentsRepository,
 
-		private metaService: MetaService,
 		private cacheService: CacheService,
 		private userEntityService: UserEntityService,
 		private globalEventService: GlobalEventService,
@@ -127,6 +137,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 		private moderationLogService: ModerationLogService,
 		private fanoutTimelineService: FanoutTimelineService,
 	) {
+		this.rootUserIdCache = new MemorySingleCache<MiUser['id']>(1000 * 60 * 60 * 24 * 7); // 1week. rootユーザのIDは不変なので長めに
 		this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
 		this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
 
@@ -339,8 +350,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 
 	@bindThis
 	public async getUserPolicies(userId: MiUser['id'] | null): Promise<RolePolicies> {
-		const meta = await this.metaService.fetch();
-		const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
+		const basePolicies = { ...DEFAULT_POLICIES, ...this.meta.policies };
 
 		if (userId == null) return basePolicies;
 
@@ -387,6 +397,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 			userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
 			rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
 			avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
+			canImportAntennas: calc('canImportAntennas', vs => vs.some(v => v === true)),
+			canImportBlocking: calc('canImportBlocking', vs => vs.some(v => v === true)),
+			canImportFollowing: calc('canImportFollowing', vs => vs.some(v => v === true)),
+			canImportMuting: calc('canImportMuting', vs => vs.some(v => v === true)),
+			canImportUserLists: calc('canImportUserLists', vs => vs.some(v => v === true)),
 		};
 	}
 
@@ -403,49 +418,78 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 	}
 
 	@bindThis
-	public async isExplorable(role: { id: MiRole['id']} | null): Promise<boolean> {
+	public async isExplorable(role: { id: MiRole['id'] } | null): Promise<boolean> {
 		if (role == null) return false;
 		const check = await this.rolesRepository.findOneBy({ id: role.id });
 		if (check == null) return false;
 		return check.isExplorable;
 	}
 
+	/**
+	 * モデレーター権限のロールが割り当てられているユーザID一覧を取得する.
+	 *
+	 * @param opts.includeAdmins 管理者権限も含めるか(デフォルト: true)
+	 * @param opts.includeRoot rootユーザも含めるか(デフォルト: false)
+	 * @param opts.excludeExpire 期限切れのロールを除外するか(デフォルト: false)
+	 */
 	@bindThis
-	public async getModeratorIds(includeAdmins = true, excludeExpire = false): Promise<MiUser['id'][]> {
+	public async getModeratorIds(opts?: {
+		includeAdmins?: boolean,
+		includeRoot?: boolean,
+		excludeExpire?: boolean,
+	}): Promise<MiUser['id'][]> {
+		const includeAdmins = opts?.includeAdmins ?? true;
+		const includeRoot = opts?.includeRoot ?? false;
+		const excludeExpire = opts?.excludeExpire ?? false;
+
 		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
 		const moderatorRoles = includeAdmins
 			? roles.filter(r => r.isModerator || r.isAdministrator)
 			: roles.filter(r => r.isModerator);
 
-		// TODO: isRootなアカウントも含める
 		const assigns = moderatorRoles.length > 0
 			? await this.roleAssignmentsRepository.findBy({ roleId: In(moderatorRoles.map(r => r.id)) })
 			: [];
 
+		// Setを経由して重複を除去(ユーザIDは重複する可能性があるので)
 		const now = Date.now();
-		const result = [
-			// Setを経由して重複を除去(ユーザIDは重複する可能性があるので)
-			...new Set(
-				assigns
-					.filter(it =>
-						(excludeExpire)
-							? (it.expiresAt == null || it.expiresAt.getTime() > now)
-							: true,
-					)
-					.map(a => a.userId),
-			),
-		];
-
-		return result.sort((x, y) => x.localeCompare(y));
+		const resultSet = new Set(
+			assigns
+				.filter(it =>
+					(excludeExpire)
+						? (it.expiresAt == null || it.expiresAt.getTime() > now)
+						: true,
+				)
+				.map(a => a.userId),
+		);
+
+		if (includeRoot) {
+			const rootUserId = await this.rootUserIdCache.fetch(async () => {
+				const it = await this.usersRepository.createQueryBuilder('users')
+					.select('id')
+					.where({ isRoot: true })
+					.getRawOne<{ id: string }>();
+				// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+				return it!.id;
+			});
+			resultSet.add(rootUserId);
+		}
+
+		return [...resultSet].sort((x, y) => x.localeCompare(y));
 	}
 
 	@bindThis
-	public async getModerators(includeAdmins = true): Promise<MiUser[]> {
-		const ids = await this.getModeratorIds(includeAdmins);
-		const users = ids.length > 0 ? await this.usersRepository.findBy({
-			id: In(ids),
-		}) : [];
-		return users;
+	public async getModerators(opts?: {
+		includeAdmins?: boolean,
+		includeRoot?: boolean,
+		excludeExpire?: boolean,
+	}): Promise<MiUser[]> {
+		const ids = await this.getModeratorIds(opts);
+		return ids.length > 0
+			? await this.usersRepository.findBy({
+				id: In(ids),
+			})
+			: [];
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index de4589832815..3865392b7f8b 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import bcrypt from 'bcryptjs';
 import { DataSource, IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
 import { MiUser } from '@/models/User.js';
 import { MiUserProfile } from '@/models/UserProfile.js';
 import { IdService } from '@/core/IdService.js';
@@ -20,7 +20,6 @@ import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { bindThis } from '@/decorators.js';
 import UsersChart from '@/core/chart/charts/users.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { UserService } from '@/core/UserService.js';
 
 @Injectable()
@@ -29,6 +28,9 @@ export class SignupService {
 		@Inject(DI.db)
 		private db: DataSource,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -39,7 +41,6 @@ export class SignupService {
 		private userService: UserService,
 		private userEntityService: UserEntityService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private instanceActorService: InstanceActorService,
 		private usersChart: UsersChart,
 	) {
@@ -88,8 +89,7 @@ export class SignupService {
 		const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
 
 		if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
-			const instance = await this.metaService.fetch(true);
-			const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
+			const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
 			if (isPreserved) {
 				throw new Error('USED_USERNAME');
 			}
@@ -150,8 +150,8 @@ export class SignupService {
 			}));
 		});
 
-		this.usersChart.update(account, true).then();
-		this.userService.notifySystemWebhook(account, 'userCreated').then();
+		this.usersChart.update(account, true);
+		this.userService.notifySystemWebhook(account, 'userCreated');
 
 		return { account, secret };
 	}
diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts
index bc6851f788da..db6407dcb3cc 100644
--- a/packages/backend/src/core/SystemWebhookService.ts
+++ b/packages/backend/src/core/SystemWebhookService.ts
@@ -54,7 +54,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
 	 * SystemWebhook の一覧を取得する.
 	 */
 	@bindThis
-	public async fetchSystemWebhooks(params?: {
+	public fetchSystemWebhooks(params?: {
 		ids?: MiSystemWebhook['id'][];
 		isActive?: MiSystemWebhook['isActive'];
 		on?: MiSystemWebhook['on'];
@@ -101,8 +101,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
 			.log(updater, 'createSystemWebhook', {
 				systemWebhookId: webhook.id,
 				webhook: webhook,
-			})
-			.then();
+			});
 
 		return webhook;
 	}
@@ -139,8 +138,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
 				systemWebhookId: beforeEntity.id,
 				before: beforeEntity,
 				after: afterEntity,
-			})
-			.then();
+			});
 
 		return afterEntity;
 	}
@@ -158,26 +156,30 @@ export class SystemWebhookService implements OnApplicationShutdown {
 			.log(updater, 'deleteSystemWebhook', {
 				systemWebhookId: webhook.id,
 				webhook,
-			})
-			.then();
+			});
 	}
 
 	/**
 	 * SystemWebhook をWebhook配送キューに追加する
 	 * @see QueueService.systemWebhookDeliver
+	 * // TODO: contentの型を厳格化する
 	 */
 	@bindThis
-	public async enqueueSystemWebhook(webhook: MiSystemWebhook | MiSystemWebhook['id'], type: SystemWebhookEventType, content: unknown) {
+	public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
+		webhook: MiSystemWebhook | MiSystemWebhook['id'],
+		type: T,
+		content: unknown,
+	) {
 		const webhookEntity = typeof webhook === 'string'
 			? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
 			: webhook;
 		if (!webhookEntity || !webhookEntity.isActive) {
-			this.logger.info(`Webhook is not active or not found : ${webhook}`);
+			this.logger.info(`SystemWebhook is not active or not found : ${webhook}`);
 			return;
 		}
 
 		if (!webhookEntity.on.includes(type)) {
-			this.logger.info(`Webhook ${webhookEntity.id} is not listening to ${type}`);
+			this.logger.info(`SystemWebhook ${webhookEntity.id} is not listening to ${type}`);
 			return;
 		}
 
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 6aab8fde70c2..8963003057cf 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -13,23 +13,20 @@ import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { IdService } from '@/core/IdService.js';
 import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
-import type { Packed } from '@/misc/json-schema.js';
 import InstanceChart from '@/core/chart/charts/instance.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { UserWebhookService } from '@/core/UserWebhookService.js';
 import { NotificationService } from '@/core/NotificationService.js';
 import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { FollowingsRepository, FollowRequestsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { bindThis } from '@/decorators.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { CacheService } from '@/core/CacheService.js';
 import type { Config } from '@/config.js';
 import { AccountMoveService } from '@/core/AccountMoveService.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 import type { ThinUser } from '@/queue/types.js';
 import Logger from '../logger.js';
 
@@ -58,6 +55,9 @@ export class UserFollowingService implements OnModuleInit {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -79,13 +79,11 @@ export class UserFollowingService implements OnModuleInit {
 		private idService: IdService,
 		private queueService: QueueService,
 		private globalEventService: GlobalEventService,
-		private metaService: MetaService,
 		private notificationService: NotificationService,
 		private federatedInstanceService: FederatedInstanceService,
 		private webhookService: UserWebhookService,
 		private apRendererService: ApRendererService,
 		private accountMoveService: AccountMoveService,
-		private fanoutTimelineService: FanoutTimelineService,
 		private perUserFollowingChart: PerUserFollowingChart,
 		private instanceChart: InstanceChart,
 	) {
@@ -172,7 +170,7 @@ export class UserFollowingService implements OnModuleInit {
 			followee.isLocked ||
 			(followeeProfile.carefulBot && follower.isBot) ||
 			(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true') ||
-			(this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, follower.host))
+			(this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower) && this.utilityService.isSilencedHost(this.meta.silencedHosts, follower.host))
 		) {
 			let autoAccept = false;
 
@@ -277,16 +275,19 @@ export class UserFollowingService implements OnModuleInit {
 				followeeId: followee.id,
 				followerId: follower.id,
 			});
-
-			// 通知を作成
-			if (follower.host === null) {
-				this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
-				}, followee.id);
-			}
 		}
 
 		if (alreadyFollowed) return;
 
+		// 通知を作成
+		if (follower.host === null) {
+			const profile = await this.cacheService.userProfileCache.fetch(followee.id);
+
+			this.notificationService.createNotification(follower.id, 'followRequestAccepted', {
+				message: profile.followedMessage,
+			}, followee.id);
+		}
+
 		this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
 
 		const [followeeUser, followerUser] = await Promise.all([
@@ -304,20 +305,22 @@ export class UserFollowingService implements OnModuleInit {
 			//#endregion
 
 			//#region Update instance stats
-			if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-				this.federatedInstanceService.fetch(follower.host).then(async i => {
-					this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
-					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-						this.instanceChart.updateFollowing(i.host, true);
-					}
-				});
-			} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-				this.federatedInstanceService.fetch(followee.host).then(async i => {
-					this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
-					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-						this.instanceChart.updateFollowers(i.host, true);
-					}
-				});
+			if (this.meta.enableStatsForFederatedInstances) {
+				if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
+					this.federatedInstanceService.fetchOrRegister(follower.host).then(async i => {
+						this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
+						if (this.meta.enableChartsForFederatedInstances) {
+							this.instanceChart.updateFollowing(i.host, true);
+						}
+					});
+				} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
+					this.federatedInstanceService.fetchOrRegister(followee.host).then(async i => {
+						this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
+						if (this.meta.enableChartsForFederatedInstances) {
+							this.instanceChart.updateFollowers(i.host, true);
+						}
+					});
+				}
 			}
 			//#endregion
 
@@ -436,20 +439,22 @@ export class UserFollowingService implements OnModuleInit {
 			//#endregion
 
 			//#region Update instance stats
-			if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-				this.federatedInstanceService.fetch(follower.host).then(async i => {
-					this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
-					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-						this.instanceChart.updateFollowing(i.host, false);
-					}
-				});
-			} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-				this.federatedInstanceService.fetch(followee.host).then(async i => {
-					this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
-					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-						this.instanceChart.updateFollowers(i.host, false);
-					}
-				});
+			if (this.meta.enableStatsForFederatedInstances) {
+				if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
+					this.federatedInstanceService.fetchOrRegister(follower.host).then(async i => {
+						this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
+						if (this.meta.enableChartsForFederatedInstances) {
+							this.instanceChart.updateFollowing(i.host, false);
+						}
+					});
+				} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
+					this.federatedInstanceService.fetchOrRegister(followee.host).then(async i => {
+						this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
+						if (this.meta.enableChartsForFederatedInstances) {
+							this.instanceChart.updateFollowers(i.host, false);
+						}
+					});
+				}
 			}
 			//#endregion
 
diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts
index e96bfeea9581..8a40a5368804 100644
--- a/packages/backend/src/core/UserWebhookService.ts
+++ b/packages/backend/src/core/UserWebhookService.ts
@@ -5,8 +5,8 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import type { WebhooksRepository } from '@/models/_.js';
-import type { MiWebhook } from '@/models/Webhook.js';
+import { type WebhooksRepository } from '@/models/_.js';
+import { MiWebhook } from '@/models/Webhook.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 import { GlobalEvents } from '@/core/GlobalEventService.js';
@@ -38,6 +38,31 @@ export class UserWebhookService implements OnApplicationShutdown {
 		return this.activeWebhooks;
 	}
 
+	/**
+	 * UserWebhook の一覧を取得する.
+	 */
+	@bindThis
+	public fetchWebhooks(params?: {
+		ids?: MiWebhook['id'][];
+		isActive?: MiWebhook['active'];
+		on?: MiWebhook['on'];
+	}): Promise<MiWebhook[]> {
+		const query = this.webhooksRepository.createQueryBuilder('webhook');
+		if (params) {
+			if (params.ids && params.ids.length > 0) {
+				query.andWhere('webhook.id IN (:...ids)', { ids: params.ids });
+			}
+			if (params.isActive !== undefined) {
+				query.andWhere('webhook.active = :isActive', { isActive: params.isActive });
+			}
+			if (params.on && params.on.length > 0) {
+				query.andWhere(':on <@ webhook.on', { on: params.on });
+			}
+		}
+
+		return query.getMany();
+	}
+
 	@bindThis
 	private async onMessage(_: string, data: string): Promise<void> {
 		const obj = JSON.parse(data);
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 94729250a614..86082ccdcdb1 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -10,12 +10,16 @@ import RE2 from 're2';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { bindThis } from '@/decorators.js';
+import { MiMeta } from '@/models/Meta.js';
 
 @Injectable()
 export class UtilityService {
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
+
+		@Inject(DI.meta)
+		private meta: MiMeta,
 	) {
 	}
 
@@ -105,4 +109,19 @@ export class UtilityService {
 		if (host == null) return null;
 		return toASCII(host.toLowerCase());
 	}
+
+	@bindThis
+	public isFederationAllowedHost(host: string): boolean {
+		if (this.meta.federation === 'none') return false;
+		if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false;
+		if (this.isBlockedHost(this.meta.blockedHosts, host)) return false;
+
+		return true;
+	}
+
+	@bindThis
+	public isFederationAllowedUri(uri: string): boolean {
+		const host = this.extractDbHost(uri);
+		return this.isFederationAllowedHost(host);
+	}
 }
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index ec9f4484a4c0..75ab0a207c45 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -12,10 +12,9 @@ import {
 } from '@simplewebauthn/server';
 import { AttestationFormat, isoCBOR, isoUint8Array } from '@simplewebauthn/server/helpers';
 import { DI } from '@/di-symbols.js';
-import type { UserSecurityKeysRepository } from '@/models/_.js';
+import type { MiMeta, UserSecurityKeysRepository } from '@/models/_.js';
 import type { Config } from '@/config.js';
 import { bindThis } from '@/decorators.js';
-import { MetaService } from '@/core/MetaService.js';
 import { MiUser } from '@/models/_.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 import type {
@@ -23,7 +22,6 @@ import type {
 	AuthenticatorTransportFuture,
 	CredentialDeviceType,
 	PublicKeyCredentialCreationOptionsJSON,
-	PublicKeyCredentialDescriptorFuture,
 	PublicKeyCredentialRequestOptionsJSON,
 	RegistrationResponseJSON,
 } from '@simplewebauthn/types';
@@ -31,33 +29,33 @@ import type {
 @Injectable()
 export class WebAuthnService {
 	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
-
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
 		@Inject(DI.userSecurityKeysRepository)
 		private userSecurityKeysRepository: UserSecurityKeysRepository,
-
-		private metaService: MetaService,
 	) {
 	}
 
 	@bindThis
-	public async getRelyingParty(): Promise<{ origin: string; rpId: string; rpName: string; rpIcon?: string; }> {
-		const instance = await this.metaService.fetch();
+	public getRelyingParty(): { origin: string; rpId: string; rpName: string; rpIcon?: string; } {
 		return {
 			origin: this.config.url,
 			rpId: this.config.hostname,
-			rpName: instance.name ?? this.config.host,
-			rpIcon: instance.iconUrl ?? undefined,
+			rpName: this.meta.name ?? this.config.host,
+			rpIcon: this.meta.iconUrl ?? undefined,
 		};
 	}
 
 	@bindThis
 	public async initiateRegistration(userId: MiUser['id'], userName: string, userDisplayName?: string): Promise<PublicKeyCredentialCreationOptionsJSON> {
-		const relyingParty = await this.getRelyingParty();
+		const relyingParty = this.getRelyingParty();
 		const keys = await this.userSecurityKeysRepository.findBy({
 			userId: userId,
 		});
@@ -104,7 +102,7 @@ export class WebAuthnService {
 
 		await this.redisClient.del(`webauthn:challenge:${userId}`);
 
-		const relyingParty = await this.getRelyingParty();
+		const relyingParty = this.getRelyingParty();
 
 		let verification;
 		try {
@@ -143,7 +141,7 @@ export class WebAuthnService {
 
 	@bindThis
 	public async initiateAuthentication(userId: MiUser['id']): Promise<PublicKeyCredentialRequestOptionsJSON> {
-		const relyingParty = await this.getRelyingParty();
+		const relyingParty = this.getRelyingParty();
 		const keys = await this.userSecurityKeysRepository.findBy({
 			userId: userId,
 		});
@@ -166,6 +164,86 @@ export class WebAuthnService {
 		return authenticationOptions;
 	}
 
+	/**
+	 * Initiate Passkey Auth (Without specifying user)
+	 * @returns authenticationOptions
+	 */
+	@bindThis
+	public async initiateSignInWithPasskeyAuthentication(context: string): Promise<PublicKeyCredentialRequestOptionsJSON> {
+		const relyingParty = await this.getRelyingParty();
+
+		const authenticationOptions = await generateAuthenticationOptions({
+			rpID: relyingParty.rpId,
+			userVerification: 'preferred',
+		});
+
+		await this.redisClient.setex(`webauthn:challenge:${context}`, 90, authenticationOptions.challenge);
+
+		return authenticationOptions;
+	}
+
+	/**
+	 * Verify Webauthn AuthenticationCredential
+	 * @throws IdentifiableError
+	 * @returns If the challenge is successful, return the user ID. Otherwise, return null.
+	 */
+	@bindThis
+	public async verifySignInWithPasskeyAuthentication(context: string, response: AuthenticationResponseJSON): Promise<MiUser['id'] | null> {
+		const challenge = await this.redisClient.get(`webauthn:challenge:${context}`);
+
+		if (!challenge) {
+			throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', `challenge '${context}' not found`);
+		}
+
+		await this.redisClient.del(`webauthn:challenge:${context}`);
+
+		const key = await this.userSecurityKeysRepository.findOneBy({
+			id: response.id,
+		});
+
+		if (!key) {
+			throw new IdentifiableError('36b96a7d-b547-412d-aeed-2d611cdc8cdc', 'Unknown Webauthn key');
+		}
+
+		const relyingParty = await this.getRelyingParty();
+
+		let verification;
+		try {
+			verification = await verifyAuthenticationResponse({
+				response: response,
+				expectedChallenge: challenge,
+				expectedOrigin: relyingParty.origin,
+				expectedRPID: relyingParty.rpId,
+				authenticator: {
+					credentialID: key.id,
+					credentialPublicKey: Buffer.from(key.publicKey, 'base64url'),
+					counter: key.counter,
+					transports: key.transports ? key.transports as AuthenticatorTransportFuture[] : undefined,
+				},
+				requireUserVerification: true,
+			});
+		} catch (error) {
+			throw new IdentifiableError('b18c89a7-5b5e-4cec-bb5b-0419f332d430', `verification failed: ${error}`);
+		}
+
+		const { verified, authenticationInfo } = verification;
+
+		if (!verified) {
+			return null;
+		}
+
+		await this.userSecurityKeysRepository.update({
+			id: response.id,
+		}, {
+			lastUsed: new Date(),
+			counter: authenticationInfo.newCounter,
+			credentialDeviceType: authenticationInfo.credentialDeviceType,
+			credentialBackedUp: authenticationInfo.credentialBackedUp,
+		});
+
+		return key.userId;
+	}
+
 	@bindThis
 	public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> {
 		const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
@@ -209,7 +287,7 @@ export class WebAuthnService {
 			}
 		}
 
-		const relyingParty = await this.getRelyingParty();
+		const relyingParty = this.getRelyingParty();
 
 		let verification;
 		try {
diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts
new file mode 100644
index 000000000000..c826a28963ae
--- /dev/null
+++ b/packages/backend/src/core/WebhookTestService.ts
@@ -0,0 +1,471 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
+import { bindThis } from '@/decorators.js';
+import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+import { Packed } from '@/misc/json-schema.js';
+import { type WebhookEventTypes } from '@/models/Webhook.js';
+import { UserWebhookService } from '@/core/UserWebhookService.js';
+import { QueueService } from '@/core/QueueService.js';
+import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
+
+const oneDayMillis = 24 * 60 * 60 * 1000;
+
+type AbuseUserReportDto = Omit<MiAbuseUserReport, 'targetUser' | 'reporter' | 'assignee'> & {
+	targetUser: Packed<'UserLite'> | null,
+	reporter: Packed<'UserLite'> | null,
+	assignee: Packed<'UserLite'> | null,
+};
+
+function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseUserReportDto {
+	const result: MiAbuseUserReport = {
+		id: 'dummy-abuse-report1',
+		targetUserId: 'dummy-target-user',
+		targetUser: null,
+		reporterId: 'dummy-reporter-user',
+		reporter: null,
+		assigneeId: null,
+		assignee: null,
+		resolved: false,
+		forwarded: false,
+		comment: 'This is a dummy report for testing purposes.',
+		targetUserHost: null,
+		reporterHost: null,
+		resolvedAs: null,
+		moderationNote: 'foo',
+		...override,
+	};
+
+	return {
+		...result,
+		targetUser: result.targetUser ? toPackedUserLite(result.targetUser) : null,
+		reporter: result.reporter ? toPackedUserLite(result.reporter) : null,
+		assignee: result.assignee ? toPackedUserLite(result.assignee) : null,
+	};
+}
+
+function generateDummyUser(override?: Partial<MiUser>): MiUser {
+	return {
+		id: 'dummy-user-1',
+		updatedAt: new Date(Date.now() - oneDayMillis * 7),
+		lastFetchedAt: new Date(Date.now() - oneDayMillis * 5),
+		lastActiveDate: new Date(Date.now() - oneDayMillis * 3),
+		hideOnlineStatus: false,
+		username: 'dummy1',
+		usernameLower: 'dummy1',
+		name: 'DummyUser1',
+		followersCount: 10,
+		followingCount: 5,
+		movedToUri: null,
+		movedAt: null,
+		alsoKnownAs: null,
+		notesCount: 30,
+		avatarId: null,
+		avatar: null,
+		bannerId: null,
+		banner: null,
+		avatarUrl: null,
+		bannerUrl: null,
+		avatarBlurhash: null,
+		bannerBlurhash: null,
+		avatarDecorations: [],
+		tags: [],
+		isSuspended: false,
+		isLocked: false,
+		isBot: false,
+		isCat: true,
+		isRoot: false,
+		isExplorable: true,
+		isHibernated: false,
+		isDeleted: false,
+		requireSigninToViewContents: false,
+		makeNotesFollowersOnlyBefore: null,
+		makeNotesHiddenBefore: null,
+		emojis: [],
+		score: 0,
+		host: null,
+		inbox: null,
+		sharedInbox: null,
+		featured: null,
+		uri: null,
+		followersUri: null,
+		token: null,
+		...override,
+	};
+}
+
+function generateDummyNote(override?: Partial<MiNote>): MiNote {
+	return {
+		id: 'dummy-note-1',
+		replyId: null,
+		reply: null,
+		renoteId: null,
+		renote: null,
+		threadId: null,
+		text: 'This is a dummy note for testing purposes.',
+		name: null,
+		cw: null,
+		userId: 'dummy-user-1',
+		user: null,
+		localOnly: true,
+		reactionAcceptance: 'likeOnly',
+		renoteCount: 10,
+		repliesCount: 5,
+		clippedCount: 0,
+		reactions: {},
+		visibility: 'public',
+		uri: null,
+		url: null,
+		fileIds: [],
+		attachedFileTypes: [],
+		visibleUserIds: [],
+		mentions: [],
+		mentionedRemoteUsers: '[]',
+		reactionAndUserPairCache: [],
+		emojis: [],
+		tags: [],
+		hasPoll: false,
+		channelId: null,
+		channel: null,
+		userHost: null,
+		replyUserId: null,
+		replyUserHost: null,
+		renoteUserId: null,
+		renoteUserHost: null,
+		...override,
+	};
+}
+
+function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> {
+	return {
+		id: note.id,
+		createdAt: new Date().toISOString(),
+		deletedAt: null,
+		text: note.text,
+		cw: note.cw,
+		userId: note.userId,
+		user: toPackedUserLite(note.user ?? generateDummyUser()),
+		replyId: note.replyId,
+		renoteId: note.renoteId,
+		isHidden: false,
+		visibility: note.visibility,
+		mentions: note.mentions,
+		visibleUserIds: note.visibleUserIds,
+		fileIds: note.fileIds,
+		files: [],
+		tags: note.tags,
+		poll: null,
+		emojis: note.emojis,
+		channelId: note.channelId,
+		channel: note.channel,
+		localOnly: note.localOnly,
+		reactionAcceptance: note.reactionAcceptance,
+		reactionEmojis: {},
+		reactions: {},
+		reactionCount: 0,
+		renoteCount: note.renoteCount,
+		repliesCount: note.repliesCount,
+		uri: note.uri ?? undefined,
+		url: note.url ?? undefined,
+		reactionAndUserPairCache: note.reactionAndUserPairCache,
+		...(detail ? {
+			clippedCount: note.clippedCount,
+			reply: note.reply ? toPackedNote(note.reply, false) : null,
+			renote: note.renote ? toPackedNote(note.renote, true) : null,
+			myReaction: null,
+		} : {}),
+		...override,
+	};
+}
+
+function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> {
+	return {
+		id: user.id,
+		name: user.name,
+		username: user.username,
+		host: user.host,
+		avatarUrl: user.avatarUrl,
+		avatarBlurhash: user.avatarBlurhash,
+		avatarDecorations: user.avatarDecorations.map(it => ({
+			id: it.id,
+			angle: it.angle,
+			flipH: it.flipH,
+			url: 'https://example.com/dummy-image001.png',
+			offsetX: it.offsetX,
+			offsetY: it.offsetY,
+		})),
+		isBot: user.isBot,
+		isCat: user.isCat,
+		emojis: user.emojis,
+		onlineStatus: 'active',
+		badgeRoles: [],
+		...override,
+	};
+}
+
+function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> {
+	return {
+		...toPackedUserLite(user),
+		url: null,
+		uri: null,
+		movedTo: null,
+		alsoKnownAs: [],
+		createdAt: new Date().toISOString(),
+		updatedAt: user.updatedAt?.toISOString() ?? null,
+		lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
+		bannerUrl: user.bannerUrl,
+		bannerBlurhash: user.bannerBlurhash,
+		isLocked: user.isLocked,
+		isSilenced: false,
+		isSuspended: user.isSuspended,
+		description: null,
+		location: null,
+		birthday: null,
+		lang: null,
+		fields: [],
+		verifiedLinks: [],
+		followersCount: user.followersCount,
+		followingCount: user.followingCount,
+		notesCount: user.notesCount,
+		pinnedNoteIds: [],
+		pinnedNotes: [],
+		pinnedPageId: null,
+		pinnedPage: null,
+		publicReactions: true,
+		followersVisibility: 'public',
+		followingVisibility: 'public',
+		twoFactorEnabled: false,
+		usePasswordLessLogin: false,
+		securityKeys: false,
+		roles: [],
+		memo: null,
+		moderationNote: undefined,
+		isFollowing: false,
+		isFollowed: false,
+		hasPendingFollowRequestFromYou: false,
+		hasPendingFollowRequestToYou: false,
+		isBlocking: false,
+		isBlocked: false,
+		isMuted: false,
+		isRenoteMuted: false,
+		notify: 'none',
+		withReplies: true,
+		...override,
+	};
+}
+
+const dummyUser1 = generateDummyUser();
+const dummyUser2 = generateDummyUser({
+	id: 'dummy-user-2',
+	updatedAt: new Date(Date.now() - oneDayMillis * 30),
+	lastFetchedAt: new Date(Date.now() - oneDayMillis),
+	lastActiveDate: new Date(Date.now() - oneDayMillis),
+	username: 'dummy2',
+	usernameLower: 'dummy2',
+	name: 'DummyUser2',
+	followersCount: 40,
+	followingCount: 50,
+	notesCount: 900,
+});
+const dummyUser3 = generateDummyUser({
+	id: 'dummy-user-3',
+	updatedAt: new Date(Date.now() - oneDayMillis * 15),
+	lastFetchedAt: new Date(Date.now() - oneDayMillis * 2),
+	lastActiveDate: new Date(Date.now() - oneDayMillis * 2),
+	username: 'dummy3',
+	usernameLower: 'dummy3',
+	name: 'DummyUser3',
+	followersCount: 60,
+	followingCount: 70,
+	notesCount: 15900,
+});
+
+@Injectable()
+export class WebhookTestService {
+	public static NoSuchWebhookError = class extends Error {
+	};
+
+	constructor(
+		private userWebhookService: UserWebhookService,
+		private systemWebhookService: SystemWebhookService,
+		private queueService: QueueService,
+	) {
+	}
+
+	/**
+	 * UserWebhookのテスト送信を行う.
+	 * 送信されるペイロードはいずれもダミーの値で、実際にはデータベース上に存在しない.
+	 *
+	 * また、この関数経由で送信されるWebhookは以下の設定を無視する.
+	 * - Webhookそのものの有効・無効設定(active)
+	 * - 送信対象イベント(on)に関する設定
+	 */
+	@bindThis
+	public async testUserWebhook(
+		params: {
+			webhookId: MiWebhook['id'],
+			type: WebhookEventTypes,
+			override?: Partial<Omit<MiWebhook, 'id'>>,
+		},
+		sender: MiUser | null,
+	) {
+		const webhooks = await this.userWebhookService.fetchWebhooks({ ids: [params.webhookId] })
+			.then(it => it.filter(it => it.userId === sender?.id));
+		if (webhooks.length === 0) {
+			throw new WebhookTestService.NoSuchWebhookError();
+		}
+
+		const webhook = webhooks[0];
+		const send = (contents: unknown) => {
+			const merged = {
+				...webhook,
+				...params.override,
+			};
+
+			// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
+			// また、Jobの試行回数も1回だけ.
+			this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
+		};
+
+		const dummyNote1 = generateDummyNote({
+			userId: dummyUser1.id,
+			user: dummyUser1,
+		});
+		const dummyReply1 = generateDummyNote({
+			id: 'dummy-reply-1',
+			replyId: dummyNote1.id,
+			reply: dummyNote1,
+			userId: dummyUser1.id,
+			user: dummyUser1,
+		});
+		const dummyRenote1 = generateDummyNote({
+			id: 'dummy-renote-1',
+			renoteId: dummyNote1.id,
+			renote: dummyNote1,
+			userId: dummyUser2.id,
+			user: dummyUser2,
+			text: null,
+		});
+		const dummyMention1 = generateDummyNote({
+			id: 'dummy-mention-1',
+			userId: dummyUser1.id,
+			user: dummyUser1,
+			text: `@${dummyUser2.username} This is a mention to you.`,
+			mentions: [dummyUser2.id],
+		});
+
+		switch (params.type) {
+			case 'note': {
+				send(toPackedNote(dummyNote1));
+				break;
+			}
+			case 'reply': {
+				send(toPackedNote(dummyReply1));
+				break;
+			}
+			case 'renote': {
+				send(toPackedNote(dummyRenote1));
+				break;
+			}
+			case 'mention': {
+				send(toPackedNote(dummyMention1));
+				break;
+			}
+			case 'follow': {
+				send(toPackedUserDetailedNotMe(dummyUser1));
+				break;
+			}
+			case 'followed': {
+				send(toPackedUserLite(dummyUser2));
+				break;
+			}
+			case 'unfollow': {
+				send(toPackedUserDetailedNotMe(dummyUser3));
+				break;
+			}
+		}
+	}
+
+	/**
+	 * SystemWebhookのテスト送信を行う.
+	 * 送信されるペイロードはいずれもダミーの値で、実際にはデータベース上に存在しない.
+	 *
+	 * また、この関数経由で送信されるWebhookは以下の設定を無視する.
+	 * - Webhookそのものの有効・無効設定(isActive)
+	 * - 送信対象イベント(on)に関する設定
+	 */
+	@bindThis
+	public async testSystemWebhook(
+		params: {
+			webhookId: MiSystemWebhook['id'],
+			type: SystemWebhookEventType,
+			override?: Partial<Omit<MiSystemWebhook, 'id'>>,
+		},
+	) {
+		const webhooks = await this.systemWebhookService.fetchSystemWebhooks({ ids: [params.webhookId] });
+		if (webhooks.length === 0) {
+			throw new WebhookTestService.NoSuchWebhookError();
+		}
+
+		const webhook = webhooks[0];
+		const send = (contents: unknown) => {
+			const merged = {
+				...webhook,
+				...params.override,
+			};
+
+			// テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
+			// また、Jobの試行回数も1回だけ.
+			this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 });
+		};
+
+		switch (params.type) {
+			case 'abuseReport': {
+				send(generateAbuseReport({
+					targetUserId: dummyUser1.id,
+					targetUser: dummyUser1,
+					reporterId: dummyUser2.id,
+					reporter: dummyUser2,
+				}));
+				break;
+			}
+			case 'abuseReportResolved': {
+				send(generateAbuseReport({
+					targetUserId: dummyUser1.id,
+					targetUser: dummyUser1,
+					reporterId: dummyUser2.id,
+					reporter: dummyUser2,
+					assigneeId: dummyUser3.id,
+					assignee: dummyUser3,
+					resolved: true,
+				}));
+				break;
+			}
+			case 'userCreated': {
+				send(toPackedUserLite(dummyUser1));
+				break;
+			}
+			case 'inactiveModeratorsWarning': {
+				const dummyTime: ModeratorInactivityRemainingTime = {
+					time: 100000,
+					asDays: 1,
+					asHours: 24,
+				};
+
+				send({
+					remainingTime: dummyTime,
+				});
+				break;
+			}
+			case 'inactiveModeratorsInvitationOnlyChanged': {
+				send({});
+				break;
+			}
+		}
+	}
+}
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index e2164fec1d93..376c9c015174 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -17,14 +17,13 @@ import { NoteCreateService } from '@/core/NoteCreateService.js';
 import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
 import { AppLockService } from '@/core/AppLockService.js';
 import type Logger from '@/logger.js';
-import { MetaService } from '@/core/MetaService.js';
 import { IdService } from '@/core/IdService.js';
 import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { QueueService } from '@/core/QueueService.js';
-import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import type { MiRemoteUser } from '@/models/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
@@ -48,6 +47,9 @@ export class ApInboxService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -64,7 +66,6 @@ export class ApInboxService {
 		private noteEntityService: NoteEntityService,
 		private utilityService: UtilityService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private abuseReportService: AbuseReportService,
 		private userFollowingService: UserFollowingService,
 		private apAudienceService: ApAudienceService,
@@ -289,9 +290,8 @@ export class ApInboxService {
 			return;
 		}
 
-		// アナウンス先をブロックしてたら中断
-		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) return;
+		// アナウンス先が許可されているかチェック
+		if (!this.utilityService.isFederationAllowedUri(uri)) return;
 
 		const unlock = await this.appLockService.getApLock(uri);
 
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 98e944f347a5..5617a29bab22 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -494,6 +494,10 @@ export class ApRendererService {
 			name: user.name,
 			summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
 			_misskey_summary: profile.description,
+			_misskey_followedMessage: profile.followedMessage,
+			_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
+			_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
+			_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
 			icon: avatar ? this.renderImage(avatar) : null,
 			image: banner ? this.renderImage(banner) : null,
 			tag,
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index 7cf8359212c1..c7d19adfd5fa 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -205,18 +205,47 @@ export class ApRequestService {
 		//#region リクエスト先がhtmlかつactivity+jsonへのalternate linkタグがあるとき
 		const contentType = res.headers.get('content-type');
 
-		if ((contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' && _followAlternate === true) {
+		if (
+			res.ok &&
+			(contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html' &&
+			_followAlternate === true
+		) {
 			const html = await res.text();
-			const window = new Window();
+			const { window, happyDOM } = new Window({
+				settings: {
+					disableJavaScriptEvaluation: true,
+					disableJavaScriptFileLoading: true,
+					disableCSSFileLoading: true,
+					disableComputedStyleRendering: true,
+					handleDisabledFileLoadingAsSuccess: true,
+					navigation: {
+						disableMainFrameNavigation: true,
+						disableChildFrameNavigation: true,
+						disableChildPageNavigation: true,
+						disableFallbackToSetURL: true,
+					},
+					timer: {
+						maxTimeout: 0,
+						maxIntervalTime: 0,
+						maxIntervalIterations: 0,
+					},
+				},
+			});
 			const document = window.document;
-			document.documentElement.innerHTML = html;
-
-			const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]');
-			if (alternate) {
-				const href = alternate.getAttribute('href');
-				if (href) {
-					return await this.signedGet(href, user, false);
+			try {
+				document.documentElement.innerHTML = html;
+
+				const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]');
+				if (alternate) {
+					const href = alternate.getAttribute('href');
+					if (href) {
+						return await this.signedGet(href, user, false);
+					}
 				}
+			} catch (e) {
+				// something went wrong parsing the HTML, ignore the whole thing
+			} finally {
+				happyDOM.close().catch(err => {});
 			}
 		}
 		//#endregion
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index bb3c40f0939b..ca35608d9bdc 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
 import { IsNull, Not } from 'typeorm';
 import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
 import { InstanceActorService } from '@/core/InstanceActorService.js';
-import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
+import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js';
 import type { Config } from '@/config.js';
-import { MetaService } from '@/core/MetaService.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
 import { DI } from '@/di-symbols.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -29,6 +28,7 @@ export class Resolver {
 
 	constructor(
 		private config: Config,
+		private meta: MiMeta,
 		private usersRepository: UsersRepository,
 		private notesRepository: NotesRepository,
 		private pollsRepository: PollsRepository,
@@ -36,7 +36,6 @@ export class Resolver {
 		private followRequestsRepository: FollowRequestsRepository,
 		private utilityService: UtilityService,
 		private instanceActorService: InstanceActorService,
-		private metaService: MetaService,
 		private apRequestService: ApRequestService,
 		private httpRequestService: HttpRequestService,
 		private apRendererService: ApRendererService,
@@ -94,8 +93,7 @@ export class Resolver {
 			return await this.resolveLocal(value);
 		}
 
-		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
+		if (!this.utilityService.isFederationAllowedHost(host)) {
 			throw new Error('Instance is blocked');
 		}
 
@@ -178,6 +176,9 @@ export class ApResolverService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -195,7 +196,6 @@ export class ApResolverService {
 
 		private utilityService: UtilityService,
 		private instanceActorService: InstanceActorService,
-		private metaService: MetaService,
 		private apRequestService: ApRequestService,
 		private httpRequestService: HttpRequestService,
 		private apRendererService: ApRendererService,
@@ -208,6 +208,7 @@ export class ApResolverService {
 	public createResolver(): Resolver {
 		return new Resolver(
 			this.config,
+			this.meta,
 			this.usersRepository,
 			this.notesRepository,
 			this.pollsRepository,
@@ -215,7 +216,6 @@ export class ApResolverService {
 			this.followRequestsRepository,
 			this.utilityService,
 			this.instanceActorService,
-			this.metaService,
 			this.apRequestService,
 			this.httpRequestService,
 			this.apRendererService,
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index feb8c42c563c..94cb0785cbb2 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -554,6 +554,10 @@ const extension_context_definition = {
 	'_misskey_reaction': 'misskey:_misskey_reaction',
 	'_misskey_votes': 'misskey:_misskey_votes',
 	'_misskey_summary': 'misskey:_misskey_summary',
+	'_misskey_followedMessage': 'misskey:_misskey_followedMessage',
+	'_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents',
+	'_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore',
+	'_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore',
 	'isCat': 'misskey:isCat',
 	// vcard
 	vcard: 'http://www.w3.org/2006/vcard/ns#',
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index 369196727014..e7ece87b01e1 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -5,10 +5,9 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import type { DriveFilesRepository } from '@/models/_.js';
+import type { DriveFilesRepository, MiMeta } from '@/models/_.js';
 import type { MiRemoteUser } from '@/models/User.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
-import { MetaService } from '@/core/MetaService.js';
 import { truncate } from '@/misc/truncate.js';
 import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 import { DriveService } from '@/core/DriveService.js';
@@ -24,10 +23,12 @@ export class ApImageService {
 	private logger: Logger;
 
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
 
-		private metaService: MetaService,
 		private apResolverService: ApResolverService,
 		private driveService: DriveService,
 		private apLoggerService: ApLoggerService,
@@ -63,12 +64,10 @@ export class ApImageService {
 
 		this.logger.info(`Creating the Image: ${image.url}`);
 
-		const instance = await this.metaService.fetch();
-
 		// Cache if remote file cache is on AND either
 		// 1. remote sensitive file is also on
 		// 2. or the image is not sensitive
-		const shouldBeCached = instance.cacheRemoteFiles && (instance.cacheRemoteSensitiveFiles || !image.sensitive);
+		const shouldBeCached = this.meta.cacheRemoteFiles && (this.meta.cacheRemoteSensitiveFiles || !image.sensitive);
 
 		const file = await this.driveService.uploadFromUrl({
 			url: image.url,
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 5b75da22a033..2d333b3634d6 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -6,13 +6,12 @@
 import { forwardRef, Inject, Injectable } from '@nestjs/common';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { PollsRepository, EmojisRepository } from '@/models/_.js';
+import type { PollsRepository, EmojisRepository, MiMeta } from '@/models/_.js';
 import type { Config } from '@/config.js';
 import type { MiRemoteUser } from '@/models/User.js';
 import type { MiNote } from '@/models/Note.js';
 import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
 import type { MiEmoji } from '@/models/Emoji.js';
-import { MetaService } from '@/core/MetaService.js';
 import { AppLockService } from '@/core/AppLockService.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import { NoteCreateService } from '@/core/NoteCreateService.js';
@@ -46,6 +45,9 @@ export class ApNoteService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.pollsRepository)
 		private pollsRepository: PollsRepository,
 
@@ -65,7 +67,6 @@ export class ApNoteService {
 		private apMentionService: ApMentionService,
 		private apImageService: ApImageService,
 		private apQuestionService: ApQuestionService,
-		private metaService: MetaService,
 		private appLockService: AppLockService,
 		private pollService: PollService,
 		private noteCreateService: NoteCreateService,
@@ -182,7 +183,7 @@ export class ApNoteService {
 		/**
 		 * 禁止ワードチェック
 		 */
-		const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
+		const hasProhibitedWords = this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
 		if (hasProhibitedWords) {
 			throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
 		}
@@ -335,9 +336,7 @@ export class ApNoteService {
 	public async resolveNote(value: string | IObject, options: { sentFrom?: URL, resolver?: Resolver } = {}): Promise<MiNote | null> {
 		const uri = getApId(value);
 
-		// ブロックしていたら中断
-		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) {
+		if (!this.utilityService.isFederationAllowedUri(uri)) {
 			throw new StatusError('blocked host', 451);
 		}
 
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index f3ddf3952c84..c9de67b3a006 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -8,7 +8,7 @@ import promiseLimit from 'promise-limit';
 import { DataSource } from 'typeorm';
 import { ModuleRef } from '@nestjs/core';
 import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
+import type { FollowingsRepository, InstancesRepository, MiMeta, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/_.js';
 import type { Config } from '@/config.js';
 import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
 import { MiUser } from '@/models/User.js';
@@ -35,7 +35,6 @@ import type { UtilityService } from '@/core/UtilityService.js';
 import type { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import { RoleService } from '@/core/RoleService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import type { AccountMoveService } from '@/core/AccountMoveService.js';
 import { checkHttps } from '@/misc/check-https.js';
@@ -46,7 +45,7 @@ import type { ApNoteService } from './ApNoteService.js';
 import type { ApMfmService } from '../ApMfmService.js';
 import type { ApResolverService, Resolver } from '../ApResolverService.js';
 import type { ApLoggerService } from '../ApLoggerService.js';
-// eslint-disable-next-line @typescript-eslint/consistent-type-imports
+
 import type { ApImageService } from './ApImageService.js';
 import type { IActor, ICollection, IObject, IOrderedCollection } from '../type.js';
 
@@ -62,7 +61,6 @@ export class ApPersonService implements OnModuleInit {
 	private driveFileEntityService: DriveFileEntityService;
 	private idService: IdService;
 	private globalEventService: GlobalEventService;
-	private metaService: MetaService;
 	private federatedInstanceService: FederatedInstanceService;
 	private fetchInstanceMetadataService: FetchInstanceMetadataService;
 	private cacheService: CacheService;
@@ -84,6 +82,9 @@ export class ApPersonService implements OnModuleInit {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.db)
 		private db: DataSource,
 
@@ -112,7 +113,6 @@ export class ApPersonService implements OnModuleInit {
 		this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
 		this.idService = this.moduleRef.get('IdService');
 		this.globalEventService = this.moduleRef.get('GlobalEventService');
-		this.metaService = this.moduleRef.get('MetaService');
 		this.federatedInstanceService = this.moduleRef.get('FederatedInstanceService');
 		this.fetchInstanceMetadataService = this.moduleRef.get('FetchInstanceMetadataService');
 		this.cacheService = this.moduleRef.get('CacheService');
@@ -232,6 +232,12 @@ export class ApPersonService implements OnModuleInit {
 		if (user == null) throw new Error('failed to create user: user is null');
 
 		const [avatar, banner] = await Promise.all([icon, image].map(img => {
+			// icon and image may be arrays
+			// see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon
+			if (Array.isArray(img)) {
+				img = img.find(item => item && item.url) ?? null;
+			}
+			
 			// if we have an explicitly missing image, return an
 			// explicitly-null set of values
 			if ((img == null) || (typeof img === 'object' && img.url == null)) {
@@ -307,8 +313,8 @@ export class ApPersonService implements OnModuleInit {
 						this.logger.error('error occurred while fetching following/followers collection', { stack: err });
 					}
 					return 'private';
-				})
-			)
+				}),
+			),
 		);
 
 		const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -356,6 +362,9 @@ export class ApPersonService implements OnModuleInit {
 					tags,
 					isBot,
 					isCat: (person as any).isCat === true,
+					requireSigninToViewContents: (person as any).requireSigninToViewContents === true,
+					makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null,
+					makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null,
 					emojis,
 				})) as MiRemoteUser;
 
@@ -370,6 +379,7 @@ export class ApPersonService implements OnModuleInit {
 				await transactionalEntityManager.save(new MiUserProfile({
 					userId: user.id,
 					description: _description,
+					followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
 					url,
 					fields,
 					followingVisibility,
@@ -407,13 +417,15 @@ export class ApPersonService implements OnModuleInit {
 		this.cacheService.uriPersonCache.set(user.uri, user);
 
 		// Register host
-		this.federatedInstanceService.fetch(host).then(async i => {
-			this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
-			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
-			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-				this.instanceChart.newUser(i.host);
-			}
-		});
+		if (this.meta.enableStatsForFederatedInstances) {
+			this.federatedInstanceService.fetchOrRegister(host).then(i => {
+				this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
+				if (this.meta.enableChartsForFederatedInstances) {
+					this.instanceChart.newUser(i.host);
+				}
+				this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
+			});
+		}
 
 		this.usersChart.update(user, true);
 
@@ -494,8 +506,8 @@ export class ApPersonService implements OnModuleInit {
 						return undefined;
 					}
 					return 'private';
-				})
-			)
+				}),
+			),
 		);
 
 		const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -566,6 +578,7 @@ export class ApPersonService implements OnModuleInit {
 			url,
 			fields,
 			description: _description,
+			followedMessage: person._misskey_followedMessage != null ? truncate(person._misskey_followedMessage, 256) : null,
 			followingVisibility,
 			followersVisibility,
 			birthday: bday?.[0] ?? null,
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 16812b7a4d7b..7496315f0970 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -13,6 +13,10 @@ export interface IObject {
 	name?: string | null;
 	summary?: string;
 	_misskey_summary?: string;
+	_misskey_followedMessage?: string | null;
+	_misskey_requireSigninToViewContents?: boolean;
+	_misskey_makeNotesFollowersOnlyBefore?: number | null;
+	_misskey_makeNotesHiddenBefore?: number | null;
 	published?: string;
 	cc?: ApObject;
 	to?: ApObject;
diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts
index f40a26495da9..c9b43cc66d7c 100644
--- a/packages/backend/src/core/chart/charts/federation.ts
+++ b/packages/backend/src/core/chart/charts/federation.ts
@@ -5,10 +5,9 @@
 
 import { Injectable, Inject } from '@nestjs/common';
 import { DataSource } from 'typeorm';
-import type { FollowingsRepository, InstancesRepository } from '@/models/_.js';
+import type { FollowingsRepository, InstancesRepository, MiMeta } from '@/models/_.js';
 import { AppLockService } from '@/core/AppLockService.js';
 import { DI } from '@/di-symbols.js';
-import { MetaService } from '@/core/MetaService.js';
 import { bindThis } from '@/decorators.js';
 import Chart from '../core.js';
 import { ChartLoggerService } from '../ChartLoggerService.js';
@@ -24,13 +23,15 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 		@Inject(DI.db)
 		private db: DataSource,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
 		@Inject(DI.instancesRepository)
 		private instancesRepository: InstancesRepository,
 
-		private metaService: MetaService,
 		private appLockService: AppLockService,
 		private chartLoggerService: ChartLoggerService,
 	) {
@@ -43,8 +44,6 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 	}
 
 	protected async tickMinor(): Promise<Partial<KVs<typeof schema>>> {
-		const meta = await this.metaService.fetch();
-
 		const suspendedInstancesQuery = this.instancesRepository.createQueryBuilder('instance')
 			.select('instance.host')
 			.where('instance.suspensionState != \'none\'');
@@ -65,21 +64,21 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 			this.followingsRepository.createQueryBuilder('following')
 				.select('COUNT(DISTINCT following.followeeHost)')
 				.where('following.followeeHost IS NOT NULL')
-				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
+				.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 				.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
 				.getRawOne()
 				.then(x => parseInt(x.count, 10)),
 			this.followingsRepository.createQueryBuilder('following')
 				.select('COUNT(DISTINCT following.followerHost)')
 				.where('following.followerHost IS NOT NULL')
-				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
+				.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followerHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 				.andWhere(`following.followerHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
 				.getRawOne()
 				.then(x => parseInt(x.count, 10)),
 			this.followingsRepository.createQueryBuilder('following')
 				.select('COUNT(DISTINCT following.followeeHost)')
 				.where('following.followeeHost IS NOT NULL')
-				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
+				.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'following.followeeHost NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 				.andWhere(`following.followeeHost NOT IN (${ suspendedInstancesQuery.getQuery() })`)
 				.andWhere(`following.followeeHost IN (${ pubsubSubQuery.getQuery() })`)
 				.setParameters(pubsubSubQuery.getParameters())
@@ -88,7 +87,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 			this.instancesRepository.createQueryBuilder('instance')
 				.select('COUNT(instance.id)')
 				.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
-				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
+				.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 				.andWhere('instance.suspensionState = \'none\'')
 				.andWhere('instance.isNotResponding = false')
 				.getRawOne()
@@ -96,7 +95,7 @@ export default class FederationChart extends Chart<typeof schema> { // eslint-di
 			this.instancesRepository.createQueryBuilder('instance')
 				.select('COUNT(instance.id)')
 				.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
-				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
+				.andWhere(this.meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT ILIKE ALL(ARRAY[:...blocked])', { blocked: this.meta.blockedHosts.flatMap(x => [x, `%.${x}`]) })
 				.andWhere('instance.suspensionState = \'none\'')
 				.andWhere('instance.isNotResponding = false')
 				.getRawOne()
diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
index a13c244c19a1..70ead890ab45 100644
--- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
+++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
@@ -53,6 +53,8 @@ export class AbuseUserReportEntityService {
 				schema: 'UserDetailedNotMe',
 			}) : null,
 			forwarded: report.forwarded,
+			resolvedAs: report.resolvedAs,
+			moderationNote: report.moderationNote,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index 4aa7104c1e32..7b0150f5b68f 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -5,10 +5,8 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import type { FlashsRepository, FlashLikesRepository } from '@/models/_.js';
-import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { FlashLikesRepository, FlashsRepository } from '@/models/_.js';
 import type { Packed } from '@/misc/json-schema.js';
-import type { } from '@/models/Blocking.js';
 import type { MiUser } from '@/models/User.js';
 import type { MiFlash } from '@/models/Flash.js';
 import { bindThis } from '@/decorators.js';
@@ -20,10 +18,8 @@ export class FlashEntityService {
 	constructor(
 		@Inject(DI.flashsRepository)
 		private flashsRepository: FlashsRepository,
-
 		@Inject(DI.flashLikesRepository)
 		private flashLikesRepository: FlashLikesRepository,
-
 		private userEntityService: UserEntityService,
 		private idService: IdService,
 	) {
@@ -34,25 +30,36 @@ export class FlashEntityService {
 		src: MiFlash['id'] | MiFlash,
 		me?: { id: MiUser['id'] } | null | undefined,
 		hint?: {
-			packedUser?: Packed<'UserLite'>
+			packedUser?: Packed<'UserLite'>,
+			likedFlashIds?: MiFlash['id'][],
 		},
 	): Promise<Packed<'Flash'>> {
 		const meId = me ? me.id : null;
 		const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src });
 
-		return await awaitAll({
+		// { schema: 'UserDetailed' } すると無限ループするので注意
+		const user = hint?.packedUser ?? await this.userEntityService.pack(flash.user ?? flash.userId, me);
+
+		let isLiked = undefined;
+		if (meId) {
+			isLiked = hint?.likedFlashIds
+				? hint.likedFlashIds.includes(flash.id)
+				: await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } });
+		}
+
+		return {
 			id: flash.id,
 			createdAt: this.idService.parse(flash.id).date.toISOString(),
 			updatedAt: flash.updatedAt.toISOString(),
 			userId: flash.userId,
-			user: hint?.packedUser ?? this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
+			user: user,
 			title: flash.title,
 			summary: flash.summary,
 			script: flash.script,
 			visibility: flash.visibility,
 			likedCount: flash.likedCount,
-			isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined,
-		});
+			isLiked: isLiked,
+		};
 	}
 
 	@bindThis
@@ -63,7 +70,19 @@ export class FlashEntityService {
 		const _users = flashes.map(({ user, userId }) => user ?? userId);
 		const _userMap = await this.userEntityService.packMany(_users, me)
 			.then(users => new Map(users.map(u => [u.id, u])));
-		return Promise.all(flashes.map(flash => this.pack(flash, me, { packedUser: _userMap.get(flash.userId) })));
+		const _likedFlashIds = me
+			? await this.flashLikesRepository.createQueryBuilder('flashLike')
+				.select('flashLike.flashId')
+				.where('flashLike.userId = :userId', { userId: me.id })
+				.getRawMany<{ flashLike_flashId: string }>()
+				.then(likes => [...new Set(likes.map(like => like.flashLike_flashId))])
+			: [];
+		return Promise.all(
+			flashes.map(flash => this.pack(flash, me, {
+				packedUser: _userMap.get(flash.userId),
+				likedFlashIds: _likedFlashIds,
+			})),
+		);
 	}
 }
 
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index 4956bc22cefd..284537b9861f 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -3,19 +3,22 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import type { Packed } from '@/misc/json-schema.js';
 import type { MiInstance } from '@/models/Instance.js';
-import { MetaService } from '@/core/MetaService.js';
 import { bindThis } from '@/decorators.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { RoleService } from '@/core/RoleService.js';
 import { MiUser } from '@/models/User.js';
+import { DI } from '@/di-symbols.js';
+import { MiMeta } from '@/models/_.js';
 
 @Injectable()
 export class InstanceEntityService {
 	constructor(
-		private metaService: MetaService,
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		private roleService: RoleService,
 
 		private utilityService: UtilityService,
@@ -27,7 +30,6 @@ export class InstanceEntityService {
 		instance: MiInstance,
 		me?: { id: MiUser['id']; } | null | undefined,
 	): Promise<Packed<'FederationInstance'>> {
-		const meta = await this.metaService.fetch();
 		const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
 
 		return {
@@ -41,7 +43,7 @@ export class InstanceEntityService {
 			isNotResponding: instance.isNotResponding,
 			isSuspended: instance.suspensionState !== 'none',
 			suspensionState: instance.suspensionState,
-			isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host),
+			isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host),
 			softwareName: instance.softwareName,
 			softwareVersion: instance.softwareVersion,
 			openRegistrations: instance.openRegistrations,
@@ -49,8 +51,8 @@ export class InstanceEntityService {
 			description: instance.description,
 			maintainerName: instance.maintainerName,
 			maintainerEmail: instance.maintainerEmail,
-			isSilenced: this.utilityService.isSilencedHost(meta.silencedHosts, instance.host),
-			isMediaSilenced: this.utilityService.isMediaSilencedHost(meta.mediaSilencedHosts, instance.host),
+			isSilenced: this.utilityService.isSilencedHost(this.meta.silencedHosts, instance.host),
+			isMediaSilenced: this.utilityService.isMediaSilencedHost(this.meta.mediaSilencedHosts, instance.host),
 			iconUrl: instance.iconUrl,
 			faviconUrl: instance.faviconUrl,
 			themeColor: instance.themeColor,
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index f4b1e302d0f8..409dca34263b 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -10,7 +10,6 @@ import type { Packed } from '@/misc/json-schema.js';
 import type { MiMeta } from '@/models/Meta.js';
 import type { AdsRepository } from '@/models/_.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { MetaService } from '@/core/MetaService.js';
 import { bindThis } from '@/decorators.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { InstanceActorService } from '@/core/InstanceActorService.js';
@@ -24,11 +23,13 @@ export class MetaEntityService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.adsRepository)
 		private adsRepository: AdsRepository,
 
 		private userEntityService: UserEntityService,
-		private metaService: MetaService,
 		private instanceActorService: InstanceActorService,
 	) { }
 
@@ -37,7 +38,7 @@ export class MetaEntityService {
 		let instance = meta;
 
 		if (!instance) {
-			instance = await this.metaService.fetch();
+			instance = this.meta;
 		}
 
 		const ads = await this.adsRepository.createQueryBuilder('ads')
@@ -95,6 +96,7 @@ export class MetaEntityService {
 			recaptchaSiteKey: instance.recaptchaSiteKey,
 			enableTurnstile: instance.enableTurnstile,
 			turnstileSiteKey: instance.turnstileSiteKey,
+			enableTestcaptcha: instance.enableTestcaptcha,
 			swPublickey: instance.swPublicKey,
 			themeColor: instance.themeColor,
 			mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
@@ -140,7 +142,7 @@ export class MetaEntityService {
 		let instance = meta;
 
 		if (!instance) {
-			instance = await this.metaService.fetch();
+			instance = this.meta;
 		}
 
 		const packed = await this.pack(instance);
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 2cd092231cf5..96cc6b028ec0 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -11,29 +11,57 @@ import type { Packed } from '@/misc/json-schema.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import type { MiUser } from '@/models/User.js';
 import type { MiNote } from '@/models/Note.js';
-import type { MiNoteReaction } from '@/models/NoteReaction.js';
-import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository, MiMeta } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import { DebounceLoader } from '@/misc/loader.js';
 import { IdService } from '@/core/IdService.js';
+import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
 import type { OnModuleInit } from '@nestjs/common';
 import type { CustomEmojiService } from '../CustomEmojiService.js';
 import type { ReactionService } from '../ReactionService.js';
 import type { UserEntityService } from './UserEntityService.js';
 import type { DriveFileEntityService } from './DriveFileEntityService.js';
 
+// is-renote.tsとよしなにリンク
+function isPureRenote(note: MiNote): note is MiNote & { renoteId: MiNote['id']; renote: MiNote } {
+	return (
+		note.renote != null &&
+		note.reply == null &&
+		note.text == null &&
+		note.cw == null &&
+		(note.fileIds == null || note.fileIds.length === 0) &&
+		!note.hasPoll
+	);
+}
+
+function getAppearNoteIds(notes: MiNote[]): Set<string> {
+	const appearNoteIds = new Set<string>();
+	for (const note of notes) {
+		if (isPureRenote(note)) {
+			appearNoteIds.add(note.renoteId);
+		} else {
+			appearNoteIds.add(note.id);
+		}
+	}
+	return appearNoteIds;
+}
+
 @Injectable()
 export class NoteEntityService implements OnModuleInit {
 	private userEntityService: UserEntityService;
 	private driveFileEntityService: DriveFileEntityService;
 	private customEmojiService: CustomEmojiService;
 	private reactionService: ReactionService;
+	private reactionsBufferingService: ReactionsBufferingService;
 	private idService: IdService;
 	private noteLoader = new DebounceLoader(this.findNoteOrFail);
 
 	constructor(
 		private moduleRef: ModuleRef,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -59,6 +87,8 @@ export class NoteEntityService implements OnModuleInit {
 		//private driveFileEntityService: DriveFileEntityService,
 		//private customEmojiService: CustomEmojiService,
 		//private reactionService: ReactionService,
+		//private reactionsBufferingService: ReactionsBufferingService,
+		//private idService: IdService,
 	) {
 	}
 
@@ -67,54 +97,85 @@ export class NoteEntityService implements OnModuleInit {
 		this.driveFileEntityService = this.moduleRef.get('DriveFileEntityService');
 		this.customEmojiService = this.moduleRef.get('CustomEmojiService');
 		this.reactionService = this.moduleRef.get('ReactionService');
+		this.reactionsBufferingService = this.moduleRef.get('ReactionsBufferingService');
 		this.idService = this.moduleRef.get('IdService');
 	}
 
 	@bindThis
-	private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) {
+	private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
+		// FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある)
+		if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
+			const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
+			if ((followersOnlyBefore != null)
+				&& (
+					(followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000)))
+					|| (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000))
+				)
+			) {
+				packedNote.visibility = 'followers';
+			}
+		}
+
+		if (meId === packedNote.userId) return;
+
 		// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
 		let hide = false;
 
-		// visibility が specified かつ自分が指定されていなかったら非表示
-		if (packedNote.visibility === 'specified') {
-			if (meId == null) {
+		if (packedNote.user.requireSigninToViewContents && meId == null) {
+			hide = true;
+		}
+
+		if (!hide) {
+			const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
+			if ((hiddenBefore != null)
+				&& (
+					(hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000)))
+					|| (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000))
+				)
+			) {
 				hide = true;
-			} else if (meId === packedNote.userId) {
-				hide = false;
-			} else {
-				// 指定されているかどうか
-				const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
+			}
+		}
 
-				if (specified) {
-					hide = false;
-				} else {
+		// visibility が specified かつ自分が指定されていなかったら非表示
+		if (!hide) {
+			if (packedNote.visibility === 'specified') {
+				if (meId == null) {
 					hide = true;
+				} else {
+					// 指定されているかどうか
+					const specified = packedNote.visibleUserIds!.some(id => meId === id);
+
+					if (!specified) {
+						hide = true;
+					}
 				}
 			}
 		}
 
 		// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
-		if (packedNote.visibility === 'followers') {
-			if (meId == null) {
-				hide = true;
-			} else if (meId === packedNote.userId) {
-				hide = false;
-			} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
-				// 自分の投稿に対するリプライ
-				hide = false;
-			} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
-				// 自分へのメンション
-				hide = false;
-			} else {
-				// フォロワーかどうか
-				const isFollowing = await this.followingsRepository.exists({
-					where: {
-						followeeId: packedNote.userId,
-						followerId: meId,
-					},
-				});
+		if (!hide) {
+			if (packedNote.visibility === 'followers') {
+				if (meId == null) {
+					hide = true;
+				} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
+					// 自分の投稿に対するリプライ
+					hide = false;
+				} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
+					// 自分へのメンション
+					hide = false;
+				} else {
+					// フォロワーかどうか
+					// TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
+					const isFollowing = await this.followingsRepository.exists({
+						where: {
+							followeeId: packedNote.userId,
+							followerId: meId,
+						},
+					});
 
-				hide = !isFollowing;
+					hide = !isFollowing;
+				}
 			}
 		}
 
@@ -126,6 +187,7 @@ export class NoteEntityService implements OnModuleInit {
 			packedNote.poll = undefined;
 			packedNote.cw = null;
 			packedNote.isHidden = true;
+			// TODO: hiddenReason みたいなのを提供しても良さそう
 		}
 	}
 
@@ -220,7 +282,7 @@ export class NoteEntityService implements OnModuleInit {
 				return true;
 			} else {
 				// 指定されているかどうか
-				return note.visibleUserIds.some((id: any) => meId === id);
+				return note.visibleUserIds.some(id => meId === id);
 			}
 		}
 
@@ -287,6 +349,7 @@ export class NoteEntityService implements OnModuleInit {
 			skipHide?: boolean;
 			withReactionAndUserPairCache?: boolean;
 			_hint_?: {
+				bufferedReactions: Map<MiNote['id'], { deltas: Record<string, number>; pairs: ([MiUser['id'], string])[] }> | null;
 				myReactions: Map<MiNote['id'], string | null>;
 				packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
 				packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
@@ -303,6 +366,15 @@ export class NoteEntityService implements OnModuleInit {
 		const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
 		const host = note.userHost;
 
+		const bufferedReactions = opts._hint_?.bufferedReactions != null
+			? (opts._hint_.bufferedReactions.get(note.id) ?? { deltas: {}, pairs: [] })
+			: this.meta.enableReactionsBuffering
+				? await this.reactionsBufferingService.get(note.id)
+				: { deltas: {}, pairs: [] };
+		const reactions = this.reactionService.convertLegacyReactions(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions.deltas ?? {}));
+
+		const reactionAndUserPairCache = note.reactionAndUserPairCache.concat(bufferedReactions.pairs.map(x => x.join('/')));
+
 		let text = note.text;
 
 		if (note.name && (note.url ?? note.uri)) {
@@ -315,7 +387,7 @@ export class NoteEntityService implements OnModuleInit {
 				: await this.channelsRepository.findOneBy({ id: note.channelId })
 			: null;
 
-		const reactionEmojiNames = Object.keys(note.reactions)
+		const reactionEmojiNames = Object.keys(reactions)
 			.filter(x => x.startsWith(':') && x.includes('@') && !x.includes('@.')) // リモートカスタム絵文字のみ
 			.map(x => this.reactionService.decodeReaction(x).reaction.replaceAll(':', ''));
 		const packedFiles = options?._hint_?.packedFiles;
@@ -334,10 +406,10 @@ export class NoteEntityService implements OnModuleInit {
 			visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
 			renoteCount: note.renoteCount,
 			repliesCount: note.repliesCount,
-			reactionCount: Object.values(note.reactions).reduce((a, b) => a + b, 0),
-			reactions: this.reactionService.convertLegacyReactions(note.reactions),
+			reactionCount: Object.values(reactions).reduce((a, b) => a + b, 0),
+			reactions: reactions,
 			reactionEmojis: this.customEmojiService.populateEmojis(reactionEmojiNames, host),
-			reactionAndUserPairCache: opts.withReactionAndUserPairCache ? note.reactionAndUserPairCache : undefined,
+			reactionAndUserPairCache: opts.withReactionAndUserPairCache ? reactionAndUserPairCache : undefined,
 			emojis: host != null ? this.customEmojiService.populateEmojis(note.emojis, host) : undefined,
 			tags: note.tags.length > 0 ? note.tags : undefined,
 			fileIds: note.fileIds,
@@ -376,8 +448,12 @@ export class NoteEntityService implements OnModuleInit {
 
 				poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
 
-				...(meId && Object.keys(note.reactions).length > 0 ? {
-					myReaction: this.populateMyReaction(note, meId, options?._hint_),
+				...(meId && Object.keys(reactions).length > 0 ? {
+					myReaction: this.populateMyReaction({
+						id: note.id,
+						reactions: reactions,
+						reactionAndUserPairCache: reactionAndUserPairCache,
+					}, meId, options?._hint_),
 				} : {}),
 			} : {}),
 		});
@@ -400,6 +476,8 @@ export class NoteEntityService implements OnModuleInit {
 	) {
 		if (notes.length === 0) return [];
 
+		const bufferedReactions = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany([...getAppearNoteIds(notes)]) : null;
+
 		const meId = me ? me.id : null;
 		const myReactionsMap = new Map<MiNote['id'], string | null>();
 		if (meId) {
@@ -409,24 +487,34 @@ export class NoteEntityService implements OnModuleInit {
 			const oldId = this.idService.gen(Date.now() - 2000);
 
 			for (const note of notes) {
-				if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
-					const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0);
+				if (isPureRenote(note)) {
+					const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
 					if (reactionsCount === 0) {
 						myReactionsMap.set(note.renote.id, null);
-					} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) {
-						const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
-						myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
+					} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length + (bufferedReactions?.get(note.renote.id)?.pairs.length ?? 0)) {
+						const pairInBuffer = bufferedReactions?.get(note.renote.id)?.pairs.find(p => p[0] === meId);
+						if (pairInBuffer) {
+							myReactionsMap.set(note.renote.id, pairInBuffer[1]);
+						} else {
+							const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
+							myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
+						}
 					} else {
 						idsNeedFetchMyReaction.add(note.renote.id);
 					}
 				} else {
 					if (note.id < oldId) {
-						const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
+						const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.reactions, bufferedReactions?.get(note.id)?.deltas ?? {})).reduce((a, b) => a + b, 0);
 						if (reactionsCount === 0) {
 							myReactionsMap.set(note.id, null);
-						} else if (reactionsCount <= note.reactionAndUserPairCache.length) {
-							const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
-							myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
+						} else if (reactionsCount <= note.reactionAndUserPairCache.length + (bufferedReactions?.get(note.id)?.pairs.length ?? 0)) {
+							const pairInBuffer = bufferedReactions?.get(note.id)?.pairs.find(p => p[0] === meId);
+							if (pairInBuffer) {
+								myReactionsMap.set(note.id, pairInBuffer[1]);
+							} else {
+								const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
+								myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
+							}
 						} else {
 							idsNeedFetchMyReaction.add(note.id);
 						}
@@ -461,6 +549,7 @@ export class NoteEntityService implements OnModuleInit {
 		return await Promise.all(notes.map(n => this.pack(n, me, {
 			...options,
 			_hint_: {
+				bufferedReactions,
 				myReactions: myReactionsMap,
 				packedFiles,
 				packedUsers,
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index f393513510e4..dff6968f9c9a 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -59,7 +59,7 @@ export class NotificationEntityService implements OnModuleInit {
 	async #packInternal <T extends MiNotification | MiGroupedNotification> (
 		src: T,
 		meId: MiUser['id'],
-		// eslint-disable-next-line @typescript-eslint/ban-types
+		 
 		options: {
 			checkValidNotifier?: boolean;
 		},
@@ -159,9 +159,16 @@ export class NotificationEntityService implements OnModuleInit {
 			...(notification.type === 'roleAssigned' ? {
 				role: role,
 			} : {}),
+			...(notification.type === 'followRequestAccepted' ? {
+				message: notification.message,
+			} : {}),
 			...(notification.type === 'achievementEarned' ? {
 				achievement: notification.achievement,
 			} : {}),
+			...(notification.type === 'exportCompleted' ? {
+				exportedEntity: notification.exportedEntity,
+				fileId: notification.fileId,
+			} : {}),
 			...(notification.type === 'app' ? {
 				body: notification.customBody,
 				header: notification.customHeader,
@@ -229,7 +236,7 @@ export class NotificationEntityService implements OnModuleInit {
 	public async pack(
 		src: MiNotification | MiGroupedNotification,
 		meId: MiUser['id'],
-		// eslint-disable-next-line @typescript-eslint/ban-types
+		 
 		options: {
 			checkValidNotifier?: boolean;
 		},
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 9bf568bc9088..d3c087a15376 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -490,6 +490,9 @@ export class UserEntityService implements OnModuleInit {
 			}))) : [],
 			isBot: user.isBot,
 			isCat: user.isCat,
+			requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true,
+			makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore ?? undefined,
+			makeNotesHiddenBefore: user.makeNotesHiddenBefore ?? undefined,
 			instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
 				name: instance.name,
 				softwareName: instance.softwareName,
@@ -508,7 +511,7 @@ export class UserEntityService implements OnModuleInit {
 					name: r.name,
 					iconUrl: r.iconUrl,
 					displayOrder: r.displayOrder,
-				}))
+				})),
 			) : undefined,
 
 			...(isDetailed ? {
@@ -545,11 +548,6 @@ export class UserEntityService implements OnModuleInit {
 				publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
 				followersVisibility: profile!.followersVisibility,
 				followingVisibility: profile!.followingVisibility,
-				twoFactorEnabled: profile!.twoFactorEnabled,
-				usePasswordLessLogin: profile!.usePasswordLessLogin,
-				securityKeys: profile!.twoFactorEnabled
-					? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1)
-					: false,
 				roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({
 					id: role.id,
 					name: role.name,
@@ -564,9 +562,18 @@ export class UserEntityService implements OnModuleInit {
 				moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
 			} : {}),
 
+			...(isDetailed && (isMe || iAmModerator) ? {
+				twoFactorEnabled: profile!.twoFactorEnabled,
+				usePasswordLessLogin: profile!.usePasswordLessLogin,
+				securityKeys: profile!.twoFactorEnabled
+					? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1)
+					: false,
+			} : {}),
+
 			...(isDetailed && isMe ? {
 				avatarId: user.avatarId,
 				bannerId: user.bannerId,
+				followedMessage: profile!.followedMessage,
 				isModerator: isModerator,
 				isAdmin: isAdmin,
 				injectFeaturedNote: profile!.injectFeaturedNote,
@@ -635,6 +642,7 @@ export class UserEntityService implements OnModuleInit {
 				isRenoteMuted: relation.isRenoteMuted,
 				notify: relation.following?.notify ?? 'none',
 				withReplies: relation.following?.withReplies ?? false,
+				followedMessage: relation.isFollowing ? profile!.followedMessage : undefined,
 			} : {}),
 		} as Promiseable<Packed<S>>;
 
diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts
index 2c70344c941a..d229efb12346 100644
--- a/packages/backend/src/daemons/ServerStatsService.ts
+++ b/packages/backend/src/daemons/ServerStatsService.ts
@@ -3,13 +3,14 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import si from 'systeminformation';
 import Xev from 'xev';
 import * as osUtils from 'os-utils';
 import { bindThis } from '@/decorators.js';
-import { MetaService } from '@/core/MetaService.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
+import { MiMeta } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
 
 const ev = new Xev();
 
@@ -23,7 +24,8 @@ export class ServerStatsService implements OnApplicationShutdown {
 	private intervalId: NodeJS.Timeout | null = null;
 
 	constructor(
-		private metaService: MetaService,
+		@Inject(DI.meta)
+		private meta: MiMeta,
 	) {
 	}
 
@@ -32,7 +34,7 @@ export class ServerStatsService implements OnApplicationShutdown {
 	 */
 	@bindThis
 	public async start(): Promise<void> {
-		if (!(await this.metaService.fetch(true)).enableServerMachineStats) return;
+		if (!this.meta.enableServerMachineStats) return;
 
 		const log = [] as any[];
 
diff --git a/packages/backend/src/decorators.ts b/packages/backend/src/decorators.ts
index 21777657d185..42f925e1251c 100644
--- a/packages/backend/src/decorators.ts
+++ b/packages/backend/src/decorators.ts
@@ -10,8 +10,9 @@
  * The getter will return a .bind version of the function
  * and memoize the result against a symbol on the instance
  */
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
 export function bindThis(target: any, key: string, descriptor: any) {
-	let fn = descriptor.value;
+	const fn = descriptor.value;
 
 	if (typeof fn !== 'function') {
 		throw new TypeError(`@bindThis decorator can only be applied to methods not: ${typeof fn}`);
@@ -21,26 +22,18 @@ export function bindThis(target: any, key: string, descriptor: any) {
 		configurable: true,
 		get() {
 			// eslint-disable-next-line no-prototype-builtins
-			if (this === target.prototype || this.hasOwnProperty(key) ||
-        typeof fn !== 'function') {
+			if (this === target.prototype || this.hasOwnProperty(key)) {
 				return fn;
 			}
 
 			const boundFn = fn.bind(this);
-			Object.defineProperty(this, key, {
+			Reflect.defineProperty(this, key, {
+				value: boundFn,
 				configurable: true,
-				get() {
-					return boundFn;
-				},
-				set(value) {
-					fn = value;
-					delete this[key];
-				},
+				writable: true,
 			});
+
 			return boundFn;
 		},
-		set(value: any) {
-			fn = value;
-		},
 	};
 }
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 271082b4ff35..e599fc7b3737 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -6,11 +6,13 @@
 export const DI = {
 	config: Symbol('config'),
 	db: Symbol('db'),
+	meta: Symbol('meta'),
 	meilisearch: Symbol('meilisearch'),
 	redis: Symbol('redis'),
 	redisForPub: Symbol('redisForPub'),
 	redisForSub: Symbol('redisForSub'),
 	redisForTimelines: Symbol('redisForTimelines'),
+	redisForReactions: Symbol('redisForReactions'),
 
 	//#region Repositories
 	usersRepository: Symbol('usersRepository'),
diff --git a/packages/backend/src/misc/collapsed-queue.ts b/packages/backend/src/misc/collapsed-queue.ts
new file mode 100644
index 000000000000..5bc20a78ae29
--- /dev/null
+++ b/packages/backend/src/misc/collapsed-queue.ts
@@ -0,0 +1,44 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+type Job<V> = {
+	value: V;
+	timer: NodeJS.Timeout;
+};
+
+// TODO: redis使えるようにする
+export class CollapsedQueue<K, V> {
+	private jobs: Map<K, Job<V>> = new Map();
+
+	constructor(
+		private timeout: number,
+		private collapse: (oldValue: V, newValue: V) => V,
+		private perform: (key: K, value: V) => Promise<void>,
+	) {}
+
+	enqueue(key: K, value: V) {
+		if (this.jobs.has(key)) {
+			const old = this.jobs.get(key)!;
+			const merged = this.collapse(old.value, value);
+			this.jobs.set(key, { ...old, value: merged });
+		} else {
+			const timer = setTimeout(() => {
+				const job = this.jobs.get(key)!;
+				this.jobs.delete(key);
+				this.perform(key, job.value);
+			}, this.timeout);
+			this.jobs.set(key, { value, timer });
+		}
+	}
+
+	async performAllNow() {
+		const entries = [...this.jobs.entries()];
+		this.jobs.clear();
+		for (const [_key, job] of entries) {
+			clearTimeout(job.timer);
+		}
+		await Promise.allSettled(entries.map(([key, job]) => this.perform(key, job.value)));
+	}
+}
diff --git a/packages/backend/src/misc/fastify-hook-handlers.ts b/packages/backend/src/misc/fastify-hook-handlers.ts
index 3e1c099e000c..fa3ef0a267d6 100644
--- a/packages/backend/src/misc/fastify-hook-handlers.ts
+++ b/packages/backend/src/misc/fastify-hook-handlers.ts
@@ -8,7 +8,7 @@ import type { onRequestHookHandler } from 'fastify';
 export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
 	const index = request.url.indexOf('?');
 	if (~index) {
-		reply.redirect(301, request.url.slice(0, index));
+		reply.redirect(request.url.slice(0, index), 301);
 	}
 	done();
 };
diff --git a/packages/backend/src/misc/is-renote.ts b/packages/backend/src/misc/is-renote.ts
index 48f821806c1a..f4bb329d8038 100644
--- a/packages/backend/src/misc/is-renote.ts
+++ b/packages/backend/src/misc/is-renote.ts
@@ -6,6 +6,8 @@
 import type { MiNote } from '@/models/Note.js';
 import type { Packed } from '@/misc/json-schema.js';
 
+// NoteEntityService.isPureRenote とよしなにリンク
+
 type Renote =
 	MiNote & {
 		renoteId: NonNullable<MiNote['renoteId']>
diff --git a/packages/backend/src/misc/sql-like-escape.ts b/packages/backend/src/misc/sql-like-escape.ts
index 0c052556744e..6b4f51b00e44 100644
--- a/packages/backend/src/misc/sql-like-escape.ts
+++ b/packages/backend/src/misc/sql-like-escape.ts
@@ -4,5 +4,5 @@
  */
 
 export function sqlLikeEscape(s: string) {
-	return s.replace(/([%_])/g, '\\$1');
+	return s.replace(/([\\%_])/g, '\\$1');
 }
diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts
index 0615fd7eb5e3..cb5672e4ac2a 100644
--- a/packages/backend/src/models/AbuseUserReport.ts
+++ b/packages/backend/src/models/AbuseUserReport.ts
@@ -50,6 +50,9 @@ export class MiAbuseUserReport {
 	})
 	public resolved: boolean;
 
+	/**
+	 * リモートサーバーに転送したかどうか
+	 */
 	@Column('boolean', {
 		default: false,
 	})
@@ -60,6 +63,21 @@ export class MiAbuseUserReport {
 	})
 	public comment: string;
 
+	@Column('varchar', {
+		length: 8192, default: '',
+	})
+	public moderationNote: string;
+
+	/**
+	 * accept 是認 ... 通報内容が正当であり、肯定的に対応された
+	 * reject 否認 ... 通報内容が正当でなく、否定的に対応された
+	 * null ... その他
+	 */
+	@Column('varchar', {
+		length: 128, nullable: true,
+	})
+	public resolvedAs: 'accept' | 'reject' | null;
+
 	//#region Denormalized fields
 	@Index()
 	@Column('varchar', {
diff --git a/packages/backend/src/models/Flash.ts b/packages/backend/src/models/Flash.ts
index a1469a0d9478..5db7dca99228 100644
--- a/packages/backend/src/models/Flash.ts
+++ b/packages/backend/src/models/Flash.ts
@@ -7,6 +7,9 @@ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typ
 import { id } from './util/id.js';
 import { MiUser } from './User.js';
 
+export const flashVisibility = ['public', 'private'] as const;
+export type FlashVisibility = typeof flashVisibility[number];
+
 @Entity('flash')
 export class MiFlash {
 	@PrimaryColumn(id())
@@ -63,5 +66,5 @@ export class MiFlash {
 	@Column('varchar', {
 		length: 512, default: 'public',
 	})
-	public visibility: 'public' | 'private';
+	public visibility: FlashVisibility;
 }
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 70d41801b5ee..ad5e31ad6ff2 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -81,6 +81,11 @@ export class MiMeta {
 	})
 	public prohibitedWords: string[];
 
+	@Column('varchar', {
+		length: 1024, array: true, default: '{}',
+	})
+	public prohibitedWordsForNameOfUser: string[];
+
 	@Column('varchar', {
 		length: 1024, array: true, default: '{}',
 	})
@@ -258,6 +263,11 @@ export class MiMeta {
 	})
 	public turnstileSecretKey: string | null;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableTestcaptcha: boolean;
+
 	// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること
 
 	@Column('enum', {
@@ -519,6 +529,11 @@ export class MiMeta {
 	})
 	public enableChartsForFederatedInstances: boolean;
 
+	@Column('boolean', {
+		default: true,
+	})
+	public enableStatsForFederatedInstances: boolean;
+
 	@Column('boolean', {
 		default: false,
 	})
@@ -589,6 +604,11 @@ export class MiMeta {
 	})
 	public perUserListTimelineCacheMax: number;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableReactionsBuffering: boolean;
+
 	@Column('integer', {
 		default: 0,
 	})
@@ -625,4 +645,17 @@ export class MiMeta {
 		nullable: true,
 	})
 	public urlPreviewUserAgent: string | null;
+
+	@Column('varchar', {
+		length: 128,
+		default: 'all',
+	})
+	public federation: 'all' | 'specified' | 'none';
+
+	@Column('varchar', {
+		length: 1024,
+		array: true,
+		default: '{}',
+	})
+	public federationHosts: string[];
 }
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 87d8c16cb3ee..b7f8e94d691d 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -3,10 +3,12 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { userExportableEntities } from '@/types.js';
 import { MiUser } from './User.js';
 import { MiNote } from './Note.js';
 import { MiAccessToken } from './AccessToken.js';
 import { MiRole } from './Role.js';
+import { MiDriveFile } from './DriveFile.js';
 
 export type MiNotification = {
 	type: 'note';
@@ -67,6 +69,7 @@ export type MiNotification = {
 	id: string;
 	createdAt: string;
 	notifierId: MiUser['id'];
+	message: string | null;
 } | {
 	type: 'roleAssigned';
 	id: string;
@@ -77,6 +80,16 @@ export type MiNotification = {
 	id: string;
 	createdAt: string;
 	achievement: string;
+} | {
+	type: 'exportCompleted';
+	id: string;
+	createdAt: string;
+	exportedEntity: typeof userExportableEntities[number];
+	fileId: MiDriveFile['id'];
+} | {
+	type: 'login';
+	id: string;
+	createdAt: string;
 } | {
 	type: 'app';
 	id: string;
diff --git a/packages/backend/src/models/SystemWebhook.ts b/packages/backend/src/models/SystemWebhook.ts
index d6c27eae510d..1a7ce4962bc7 100644
--- a/packages/backend/src/models/SystemWebhook.ts
+++ b/packages/backend/src/models/SystemWebhook.ts
@@ -14,6 +14,10 @@ export const systemWebhookEventTypes = [
 	'abuseReportResolved',
 	// ユーザが作成された時
 	'userCreated',
+	// モデレータが一定期間不在である警告
+	'inactiveModeratorsWarning',
+	// モデレータが一定期間不在のためシステムにより招待制へと変更された
+	'inactiveModeratorsInvitationOnlyChanged',
 ] as const;
 export type SystemWebhookEventType = typeof systemWebhookEventTypes[number];
 
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 9e2d7a34447d..96de30c4c2c9 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -155,6 +155,11 @@ export class MiUser {
 	})
 	public tags: string[];
 
+	@Column('integer', {
+		default: 0,
+	})
+	public score: number;
+
 	@Column('boolean', {
 		default: false,
 		comment: 'Whether the User is suspended.',
@@ -197,6 +202,23 @@ export class MiUser {
 	})
 	public isHibernated: boolean;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public requireSigninToViewContents: boolean;
+
+	// in sec, マイナスで相対時間
+	@Column('integer', {
+		nullable: true,
+	})
+	public makeNotesFollowersOnlyBefore: number | null;
+
+	// in sec, マイナスで相対時間
+	@Column('integer', {
+		nullable: true,
+	})
+	public makeNotesHiddenBefore: number | null;
+
 	// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
 	@Column('boolean', {
 		default: false,
@@ -289,5 +311,6 @@ export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toStr
 export const passwordSchema = { type: 'string', minLength: 1 } as const;
 export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
 export const descriptionSchema = { type: 'string', minLength: 1, maxLength: 1500 } as const;
+export const followedMessageSchema = { type: 'string', minLength: 1, maxLength: 256 } as const;
 export const locationSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
 export const birthdaySchema = { type: 'string', pattern: /^([0-9]{4})-([0-9]{2})-([0-9]{2})$/.toString().slice(1, -1) } as const;
diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts
index 7dbe0b3717c6..554455529611 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -42,6 +42,14 @@ export class MiUserProfile {
 	})
 	public description: string | null;
 
+	// フォローされた際のメッセージ
+	@Column('varchar', {
+		length: 256, nullable: true,
+	})
+	public followedMessage: string | null;
+
+	// TODO: 鍵アカウントの場合の、フォローリクエスト受信時のメッセージも設定できるようにする
+
 	@Column('jsonb', {
 		default: [],
 	})
diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts
index db24c03b3dba..b4cab4edc8ec 100644
--- a/packages/backend/src/models/Webhook.ts
+++ b/packages/backend/src/models/Webhook.ts
@@ -8,6 +8,7 @@ import { id } from './util/id.js';
 import { MiUser } from './User.js';
 
 export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
+export type WebhookEventTypes = typeof webhookEventTypes[number];
 
 @Entity('webhook')
 export class MiWebhook {
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index 99feeaa7d757..e3fd63464a81 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -115,6 +115,10 @@ export const packedMetaLiteSchema = {
 			type: 'string',
 			optional: false, nullable: true,
 		},
+		enableTestcaptcha: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
 		swPublickey: {
 			type: 'string',
 			optional: false, nullable: true,
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index b05ec8b7625b..cddaf4bc8370 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -4,7 +4,7 @@
  */
 
 import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
-import { notificationTypes } from '@/types.js';
+import { notificationTypes, userExportableEntities } from '@/types.js';
 
 const baseSchema = {
 	type: 'object',
@@ -267,6 +267,10 @@ export const packedNotificationSchema = {
 				optional: false, nullable: false,
 				format: 'id',
 			},
+			message: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 		},
 	}, {
 		type: 'object',
@@ -298,6 +302,36 @@ export const packedNotificationSchema = {
 				enum: ACHIEVEMENT_TYPES,
 			},
 		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['exportCompleted'],
+			},
+			exportedEntity: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: userExportableEntities,
+			},
+			fileId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['login'],
+			},
+		},
 	}, {
 		type: 'object',
 		properties: {
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index 7366f053560d..3537de94c891 100644
--- a/packages/backend/src/models/json-schema/role.ts
+++ b/packages/backend/src/models/json-schema/role.ts
@@ -272,6 +272,26 @@ export const packedRolePoliciesSchema = {
 			type: 'integer',
 			optional: false, nullable: false,
 		},
+		canImportAntennas: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canImportBlocking: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canImportFollowing: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canImportMuting: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canImportUserLists: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
 	},
 } as const;
 
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 947a9317d7a6..38631f907dd5 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -115,6 +115,18 @@ export const packedUserLiteSchema = {
 			type: 'boolean',
 			nullable: false, optional: true,
 		},
+		requireSigninToViewContents: {
+			type: 'boolean',
+			nullable: false, optional: true,
+		},
+		makeNotesFollowersOnlyBefore: {
+			type: 'number',
+			nullable: true, optional: true,
+		},
+		makeNotesHiddenBefore: {
+			type: 'number',
+			nullable: true, optional: true,
+		},
 		instance: {
 			type: 'object',
 			nullable: false, optional: true,
@@ -346,21 +358,6 @@ export const packedUserDetailedNotMeOnlySchema = {
 			nullable: false, optional: false,
 			enum: ['public', 'followers', 'private'],
 		},
-		twoFactorEnabled: {
-			type: 'boolean',
-			nullable: false, optional: false,
-			default: false,
-		},
-		usePasswordLessLogin: {
-			type: 'boolean',
-			nullable: false, optional: false,
-			default: false,
-		},
-		securityKeys: {
-			type: 'boolean',
-			nullable: false, optional: false,
-			default: false,
-		},
 		roles: {
 			type: 'array',
 			nullable: false, optional: false,
@@ -370,6 +367,10 @@ export const packedUserDetailedNotMeOnlySchema = {
 				ref: 'RoleLite',
 			},
 		},
+		followedMessage: {
+			type: 'string',
+			nullable: true, optional: true,
+		},
 		memo: {
 			type: 'string',
 			nullable: true, optional: false,
@@ -378,6 +379,18 @@ export const packedUserDetailedNotMeOnlySchema = {
 			type: 'string',
 			nullable: false, optional: true,
 		},
+		twoFactorEnabled: {
+			type: 'boolean',
+			nullable: false, optional: true,
+		},
+		usePasswordLessLogin: {
+			type: 'boolean',
+			nullable: false, optional: true,
+		},
+		securityKeys: {
+			type: 'boolean',
+			nullable: false, optional: true,
+		},
 		//#region relations
 		isFollowing: {
 			type: 'boolean',
@@ -437,6 +450,10 @@ export const packedMeDetailedOnlySchema = {
 			nullable: true, optional: false,
 			format: 'id',
 		},
+		followedMessage: {
+			type: 'string',
+			nullable: true, optional: false,
+		},
 		isModerator: {
 			type: 'boolean',
 			nullable: true, optional: false,
@@ -622,6 +639,21 @@ export const packedMeDetailedOnlySchema = {
 			nullable: false, optional: false,
 			ref: 'RolePolicies',
 		},
+		twoFactorEnabled: {
+			type: 'boolean',
+			nullable: false, optional: false,
+			default: false,
+		},
+		usePasswordLessLogin: {
+			type: 'boolean',
+			nullable: false, optional: false,
+			default: false,
+		},
+		securityKeys: {
+			type: 'boolean',
+			nullable: false, optional: false,
+			default: false,
+		},
 		//#region secrets
 		email: {
 			type: 'string',
diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts
index a1fd38fcc58c..9044285bf67f 100644
--- a/packages/backend/src/queue/QueueProcessorModule.ts
+++ b/packages/backend/src/queue/QueueProcessorModule.ts
@@ -6,6 +6,7 @@
 import { Module } from '@nestjs/common';
 import { CoreModule } from '@/core/CoreModule.js';
 import { GlobalModule } from '@/GlobalModule.js';
+import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
 import { QueueLoggerService } from './QueueLoggerService.js';
 import { QueueProcessorService } from './QueueProcessorService.js';
 import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
@@ -14,6 +15,7 @@ import { InboxProcessorService } from './processors/InboxProcessorService.js';
 import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
 import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
 import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
+import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
 import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
 import { CleanProcessorService } from './processors/CleanProcessorService.js';
 import { CleanRemoteFilesProcessorService } from './processors/CleanRemoteFilesProcessorService.js';
@@ -51,6 +53,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
 		ResyncChartsProcessorService,
 		CleanChartsProcessorService,
 		CheckExpiredMutingsProcessorService,
+		BakeBufferedReactionsProcessorService,
 		CleanProcessorService,
 		DeleteDriveFilesProcessorService,
 		ExportCustomEmojisProcessorService,
@@ -78,6 +81,8 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
 		DeliverProcessorService,
 		InboxProcessorService,
 		AggregateRetentionProcessorService,
+		CheckExpiredMutingsProcessorService,
+		CheckModeratorsActivityProcessorService,
 		QueueProcessorService,
 	],
 	exports: [
diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts
index 7bd74f3210f8..6940e1c18896 100644
--- a/packages/backend/src/queue/QueueProcessorService.ts
+++ b/packages/backend/src/queue/QueueProcessorService.ts
@@ -10,6 +10,7 @@ import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
+import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
 import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js';
 import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js';
 import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
@@ -39,6 +40,7 @@ import { TickChartsProcessorService } from './processors/TickChartsProcessorServ
 import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
 import { CleanChartsProcessorService } from './processors/CleanChartsProcessorService.js';
 import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMutingsProcessorService.js';
+import { BakeBufferedReactionsProcessorService } from './processors/BakeBufferedReactionsProcessorService.js';
 import { CleanProcessorService } from './processors/CleanProcessorService.js';
 import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
 import { QueueLoggerService } from './QueueLoggerService.js';
@@ -65,7 +67,7 @@ function getJobInfo(job: Bull.Job | undefined, increment = false): string {
 
 	// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
 	const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
-	const maxAttempts = job.opts ? job.opts.attempts : 0;
+	const maxAttempts = job.opts.attempts ?? 0;
 
 	return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
 }
@@ -118,24 +120,36 @@ export class QueueProcessorService implements OnApplicationShutdown {
 		private cleanChartsProcessorService: CleanChartsProcessorService,
 		private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
 		private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
+		private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
+		private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService,
 		private cleanProcessorService: CleanProcessorService,
 	) {
 		this.logger = this.queueLoggerService.logger;
 
-		function renderError(e: Error): any {
-			if (e) { // 何故かeがundefinedで来ることがある
-				return {
-					stack: e.stack,
-					message: e.message,
-					name: e.name,
-				};
-			} else {
-				return {
-					stack: '?',
-					message: '?',
-					name: '?',
-				};
+		function renderError(e?: Error) {
+			// 何故かeがundefinedで来ることがある
+			if (!e) return '?';
+
+			if (e instanceof Bull.UnrecoverableError || e.name === 'AbortError') {
+				return `${e.name}: ${e.message}`;
 			}
+
+			return {
+				stack: e.stack,
+				message: e.message,
+				name: e.name,
+			};
+		}
+
+		function renderJob(job?: Bull.Job) {
+			if (!job) return '?';
+
+			return {
+				name: job.name || undefined,
+				info: getJobInfo(job),
+				failedReason: job.failedReason || undefined,
+				data: job.data,
+			};
 		}
 
 		//#region system
@@ -147,6 +161,8 @@ export class QueueProcessorService implements OnApplicationShutdown {
 					case 'cleanCharts': return this.cleanChartsProcessorService.process();
 					case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
 					case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
+					case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process();
+					case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process();
 					case 'clean': return this.cleanProcessorService.process();
 					default: throw new Error(`unrecognized job type ${job.name} for system`);
 				}
@@ -169,15 +185,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active id=${job.id}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
 				.on('failed', (job, err: Error) => {
-					logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
+					logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.message}`, {
+						Sentry.captureMessage(`Queue: System: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -226,15 +242,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active id=${job.id}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
+					logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.message}`, {
+						Sentry.captureMessage(`Queue: DB: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -266,15 +282,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
+					logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: Deliver: ${err.message}`, {
+						Sentry.captureMessage(`Queue: Deliver: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -306,15 +322,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) });
+					logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job: renderJob(job), e: renderError(err) });
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: Inbox: ${err.message}`, {
+						Sentry.captureMessage(`Queue: Inbox: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -346,15 +362,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
+					logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.message}`, {
+						Sentry.captureMessage(`Queue: UserWebhookDeliver: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -386,15 +402,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
+					logger.error(`failed(${err.name}: ${err.message}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`);
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.message}`, {
+						Sentry.captureMessage(`Queue: SystemWebhookDeliver: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -433,15 +449,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active id=${job.id}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
+					logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.message}`, {
+						Sentry.captureMessage(`Queue: Relationship: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
@@ -474,15 +490,15 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				.on('active', (job) => logger.debug(`active id=${job.id}`))
 				.on('completed', (job, result) => logger.debug(`completed(${result}) id=${job.id}`))
 				.on('failed', (job, err) => {
-					logger.error(`failed(${err.stack}) id=${job ? job.id : '-'}`, { job, e: renderError(err) });
+					logger.error(`failed(${err.name}: ${err.message}) id=${job?.id ?? '?'}`, { job: renderJob(job), e: renderError(err) });
 					if (config.sentryForBackend) {
-						Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.message}`, {
+						Sentry.captureMessage(`Queue: ObjectStorage: ${job?.name ?? '?'}: ${err.name}: ${err.message}`, {
 							level: 'error',
 							extra: { job, err },
 						});
 					}
 				})
-				.on('error', (err: Error) => logger.error(`error ${err.stack}`, { e: renderError(err) }))
+				.on('error', (err: Error) => logger.error(`error ${err.name}: ${err.message}`, { e: renderError(err) }))
 				.on('stalled', (jobId) => logger.warn(`stalled id=${jobId}`));
 		}
 		//#endregion
diff --git a/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts b/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts
new file mode 100644
index 000000000000..d49c99f694af
--- /dev/null
+++ b/packages/backend/src/queue/processors/BakeBufferedReactionsProcessorService.ts
@@ -0,0 +1,42 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type Logger from '@/logger.js';
+import { bindThis } from '@/decorators.js';
+import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import { MiMeta } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+
+@Injectable()
+export class BakeBufferedReactionsProcessorService {
+	private logger: Logger;
+
+	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
+		private reactionsBufferingService: ReactionsBufferingService,
+		private queueLoggerService: QueueLoggerService,
+	) {
+		this.logger = this.queueLoggerService.logger.createSubLogger('bake-buffered-reactions');
+	}
+
+	@bindThis
+	public async process(): Promise<void> {
+		if (!this.meta.enableReactionsBuffering) {
+			this.logger.info('Reactions buffering is disabled. Skipping...');
+			return;
+		}
+
+		this.logger.info('Baking buffered reactions...');
+
+		await this.reactionsBufferingService.bake();
+
+		this.logger.succ('All buffered reactions baked.');
+	}
+}
diff --git a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
new file mode 100644
index 000000000000..87183cb34218
--- /dev/null
+++ b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -0,0 +1,292 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { In } from 'typeorm';
+import type Logger from '@/logger.js';
+import { bindThis } from '@/decorators.js';
+import { MetaService } from '@/core/MetaService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { EmailService } from '@/core/EmailService.js';
+import { MiUser, type UserProfilesRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+import { AnnouncementService } from '@/core/AnnouncementService.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+
+// モデレーターが不在と判断する日付の閾値
+const MODERATOR_INACTIVITY_LIMIT_DAYS = 7;
+// 警告通知やログ出力を行う残日数の閾値
+const MODERATOR_INACTIVITY_WARNING_REMAINING_DAYS = 2;
+// 期限から6時間ごとに通知を行う
+const MODERATOR_INACTIVITY_WARNING_NOTIFY_INTERVAL_HOURS = 6;
+const ONE_HOUR_MILLI_SEC = 1000 * 60 * 60;
+const ONE_DAY_MILLI_SEC = ONE_HOUR_MILLI_SEC * 24;
+
+export type ModeratorInactivityEvaluationResult = {
+	isModeratorsInactive: boolean;
+	inactiveModerators: MiUser[];
+	remainingTime: ModeratorInactivityRemainingTime;
+}
+
+export type ModeratorInactivityRemainingTime = {
+	time: number;
+	asHours: number;
+	asDays: number;
+};
+
+function generateModeratorInactivityMail(remainingTime: ModeratorInactivityRemainingTime) {
+	const subject = 'Moderator Inactivity Warning / モデレーター不在の通知';
+
+	const timeVariant = remainingTime.asDays === 0 ? `${remainingTime.asHours} hours` : `${remainingTime.asDays} days`;
+	const timeVariantJa = remainingTime.asDays === 0 ? `${remainingTime.asHours} 時間` : `${remainingTime.asDays} 日間`;
+	const message = [
+		'To Moderators,',
+		'',
+		`A moderator has been inactive for a period of time. If there are ${timeVariant} of inactivity left, it will switch to invitation only.`,
+		'If you do not wish to move to invitation only, you must log into Misskey and update your last active date and time.',
+		'',
+		'---------------',
+		'',
+		'To モデレーター各位',
+		'',
+		`モデレーターが一定期間活動していないようです。あと${timeVariantJa}活動していない状態が続くと招待制に切り替わります。`,
+		'招待制に切り替わることを望まない場合は、Misskeyにログインして最終アクティブ日時を更新してください。',
+		'',
+	];
+
+	const html = message.join('<br>');
+	const text = message.join('\n');
+
+	return {
+		subject,
+		html,
+		text,
+	};
+}
+
+function generateInvitationOnlyChangedMail() {
+	const subject = 'Change to Invitation-Only / 招待制に変更されました';
+
+	const message = [
+		'To Moderators,',
+		'',
+		`Changed to invitation only because no moderator activity was detected for ${MODERATOR_INACTIVITY_LIMIT_DAYS} days.`,
+		'To cancel the invitation only, you need to access the control panel.',
+		'',
+		'---------------',
+		'',
+		'To モデレーター各位',
+		'',
+		`モデレーターの活動が${MODERATOR_INACTIVITY_LIMIT_DAYS}日間検出されなかったため、招待制に変更されました。`,
+		'招待制を解除するには、コントロールパネルにアクセスする必要があります。',
+		'',
+	];
+
+	const html = message.join('<br>');
+	const text = message.join('\n');
+
+	return {
+		subject,
+		html,
+		text,
+	};
+}
+
+@Injectable()
+export class CheckModeratorsActivityProcessorService {
+	private logger: Logger;
+
+	constructor(
+		@Inject(DI.userProfilesRepository)
+		private userProfilesRepository: UserProfilesRepository,
+		private metaService: MetaService,
+		private roleService: RoleService,
+		private emailService: EmailService,
+		private announcementService: AnnouncementService,
+		private systemWebhookService: SystemWebhookService,
+		private queueLoggerService: QueueLoggerService,
+	) {
+		this.logger = this.queueLoggerService.logger.createSubLogger('check-moderators-activity');
+	}
+
+	@bindThis
+	public async process(): Promise<void> {
+		this.logger.info('start.');
+
+		const meta = await this.metaService.fetch(false);
+		if (!meta.disableRegistration) {
+			await this.processImpl();
+		} else {
+			this.logger.info('is already invitation only.');
+		}
+
+		this.logger.succ('finish.');
+	}
+
+	@bindThis
+	private async processImpl() {
+		const evaluateResult = await this.evaluateModeratorsInactiveDays();
+		if (evaluateResult.isModeratorsInactive) {
+			this.logger.warn(`The moderator has been inactive for ${MODERATOR_INACTIVITY_LIMIT_DAYS} days. We will move to invitation only.`);
+
+			await this.changeToInvitationOnly();
+			await this.notifyChangeToInvitationOnly();
+		} else {
+			const remainingTime = evaluateResult.remainingTime;
+			if (remainingTime.asDays <= MODERATOR_INACTIVITY_WARNING_REMAINING_DAYS) {
+				const timeVariant = remainingTime.asDays === 0 ? `${remainingTime.asHours} hours` : `${remainingTime.asDays} days`;
+				this.logger.warn(`A moderator has been inactive for a period of time. If you are inactive for an additional ${timeVariant}, it will switch to invitation only.`);
+
+				if (remainingTime.asHours % MODERATOR_INACTIVITY_WARNING_NOTIFY_INTERVAL_HOURS === 0) {
+					// ジョブの実行頻度と同等だと通知が多すぎるため期限から6時間ごとに通知する
+					// つまり、のこり2日を切ったら6時間ごとに通知が送られる
+					await this.notifyInactiveModeratorsWarning(remainingTime);
+				}
+			}
+		}
+	}
+
+	/**
+	 * モデレーターが不在であるかどうかを確認する。trueの場合はモデレーターが不在である。
+	 * isModerator, isAdministrator, isRootのいずれかがtrueのユーザを対象に、
+	 * {@link MiUser.lastActiveDate}の値が実行日時の{@link MODERATOR_INACTIVITY_LIMIT_DAYS}日前よりも古いユーザがいるかどうかを確認する。
+	 * {@link MiUser.lastActiveDate}がnullの場合は、そのユーザは確認の対象外とする。
+	 *
+	 * -----
+	 *
+	 * ### サンプルパターン
+	 * - 実行日時: 2022-01-30 12:00:00
+	 * - 判定基準: 2022-01-23 12:00:00(実行日時の{@link MODERATOR_INACTIVITY_LIMIT_DAYS}日前)
+	 *
+	 * #### パターン①
+	 * - モデレータA: lastActiveDate = 2022-01-20 00:00:00 ※アウト
+	 * - モデレータB: lastActiveDate = 2022-01-23 12:00:00 ※セーフ(判定基準と同値なのでギリギリ残り0日)
+	 * - モデレータC: lastActiveDate = 2022-01-23 11:59:59 ※アウト(残り-1日)
+	 * - モデレータD: lastActiveDate = null
+	 *
+	 * この場合、モデレータBのアクティビティのみ判定基準日よりも古くないため、モデレーターが在席と判断される。
+	 *
+	 * #### パターン②
+	 * - モデレータA: lastActiveDate = 2022-01-20 00:00:00 ※アウト
+	 * - モデレータB: lastActiveDate = 2022-01-22 12:00:00 ※アウト(残り-1日)
+	 * - モデレータC: lastActiveDate = 2022-01-23 11:59:59 ※アウト(残り-1日)
+	 * - モデレータD: lastActiveDate = null
+	 *
+	 * この場合、モデレータA, B, Cのアクティビティは判定基準日よりも古いため、モデレーターが不在と判断される。
+	 */
+	@bindThis
+	public async evaluateModeratorsInactiveDays(): Promise<ModeratorInactivityEvaluationResult> {
+		const today = new Date();
+		const inactivePeriod = new Date(today);
+		inactivePeriod.setDate(today.getDate() - MODERATOR_INACTIVITY_LIMIT_DAYS);
+
+		const moderators = await this.fetchModerators()
+			.then(it => it.filter(it => it.lastActiveDate != null));
+		const inactiveModerators = moderators
+			// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+			.filter(it => it.lastActiveDate!.getTime() < inactivePeriod.getTime());
+
+		// 残りの猶予を示したいので、最終アクティブ日時が一番若いモデレータの日数を基準に猶予を計算する
+		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+		const newestLastActiveDate = new Date(Math.max(...moderators.map(it => it.lastActiveDate!.getTime())));
+		const remainingTime = newestLastActiveDate.getTime() - inactivePeriod.getTime();
+		const remainingTimeAsDays = Math.floor(remainingTime / ONE_DAY_MILLI_SEC);
+		const remainingTimeAsHours = Math.floor((remainingTime / ONE_HOUR_MILLI_SEC));
+
+		return {
+			isModeratorsInactive: inactiveModerators.length === moderators.length,
+			inactiveModerators,
+			remainingTime: {
+				time: remainingTime,
+				asHours: remainingTimeAsHours,
+				asDays: remainingTimeAsDays,
+			},
+		};
+	}
+
+	@bindThis
+	private async changeToInvitationOnly() {
+		await this.metaService.update({ disableRegistration: true });
+	}
+
+	@bindThis
+	public async notifyInactiveModeratorsWarning(remainingTime: ModeratorInactivityRemainingTime) {
+		// -- モデレータへのメール送信
+
+		const moderators = await this.fetchModerators();
+		const moderatorProfiles = await this.userProfilesRepository
+			.findBy({ userId: In(moderators.map(it => it.id)) })
+			.then(it => new Map(it.map(it => [it.userId, it])));
+
+		const mail = generateModeratorInactivityMail(remainingTime);
+		for (const moderator of moderators) {
+			const profile = moderatorProfiles.get(moderator.id);
+			if (profile && profile.email && profile.emailVerified) {
+				this.emailService.sendEmail(profile.email, mail.subject, mail.html, mail.text);
+			}
+		}
+
+		// -- SystemWebhook
+
+		const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks()
+			.then(it => it.filter(it => it.on.includes('inactiveModeratorsWarning')));
+		for (const systemWebhook of systemWebhooks) {
+			this.systemWebhookService.enqueueSystemWebhook(
+				systemWebhook,
+				'inactiveModeratorsWarning',
+				{ remainingTime: remainingTime },
+			);
+		}
+	}
+
+	@bindThis
+	public async notifyChangeToInvitationOnly() {
+		// -- モデレータへのメールとお知らせ(個人向け)送信
+
+		const moderators = await this.fetchModerators();
+		const moderatorProfiles = await this.userProfilesRepository
+			.findBy({ userId: In(moderators.map(it => it.id)) })
+			.then(it => new Map(it.map(it => [it.userId, it])));
+
+		const mail = generateInvitationOnlyChangedMail();
+		for (const moderator of moderators) {
+			this.announcementService.create({
+				title: mail.subject,
+				text: mail.text,
+				forExistingUsers: true,
+				needConfirmationToRead: true,
+				userId: moderator.id,
+			});
+
+			const profile = moderatorProfiles.get(moderator.id);
+			if (profile && profile.email && profile.emailVerified) {
+				this.emailService.sendEmail(profile.email, mail.subject, mail.html, mail.text);
+			}
+		}
+
+		// -- SystemWebhook
+
+		const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks()
+			.then(it => it.filter(it => it.on.includes('inactiveModeratorsInvitationOnlyChanged')));
+		for (const systemWebhook of systemWebhooks) {
+			this.systemWebhookService.enqueueSystemWebhook(
+				systemWebhook,
+				'inactiveModeratorsInvitationOnlyChanged',
+				{},
+			);
+		}
+	}
+
+	@bindThis
+	private async fetchModerators() {
+		// TODO: モデレーター以外にも特別な権限を持つユーザーがいる場合は考慮する
+		return this.roleService.getModerators({
+			includeAdmins: true,
+			includeRoot: true,
+			excludeExpire: true,
+		});
+	}
+}
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 4076e9da9038..5a16496011bc 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
 import * as Bull from 'bullmq';
 import { Not } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { InstancesRepository } from '@/models/_.js';
+import type { InstancesRepository, MiMeta } from '@/models/_.js';
 import type Logger from '@/logger.js';
-import { MetaService } from '@/core/MetaService.js';
 import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
@@ -31,10 +30,12 @@ export class DeliverProcessorService {
 	private latest: string | null;
 
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.instancesRepository)
 		private instancesRepository: InstancesRepository,
 
-		private metaService: MetaService,
 		private utilityService: UtilityService,
 		private federatedInstanceService: FederatedInstanceService,
 		private fetchInstanceMetadataService: FetchInstanceMetadataService,
@@ -52,9 +53,7 @@ export class DeliverProcessorService {
 	public async process(job: Bull.Job<DeliverJobData>): Promise<string> {
 		const { host } = new URL(job.data.to);
 
-		// ブロックしてたら中断
-		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.toPuny(host))) {
+		if (!this.utilityService.isFederationAllowedUri(job.data.to)) {
 			return 'skip (blocked)';
 		}
 
@@ -75,8 +74,17 @@ export class DeliverProcessorService {
 		try {
 			await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
 
-			// Update stats
-			this.federatedInstanceService.fetch(host).then(i => {
+			this.apRequestChart.deliverSucc();
+			this.federationChart.deliverd(host, true);
+
+			// Update instance stats
+			process.nextTick(async () => {
+				const i = await (this.meta.enableStatsForFederatedInstances
+					? this.federatedInstanceService.fetchOrRegister(host)
+					: this.federatedInstanceService.fetch(host));
+
+				if (i == null) return;
+
 				if (i.isNotResponding) {
 					this.federatedInstanceService.update(i.id, {
 						isNotResponding: false,
@@ -84,19 +92,22 @@ export class DeliverProcessorService {
 					});
 				}
 
-				this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
-				this.apRequestChart.deliverSucc();
-				this.federationChart.deliverd(i.host, true);
+				if (this.meta.enableStatsForFederatedInstances) {
+					this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
+				}
 
-				if (meta.enableChartsForFederatedInstances) {
+				if (this.meta.enableChartsForFederatedInstances) {
 					this.instanceChart.requestSent(i.host, true);
 				}
 			});
 
 			return 'Success';
 		} catch (res) {
-			// Update stats
-			this.federatedInstanceService.fetch(host).then(i => {
+			this.apRequestChart.deliverFail();
+			this.federationChart.deliverd(host, false);
+
+			// Update instance stats
+			this.federatedInstanceService.fetchOrRegister(host).then(i => {
 				if (!i.isNotResponding) {
 					this.federatedInstanceService.update(i.id, {
 						isNotResponding: true,
@@ -117,10 +128,7 @@ export class DeliverProcessorService {
 					});
 				}
 
-				this.apRequestChart.deliverFail();
-				this.federationChart.deliverd(i.host, false);
-
-				if (meta.enableChartsForFederatedInstances) {
+				if (this.meta.enableChartsForFederatedInstances) {
 					this.instanceChart.requestSent(i.host, false);
 				}
 			});
@@ -130,7 +138,7 @@ export class DeliverProcessorService {
 				if (!res.isRetryable) {
 					// 相手が閉鎖していることを明示しているため、配送停止する
 					if (job.data.isSharedInbox && res.statusCode === 410) {
-						this.federatedInstanceService.fetch(host).then(i => {
+						this.federatedInstanceService.fetchOrRegister(host).then(i => {
 							this.federatedInstanceService.update(i.id, {
 								suspensionState: 'goneSuspended',
 							});
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index 88c4ea29c0e6..b3111865adc6 100644
--- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js';
 import { bindThis } from '@/decorators.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type { DBExportAntennasData } from '../types.js';
 import type * as Bull from 'bullmq';
@@ -35,6 +36,7 @@ export class ExportAntennasProcessorService {
 		private driveService: DriveService,
 		private utilityService: UtilityService,
 		private queueLoggerService: QueueLoggerService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas');
 	}
@@ -95,6 +97,11 @@ export class ExportAntennasProcessorService {
 			const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
 			this.logger.succ('Exported to: ' + driveFile.id);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'antenna',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
index 6ec3c18786fe..ecc439db69ce 100644
--- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
@@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
@@ -30,6 +31,7 @@ export class ExportBlockingProcessorService {
 		private blockingsRepository: BlockingsRepository,
 
 		private utilityService: UtilityService,
+		private notificationService: NotificationService,
 		private driveService: DriveService,
 		private queueLoggerService: QueueLoggerService,
 	) {
@@ -109,6 +111,11 @@ export class ExportBlockingProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'blocking',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
index 01eab26e96ab..583ddbb745a4 100644
--- a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
@@ -19,6 +19,7 @@ import { bindThis } from '@/decorators.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { Packed } from '@/misc/json-schema.js';
 import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
 import type { DbJobDataWithUser } from '../types.js';
@@ -43,6 +44,7 @@ export class ExportClipsProcessorService {
 		private driveService: DriveService,
 		private queueLoggerService: QueueLoggerService,
 		private idService: IdService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
 	}
@@ -79,6 +81,11 @@ export class ExportClipsProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'clip',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
index e4eb4791bd7f..e237cd4975bc 100644
--- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
@@ -16,6 +16,7 @@ import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { createTemp, createTempDir } from '@/misc/create-temp.js';
 import { DownloadService } from '@/core/DownloadService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
@@ -37,6 +38,7 @@ export class ExportCustomEmojisProcessorService {
 		private driveService: DriveService,
 		private downloadService: DownloadService,
 		private queueLoggerService: QueueLoggerService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-custom-emojis');
 	}
@@ -134,6 +136,12 @@ export class ExportCustomEmojisProcessorService {
 				const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
 
 				this.logger.succ(`Exported to: ${driveFile.id}`);
+
+				this.notificationService.createNotification(user.id, 'exportCompleted', {
+					exportedEntity: 'customEmoji',
+					fileId: driveFile.id,
+				});
+
 				cleanup();
 				archiveCleanup();
 				resolve();
diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
index 7bb626dd31bf..b81feece018e 100644
--- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
@@ -16,6 +16,7 @@ import type { MiPoll } from '@/models/Poll.js';
 import type { MiNote } from '@/models/Note.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
 import type { DbJobDataWithUser } from '../types.js';
@@ -37,6 +38,7 @@ export class ExportFavoritesProcessorService {
 		private driveService: DriveService,
 		private queueLoggerService: QueueLoggerService,
 		private idService: IdService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-favorites');
 	}
@@ -123,6 +125,11 @@ export class ExportFavoritesProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'favorite',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
index 1cc80e66d718..903f96251587 100644
--- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
@@ -14,6 +14,7 @@ import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
 import type { MiFollowing } from '@/models/Following.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
@@ -36,6 +37,7 @@ export class ExportFollowingProcessorService {
 		private utilityService: UtilityService,
 		private driveService: DriveService,
 		private queueLoggerService: QueueLoggerService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-following');
 	}
@@ -113,6 +115,11 @@ export class ExportFollowingProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'following',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
index 243b74f2c2be..f9867ade292a 100644
--- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
@@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
@@ -32,6 +33,7 @@ export class ExportMutingProcessorService {
 		private utilityService: UtilityService,
 		private driveService: DriveService,
 		private queueLoggerService: QueueLoggerService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-muting');
 	}
@@ -110,6 +112,11 @@ export class ExportMutingProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'muting',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
index c7611012d74b..9e2b678219cf 100644
--- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
@@ -18,6 +18,7 @@ import { bindThis } from '@/decorators.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { Packed } from '@/misc/json-schema.js';
 import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { JsonArrayStream } from '@/misc/JsonArrayStream.js';
 import { FileWriterStream } from '@/misc/FileWriterStream.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
@@ -112,6 +113,7 @@ export class ExportNotesProcessorService {
 		private queueLoggerService: QueueLoggerService,
 		private driveFileEntityService: DriveFileEntityService,
 		private idService: IdService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-notes');
 	}
@@ -150,6 +152,11 @@ export class ExportNotesProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'note',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index ee87cff5d360..c483d79854d0 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -13,6 +13,7 @@ import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
@@ -35,6 +36,7 @@ export class ExportUserListsProcessorService {
 		private utilityService: UtilityService,
 		private driveService: DriveService,
 		private queueLoggerService: QueueLoggerService,
+		private notificationService: NotificationService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('export-user-lists');
 	}
@@ -89,6 +91,11 @@ export class ExportUserListsProcessorService {
 			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
+
+			this.notificationService.createNotification(user.id, 'exportCompleted', {
+				exportedEntity: 'userList',
+				fileId: driveFile.id,
+			});
 		} finally {
 			cleanup();
 		}
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index 171809d25c3a..9e1b8fee7034 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -87,23 +87,30 @@ export class ImportCustomEmojisProcessorService {
 				await this.emojisRepository.delete({
 					name: emojiInfo.name,
 				});
-				const driveFile = await this.driveService.addFile({
-					user: null,
-					path: emojiPath,
-					name: record.fileName,
-					force: true,
-				});
-				await this.customEmojiService.add({
-					name: emojiInfo.name,
-					category: emojiInfo.category,
-					host: null,
-					aliases: emojiInfo.aliases,
-					driveFile,
-					license: emojiInfo.license,
-					isSensitive: emojiInfo.isSensitive,
-					localOnly: emojiInfo.localOnly,
-					roleIdsThatCanBeUsedThisEmojiAsReaction: [],
-				});
+				try {
+					const driveFile = await this.driveService.addFile({
+						user: null,
+						path: emojiPath,
+						name: record.fileName,
+						force: true,
+					});
+					await this.customEmojiService.add({
+						name: emojiInfo.name,
+						category: emojiInfo.category,
+						host: null,
+						aliases: emojiInfo.aliases,
+						driveFile,
+						license: emojiInfo.license,
+						isSensitive: emojiInfo.isSensitive,
+						localOnly: emojiInfo.localOnly,
+						roleIdsThatCanBeUsedThisEmojiAsReaction: [],
+					});
+				} catch (e) {
+					if (e instanceof Error || typeof e === 'string') {
+						this.logger.error(`couldn't import ${emojiPath} for ${emojiInfo.name}: ${e}`);
+					}
+					continue;
+				}
 			}
 
 			cleanup();
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index fa7009f8f5d9..95d764e4d888 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -4,11 +4,10 @@
  */
 
 import { URL } from 'node:url';
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import httpSignature from '@peertube/http-signature';
 import * as Bull from 'bullmq';
 import type Logger from '@/logger.js';
-import { MetaService } from '@/core/MetaService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
 import InstanceChart from '@/core/chart/charts/instance.js';
@@ -26,16 +25,28 @@ import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
 import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
 import { bindThis } from '@/decorators.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { CollapsedQueue } from '@/misc/collapsed-queue.js';
+import { MiNote } from '@/models/Note.js';
+import { MiMeta } from '@/models/Meta.js';
+import { DI } from '@/di-symbols.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type { InboxJobData } from '../types.js';
 
+type UpdateInstanceJob = {
+	latestRequestReceivedAt: Date,
+	shouldUnsuspend: boolean,
+};
+
 @Injectable()
-export class InboxProcessorService {
+export class InboxProcessorService implements OnApplicationShutdown {
 	private logger: Logger;
+	private updateInstanceQueue: CollapsedQueue<MiNote['id'], UpdateInstanceJob>;
 
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		private utilityService: UtilityService,
-		private metaService: MetaService,
 		private apInboxService: ApInboxService,
 		private federatedInstanceService: FederatedInstanceService,
 		private fetchInstanceMetadataService: FetchInstanceMetadataService,
@@ -48,6 +59,7 @@ export class InboxProcessorService {
 		private queueLoggerService: QueueLoggerService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('inbox');
+		this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance);
 	}
 
 	@bindThis
@@ -63,9 +75,7 @@ export class InboxProcessorService {
 
 		const host = this.utilityService.toPuny(new URL(signature.keyId).hostname);
 
-		// ブロックしてたら中断
-		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, host)) {
+		if (!this.utilityService.isFederationAllowedHost(host)) {
 			return `Blocked request: ${host}`;
 		}
 
@@ -164,9 +174,8 @@ export class InboxProcessorService {
 					throw new Bull.UnrecoverableError(`skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`);
 				}
 
-				// ブロックしてたら中断
 				const ldHost = this.utilityService.extractDbHost(authUser.user.uri);
-				if (this.utilityService.isBlockedHost(meta.blockedHosts, ldHost)) {
+				if (!this.utilityService.isFederationAllowedHost(ldHost)) {
 					throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
 				}
 			} else {
@@ -183,23 +192,27 @@ export class InboxProcessorService {
 			}
 		}
 
-		// Update stats
-		this.federatedInstanceService.fetch(authUser.user.host).then(i => {
-			this.federatedInstanceService.update(i.id, {
-				latestRequestReceivedAt: new Date(),
-				isNotResponding: false,
-				// もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる
-				suspensionState: i.suspensionState === 'autoSuspendedForNotResponding' ? 'none' : undefined,
-			});
+		this.apRequestChart.inbox();
+		this.federationChart.inbox(authUser.user.host);
 
-			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
+		// Update instance stats
+		process.nextTick(async () => {
+			const i = await (this.meta.enableStatsForFederatedInstances
+				? this.federatedInstanceService.fetchOrRegister(authUser.user.host)
+				: this.federatedInstanceService.fetch(authUser.user.host));
+
+			if (i == null) return;
 
-			this.apRequestChart.inbox();
-			this.federationChart.inbox(i.host);
+			this.updateInstanceQueue.enqueue(i.id, {
+				latestRequestReceivedAt: new Date(),
+				shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding',
+			});
 
-			if (meta.enableChartsForFederatedInstances) {
+			if (this.meta.enableChartsForFederatedInstances) {
 				this.instanceChart.requestReceived(i.host);
 			}
+
+			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
 		});
 
 		// アクティビティを処理
@@ -225,4 +238,36 @@ export class InboxProcessorService {
 		}
 		return 'ok';
 	}
+
+	@bindThis
+	public collapseUpdateInstanceJobs(oldJob: UpdateInstanceJob, newJob: UpdateInstanceJob) {
+		const latestRequestReceivedAt = oldJob.latestRequestReceivedAt < newJob.latestRequestReceivedAt
+			? newJob.latestRequestReceivedAt
+			: oldJob.latestRequestReceivedAt;
+		const shouldUnsuspend = oldJob.shouldUnsuspend || newJob.shouldUnsuspend;
+		return {
+			latestRequestReceivedAt,
+			shouldUnsuspend,
+		};
+	}
+
+	@bindThis
+	public async performUpdateInstance(id: string, job: UpdateInstanceJob) {
+		await this.federatedInstanceService.update(id, {
+			latestRequestReceivedAt: new Date(),
+			isNotResponding: false,
+			// もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる
+			suspensionState: job.shouldUnsuspend ? 'none' : undefined,
+		});
+	}
+
+	@bindThis
+	public async dispose(): Promise<void> {
+		await this.updateInstanceQueue.performAllNow();
+	}
+
+	@bindThis
+	async onApplicationShutdown(signal?: string) {
+		await this.dispose();
+	}
 }
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 3255d64621db..ba2342b630e1 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -29,6 +29,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import { IActivity } from '@/core/activitypub/type.js';
 import { isQuote, isRenote } from '@/misc/is-renote.js';
+import * as Acct from '@/misc/acct.js';
 import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
 import type { FindOptionsWhere } from 'typeorm';
 
@@ -486,6 +487,16 @@ export class ActivityPubServerService {
 			return;
 		}
 
+		// リモートだったらリダイレクト
+		if (user.host != null) {
+			if (user.uri == null || this.utilityService.isSelfHost(user.host)) {
+				reply.code(500);
+				return;
+			}
+			reply.redirect(user.uri, 301);
+			return;
+		}
+
 		reply.header('Cache-Control', 'public, max-age=180');
 		this.setResponseType(request, reply);
 		return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser)));
@@ -654,19 +665,20 @@ export class ActivityPubServerService {
 
 			const user = await this.usersRepository.findOneBy({
 				id: userId,
-				host: IsNull(),
 				isSuspended: false,
 			});
 
 			return await this.userInfo(request, reply, user);
 		});
 
-		fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
+		fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			vary(reply.raw, 'Accept');
 
+			const acct = Acct.parse(request.params.acct);
+
 			const user = await this.usersRepository.findOneBy({
-				usernameLower: request.params.user.toLowerCase(),
-				host: IsNull(),
+				usernameLower: acct.username,
+				host: acct.host ?? IsNull(),
 				isSuspended: false,
 			});
 
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index 77a637d89516..bf0a011699b8 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -82,7 +82,7 @@ export class FileServerService {
 					.catch(err => this.errorHandler(request, reply, err));
 			});
 			fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
-				return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`);
+				return await reply.redirect(`${this.config.url}/files/${request.params.key}`, 301);
 			});
 			done();
 		});
@@ -147,12 +147,12 @@ export class FileServerService {
 						url.searchParams.set('static', '1');
 
 						file.cleanup();
-						return await reply.redirect(301, url.toString());
+						return await reply.redirect(url.toString(), 301);
 					} else if (file.mime.startsWith('video/')) {
 						const externalThumbnail = this.videoProcessingService.getExternalVideoThumbnailUrl(file.url);
 						if (externalThumbnail) {
 							file.cleanup();
-							return await reply.redirect(301, externalThumbnail);
+							return await reply.redirect(externalThumbnail, 301);
 						}
 
 						image = await this.videoProcessingService.generateVideoThumbnail(file.path);
@@ -167,7 +167,7 @@ export class FileServerService {
 						url.searchParams.set('url', file.url);
 
 						file.cleanup();
-						return await reply.redirect(301, url.toString());
+						return await reply.redirect(url.toString(), 301);
 					}
 				}
 
@@ -314,11 +314,17 @@ export class FileServerService {
 			}
 
 			return await reply.redirect(
-				301,
 				url.toString(),
+				301,
 			);
 		}
 
+		if (!request.headers['user-agent']) {
+			throw new StatusError('User-Agent is required', 400, 'User-Agent is required');
+		} else if (request.headers['user-agent'].toLowerCase().indexOf('misskey/') !== -1) {
+			throw new StatusError('Refusing to proxy a request from another proxy', 403, 'Proxy is recursive');
+		}
+
 		// Create temp file
 		const file = await this.getStreamAndTypeFromUrl(url);
 		if (file === '404') {
diff --git a/packages/backend/src/server/HealthServerService.ts b/packages/backend/src/server/HealthServerService.ts
index 2c3ed85925c9..5980609f02bd 100644
--- a/packages/backend/src/server/HealthServerService.ts
+++ b/packages/backend/src/server/HealthServerService.ts
@@ -27,6 +27,9 @@ export class HealthServerService {
 		@Inject(DI.redisForTimelines)
 		private redisForTimelines: Redis.Redis,
 
+		@Inject(DI.redisForReactions)
+		private redisForReactions: Redis.Redis,
+
 		@Inject(DI.db)
 		private db: DataSource,
 
@@ -43,6 +46,7 @@ export class HealthServerService {
 				this.redisForPub.ping(),
 				this.redisForSub.ping(),
 				this.redisForTimelines.ping(),
+				this.redisForReactions.ping(),
 				this.db.query('SELECT 1'),
 				...(this.meilisearch ? [this.meilisearch.health()] : []),
 			]).then(() => 200, () => 503));
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index 12d50619856c..3ab0b815f232 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -46,6 +46,7 @@ import { UserListChannelService } from './api/stream/channels/user-list.js';
 import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
 import { ReversiChannelService } from './api/stream/channels/reversi.js';
 import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
+import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.js';
 
 @Module({
 	imports: [
@@ -71,6 +72,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js
 		AuthenticateService,
 		RateLimiterService,
 		SigninApiService,
+		SigninWithPasskeyApiService,
 		SigninService,
 		SignupApiService,
 		StreamingApiServerService,
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 9c849480f21f..fd2bd3267d62 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -13,7 +13,7 @@ import fastifyRawBody from 'fastify-raw-body';
 import { IsNull } from 'typeorm';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import type { Config } from '@/config.js';
-import type { EmojisRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { EmojisRepository, MiMeta, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import type Logger from '@/logger.js';
 import * as Acct from '@/misc/acct.js';
@@ -21,7 +21,6 @@ import { genIdenticon } from '@/misc/gen-identicon.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { LoggerService } from '@/core/LoggerService.js';
 import { bindThis } from '@/decorators.js';
-import { MetaService } from '@/core/MetaService.js';
 import { ActivityPubServerService } from './ActivityPubServerService.js';
 import { NodeinfoServerService } from './NodeinfoServerService.js';
 import { ApiServerService } from './api/ApiServerService.js';
@@ -44,6 +43,9 @@ export class ServerService implements OnApplicationShutdown {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -53,7 +55,6 @@ export class ServerService implements OnApplicationShutdown {
 		@Inject(DI.emojisRepository)
 		private emojisRepository: EmojisRepository,
 
-		private metaService: MetaService,
 		private userEntityService: UserEntityService,
 		private apiServerService: ApiServerService,
 		private openApiServerService: OpenApiServerService,
@@ -165,8 +166,8 @@ export class ServerService implements OnApplicationShutdown {
 			}
 
 			return await reply.redirect(
-				301,
 				url.toString(),
+				301,
 			);
 		});
 
@@ -193,7 +194,7 @@ export class ServerService implements OnApplicationShutdown {
 			reply.header('Content-Type', 'image/png');
 			reply.header('Cache-Control', 'public, max-age=86400');
 
-			if ((await this.metaService.fetch()).enableIdenticonGeneration) {
+			if (this.meta.enableIdenticonGeneration) {
 				return await genIdenticon(request.params.x);
 			} else {
 				return reply.redirect('/static-assets/avatar.png');
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index f95c27275705..aad833f1261e 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -13,8 +13,7 @@ import { getIpHash } from '@/misc/get-ip-hash.js';
 import type { MiLocalUser, MiUser } from '@/models/User.js';
 import type { MiAccessToken } from '@/models/AccessToken.js';
 import type Logger from '@/logger.js';
-import type { UserIpsRepository } from '@/models/_.js';
-import { MetaService } from '@/core/MetaService.js';
+import type { MiMeta, UserIpsRepository } from '@/models/_.js';
 import { createTemp } from '@/misc/create-temp.js';
 import { bindThis } from '@/decorators.js';
 import { RoleService } from '@/core/RoleService.js';
@@ -40,13 +39,15 @@ export class ApiCallService implements OnApplicationShutdown {
 	private userIpHistoriesClearIntervalId: NodeJS.Timeout;
 
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.config)
 		private config: Config,
 
 		@Inject(DI.userIpsRepository)
 		private userIpsRepository: UserIpsRepository,
 
-		private metaService: MetaService,
 		private authenticateService: AuthenticateService,
 		private rateLimiterService: RateLimiterService,
 		private roleService: RoleService,
@@ -64,15 +65,6 @@ export class ApiCallService implements OnApplicationShutdown {
 		let statusCode = err.httpStatusCode;
 		if (err.httpStatusCode === 401) {
 			reply.header('WWW-Authenticate', 'Bearer realm="Misskey"');
-		} else if (err.kind === 'client') {
-			reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`);
-			statusCode = statusCode ?? 400;
-		} else if (err.kind === 'permission') {
-			// (ROLE_PERMISSION_DENIEDは関係ない)
-			if (err.code === 'PERMISSION_DENIED') {
-				reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`);
-			}
-			statusCode = statusCode ?? 403;
 		} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
 			const info: unknown = err.info;
 			const unixEpochInSeconds = Date.now();
@@ -83,6 +75,15 @@ export class ApiCallService implements OnApplicationShutdown {
 			} else {
 				this.logger.warn(`rate limit information has unexpected type ${typeof(err.info?.reset)}`);
 			}
+		} else if (err.kind === 'client') {
+			reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="invalid_request", error_description="${err.message}"`);
+			statusCode = statusCode ?? 400;
+		} else if (err.kind === 'permission') {
+			// (ROLE_PERMISSION_DENIEDは関係ない)
+			if (err.code === 'PERMISSION_DENIED') {
+				reply.header('WWW-Authenticate', `Bearer realm="Misskey", error="insufficient_scope", error_description="${err.message}"`);
+			}
+			statusCode = statusCode ?? 403;
 		} else if (!statusCode) {
 			statusCode = 500;
 		}
@@ -265,9 +266,8 @@ export class ApiCallService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	private async logIp(request: FastifyRequest, user: MiLocalUser) {
-		const meta = await this.metaService.fetch();
-		if (!meta.enableIpLogging) return;
+	private logIp(request: FastifyRequest, user: MiLocalUser) {
+		if (!this.meta.enableIpLogging) return;
 		const ip = request.ip;
 		const ips = this.userIpHistories.get(user.id);
 		if (ips == null || !ips.has(ip)) {
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 13cbdfc3beb0..3a8cb19f01b4 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -8,6 +8,7 @@ import cors from '@fastify/cors';
 import multipart from '@fastify/multipart';
 import fastifyCookie from '@fastify/cookie';
 import { ModuleRef } from '@nestjs/core';
+import { AuthenticationResponseJSON } from '@simplewebauthn/types';
 import type { Config } from '@/config.js';
 import type { InstancesRepository, AccessTokensRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
@@ -17,6 +18,7 @@ import endpoints from './endpoints.js';
 import { ApiCallService } from './ApiCallService.js';
 import { SignupApiService } from './SignupApiService.js';
 import { SigninApiService } from './SigninApiService.js';
+import { SigninWithPasskeyApiService } from './SigninWithPasskeyApiService.js';
 import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
 
 @Injectable()
@@ -37,6 +39,7 @@ export class ApiServerService {
 		private apiCallService: ApiCallService,
 		private signupApiService: SignupApiService,
 		private signinApiService: SigninApiService,
+		private signinWithPasskeyApiService: SigninWithPasskeyApiService,
 	) {
 		//this.createServer = this.createServer.bind(this);
 	}
@@ -115,21 +118,31 @@ export class ApiServerService {
 				'hcaptcha-response'?: string;
 				'g-recaptcha-response'?: string;
 				'turnstile-response'?: string;
+				'm-captcha-response'?: string;
+				'testcaptcha-response'?: string;
 			}
 		}>('/signup', (request, reply) => this.signupApiService.signup(request, reply));
 
 		fastify.post<{
 			Body: {
 				username: string;
-				password: string;
+				password?: string;
 				token?: string;
-				signature?: string;
-				authenticatorData?: string;
-				clientDataJSON?: string;
-				credentialId?: string;
-				challengeId?: string;
+				credential?: AuthenticationResponseJSON;
+				'hcaptcha-response'?: string;
+				'g-recaptcha-response'?: string;
+				'turnstile-response'?: string;
+				'm-captcha-response'?: string;
+				'testcaptcha-response'?: string;
+			};
+		}>('/signin-flow', (request, reply) => this.signinApiService.signin(request, reply));
+
+		fastify.post<{
+			Body: {
+				credential?: AuthenticationResponseJSON;
+				context?: string;
 			};
-		}>('/signin', (request, reply) => this.signinApiService.signin(request, reply));
+		}>('/signin-with-passkey', (request, reply) => this.signinWithPasskeyApiService.signin(request, reply));
 
 		fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiService.signupPending(request, reply));
 
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 41576bedaae7..3557fa40a509 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -68,6 +68,8 @@ import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
 import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
 import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
 import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
+import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
+import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
 import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
 import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
 import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
@@ -92,6 +94,7 @@ import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webho
 import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
 import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
 import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
+import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
 import * as ep___announcements from './endpoints/announcements.js';
 import * as ep___announcements_show from './endpoints/announcements/show.js';
 import * as ep___antennas_create from './endpoints/antennas/create.js';
@@ -258,6 +261,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
 import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
 import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
 import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
+import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
 import * as ep___invite_create from './endpoints/invite/create.js';
 import * as ep___invite_delete from './endpoints/invite/delete.js';
 import * as ep___invite_list from './endpoints/invite/list.js';
@@ -451,6 +455,8 @@ const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass
 const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default };
 const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default };
 const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default };
+const $admin_forwardAbuseUserReport: Provider = { provide: 'ep:admin/forward-abuse-user-report', useClass: ep___admin_forwardAbuseUserReport.default };
+const $admin_updateAbuseUserReport: Provider = { provide: 'ep:admin/update-abuse-user-report', useClass: ep___admin_updateAbuseUserReport.default };
 const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default };
 const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default };
 const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default };
@@ -475,6 +481,7 @@ const $admin_systemWebhook_delete: Provider = { provide: 'ep:admin/system-webhoo
 const $admin_systemWebhook_list: Provider = { provide: 'ep:admin/system-webhook/list', useClass: ep___admin_systemWebhook_list.default };
 const $admin_systemWebhook_show: Provider = { provide: 'ep:admin/system-webhook/show', useClass: ep___admin_systemWebhook_show.default };
 const $admin_systemWebhook_update: Provider = { provide: 'ep:admin/system-webhook/update', useClass: ep___admin_systemWebhook_update.default };
+const $admin_systemWebhook_test: Provider = { provide: 'ep:admin/system-webhook/test', useClass: ep___admin_systemWebhook_test.default };
 const $announcements: Provider = { provide: 'ep:announcements', useClass: ep___announcements.default };
 const $announcements_show: Provider = { provide: 'ep:announcements/show', useClass: ep___announcements_show.default };
 const $antennas_create: Provider = { provide: 'ep:antennas/create', useClass: ep___antennas_create.default };
@@ -641,6 +648,7 @@ const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep
 const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
 const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass: ep___i_webhooks_update.default };
 const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default };
+const $i_webhooks_test: Provider = { provide: 'ep:i/webhooks/test', useClass: ep___i_webhooks_test.default };
 const $invite_create: Provider = { provide: 'ep:invite/create', useClass: ep___invite_create.default };
 const $invite_delete: Provider = { provide: 'ep:invite/delete', useClass: ep___invite_delete.default };
 const $invite_list: Provider = { provide: 'ep:invite/list', useClass: ep___invite_list.default };
@@ -838,6 +846,8 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_relays_remove,
 		$admin_resetPassword,
 		$admin_resolveAbuseUserReport,
+		$admin_forwardAbuseUserReport,
+		$admin_updateAbuseUserReport,
 		$admin_sendEmail,
 		$admin_serverInfo,
 		$admin_showModerationLogs,
@@ -862,6 +872,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_systemWebhook_list,
 		$admin_systemWebhook_show,
 		$admin_systemWebhook_update,
+		$admin_systemWebhook_test,
 		$announcements,
 		$announcements_show,
 		$antennas_create,
@@ -1028,6 +1039,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$i_webhooks_show,
 		$i_webhooks_update,
 		$i_webhooks_delete,
+		$i_webhooks_test,
 		$invite_create,
 		$invite_delete,
 		$invite_list,
@@ -1219,6 +1231,8 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_relays_remove,
 		$admin_resetPassword,
 		$admin_resolveAbuseUserReport,
+		$admin_forwardAbuseUserReport,
+		$admin_updateAbuseUserReport,
 		$admin_sendEmail,
 		$admin_serverInfo,
 		$admin_showModerationLogs,
@@ -1243,6 +1257,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_systemWebhook_list,
 		$admin_systemWebhook_show,
 		$admin_systemWebhook_update,
+		$admin_systemWebhook_test,
 		$announcements,
 		$announcements_show,
 		$antennas_create,
@@ -1409,6 +1424,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$i_webhooks_show,
 		$i_webhooks_update,
 		$i_webhooks_delete,
+		$i_webhooks_test,
 		$invite_create,
 		$invite_delete,
 		$invite_list,
diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts
index bff3ab96f33c..444e6db744a6 100644
--- a/packages/backend/src/server/api/GetterService.ts
+++ b/packages/backend/src/server/api/GetterService.ts
@@ -39,6 +39,17 @@ export class GetterService {
 		return note;
 	}
 
+	@bindThis
+	public async getNoteWithUser(noteId: MiNote['id']) {
+		const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
+
+		if (note == null) {
+			throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
+		}
+
+		return note;
+	}
+
 	/**
 	 * Get user for API processing
 	 */
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index edac9b3beb62..1d983ca4bc2a 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -5,12 +5,14 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import bcrypt from 'bcryptjs';
-import * as OTPAuth from 'otpauth';
 import { IsNull } from 'typeorm';
+import * as Misskey from 'misskey-js';
 import { DI } from '@/di-symbols.js';
 import type {
+	MiMeta,
 	SigninsRepository,
 	UserProfilesRepository,
+	UserSecurityKeysRepository,
 	UsersRepository,
 } from '@/models/_.js';
 import type { Config } from '@/config.js';
@@ -20,6 +22,8 @@ import { IdService } from '@/core/IdService.js';
 import { bindThis } from '@/decorators.js';
 import { WebAuthnService } from '@/core/WebAuthnService.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import { CaptchaService } from '@/core/CaptchaService.js';
+import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 import { RateLimiterService } from './RateLimiterService.js';
 import { SigninService } from './SigninService.js';
 import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
@@ -31,12 +35,18 @@ export class SigninApiService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
+		@Inject(DI.userSecurityKeysRepository)
+		private userSecurityKeysRepository: UserSecurityKeysRepository,
+
 		@Inject(DI.signinsRepository)
 		private signinsRepository: SigninsRepository,
 
@@ -45,6 +55,7 @@ export class SigninApiService {
 		private signinService: SigninService,
 		private userAuthService: UserAuthService,
 		private webAuthnService: WebAuthnService,
+		private captchaService: CaptchaService,
 	) {
 	}
 
@@ -53,9 +64,14 @@ export class SigninApiService {
 		request: FastifyRequest<{
 			Body: {
 				username: string;
-				password: string;
+				password?: string;
 				token?: string;
 				credential?: AuthenticationResponseJSON;
+				'hcaptcha-response'?: string;
+				'g-recaptcha-response'?: string;
+				'turnstile-response'?: string;
+				'm-captcha-response'?: string;
+				'testcaptcha-response'?: string;
 			};
 		}>,
 		reply: FastifyReply,
@@ -92,11 +108,6 @@ export class SigninApiService {
 			return;
 		}
 
-		if (typeof password !== 'string') {
-			reply.code(400);
-			return;
-		}
-
 		if (token != null && typeof token !== 'string') {
 			reply.code(400);
 			return;
@@ -121,11 +132,32 @@ export class SigninApiService {
 		}
 
 		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+		const securityKeysAvailable = await this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1);
+
+		if (password == null) {
+			reply.code(200);
+			if (profile.twoFactorEnabled) {
+				return {
+					finished: false,
+					next: 'password',
+				} satisfies Misskey.entities.SigninFlowResponse;
+			} else {
+				return {
+					finished: false,
+					next: 'captcha',
+				} satisfies Misskey.entities.SigninFlowResponse;
+			}
+		}
+
+		if (typeof password !== 'string') {
+			reply.code(400);
+			return;
+		}
 
 		// Compare password
 		const same = await bcrypt.compare(password, profile.password!);
 
-		const fail = async (status?: number, failure?: { id: string }) => {
+		const fail = async (status?: number, failure?: { id: string; }) => {
 			// Append signin history
 			await this.signinsRepository.insert({
 				id: this.idService.gen(),
@@ -139,6 +171,38 @@ export class SigninApiService {
 		};
 
 		if (!profile.twoFactorEnabled) {
+			if (process.env.NODE_ENV !== 'test') {
+				if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) {
+					await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
+						throw new FastifyReplyError(400, err);
+					});
+				}
+
+				if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) {
+					await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
+						throw new FastifyReplyError(400, err);
+					});
+				}
+
+				if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) {
+					await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
+						throw new FastifyReplyError(400, err);
+					});
+				}
+
+				if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) {
+					await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
+						throw new FastifyReplyError(400, err);
+					});
+				}
+
+				if (this.meta.enableTestcaptcha) {
+					await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => {
+						throw new FastifyReplyError(400, err);
+					});
+				}
+			}
+
 			if (same) {
 				return this.signinService.signin(request, reply, user);
 			} else {
@@ -180,7 +244,7 @@ export class SigninApiService {
 					id: '93b86c4b-72f9-40eb-9815-798928603d1e',
 				});
 			}
-		} else {
+		} else if (securityKeysAvailable) {
 			if (!same && !profile.usePasswordLessLogin) {
 				return await fail(403, {
 					id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
@@ -190,7 +254,23 @@ export class SigninApiService {
 			const authRequest = await this.webAuthnService.initiateAuthentication(user.id);
 
 			reply.code(200);
-			return authRequest;
+			return {
+				finished: false,
+				next: 'passkey',
+				authRequest,
+			} satisfies Misskey.entities.SigninFlowResponse;
+		} else {
+			if (!same || !profile.twoFactorEnabled) {
+				return await fail(403, {
+					id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
+				});
+			} else {
+				reply.code(200);
+				return {
+					finished: false,
+					next: 'totp',
+				} satisfies Misskey.entities.SigninFlowResponse;
+			}
 		}
 		// never get here
 	}
diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts
index 70306c31135b..640356b50c63 100644
--- a/packages/backend/src/server/api/SigninService.ts
+++ b/packages/backend/src/server/api/SigninService.ts
@@ -4,13 +4,16 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
+import * as Misskey from 'misskey-js';
 import { DI } from '@/di-symbols.js';
-import type { SigninsRepository } from '@/models/_.js';
+import type { SigninsRepository, UserProfilesRepository } from '@/models/_.js';
 import { IdService } from '@/core/IdService.js';
 import type { MiLocalUser } from '@/models/User.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { SigninEntityService } from '@/core/entities/SigninEntityService.js';
 import { bindThis } from '@/decorators.js';
+import { EmailService } from '@/core/EmailService.js';
+import { NotificationService } from '@/core/NotificationService.js';
 import type { FastifyRequest, FastifyReply } from 'fastify';
 
 @Injectable()
@@ -19,7 +22,12 @@ export class SigninService {
 		@Inject(DI.signinsRepository)
 		private signinsRepository: SigninsRepository,
 
+		@Inject(DI.userProfilesRepository)
+		private userProfilesRepository: UserProfilesRepository,
+
 		private signinEntityService: SigninEntityService,
+		private emailService: EmailService,
+		private notificationService: NotificationService,
 		private idService: IdService,
 		private globalEventService: GlobalEventService,
 	) {
@@ -28,7 +36,8 @@ export class SigninService {
 	@bindThis
 	public signin(request: FastifyRequest, reply: FastifyReply, user: MiLocalUser) {
 		setImmediate(async () => {
-			// Append signin history
+			this.notificationService.createNotification(user.id, 'login', {});
+
 			const record = await this.signinsRepository.insertOne({
 				id: this.idService.gen(),
 				userId: user.id,
@@ -37,15 +46,22 @@ export class SigninService {
 				success: true,
 			});
 
-			// Publish signin event
 			this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record));
+
+			const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+			if (profile.email && profile.emailVerified) {
+				this.emailService.sendEmail(profile.email, 'New login / ログインがありました',
+					'There is a new login. If you do not recognize this login, update the security status of your account, including changing your password. / 新しいログインがありました。このログインに心当たりがない場合は、パスワードを変更するなど、アカウントのセキュリティ状態を更新してください。',
+					'There is a new login. If you do not recognize this login, update the security status of your account, including changing your password. / 新しいログインがありました。このログインに心当たりがない場合は、パスワードを変更するなど、アカウントのセキュリティ状態を更新してください。');
+			}
 		});
 
 		reply.code(200);
 		return {
+			finished: true,
 			id: user.id,
-			i: user.token,
-		};
+			i: user.token!,
+		} satisfies Misskey.entities.SigninFlowResponse;
 	}
 }
 
diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts
new file mode 100644
index 000000000000..9ba23c54e226
--- /dev/null
+++ b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts
@@ -0,0 +1,173 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { randomUUID } from 'crypto';
+import { Inject, Injectable } from '@nestjs/common';
+import { IsNull } from 'typeorm';
+import { DI } from '@/di-symbols.js';
+import type {
+	SigninsRepository,
+	UserProfilesRepository,
+	UsersRepository,
+} from '@/models/_.js';
+import type { Config } from '@/config.js';
+import { getIpHash } from '@/misc/get-ip-hash.js';
+import type { MiLocalUser, MiUser } from '@/models/User.js';
+import { IdService } from '@/core/IdService.js';
+import { bindThis } from '@/decorators.js';
+import { WebAuthnService } from '@/core/WebAuthnService.js';
+import Logger from '@/logger.js';
+import { LoggerService } from '@/core/LoggerService.js';
+import type { IdentifiableError } from '@/misc/identifiable-error.js';
+import { RateLimiterService } from './RateLimiterService.js';
+import { SigninService } from './SigninService.js';
+import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
+import type { FastifyReply, FastifyRequest } from 'fastify';
+
+@Injectable()
+export class SigninWithPasskeyApiService {
+	private logger: Logger;
+	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.userProfilesRepository)
+		private userProfilesRepository: UserProfilesRepository,
+
+		@Inject(DI.signinsRepository)
+		private signinsRepository: SigninsRepository,
+
+		private idService: IdService,
+		private rateLimiterService: RateLimiterService,
+		private signinService: SigninService,
+		private webAuthnService: WebAuthnService,
+		private loggerService: LoggerService,
+	) {
+		this.logger = this.loggerService.getLogger('PasskeyAuth');
+	}
+
+	@bindThis
+	public async signin(
+		request: FastifyRequest<{
+			Body: {
+				credential?: AuthenticationResponseJSON;
+				context?: string;
+			};
+		}>,
+		reply: FastifyReply,
+	) {
+		reply.header('Access-Control-Allow-Origin', this.config.url);
+		reply.header('Access-Control-Allow-Credentials', 'true');
+
+		const body = request.body;
+		const credential = body['credential'];
+
+		function error(status: number, error: { id: string }) {
+			reply.code(status);
+			return { error };
+		}
+
+		const fail = async (userId: MiUser['id'], status?: number, failure?: { id: string }) => {
+			// Append signin history
+			await this.signinsRepository.insert({
+				id: this.idService.gen(),
+				userId: userId,
+				ip: request.ip,
+				headers: request.headers as any,
+				success: false,
+			});
+			return error(status ?? 500, failure ?? { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' });
+		};
+
+		try {
+			// Not more than 1 API call per 250ms and not more than 100 attempts per 30min
+			// NOTE: 1 Sign-in require 2 API calls
+			await this.rateLimiterService.limit({ key: 'signin-with-passkey', duration: 60 * 30 * 1000, max: 200, minInterval: 250 }, getIpHash(request.ip));
+		} catch (err) {
+			reply.code(429);
+			return {
+				error: {
+					message: 'Too many failed attempts to sign in. Try again later.',
+					code: 'TOO_MANY_AUTHENTICATION_FAILURES',
+					id: '22d05606-fbcf-421a-a2db-b32610dcfd1b',
+				},
+			};
+		}
+
+		// Initiate Passkey Auth challenge with context
+		if (!credential) {
+			const context = randomUUID();
+			this.logger.info(`Initiate Passkey challenge: context: ${context}`);
+			const authChallengeOptions = {
+				option: await this.webAuthnService.initiateSignInWithPasskeyAuthentication(context),
+				context: context,
+			};
+			reply.code(200);
+			return authChallengeOptions;
+		}
+
+		const context = body.context;
+		if (!context || typeof context !== 'string') {
+			// If try Authentication without context
+			return error(400, {
+				id: '1658cc2e-4495-461f-aee4-d403cdf073c1',
+			});
+		}
+
+		this.logger.debug(`Try Sign-in with Passkey: context: ${context}`);
+
+		let authorizedUserId: MiUser['id'] | null;
+		try {
+			authorizedUserId = await this.webAuthnService.verifySignInWithPasskeyAuthentication(context, credential);
+		} catch (err) {
+			this.logger.warn(`Passkey challenge Verify error! : ${err}`);
+			const errorId = (err as IdentifiableError).id;
+			return error(403, {
+				id: errorId,
+			});
+		}
+
+		if (!authorizedUserId) {
+			return error(403, {
+				id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c',
+			});
+		}
+
+		// Fetch user
+		const user = await this.usersRepository.findOneBy({
+			id: authorizedUserId,
+			host: IsNull(),
+		}) as MiLocalUser | null;
+
+		if (user == null) {
+			return error(403, {
+				id: '652f899f-66d4-490e-993e-6606c8ec04c3',
+			});
+		}
+
+		if (user.isSuspended) {
+			return error(403, {
+				id: 'e03a5f46-d309-4865-9b69-56282d94e1eb',
+			});
+		}
+
+		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
+
+		// Authentication was successful, but passwordless login is not enabled
+		if (!profile.usePasswordLessLogin) {
+			return await fail(user.id, 403, {
+				id: '2d84773e-f7b7-4d0b-8f72-bb69b584c912',
+			});
+		}
+
+		const signinResponse = this.signinService.signin(request, reply, user);
+		return {
+			signinResponse: signinResponse,
+		};
+	}
+}
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 632b0c62bc5d..3ec5e5d3e6ce 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -7,9 +7,8 @@ import { Inject, Injectable } from '@nestjs/common';
 import bcrypt from 'bcryptjs';
 import { IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket } from '@/models/_.js';
+import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js';
 import type { Config } from '@/config.js';
-import { MetaService } from '@/core/MetaService.js';
 import { CaptchaService } from '@/core/CaptchaService.js';
 import { IdService } from '@/core/IdService.js';
 import { SignupService } from '@/core/SignupService.js';
@@ -28,6 +27,9 @@ export class SignupApiService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -45,7 +47,6 @@ export class SignupApiService {
 
 		private userEntityService: UserEntityService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private captchaService: CaptchaService,
 		private signupService: SignupService,
 		private signinService: SigninService,
@@ -66,37 +67,42 @@ export class SignupApiService {
 				'g-recaptcha-response'?: string;
 				'turnstile-response'?: string;
 				'm-captcha-response'?: string;
+				'testcaptcha-response'?: string;
 			}
 		}>,
 		reply: FastifyReply,
 	) {
 		const body = request.body;
 
-		const instance = await this.metaService.fetch(true);
-
 		// Verify *Captcha
 		// ただしテスト時はこの機構は障害となるため無効にする
 		if (process.env.NODE_ENV !== 'test') {
-			if (instance.enableHcaptcha && instance.hcaptchaSecretKey) {
-				await this.captchaService.verifyHcaptcha(instance.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
+			if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) {
+				await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => {
+					throw new FastifyReplyError(400, err);
+				});
+			}
+
+			if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) {
+				await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
 					throw new FastifyReplyError(400, err);
 				});
 			}
 
-			if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
-				await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
+			if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) {
+				await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
 					throw new FastifyReplyError(400, err);
 				});
 			}
 
-			if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
-				await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
+			if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) {
+				await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => {
 					throw new FastifyReplyError(400, err);
 				});
 			}
 
-			if (instance.enableTurnstile && instance.turnstileSecretKey) {
-				await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-response']).catch(err => {
+			if (this.meta.enableTestcaptcha) {
+				await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => {
 					throw new FastifyReplyError(400, err);
 				});
 			}
@@ -108,7 +114,7 @@ export class SignupApiService {
 		const invitationCode = body['invitationCode'];
 		const emailAddress = body['emailAddress'];
 
-		if (instance.emailRequiredForSignup) {
+		if (this.meta.emailRequiredForSignup) {
 			if (emailAddress == null || typeof emailAddress !== 'string') {
 				reply.code(400);
 				return;
@@ -123,7 +129,7 @@ export class SignupApiService {
 
 		let ticket: MiRegistrationTicket | null = null;
 
-		if (instance.disableRegistration) {
+		if (this.meta.disableRegistration) {
 			if (invitationCode == null || typeof invitationCode !== 'string') {
 				reply.code(400);
 				return;
@@ -144,7 +150,7 @@ export class SignupApiService {
 			}
 
 			// メアド認証が有効の場合
-			if (instance.emailRequiredForSignup) {
+			if (this.meta.emailRequiredForSignup) {
 				// メアド認証済みならエラー
 				if (ticket.usedBy) {
 					reply.code(400);
@@ -162,7 +168,7 @@ export class SignupApiService {
 			}
 		}
 
-		if (instance.emailRequiredForSignup) {
+		if (this.meta.emailRequiredForSignup) {
 			if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
 				throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
 			}
@@ -172,7 +178,7 @@ export class SignupApiService {
 				throw new FastifyReplyError(400, 'USED_USERNAME');
 			}
 
-			const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
+			const isPreserved = this.meta.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
 			if (isPreserved) {
 				throw new FastifyReplyError(400, 'DENIED_USERNAME');
 			}
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 3dfb7fdad4c2..49b07d6ced83 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -74,6 +74,8 @@ import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
 import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js';
 import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js';
 import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js';
+import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js';
+import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js';
 import * as ep___admin_sendEmail from './endpoints/admin/send-email.js';
 import * as ep___admin_serverInfo from './endpoints/admin/server-info.js';
 import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js';
@@ -98,6 +100,7 @@ import * as ep___admin_systemWebhook_delete from './endpoints/admin/system-webho
 import * as ep___admin_systemWebhook_list from './endpoints/admin/system-webhook/list.js';
 import * as ep___admin_systemWebhook_show from './endpoints/admin/system-webhook/show.js';
 import * as ep___admin_systemWebhook_update from './endpoints/admin/system-webhook/update.js';
+import * as ep___admin_systemWebhook_test from './endpoints/admin/system-webhook/test.js';
 import * as ep___announcements from './endpoints/announcements.js';
 import * as ep___announcements_show from './endpoints/announcements/show.js';
 import * as ep___antennas_create from './endpoints/antennas/create.js';
@@ -264,6 +267,7 @@ import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
 import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
 import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
 import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
+import * as ep___i_webhooks_test from './endpoints/i/webhooks/test.js';
 import * as ep___invite_create from './endpoints/invite/create.js';
 import * as ep___invite_delete from './endpoints/invite/delete.js';
 import * as ep___invite_list from './endpoints/invite/list.js';
@@ -455,6 +459,8 @@ const eps = [
 	['admin/relays/remove', ep___admin_relays_remove],
 	['admin/reset-password', ep___admin_resetPassword],
 	['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport],
+	['admin/forward-abuse-user-report', ep___admin_forwardAbuseUserReport],
+	['admin/update-abuse-user-report', ep___admin_updateAbuseUserReport],
 	['admin/send-email', ep___admin_sendEmail],
 	['admin/server-info', ep___admin_serverInfo],
 	['admin/show-moderation-logs', ep___admin_showModerationLogs],
@@ -479,6 +485,7 @@ const eps = [
 	['admin/system-webhook/list', ep___admin_systemWebhook_list],
 	['admin/system-webhook/show', ep___admin_systemWebhook_show],
 	['admin/system-webhook/update', ep___admin_systemWebhook_update],
+	['admin/system-webhook/test', ep___admin_systemWebhook_test],
 	['announcements', ep___announcements],
 	['announcements/show', ep___announcements_show],
 	['antennas/create', ep___antennas_create],
@@ -645,6 +652,7 @@ const eps = [
 	['i/webhooks/show', ep___i_webhooks_show],
 	['i/webhooks/update', ep___i_webhooks_update],
 	['i/webhooks/delete', ep___i_webhooks_delete],
+	['i/webhooks/test', ep___i_webhooks_test],
 	['invite/create', ep___invite_create],
 	['invite/delete', ep___invite_delete],
 	['invite/list', ep___invite_list],
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
index cf3f257ca692..0dbfaae05440 100644
--- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
@@ -71,9 +71,22 @@ export const meta = {
 				},
 				assignee: {
 					type: 'object',
-					nullable: true, optional: true,
+					nullable: true, optional: false,
 					ref: 'UserDetailedNotMe',
 				},
+				forwarded: {
+					type: 'boolean',
+					nullable: false, optional: false,
+				},
+				resolvedAs: {
+					type: 'string',
+					nullable: true, optional: false,
+					enum: ['accept', 'reject', null],
+				},
+				moderationNote: {
+					type: 'string',
+					nullable: false, optional: false,
+				},
 			},
 		},
 	},
@@ -88,7 +101,6 @@ export const paramDef = {
 		state: { type: 'string', nullable: true, default: null },
 		reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
 		targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' },
-		forwarded: { type: 'boolean', default: false },
 	},
 	required: [],
 } as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index a7e8a3b018cd..d30131a62f47 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -12,11 +12,27 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { localUsernameSchema, passwordSchema } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
+import { ApiError } from '@/server/api/error.js';
 import { Packed } from '@/misc/json-schema.js';
 
 export const meta = {
 	tags: ['admin'],
 
+	errors: {
+		accessDenied: {
+			message: 'Access denied.',
+			code: 'ACCESS_DENIED',
+			id: '1fb7cb09-d46a-4fff-b8df-057708cce513',
+		},
+
+		wrongInitialPassword: {
+			message: 'Initial password is incorrect.',
+			code: 'INCORRECT_INITIAL_PASSWORD',
+			id: '97147c55-1ae1-4f6f-91d6-e1c3e0e76d62',
+		},
+	},
+
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -35,6 +51,7 @@ export const paramDef = {
 	properties: {
 		username: localUsernameSchema,
 		password: passwordSchema,
+		setupPassword: { type: 'string', nullable: true },
 	},
 	required: ['username', 'password'],
 } as const;
@@ -42,6 +59,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -52,7 +72,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		super(meta, paramDef, async (ps, _me, token) => {
 			const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
 			const realUsers = await this.instanceActorService.realLocalUsersPresent();
-			if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
+
+			if (!realUsers && me == null && token == null) {
+				// 初回セットアップの場合
+				if (this.config.setupPassword != null) {
+					// 初期パスワードが設定されている場合
+					if (ps.setupPassword !== this.config.setupPassword) {
+						// 初期パスワードが違う場合
+						throw new ApiError(meta.errors.wrongInitialPassword);
+					}
+				} else if (ps.setupPassword != null && ps.setupPassword.trim() !== '') {
+					// 初期パスワードが設定されていないのに初期パスワードが入力された場合
+					throw new ApiError(meta.errors.wrongInitialPassword);
+				}
+			} else if ((realUsers && !me?.isRoot) || token !== null) {
+				// 初回セットアップではなく、管理者でない場合 or 外部トークンを使用している場合
+				throw new ApiError(meta.errors.accessDenied);
+			}
 
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts
index fd213098188d..87d80cbe8032 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts
@@ -6,6 +6,7 @@
 import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
+import { IdService } from '@/core/IdService.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -13,6 +14,49 @@ export const meta = {
 	requireCredential: true,
 	requireRolePolicy: 'canManageAvatarDecorations',
 	kind: 'write:admin:avatar-decorations',
+
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		properties: {
+			id: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			createdAt: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'date-time',
+			},
+			updatedAt: {
+				type: 'string',
+				optional: false, nullable: true,
+				format: 'date-time',
+			},
+			name: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			description: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			url: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			roleIdsThatCanBeUsedThisDecoration: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+					optional: false, nullable: false,
+					format: 'id',
+				},
+			},
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -32,14 +76,25 @@ export const paramDef = {
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
 		private avatarDecorationService: AvatarDecorationService,
+		private idService: IdService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			await this.avatarDecorationService.create({
+			const created = await this.avatarDecorationService.create({
 				name: ps.name,
 				description: ps.description,
 				url: ps.url,
 				roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
 			}, me);
+
+			return {
+				id: created.id,
+				createdAt: this.idService.parse(created.id).date.toISOString(),
+				updatedAt: null,
+				name: created.name,
+				description: created.description,
+				url: created.url,
+				roleIdsThatCanBeUsedThisDecoration: created.roleIdsThatCanBeUsedThisDecoration,
+			};
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
index aee90023e145..d785f085ac7c 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
@@ -4,10 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/_.js';
-import type { MiAnnouncement } from '@/models/Announcement.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
 import { DI } from '@/di-symbols.js';
 import { IdService } from '@/core/IdService.js';
 import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index 22609a16a39a..212cba5c5dd9 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -6,7 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
-import type { DriveFilesRepository } from '@/models/_.js';
+import type { DriveFilesRepository, MiEmoji } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../../error.js';
 
@@ -78,25 +78,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
 			}
 
-			let emojiId;
-			if (ps.id) {
-				emojiId = ps.id;
-				const emoji = await this.customEmojiService.getEmojiById(ps.id);
-				if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
-				if (ps.name && (ps.name !== emoji.name)) {
-					const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
-					if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
-				}
-			} else {
-				if (!ps.name) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.');
-				const emoji = await this.customEmojiService.getEmojiByName(ps.name);
-				if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
-				emojiId = emoji.id;
-			}
+			// JSON schemeのanyOfの型変換がうまくいっていないらしい
+			const required = { id: ps.id, name: ps.name } as 
+				| { id: MiEmoji['id']; name?: string }
+				| { id?: MiEmoji['id']; name: string };
 
-			await this.customEmojiService.update(emojiId, {
+			const error = await this.customEmojiService.update({
+				...required,
 				driveFile,
-				name: ps.name,
 				category: ps.category,
 				aliases: ps.aliases,
 				license: ps.license,
@@ -104,6 +93,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				localOnly: ps.localOnly,
 				roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
 			}, me);
+
+			switch (error) {
+				case null: return;
+				case 'NO_SUCH_EMOJI': throw new ApiError(meta.errors.noSuchEmoji);
+				case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists);
+			}
+			// 網羅性チェック
+			const mustBeNever: never = error;
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/forward-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/forward-abuse-user-report.ts
new file mode 100644
index 000000000000..3e42c91fedaa
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/forward-abuse-user-report.ts
@@ -0,0 +1,55 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { AbuseUserReportsRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { AbuseReportService } from '@/core/AbuseReportService.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireModerator: true,
+	kind: 'write:admin:resolve-abuse-user-report',
+
+	errors: {
+		noSuchAbuseReport: {
+			message: 'No such abuse report.',
+			code: 'NO_SUCH_ABUSE_REPORT',
+			id: '8763e21b-d9bc-40be-acf6-54c1a6986493',
+			kind: 'server',
+			httpStatusCode: 404,
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		reportId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['reportId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.abuseUserReportsRepository)
+		private abuseUserReportsRepository: AbuseUserReportsRepository,
+		private abuseReportService: AbuseReportService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId });
+			if (!report) {
+				throw new ApiError(meta.errors.noSuchAbuseReport);
+			}
+
+			await this.abuseReportService.forward(report.id, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 2e7f73da73b2..64e3cc33bd2c 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -69,6 +69,10 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			enableTestcaptcha: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
 			swPublickey: {
 				type: 'string',
 				optional: false, nullable: true,
@@ -173,6 +177,13 @@ export const meta = {
 					type: 'string',
 				},
 			},
+			prohibitedWordsForNameOfUser: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+				},
+			},
 			bannedEmailDomains: {
 				type: 'array',
 				optional: true, nullable: false,
@@ -337,6 +348,10 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
+			enableStatsForFederatedInstances: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
 			enableServerMachineStats: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -377,6 +392,10 @@ export const meta = {
 				type: 'number',
 				optional: false, nullable: false,
 			},
+			enableReactionsBuffering: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
 			notesPerOneAd: {
 				type: 'number',
 				optional: false, nullable: false,
@@ -491,6 +510,18 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			federation: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			federationHosts: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+					optional: false, nullable: false,
+				},
+			},
 		},
 	},
 } as const;
@@ -539,6 +570,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				recaptchaSiteKey: instance.recaptchaSiteKey,
 				enableTurnstile: instance.enableTurnstile,
 				turnstileSiteKey: instance.turnstileSiteKey,
+				enableTestcaptcha: instance.enableTestcaptcha,
 				swPublickey: instance.swPublicKey,
 				themeColor: instance.themeColor,
 				mascotImageUrl: instance.mascotImageUrl,
@@ -565,6 +597,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				mediaSilencedHosts: instance.mediaSilencedHosts,
 				sensitiveWords: instance.sensitiveWords,
 				prohibitedWords: instance.prohibitedWords,
+				prohibitedWordsForNameOfUser: instance.prohibitedWordsForNameOfUser,
 				preservedUsernames: instance.preservedUsernames,
 				hcaptchaSecretKey: instance.hcaptchaSecretKey,
 				mcaptchaSecretKey: instance.mcaptchaSecretKey,
@@ -606,6 +639,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				truemailAuthKey: instance.truemailAuthKey,
 				enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
 				enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
+				enableStatsForFederatedInstances: instance.enableStatsForFederatedInstances,
 				enableServerMachineStats: instance.enableServerMachineStats,
 				enableIdenticonGeneration: instance.enableIdenticonGeneration,
 				bannedEmailDomains: instance.bannedEmailDomains,
@@ -617,6 +651,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
 				perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
 				perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
+				enableReactionsBuffering: instance.enableReactionsBuffering,
 				notesPerOneAd: instance.notesPerOneAd,
 				summalyProxy: instance.urlPreviewSummaryProxyUrl,
 				urlPreviewEnabled: instance.urlPreviewEnabled,
@@ -625,6 +660,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
 				urlPreviewUserAgent: instance.urlPreviewUserAgent,
 				urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
+				federation: instance.federation,
+				federationHosts: instance.federationHosts,
 			};
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
index 9b79100fcf50..554d324ff24d 100644
--- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
+++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
@@ -32,7 +32,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		reportId: { type: 'string', format: 'misskey:id' },
-		forward: { type: 'boolean', default: false },
+		resolvedAs: { type: 'string', enum: ['accept', 'reject', null], nullable: true },
 	},
 	required: ['reportId'],
 } as const;
@@ -50,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchAbuseReport);
 			}
 
-			await this.abuseReportService.resolve([{ reportId: report.id, forward: ps.forward }], me);
+			await this.abuseReportService.resolve([{ reportId: report.id, resolvedAs: ps.resolvedAs ?? null }], me);
 		});
 	}
 }
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 5a1c05f41aa6..655bd32bce06 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -31,6 +31,10 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
+			followedMessage: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			autoAcceptFollowed: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -226,6 +230,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			return {
 				email: profile.email,
 				emailVerified: profile.emailVerified,
+				followedMessage: profile.followedMessage,
 				autoAcceptFollowed: profile.autoAcceptFollowed,
 				noCrawle: profile.noCrawle,
 				preventAiLearning: profile.preventAiLearning,
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 2fef9abbf966..2b2c8c60abbc 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -71,13 +71,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					break;
 				}
 				case 'moderator': {
-					const moderatorIds = await this.roleService.getModeratorIds(false);
+					const moderatorIds = await this.roleService.getModeratorIds({ includeAdmins: false });
 					if (moderatorIds.length === 0) return [];
 					query.where('user.id IN (:...moderatorIds)', { moderatorIds: moderatorIds });
 					break;
 				}
 				case 'adminOrModerator': {
-					const adminOrModeratorIds = await this.roleService.getModeratorIds();
+					const adminOrModeratorIds = await this.roleService.getModeratorIds({ includeAdmins: true });
 					if (adminOrModeratorIds.length === 0) return [];
 					query.where('user.id IN (:...adminOrModeratorIds)', { adminOrModeratorIds: adminOrModeratorIds });
 					break;
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts
new file mode 100644
index 000000000000..fb2ddf4b446d
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/test.ts
@@ -0,0 +1,77 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { WebhookTestService } from '@/core/WebhookTestService.js';
+import { ApiError } from '@/server/api/error.js';
+import { systemWebhookEventTypes } from '@/models/SystemWebhook.js';
+
+export const meta = {
+	tags: ['webhooks'],
+
+	requireCredential: true,
+	requireModerator: true,
+	secure: true,
+	kind: 'read:admin:system-webhook',
+
+	limit: {
+		duration: ms('15min'),
+		max: 60,
+	},
+
+	errors: {
+		noSuchWebhook: {
+			message: 'No such webhook.',
+			code: 'NO_SUCH_WEBHOOK',
+			id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		webhookId: {
+			type: 'string',
+			format: 'misskey:id',
+		},
+		type: {
+			type: 'string',
+			enum: systemWebhookEventTypes,
+		},
+		override: {
+			type: 'object',
+			properties: {
+				url: { type: 'string', nullable: false },
+				secret: { type: 'string', nullable: false },
+			},
+		},
+	},
+	required: ['webhookId', 'type'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private webhookTestService: WebhookTestService,
+	) {
+		super(meta, paramDef, async (ps) => {
+			try {
+				await this.webhookTestService.testSystemWebhook({
+					webhookId: ps.webhookId,
+					type: ps.type,
+					override: ps.override,
+				});
+			} catch (e) {
+				if (e instanceof WebhookTestService.NoSuchWebhookError) {
+					throw new ApiError(meta.errors.noSuchWebhook);
+				}
+				throw e;
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/update-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/update-abuse-user-report.ts
new file mode 100644
index 000000000000..73d4b843f0e6
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/update-abuse-user-report.ts
@@ -0,0 +1,58 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { AbuseUserReportsRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { AbuseReportService } from '@/core/AbuseReportService.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireModerator: true,
+	kind: 'write:admin:resolve-abuse-user-report',
+
+	errors: {
+		noSuchAbuseReport: {
+			message: 'No such abuse report.',
+			code: 'NO_SUCH_ABUSE_REPORT',
+			id: '15f51cf5-46d1-4b1d-a618-b35bcbed0662',
+			kind: 'server',
+			httpStatusCode: 404,
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		reportId: { type: 'string', format: 'misskey:id' },
+		moderationNote: { type: 'string' },
+	},
+	required: ['reportId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.abuseUserReportsRepository)
+		private abuseUserReportsRepository: AbuseUserReportsRepository,
+		private abuseReportService: AbuseReportService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId });
+			if (!report) {
+				throw new ApiError(meta.errors.noSuchAbuseReport);
+			}
+
+			await this.abuseReportService.update(report.id, {
+				moderationNote: ps.moderationNote,
+			}, me);
+		});
+	}
+}
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 5efdc9d8c457..38ef0d1de837 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -46,6 +46,11 @@ export const paramDef = {
 				type: 'string',
 			},
 		},
+		prohibitedWordsForNameOfUser: {
+			type: 'array', nullable: true, items: {
+				type: 'string',
+			},
+		},
 		themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
 		mascotImageUrl: { type: 'string', nullable: true },
 		bannerUrl: { type: 'string', nullable: true },
@@ -78,6 +83,7 @@ export const paramDef = {
 		enableTurnstile: { type: 'boolean' },
 		turnstileSiteKey: { type: 'string', nullable: true },
 		turnstileSecretKey: { type: 'string', nullable: true },
+		enableTestcaptcha: { type: 'boolean' },
 		sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
 		sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
 		setSensitiveFlagAutomatically: { type: 'boolean' },
@@ -130,6 +136,7 @@ export const paramDef = {
 		truemailAuthKey: { type: 'string', nullable: true },
 		enableChartsForRemoteUser: { type: 'boolean' },
 		enableChartsForFederatedInstances: { type: 'boolean' },
+		enableStatsForFederatedInstances: { type: 'boolean' },
 		enableServerMachineStats: { type: 'boolean' },
 		enableIdenticonGeneration: { type: 'boolean' },
 		serverRules: { type: 'array', items: { type: 'string' } },
@@ -142,6 +149,7 @@ export const paramDef = {
 		perRemoteUserUserTimelineCacheMax: { type: 'integer' },
 		perUserHomeTimelineCacheMax: { type: 'integer' },
 		perUserListTimelineCacheMax: { type: 'integer' },
+		enableReactionsBuffering: { type: 'boolean' },
 		notesPerOneAd: { type: 'integer' },
 		silencedHosts: {
 			type: 'array',
@@ -167,6 +175,16 @@ export const paramDef = {
 		urlPreviewRequireContentLength: { type: 'boolean' },
 		urlPreviewUserAgent: { type: 'string', nullable: true },
 		urlPreviewSummaryProxyUrl: { type: 'string', nullable: true },
+		federation: {
+			type: 'string',
+			enum: ['all', 'none', 'specified'],
+		},
+		federationHosts: {
+			type: 'array',
+			items: {
+				type: 'string',
+			},
+		},
 	},
 	required: [],
 } as const;
@@ -202,6 +220,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (Array.isArray(ps.prohibitedWords)) {
 				set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
 			}
+			if (Array.isArray(ps.prohibitedWordsForNameOfUser)) {
+				set.prohibitedWordsForNameOfUser = ps.prohibitedWordsForNameOfUser.filter(Boolean);
+			}
 			if (Array.isArray(ps.silencedHosts)) {
 				let lastValue = '';
 				set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
@@ -346,6 +367,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.turnstileSecretKey = ps.turnstileSecretKey;
 			}
 
+			if (ps.enableTestcaptcha !== undefined) {
+				set.enableTestcaptcha = ps.enableTestcaptcha;
+			}
+
 			if (ps.sensitiveMediaDetection !== undefined) {
 				set.sensitiveMediaDetection = ps.sensitiveMediaDetection;
 			}
@@ -554,6 +579,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
 			}
 
+			if (ps.enableStatsForFederatedInstances !== undefined) {
+				set.enableStatsForFederatedInstances = ps.enableStatsForFederatedInstances;
+			}
+
 			if (ps.enableServerMachineStats !== undefined) {
 				set.enableServerMachineStats = ps.enableServerMachineStats;
 			}
@@ -598,6 +627,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax;
 			}
 
+			if (ps.enableReactionsBuffering !== undefined) {
+				set.enableReactionsBuffering = ps.enableReactionsBuffering;
+			}
+
 			if (ps.notesPerOneAd !== undefined) {
 				set.notesPerOneAd = ps.notesPerOneAd;
 			}
@@ -632,6 +665,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.urlPreviewSummaryProxyUrl = value === '' ? null : value;
 			}
 
+			if (ps.federation !== undefined) {
+				set.federation = ps.federation;
+			}
+
+			if (Array.isArray(ps.federationHosts)) {
+				set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
+			}
+
 			const before = await this.metaService.fetch(true);
 
 			await this.metaService.update(set);
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index 577b9e1b1f8c..e0c8ddcc8478 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -34,6 +34,12 @@ export const meta = {
 			code: 'TOO_MANY_ANTENNAS',
 			id: 'faf47050-e8b5-438c-913c-db2b1576fde4',
 		},
+
+		emptyKeyword: {
+			message: 'Either keywords or excludeKeywords is required.',
+			code: 'EMPTY_KEYWORD',
+			id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a',
+		},
 	},
 
 	res: {
@@ -87,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) {
-				throw new Error('either keywords or excludeKeywords is required.');
+				throw new ApiError(meta.errors.emptyKeyword);
 			}
 
 			const currentAntennasCount = await this.antennasRepository.countBy({
diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts
index 0c30bca9e0bf..10f26b19126e 100644
--- a/packages/backend/src/server/api/endpoints/antennas/update.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/update.ts
@@ -32,6 +32,12 @@ export const meta = {
 			code: 'NO_SUCH_USER_LIST',
 			id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
 		},
+
+		emptyKeyword: {
+			message: 'Either keywords or excludeKeywords is required.',
+			code: 'EMPTY_KEYWORD',
+			id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4',
+		},
 	},
 
 	res: {
@@ -85,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		super(meta, paramDef, async (ps, me) => {
 			if (ps.keywords && ps.excludeKeywords) {
 				if (ps.keywords.flat().every(x => x === '') && ps.excludeKeywords.flat().every(x => x === '')) {
-					throw new Error('either keywords or excludeKeywords is required.');
+					throw new ApiError(meta.errors.emptyKeyword);
 				}
 			}
 			// Fetch the antenna
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index d3c40dba590e..c52608cefb3f 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { MiNote } from '@/models/Note.js';
@@ -12,7 +12,6 @@ import { isActor, isPost, getApId } from '@/core/activitypub/type.js';
 import type { SchemaType } from '@/misc/json-schema.js';
 import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
 import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
 import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
@@ -91,7 +90,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private utilityService: UtilityService,
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
-		private metaService: MetaService,
 		private apResolverService: ApResolverService,
 		private apDbResolverService: ApDbResolverService,
 		private apPersonService: ApPersonService,
@@ -112,9 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	 */
 	@bindThis
 	private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
-	// ブロックしてたら中断
-		const fetchedMeta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, this.utilityService.extractDbHost(uri))) return null;
+		if (!this.utilityService.isFederationAllowedUri(uri)) return null;
 
 		let local = await this.mergePack(me, ...await Promise.all([
 			this.apDbResolverService.getUserFromApId(uri),
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 8c5567359088..d4fd75e04955 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -5,14 +5,12 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { ChannelsRepository, NotesRepository } from '@/models/_.js';
+import type { ChannelsRepository, MiMeta, NotesRepository } from '@/models/_.js';
 import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
 import { IdService } from '@/core/IdService.js';
-import { CacheService } from '@/core/CacheService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
 import { MiLocalUser } from '@/models/User.js';
 import { ApiError } from '../../error.js';
@@ -58,6 +56,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
@@ -68,16 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
 		private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
-		private cacheService: CacheService,
 		private activeUsersChart: ActiveUsersChart,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
 			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
 
-			const serverSettings = await this.metaService.fetch();
-
 			const channel = await this.channelsRepository.findOneBy({
 				id: ps.channelId,
 			});
@@ -88,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			if (me) this.activeUsersChart.read(me);
 
-			if (!serverSettings.enableFanoutTimeline) {
+			if (!this.serverSettings.enableFanoutTimeline) {
 				return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me);
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts
index 7e9b0fa0e1e4..eb45e29f9e12 100644
--- a/packages/backend/src/server/api/endpoints/drive.ts
+++ b/packages/backend/src/server/api/endpoints/drive.ts
@@ -5,7 +5,6 @@
 
 import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { MetaService } from '@/core/MetaService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { RoleService } from '@/core/RoleService.js';
 
@@ -41,14 +40,10 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		private metaService: MetaService,
 		private driveFileEntityService: DriveFileEntityService,
 		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const instance = await this.metaService.fetch(true);
-
-			// Calculate drive usage
 			const usage = await this.driveFileEntityService.calcDriveUsageOf(me.id);
 
 			const policies = await this.roleService.getUserPolicies(me.id);
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 467039202524..b86059b5e7ac 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
@@ -10,6 +10,7 @@ import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../../error.js';
+import { RoleService } from '@/core/RoleService.js';
 
 export const meta = {
 	tags: ['drive', 'notes'],
@@ -61,12 +62,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
+		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			// Fetch file
 			const file = await this.driveFilesRepository.findOneBy({
 				id: ps.fileId,
-				userId: me.id,
+				userId: await this.roleService.isModerator(me) ? undefined : me.id,
 			});
 
 			if (file == null) {
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 9c17f93ab26b..74eb4dded7ce 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -4,14 +4,15 @@
  */
 
 import ms from 'ms';
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { DriveService } from '@/core/DriveService.js';
 import { ApiError } from '../../../error.js';
+import { MiMeta } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -73,8 +74,10 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		private driveFileEntityService: DriveFileEntityService,
-		private metaService: MetaService,
 		private driveService: DriveService,
 	) {
 		super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => {
@@ -91,8 +94,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			const instance = await this.metaService.fetch();
-
 			try {
 				// Create file
 				const driveFile = await this.driveService.addFile({
@@ -103,8 +104,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					folderId: ps.folderId,
 					force: ps.force,
 					sensitive: ps.isSensitive,
-					requestIp: instance.enableIpLogging ? ip : null,
-					requestHeaders: instance.enableIpLogging ? headers : null,
+					requestIp: this.serverSettings.enableIpLogging ? ip : null,
+					requestHeaders: this.serverSettings.enableIpLogging ? headers : null,
 				});
 				return await this.driveFileEntityService.pack(driveFile, { self: true });
 			} catch (err) {
diff --git a/packages/backend/src/server/api/endpoints/flash/featured.ts b/packages/backend/src/server/api/endpoints/flash/featured.ts
index c2d6ab508577..9a0cb461f283 100644
--- a/packages/backend/src/server/api/endpoints/flash/featured.ts
+++ b/packages/backend/src/server/api/endpoints/flash/featured.ts
@@ -8,6 +8,7 @@ import type { FlashsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { FlashService } from '@/core/FlashService.js';
 
 export const meta = {
 	tags: ['flash'],
@@ -27,26 +28,25 @@ export const meta = {
 
 export const paramDef = {
 	type: 'object',
-	properties: {},
+	properties: {
+		offset: { type: 'integer', minimum: 0, default: 0 },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+	},
 	required: [],
 } as const;
 
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.flashsRepository)
-		private flashsRepository: FlashsRepository,
-
+		private flashService: FlashService,
 		private flashEntityService: FlashEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const query = this.flashsRepository.createQueryBuilder('flash')
-				.andWhere('flash.likedCount > 0')
-				.orderBy('flash.likedCount', 'DESC');
-
-			const flashs = await query.limit(10).getMany();
-
-			return await this.flashEntityService.packMany(flashs, me);
+			const result = await this.flashService.featured({
+				offset: ps.offset,
+				limit: ps.limit,
+			});
+			return await this.flashEntityService.packMany(result, me);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index bc46163e3d27..bdf6c065e89c 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -16,6 +16,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	requireRolePolicy: 'canImportAntennas',
 	prohibitMoved: true,
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index 260610853930..d7bb6bcd2256 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	requireRolePolicy: 'canImportBlocking',
 	prohibitMoved: true,
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index d5e824df2740..e03192d8c67b 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	requireRolePolicy: 'canImportFollowing',
 	prohibitMoved: true,
 	limit: {
 		duration: ms('1hour'),
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 0f5800404eaf..76b285bb7e3f 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	requireRolePolicy: 'canImportMuting',
 	prohibitMoved: true,
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index bacdd5c88f28..76ecfd082ca3 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -15,6 +15,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	requireRolePolicy: 'canImportUserLists',
 	prohibitMoved: true,
 	limit: {
 		duration: ms('1hour'),
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index eea657ebbd07..da1faee30d15 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import bcrypt from 'bcryptjs';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UserProfilesRepository } from '@/models/_.js';
+import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { EmailService } from '@/core/EmailService.js';
 import type { Config } from '@/config.js';
@@ -15,7 +15,6 @@ import { DI } from '@/di-symbols.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -70,10 +69,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
-		private metaService: MetaService,
 		private userEntityService: UserEntityService,
 		private emailService: EmailService,
 		private userAuthService: UserAuthService,
@@ -105,7 +106,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (!res.available) {
 					throw new ApiError(meta.errors.unavailable);
 				}
-			} else if ((await this.metaService.fetch()).emailRequiredForSignup) {
+			} else if (this.serverSettings.emailRequiredForSignup) {
 				throw new ApiError(meta.errors.emailRequired);
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index a1e2fa5e4cdf..d3eeb75b27c7 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -11,11 +11,10 @@ import { JSDOM } from 'jsdom';
 import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
 import { extractHashtags } from '@/misc/extract-hashtags.js';
 import * as Acct from '@/misc/acct.js';
-import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js';
+import type { UsersRepository, DriveFilesRepository, MiMeta, UserProfilesRepository, PagesRepository } from '@/models/_.js';
 import type { MiLocalUser, MiUser } from '@/models/User.js';
-import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
+import { birthdaySchema, descriptionSchema, followedMessageSchema, locationSchema, nameSchema } from '@/models/User.js';
 import type { MiUserProfile } from '@/models/UserProfile.js';
-import { notificationTypes } from '@/types.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { langmap } from '@/misc/langmap.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -23,6 +22,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
 import { AccountUpdateService } from '@/core/AccountUpdateService.js';
+import { UtilityService } from '@/core/UtilityService.js';
 import { HashtagService } from '@/core/HashtagService.js';
 import { DI } from '@/di-symbols.js';
 import { RolePolicies, RoleService } from '@/core/RoleService.js';
@@ -115,6 +115,13 @@ export const meta = {
 			code: 'RESTRICTED_BY_ROLE',
 			id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
 		},
+
+		nameContainsProhibitedWords: {
+			message: 'Your new name contains prohibited words.',
+			code: 'YOUR_NAME_CONTAINS_PROHIBITED_WORDS',
+			id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191',
+			httpStatusCode: 422,
+		},
 	},
 
 	res: {
@@ -134,6 +141,7 @@ export const paramDef = {
 	properties: {
 		name: { ...nameSchema, nullable: true },
 		description: { ...descriptionSchema, nullable: true },
+		followedMessage: { ...followedMessageSchema, nullable: true },
 		location: { ...locationSchema, nullable: true },
 		birthday: { ...birthdaySchema, nullable: true },
 		lang: { type: 'string', enum: [null, ...Object.keys(langmap)] as string[], nullable: true },
@@ -171,6 +179,9 @@ export const paramDef = {
 		autoAcceptFollowed: { type: 'boolean' },
 		noCrawle: { type: 'boolean' },
 		preventAiLearning: { type: 'boolean' },
+		requireSigninToViewContents: { type: 'boolean' },
+		makeNotesFollowersOnlyBefore: { type: 'integer', nullable: true },
+		makeNotesHiddenBefore: { type: 'integer', nullable: true },
 		isBot: { type: 'boolean' },
 		isCat: { type: 'boolean' },
 		injectFeaturedNote: { type: 'boolean' },
@@ -223,6 +234,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private instanceMeta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -247,6 +261,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private cacheService: CacheService,
 		private httpRequestService: HttpRequestService,
 		private avatarDecorationService: AvatarDecorationService,
+		private utilityService: UtilityService,
 	) {
 		super(meta, paramDef, async (ps, _user, token) => {
 			const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser;
@@ -267,6 +282,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 			if (ps.description !== undefined) profileUpdates.description = ps.description;
+			if (ps.followedMessage !== undefined) profileUpdates.followedMessage = ps.followedMessage;
 			if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
 			if (ps.location !== undefined) profileUpdates.location = ps.location;
 			if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
@@ -321,6 +337,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
 			if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
 			if (typeof ps.preventAiLearning === 'boolean') profileUpdates.preventAiLearning = ps.preventAiLearning;
+			if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents;
+			if ((typeof ps.makeNotesFollowersOnlyBefore === 'number') || (ps.makeNotesFollowersOnlyBefore === null)) updates.makeNotesFollowersOnlyBefore = ps.makeNotesFollowersOnlyBefore;
+			if ((typeof ps.makeNotesHiddenBefore === 'number') || (ps.makeNotesHiddenBefore === null)) updates.makeNotesHiddenBefore = ps.makeNotesHiddenBefore;
 			if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
 			if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
 			if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
@@ -446,8 +465,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const newName = updates.name === undefined ? user.name : updates.name;
 			const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
 			const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields;
+			const newFollowedMessage = profileUpdates.followedMessage === undefined ? profile.followedMessage : profileUpdates.followedMessage;
 
 			if (newName != null) {
+				let hasProhibitedWords = false;
+				if (!await this.roleService.isModerator(user)) {
+					hasProhibitedWords = this.utilityService.isKeyWordIncluded(newName, this.instanceMeta.prohibitedWordsForNameOfUser);
+				}
+				if (hasProhibitedWords) {
+					throw new ApiError(meta.errors.nameContainsProhibitedWords);
+				}
+
 				const tokens = mfm.parseSimple(newName);
 				emojis = emojis.concat(extractCustomEmojisFromMfm(tokens));
 			}
@@ -467,6 +495,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				]);
 			}
 
+			if (newFollowedMessage != null) {
+				const tokens = mfm.parse(newFollowedMessage);
+				emojis = emojis.concat(extractCustomEmojisFromMfm(tokens));
+			}
+
 			updates.emojis = emojis;
 			updates.tags = tags;
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
index 9eb7f5b3a033..6e84603f7a45 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
@@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '@/server/api/error.js';
 
+// TODO: UserWebhook schemaの適用
 export const meta = {
 	tags: ['webhooks'],
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
index fe07afb2d089..394c178f2adf 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
@@ -9,6 +9,7 @@ import { webhookEventTypes } from '@/models/Webhook.js';
 import type { WebhooksRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 
+// TODO: UserWebhook schemaの適用
 export const meta = {
 	tags: ['webhooks', 'account'],
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
index 5ddb79caf283..4a0c09ff0ccb 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -10,6 +10,7 @@ import type { WebhooksRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../../error.js';
 
+// TODO: UserWebhook schemaの適用
 export const meta = {
 	tags: ['webhooks'],
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/test.ts b/packages/backend/src/server/api/endpoints/i/webhooks/test.ts
new file mode 100644
index 000000000000..2bf6df9ce2f9
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/test.ts
@@ -0,0 +1,76 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { webhookEventTypes } from '@/models/Webhook.js';
+import { WebhookTestService } from '@/core/WebhookTestService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+	tags: ['webhooks'],
+
+	requireCredential: true,
+	secure: true,
+	kind: 'read:account',
+
+	limit: {
+		duration: ms('15min'),
+		max: 60,
+	},
+
+	errors: {
+		noSuchWebhook: {
+			message: 'No such webhook.',
+			code: 'NO_SUCH_WEBHOOK',
+			id: '0c52149c-e913-18f8-5dc7-74870bfe0cf9',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		webhookId: {
+			type: 'string',
+			format: 'misskey:id',
+		},
+		type: {
+			type: 'string',
+			enum: webhookEventTypes,
+		},
+		override: {
+			type: 'object',
+			properties: {
+				url: { type: 'string' },
+				secret: { type: 'string' },
+			},
+		},
+	},
+	required: ['webhookId', 'type'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private webhookTestService: WebhookTestService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			try {
+				await this.webhookTestService.testUserWebhook({
+					webhookId: ps.webhookId,
+					type: ps.type,
+					override: ps.override,
+				}, me);
+			} catch (e) {
+				if (e instanceof WebhookTestService.NoSuchWebhookError) {
+					throw new ApiError(meta.errors.noSuchWebhook);
+				}
+				throw e;
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts
index 2786bd98d5bf..2ffd41ae28a3 100644
--- a/packages/backend/src/server/api/endpoints/invite/limit.ts
+++ b/packages/backend/src/server/api/endpoints/invite/limit.ts
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const policies = await this.roleService.getUserPolicies(me.id);
 
 			const count = policies.inviteLimit ? await this.registrationTicketsRepository.countBy({
-				id: MoreThan(this.idService.gen(Date.now() - (policies.inviteExpirationTime * 60 * 1000))),
+				id: MoreThan(this.idService.gen(Date.now() - (policies.inviteLimitCycle * 60 * 1000))),
 				createdById: me.id,
 			}) : null;
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index beb77ca7ab02..253a36081514 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -17,8 +17,6 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { NoteCreateService } from '@/core/NoteCreateService.js';
 import { DI } from '@/di-symbols.js';
 import { isQuote, isRenote } from '@/misc/is-renote.js';
-import { MetaService } from '@/core/MetaService.js';
-import { UtilityService } from '@/core/UtilityService.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { ApiError } from '../../error.js';
 
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 2a2c6599427d..aed9065bf953 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -5,7 +5,7 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository, ChannelFollowingsRepository } from '@/models/_.js';
+import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
@@ -16,7 +16,6 @@ import { CacheService } from '@/core/CacheService.js';
 import { FanoutTimelineName } from '@/core/FanoutTimelineService.js';
 import { QueryService } from '@/core/QueryService.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { MiLocalUser } from '@/models/User.js';
 import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
 import { ApiError } from '../../error.js';
@@ -74,6 +73,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
@@ -87,7 +89,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private cacheService: CacheService,
 		private queryService: QueryService,
 		private userFollowingService: UserFollowingService,
-		private metaService: MetaService,
 		private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
@@ -101,9 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
 
-			const serverSettings = await this.metaService.fetch();
-
-			if (!serverSettings.enableFanoutTimeline) {
+			if (!this.serverSettings.enableFanoutTimeline) {
 				const timeline = await this.getFromDb({
 					untilId,
 					sinceId,
@@ -156,7 +155,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				allowPartial: ps.allowPartial,
 				me,
 				redisTimelines: timelineConfig,
-				useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
+				useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
 				alwaysIncludeMyNotes: true,
 				excludePureRenotes: !ps.withRenotes,
 				noteFilter: note => {
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index be82b5a8a749..0b48f2c78bd4 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -5,16 +5,14 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository } from '@/models/_.js';
+import type { MiMeta, NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { IdService } from '@/core/IdService.js';
-import { CacheService } from '@/core/CacheService.js';
 import { QueryService } from '@/core/QueryService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { MiLocalUser } from '@/models/User.js';
 import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
 import { ApiError } from '../../error.js';
@@ -66,6 +64,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
@@ -73,10 +74,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private roleService: RoleService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
-		private cacheService: CacheService,
 		private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
 		private queryService: QueryService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
@@ -89,9 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
 
-			const serverSettings = await this.metaService.fetch();
-
-			if (!serverSettings.enableFanoutTimeline) {
+			if (!this.serverSettings.enableFanoutTimeline) {
 				const timeline = await this.getFromDb({
 					untilId,
 					sinceId,
@@ -115,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				limit: ps.limit,
 				allowPartial: ps.allowPartial,
 				me,
-				useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
+				useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
 				redisTimelines:
 					ps.withFiles ? ['localTimelineWithFiles']
 					: ps.withReplies ? ['localTimeline', 'localTimelineWithReplies']
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index adcda30a7d88..11839bce3660 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -26,6 +26,12 @@ export const meta = {
 			code: 'NO_SUCH_NOTE',
 			id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d',
 		},
+
+		signinRequired: {
+			message: 'Signin required.',
+			code: 'SIGNIN_REQUIRED',
+			id: '8e75455b-738c-471d-9f80-62693f33372e',
+		},
 	},
 } as const;
 
@@ -44,11 +50,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private getterService: GetterService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const note = await this.getterService.getNote(ps.noteId).catch(err => {
+			const note = await this.getterService.getNoteWithUser(ps.noteId).catch(err => {
 				if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 				throw err;
 			});
 
+			if (note.user!.requireSigninToViewContents && me == null) {
+				throw new ApiError(meta.errors.signinRequired);
+			}
+
 			return await this.noteEntityService.pack(note, me, {
 				detail: true,
 			});
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index c9b43b535937..7cb11cc1ebfc 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -5,7 +5,7 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository, ChannelFollowingsRepository } from '@/models/_.js';
+import type { NotesRepository, ChannelFollowingsRepository, MiMeta } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
@@ -15,7 +15,6 @@ import { IdService } from '@/core/IdService.js';
 import { CacheService } from '@/core/CacheService.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
 import { MiLocalUser } from '@/models/User.js';
-import { MetaService } from '@/core/MetaService.js';
 import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
 
 export const meta = {
@@ -56,6 +55,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
@@ -69,15 +71,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
 		private userFollowingService: UserFollowingService,
 		private queryService: QueryService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
 			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
 
-			const serverSettings = await this.metaService.fetch();
-
-			if (!serverSettings.enableFanoutTimeline) {
+			if (!this.serverSettings.enableFanoutTimeline) {
 				const timeline = await this.getFromDb({
 					untilId,
 					sinceId,
@@ -108,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				limit: ps.limit,
 				allowPartial: ps.allowPartial,
 				me,
-				useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
+				useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
 				redisTimelines: ps.withFiles ? [`homeTimelineWithFiles:${me.id}`] : [`homeTimeline:${me.id}`],
 				alwaysIncludeMyNotes: true,
 				excludePureRenotes: !ps.withRenotes,
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index 38a9660aa239..e9a6a36b0266 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -4,14 +4,15 @@
  */
 
 import { URLSearchParams } from 'node:url';
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '../../error.js';
+import { MiMeta } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -59,9 +60,11 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		private noteEntityService: NoteEntityService,
 		private getterService: GetterService,
-		private metaService: MetaService,
 		private httpRequestService: HttpRequestService,
 		private roleService: RoleService,
 	) {
@@ -84,9 +87,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				return;
 			}
 
-			const instance = await this.metaService.fetch();
-
-			if (instance.deeplAuthKey == null) {
+			if (this.serverSettings.deeplAuthKey == null) {
 				throw new ApiError(meta.errors.unavailable);
 			}
 
@@ -94,11 +95,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
 
 			const params = new URLSearchParams();
-			params.append('auth_key', instance.deeplAuthKey);
+			params.append('auth_key', this.serverSettings.deeplAuthKey);
 			params.append('text', note.text);
 			params.append('target_lang', targetLang);
 
-			const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
+			const endpoint = this.serverSettings.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
 
 			const res = await this.httpRequestService.send(endpoint, {
 				method: 'POST',
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 43877e61efaf..87f9b322a602 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -5,16 +5,14 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { Brackets } from 'typeorm';
-import type { MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
+import type { MiMeta, MiUserList, NotesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
-import { CacheService } from '@/core/CacheService.js';
 import { IdService } from '@/core/IdService.js';
 import { QueryService } from '@/core/QueryService.js';
 import { MiLocalUser } from '@/models/User.js';
-import { MetaService } from '@/core/MetaService.js';
 import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
 import { ApiError } from '../../error.js';
 
@@ -69,6 +67,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
@@ -80,11 +81,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private noteEntityService: NoteEntityService,
 		private activeUsersChart: ActiveUsersChart,
-		private cacheService: CacheService,
 		private idService: IdService,
 		private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
 		private queryService: QueryService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
@@ -99,9 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchList);
 			}
 
-			const serverSettings = await this.metaService.fetch();
-
-			if (!serverSettings.enableFanoutTimeline) {
+			if (!this.serverSettings.enableFanoutTimeline) {
 				const timeline = await this.getFromDb(list, {
 					untilId,
 					sinceId,
@@ -115,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				this.activeUsersChart.read(me);
 
-				await this.noteEntityService.packMany(timeline, me);
+				return await this.noteEntityService.packMany(timeline, me);
 			}
 
 			const timeline = await this.fanoutTimelineEndpointService.timeline({
@@ -124,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				limit: ps.limit,
 				allowPartial: ps.allowPartial,
 				me,
-				useDbFallback: serverSettings.enableFanoutTimelineDbFallback,
+				useDbFallback: this.serverSettings.enableFanoutTimelineDbFallback,
 				redisTimelines: ps.withFiles ? [`userListTimelineWithFiles:${list.id}`] : [`userListTimeline:${list.id}`],
 				alwaysIncludeMyNotes: true,
 				excludePureRenotes: !ps.withRenotes,
diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts
index 15832ef7f8d7..5b0b656c63fe 100644
--- a/packages/backend/src/server/api/endpoints/pinned-users.ts
+++ b/packages/backend/src/server/api/endpoints/pinned-users.ts
@@ -5,11 +5,10 @@
 
 import { IsNull } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsersRepository } from '@/models/_.js';
 import * as Acct from '@/misc/acct.js';
 import type { MiUser } from '@/models/User.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { MetaService } from '@/core/MetaService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
 
@@ -38,16 +37,16 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
-		private metaService: MetaService,
 		private userEntityService: UserEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const meta = await this.metaService.fetch();
-
-			const users = await Promise.all(meta.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({
+			const users = await Promise.all(this.serverSettings.pinnedUsers.map(acct => Acct.parse(acct)).map(acct => this.usersRepository.findOneBy({
 				usernameLower: acct.username.toLowerCase(),
 				host: acct.host ?? IsNull(),
 			})));
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index c13802eb0689..8301c85f2eb6 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -5,9 +5,10 @@
 
 import * as os from 'node:os';
 import si from 'systeminformation';
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { MetaService } from '@/core/MetaService.js';
+import { MiMeta } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	requireCredential: false,
@@ -73,10 +74,11 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		private metaService: MetaService,
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
 	) {
 		super(meta, paramDef, async () => {
-			if (!(await this.metaService.fetch()).enableServerMachineStats) return {
+			if (!this.serverSettings.enableServerMachineStats) return {
 				machine: '?',
 				cpu: {
 					model: '?',
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index a9a33149f95a..fd76df2d3c0b 100644
--- a/packages/backend/src/server/api/endpoints/sw/register.ts
+++ b/packages/backend/src/server/api/endpoints/sw/register.ts
@@ -5,9 +5,8 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { IdService } from '@/core/IdService.js';
-import type { SwSubscriptionsRepository } from '@/models/_.js';
+import type { MiMeta, SwSubscriptionsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { MetaService } from '@/core/MetaService.js';
 import { DI } from '@/di-symbols.js';
 import { PushNotificationService } from '@/core/PushNotificationService.js';
 
@@ -62,11 +61,13 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.swSubscriptionsRepository)
 		private swSubscriptionsRepository: SwSubscriptionsRepository,
 
 		private idService: IdService,
-		private metaService: MetaService,
 		private pushNotificationService: PushNotificationService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
@@ -78,12 +79,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				publickey: ps.publickey,
 			});
 
-			const instance = await this.metaService.fetch(true);
-
 			if (exist != null) {
 				return {
 					state: 'already-subscribed' as const,
-					key: instance.swPublicKey,
+					key: this.serverSettings.swPublicKey,
 					userId: me.id,
 					endpoint: exist.endpoint,
 					sendReadMessage: exist.sendReadMessage,
@@ -103,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			return {
 				state: 'subscribed' as const,
-				key: instance.swPublicKey,
+				key: this.serverSettings.swPublicKey,
 				userId: me.id,
 				endpoint: ps.endpoint,
 				sendReadMessage: ps.sendReadMessage,
diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts
index affb0996f182..4944be9b0541 100644
--- a/packages/backend/src/server/api/endpoints/username/available.ts
+++ b/packages/backend/src/server/api/endpoints/username/available.ts
@@ -5,11 +5,10 @@
 
 import { IsNull } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
+import type { MiMeta, UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { localUsernameSchema } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
-import { MetaService } from '@/core/MetaService.js';
 
 export const meta = {
 	tags: ['users'],
@@ -39,13 +38,14 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
 		@Inject(DI.usedUsernamesRepository)
 		private usedUsernamesRepository: UsedUsernamesRepository,
-
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const exist = await this.usersRepository.countBy({
@@ -55,8 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() });
 
-			const meta = await this.metaService.fetch();
-			const isPreserved = meta.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase());
+			const isPreserved = this.serverSettings.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase());
 
 			return {
 				available: exist === 0 && exist2 === 0 && !isPreserved,
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index cc76c12f1d89..e9c334057e76 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -5,14 +5,13 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository } from '@/models/_.js';
+import type { MiMeta, NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { CacheService } from '@/core/CacheService.js';
 import { IdService } from '@/core/IdService.js';
 import { QueryService } from '@/core/QueryService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { MiLocalUser } from '@/models/User.js';
 import { FanoutTimelineEndpointService } from '@/core/FanoutTimelineEndpointService.js';
 import { FanoutTimelineName } from '@/core/FanoutTimelineService.js';
@@ -43,6 +42,12 @@ export const meta = {
 			code: 'BOTH_WITH_REPLIES_AND_WITH_FILES',
 			id: '91c8cb9f-36ed-46e7-9ca2-7df96ed6e222',
 		},
+
+		signinRequired: {
+			message: 'Signin required.',
+			code: 'SIGNIN_REQUIRED',
+			id: 'd1588a9e-4b4d-4c07-807f-16f1486577a2',
+		},
 	},
 } as const;
 
@@ -67,6 +72,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
@@ -75,15 +83,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private cacheService: CacheService,
 		private idService: IdService,
 		private fanoutTimelineEndpointService: FanoutTimelineEndpointService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
 			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.gen(ps.sinceDate!) : null);
 			const isSelf = me && (me.id === ps.userId);
 
-			const serverSettings = await this.metaService.fetch();
-
 			if (ps.withReplies && ps.withFiles) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
 
 			// early return if me is blocked by requesting user
@@ -94,7 +99,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			if (!serverSettings.enableFanoutTimeline) {
+			if (!this.serverSettings.enableFanoutTimeline) {
 				const timeline = await this.getFromDb({
 					untilId,
 					sinceId,
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 5e0ec390f2e8..5ebec4ffd070 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
 import { Inject, Injectable } from '@nestjs/common';
 import { createBullBoard } from '@bull-board/api';
 import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
-import { FastifyAdapter } from '@bull-board/fastify';
+import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
 import ms from 'ms';
 import sharp from 'sharp';
 import pug from 'pug';
@@ -24,13 +24,13 @@ import type { Config } from '@/config.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
 import { DI } from '@/di-symbols.js';
 import * as Acct from '@/misc/acct.js';
-import { MetaService } from '@/core/MetaService.js';
 import type {
 	DbQueue,
 	DeliverQueue,
 	EndedPollNotificationQueue,
 	InboxQueue,
 	ObjectStorageQueue,
+	RelationshipQueue,
 	SystemQueue,
 	UserWebhookDeliverQueue,
 	SystemWebhookDeliverQueue,
@@ -42,13 +42,26 @@ import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
 import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
 import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
 import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
-import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type {
+	AnnouncementsRepository,
+	ChannelsRepository,
+	ClipsRepository,
+	FlashsRepository,
+	GalleryPostsRepository,
+	MiMeta,
+	NotesRepository,
+	PagesRepository,
+	ReversiGamesRepository,
+	UserProfilesRepository,
+	UsersRepository,
+} from '@/models/_.js';
 import type Logger from '@/logger.js';
 import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
 import { bindThis } from '@/decorators.js';
 import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
 import { FeedService } from './FeedService.js';
 import { UrlPreviewService } from './UrlPreviewService.js';
 import { ClientLoggerService } from './ClientLoggerService.js';
@@ -73,6 +86,9 @@ export class ClientServerService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -100,6 +116,9 @@ export class ClientServerService {
 		@Inject(DI.reversiGamesRepository)
 		private reversiGamesRepository: ReversiGamesRepository,
 
+		@Inject(DI.announcementsRepository)
+		private announcementsRepository: AnnouncementsRepository,
+
 		private flashEntityService: FlashEntityService,
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
@@ -109,7 +128,7 @@ export class ClientServerService {
 		private clipEntityService: ClipEntityService,
 		private channelEntityService: ChannelEntityService,
 		private reversiGameEntityService: ReversiGameEntityService,
-		private metaService: MetaService,
+		private announcementEntityService: AnnouncementEntityService,
 		private urlPreviewService: UrlPreviewService,
 		private feedService: FeedService,
 		private roleService: RoleService,
@@ -120,6 +139,7 @@ export class ClientServerService {
 		@Inject('queue:deliver') public deliverQueue: DeliverQueue,
 		@Inject('queue:inbox') public inboxQueue: InboxQueue,
 		@Inject('queue:db') public dbQueue: DbQueue,
+		@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
 		@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
 		@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
 		@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
@@ -129,32 +149,30 @@ export class ClientServerService {
 
 	@bindThis
 	private async manifestHandler(reply: FastifyReply) {
-		const instance = await this.metaService.fetch(true);
-
 		let manifest = {
 			// 空文字列の場合右辺を使いたいため
 			// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-			'short_name': instance.shortName || instance.name || this.config.host,
+			'short_name': this.meta.shortName || this.meta.name || this.config.host,
 			// 空文字列の場合右辺を使いたいため
 			// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-			'name': instance.name || this.config.host,
+			'name': this.meta.name || this.config.host,
 			'start_url': '/',
 			'display': 'standalone',
 			'background_color': '#313a42',
 			// 空文字列の場合右辺を使いたいため
 			// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-			'theme_color': instance.themeColor || '#86b300',
+			'theme_color': this.meta.themeColor || '#86b300',
 			'icons': [{
 				// 空文字列の場合右辺を使いたいため
 				// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-				'src': instance.app192IconUrl || '/static-assets/icons/192.png',
+				'src': this.meta.app192IconUrl || '/static-assets/icons/192.png',
 				'sizes': '192x192',
 				'type': 'image/png',
 				'purpose': 'maskable',
 			}, {
 				// 空文字列の場合右辺を使いたいため
 				// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-				'src': instance.app512IconUrl || '/static-assets/icons/512.png',
+				'src': this.meta.app512IconUrl || '/static-assets/icons/512.png',
 				'sizes': '512x512',
 				'type': 'image/png',
 				'purpose': 'maskable',
@@ -178,7 +196,7 @@ export class ClientServerService {
 
 		manifest = {
 			...manifest,
-			...JSON.parse(instance.manifestJsonOverride === '' ? '{}' : instance.manifestJsonOverride),
+			...JSON.parse(this.meta.manifestJsonOverride === '' ? '{}' : this.meta.manifestJsonOverride),
 		};
 
 		reply.header('Cache-Control', 'max-age=300');
@@ -240,7 +258,7 @@ export class ClientServerService {
 			}
 		});
 
-		const serverAdapter = new FastifyAdapter();
+		const bullBoardServerAdapter = new BullBoardFastifyAdapter();
 
 		createBullBoard({
 			queues: [
@@ -249,15 +267,16 @@ export class ClientServerService {
 				this.deliverQueue,
 				this.inboxQueue,
 				this.dbQueue,
+				this.relationshipQueue,
 				this.objectStorageQueue,
 				this.userWebhookDeliverQueue,
 				this.systemWebhookDeliverQueue,
 			].map(q => new BullMQAdapter(q)),
-			serverAdapter,
+			serverAdapter: bullBoardServerAdapter,
 		});
 
-		serverAdapter.setBasePath(bullBoardPath);
-		(fastify.register as any)(serverAdapter.registerPlugin(), { prefix: bullBoardPath });
+		bullBoardServerAdapter.setBasePath(bullBoardPath);
+		(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
 		//#endregion
 
 		fastify.register(fastifyView, {
@@ -453,9 +472,7 @@ export class ClientServerService {
 
 		// OpenSearch XML
 		fastify.get('/opensearch.xml', async (request, reply) => {
-			const meta = await this.metaService.fetch();
-
-			const name = meta.name ?? 'Misskey';
+			const name = this.meta.name ?? 'Misskey';
 			let content = '';
 			content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">';
 			content += `<ShortName>${name}</ShortName>`;
@@ -472,14 +489,13 @@ export class ClientServerService {
 		//#endregion
 
 		const renderBase = async (reply: FastifyReply, data: { [key: string]: any } = {}) => {
-			const meta = await this.metaService.fetch();
 			reply.header('Cache-Control', 'public, max-age=30');
 			return await reply.view('base', {
-				img: meta.bannerUrl,
+				img: this.meta.bannerUrl,
 				url: this.config.url,
-				title: meta.name ?? 'Misskey',
-				desc: meta.description,
-				...await this.generateCommonPugData(meta),
+				title: this.meta.name ?? 'Misskey',
+				desc: this.meta.description,
+				...await this.generateCommonPugData(this.meta),
 				...data,
 			});
 		};
@@ -557,7 +573,6 @@ export class ClientServerService {
 
 			if (user != null) {
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
-				const meta = await this.metaService.fetch();
 				const me = profile.fields
 					? profile.fields
 						.filter(filed => filed.value != null && filed.value.match(/^https?:/))
@@ -573,7 +588,7 @@ export class ClientServerService {
 					user, profile, me,
 					avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
 					sub: request.params.sub,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				// リモートユーザーなので
@@ -603,15 +618,17 @@ export class ClientServerService {
 		fastify.get<{ Params: { note: string; } }>('/notes/:note', async (request, reply) => {
 			vary(reply.raw, 'Accept');
 
-			const note = await this.notesRepository.findOneBy({
-				id: request.params.note,
-				visibility: In(['public', 'home']),
+			const note = await this.notesRepository.findOne({
+				where: {
+					id: request.params.note,
+					visibility: In(['public', 'home']),
+				},
+				relations: ['user'],
 			});
 
-			if (note) {
+			if (note && !note.user!.requireSigninToViewContents) {
 				const _note = await this.noteEntityService.pack(note);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
-				const meta = await this.metaService.fetch();
 				reply.header('Cache-Control', 'public, max-age=15');
 				if (profile.preventAiLearning) {
 					reply.header('X-Robots-Tag', 'noimageai');
@@ -623,7 +640,7 @@ export class ClientServerService {
 					avatarUrl: _note.user.avatarUrl,
 					// TODO: Let locale changeable by instance setting
 					summary: getNoteSummary(_note),
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -648,7 +665,6 @@ export class ClientServerService {
 			if (page) {
 				const _page = await this.pageEntityService.pack(page);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: page.userId });
-				const meta = await this.metaService.fetch();
 				if (['public'].includes(page.visibility)) {
 					reply.header('Cache-Control', 'public, max-age=15');
 				} else {
@@ -662,7 +678,7 @@ export class ClientServerService {
 					page: _page,
 					profile,
 					avatarUrl: _page.user.avatarUrl,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -678,7 +694,6 @@ export class ClientServerService {
 			if (flash) {
 				const _flash = await this.flashEntityService.pack(flash);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: flash.userId });
-				const meta = await this.metaService.fetch();
 				reply.header('Cache-Control', 'public, max-age=15');
 				if (profile.preventAiLearning) {
 					reply.header('X-Robots-Tag', 'noimageai');
@@ -688,7 +703,7 @@ export class ClientServerService {
 					flash: _flash,
 					profile,
 					avatarUrl: _flash.user.avatarUrl,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -704,7 +719,6 @@ export class ClientServerService {
 			if (clip && clip.isPublic) {
 				const _clip = await this.clipEntityService.pack(clip);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
-				const meta = await this.metaService.fetch();
 				reply.header('Cache-Control', 'public, max-age=15');
 				if (profile.preventAiLearning) {
 					reply.header('X-Robots-Tag', 'noimageai');
@@ -714,7 +728,7 @@ export class ClientServerService {
 					clip: _clip,
 					profile,
 					avatarUrl: _clip.user.avatarUrl,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -728,7 +742,6 @@ export class ClientServerService {
 			if (post) {
 				const _post = await this.galleryPostEntityService.pack(post);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: post.userId });
-				const meta = await this.metaService.fetch();
 				reply.header('Cache-Control', 'public, max-age=15');
 				if (profile.preventAiLearning) {
 					reply.header('X-Robots-Tag', 'noimageai');
@@ -738,7 +751,7 @@ export class ClientServerService {
 					post: _post,
 					profile,
 					avatarUrl: _post.user.avatarUrl,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -753,11 +766,10 @@ export class ClientServerService {
 
 			if (channel) {
 				const _channel = await this.channelEntityService.pack(channel);
-				const meta = await this.metaService.fetch();
 				reply.header('Cache-Control', 'public, max-age=15');
 				return await reply.view('channel', {
 					channel: _channel,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -772,11 +784,28 @@ export class ClientServerService {
 
 			if (game) {
 				const _game = await this.reversiGameEntityService.packDetail(game);
-				const meta = await this.metaService.fetch();
 				reply.header('Cache-Control', 'public, max-age=3600');
 				return await reply.view('reversi-game', {
 					game: _game,
-					...await this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(this.meta),
+				});
+			} else {
+				return await renderBase(reply);
+			}
+		});
+
+		// 個別お知らせページ
+		fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => {
+			const announcement = await this.announcementsRepository.findOneBy({
+				id: request.params.announcementId,
+			});
+
+			if (announcement) {
+				const _announcement = await this.announcementEntityService.pack(announcement);
+				reply.header('Cache-Control', 'public, max-age=3600');
+				return await reply.view('announcement', {
+					announcement: _announcement,
+					...await this.generateCommonPugData(this.meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -797,27 +826,89 @@ export class ClientServerService {
 		//#endregion
 
 		//#region embed pages
-		fastify.get('/embed/*', async (request, reply) => {
-			const meta = await this.metaService.fetch();
+		fastify.get<{ Params: { user: string; } }>('/embed/user-timeline/:user', async (request, reply) => {
+			reply.removeHeader('X-Frame-Options');
+
+			const user = await this.usersRepository.findOneBy({
+				id: request.params.user,
+			});
+
+			if (user == null) return;
+			if (user.host != null) return;
+
+			const _user = await this.userEntityService.pack(user);
+
+			reply.header('Cache-Control', 'public, max-age=3600');
+			return await reply.view('base-embed', {
+				title: this.meta.name ?? 'Misskey',
+				...await this.generateCommonPugData(this.meta),
+				embedCtx: htmlSafeJsonStringify({
+					user: _user,
+				}),
+			});
+		});
 
+		fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
 			reply.removeHeader('X-Frame-Options');
 
+			const note = await this.notesRepository.findOneBy({
+				id: request.params.note,
+			});
+
+			if (note == null) return;
+			if (note.visibility !== 'public') return;
+			if (note.userHost != null) return;
+
+			const _note = await this.noteEntityService.pack(note, null, { detail: true });
+
 			reply.header('Cache-Control', 'public, max-age=3600');
 			return await reply.view('base-embed', {
-				title: meta.name ?? 'Misskey',
-				...await this.generateCommonPugData(meta),
+				title: this.meta.name ?? 'Misskey',
+				...await this.generateCommonPugData(this.meta),
+				embedCtx: htmlSafeJsonStringify({
+					note: _note,
+				}),
 			});
 		});
 
-		fastify.get('/_info_card_', async (request, reply) => {
-			const meta = await this.metaService.fetch(true);
+		fastify.get<{ Params: { clip: string; } }>('/embed/clips/:clip', async (request, reply) => {
+			reply.removeHeader('X-Frame-Options');
+
+			const clip = await this.clipsRepository.findOneBy({
+				id: request.params.clip,
+			});
+
+			if (clip == null) return;
+
+			const _clip = await this.clipEntityService.pack(clip);
 
+			reply.header('Cache-Control', 'public, max-age=3600');
+			return await reply.view('base-embed', {
+				title: this.meta.name ?? 'Misskey',
+				...await this.generateCommonPugData(this.meta),
+				embedCtx: htmlSafeJsonStringify({
+					clip: _clip,
+				}),
+			});
+		});
+
+		fastify.get('/embed/*', async (request, reply) => {
+			reply.removeHeader('X-Frame-Options');
+
+			reply.header('Cache-Control', 'public, max-age=3600');
+			return await reply.view('base-embed', {
+				title: this.meta.name ?? 'Misskey',
+				...await this.generateCommonPugData(this.meta),
+			});
+		});
+
+		fastify.get('/_info_card_', async (request, reply) => {
 			reply.removeHeader('X-Frame-Options');
 
 			return await reply.view('info-card', {
 				version: this.config.version,
 				host: this.config.host,
-				meta: meta,
+				meta: this.meta,
 				originalUsersCount: await this.usersRepository.countBy({ host: IsNull() }),
 				originalNotesCount: await this.notesRepository.countBy({ userHost: IsNull() }),
 			});
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index 8f8f08a3051b..5d493c2c46a3 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -8,7 +8,6 @@ import { summaly } from '@misskey-dev/summaly';
 import { SummalyResult } from '@misskey-dev/summaly/built/summary.js';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
-import { MetaService } from '@/core/MetaService.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
 import type Logger from '@/logger.js';
 import { query } from '@/misc/prelude/url.js';
@@ -26,7 +25,9 @@ export class UrlPreviewService {
 		@Inject(DI.config)
 		private config: Config,
 
-		private metaService: MetaService,
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		private httpRequestService: HttpRequestService,
 		private loggerService: LoggerService,
 	) {
@@ -62,9 +63,7 @@ export class UrlPreviewService {
 			return;
 		}
 
-		const meta = await this.metaService.fetch();
-
-		if (!meta.urlPreviewEnabled) {
+		if (!this.meta.urlPreviewEnabled) {
 			reply.code(403);
 			return {
 				error: new ApiError({
@@ -75,14 +74,14 @@ export class UrlPreviewService {
 			};
 		}
 
-		this.logger.info(meta.urlPreviewSummaryProxyUrl
+		this.logger.info(this.meta.urlPreviewSummaryProxyUrl
 			? `(Proxy) Getting preview of ${url}@${lang} ...`
 			: `Getting preview of ${url}@${lang} ...`);
 
 		try {
-			const summary = meta.urlPreviewSummaryProxyUrl
-				? await this.fetchSummaryFromProxy(url, meta, lang)
-				: await this.fetchSummary(url, meta, lang);
+			const summary = this.meta.urlPreviewSummaryProxyUrl
+				? await this.fetchSummaryFromProxy(url, this.meta, lang)
+				: await this.fetchSummary(url, this.meta, lang);
 
 			this.logger.succ(`Got preview of ${url}: ${summary.title}`);
 
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 7c6a5334291d..a04640d9935b 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -98,7 +98,7 @@
 	const theme = localStorage.getItem('theme');
 	if (theme) {
 		for (const [k, v] of Object.entries(JSON.parse(theme))) {
-			document.documentElement.style.setProperty(`--${k}`, v.toString());
+			document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
 
 			// HTMLの theme-color 適用
 			if (k === 'htmlThemeColor') {
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index dbcc8f537cdd..5d81f2bed0c0 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -5,8 +5,8 @@
  */
 
 html {
-	background-color: var(--bg);
-	color: var(--fg);
+	background-color: var(--MI_THEME-bg);
+	color: var(--MI_THEME-fg);
 }
 
 #splash {
@@ -17,7 +17,7 @@ html {
 	width: 100vw;
 	height: 100vh;
 	cursor: wait;
-	background-color: var(--bg);
+	background-color: var(--MI_THEME-bg);
 	opacity: 1;
 	transition: opacity 0.5s ease;
 }
@@ -45,7 +45,7 @@ html {
 	width: 28px;
 	height: 28px;
 	transform: translateY(70px);
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 #splashSpinner > .spinner {
diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css
index a7b110d80a10..5e8786cc4ee8 100644
--- a/packages/backend/src/server/web/style.embed.css
+++ b/packages/backend/src/server/web/style.embed.css
@@ -5,8 +5,8 @@
  */
 
 html {
-	background-color: var(--bg);
-	color: var(--fg);
+	background-color: var(--MI_THEME-bg);
+	color: var(--MI_THEME-fg);
 }
 
 html.embed {
@@ -24,7 +24,7 @@ html.embed {
 	width: 100vw;
 	height: 100vh;
 	cursor: wait;
-	background-color: var(--bg);
+	background-color: var(--MI_THEME-bg);
 	opacity: 1;
 	transition: opacity 0.5s ease;
 }
@@ -33,7 +33,7 @@ html.embed #splash {
 	box-sizing: border-box;
 	min-height: 300px;
 	border-radius: var(--radius, 12px);
-	border: 1px solid var(--divider, #e8e8e8);
+	border: 1px solid var(--MI_THEME-divider, #e8e8e8);
 }
 
 html.embed.norounded #splash {
@@ -67,7 +67,7 @@ html.embed.noborder #splash {
 	width: 28px;
 	height: 28px;
 	transform: translateY(70px);
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 #splashSpinner > .spinner {
diff --git a/packages/backend/src/server/web/views/announcement.pug b/packages/backend/src/server/web/views/announcement.pug
new file mode 100644
index 000000000000..7a4052e8a45a
--- /dev/null
+++ b/packages/backend/src/server/web/views/announcement.pug
@@ -0,0 +1,21 @@
+extends ./base
+
+block vars
+	- const title = announcement.title;
+	- const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text;
+	- const url = `${config.url}/announcements/${announcement.id}`;
+
+block title
+	= `${title} | ${instanceName}`
+
+block desc
+	meta(name='description' content=description)
+
+block og
+	meta(property='og:type'        content='article')
+	meta(property='og:title'       content= title)
+	meta(property='og:description' content= description)
+	meta(property='og:url'         content= url)
+	if announcement.imageUrl
+		meta(property='og:image' content=announcement.imageUrl)
+		meta(property='twitter:card' content='summary_large_image')
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
index d773f2676a59..baa09096766c 100644
--- a/packages/backend/src/server/web/views/base-embed.pug
+++ b/packages/backend/src/server/web/views/base-embed.pug
@@ -13,6 +13,8 @@ html(class='embed')
 		meta(name='referrer' content='origin')
 		meta(name='theme-color' content= themeColor || '#86b300')
 		meta(name='theme-color-orig' content= themeColor || '#86b300')
+		meta(property='og:site_name' content= instanceName || 'Misskey')
+		meta(property='instance_url' content= instanceUrl)
 		meta(name='viewport' content='width=device-width, initial-scale=1')
 		meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
 		link(rel='icon' href= icon || '/favicon.ico')
@@ -43,6 +45,9 @@ html(class='embed')
 		script(type='application/json' id='misskey_meta' data-generated-at=now)
 			!= metaJson
 
+		script(type='application/json' id='misskey_embedCtx' data-generated-at=now)
+			!= embedCtx
+
 		script
 			include ../boot.embed.js
 
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 88714b255641..280a5923c23f 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -2,6 +2,7 @@ block vars
 
 block loadClientEntry
 	- const entry = config.frontendEntry;
+	- const baseUrl = config.url;
 
 doctype html
 
@@ -32,7 +33,7 @@ html
 		link(rel='icon' href= icon || '/favicon.ico')
 		link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
 		link(rel='manifest' href='/manifest.json')
-		link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${url}/opensearch.xml`)
+		link(rel='search' type='application/opensearchdescription+xml' title=(title || "Misskey") href=`${baseUrl}/opensearch.xml`)
 		link(rel='prefetch' href=serverErrorImageUrl)
 		link(rel='prefetch' href=infoImageUrl)
 		link(rel='prefetch' href=notFoundImageUrl)
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index e852cf5ae29e..df3cfee17103 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -16,6 +16,8 @@
  * followRequestAccepted - 自分の送ったフォローリクエストが承認された
  * roleAssigned - ロールが付与された
  * achievementEarned - 実績を獲得
+ * exportCompleted - エクスポートが完了
+ * login - ログイン
  * app - アプリ通知
  * test - テスト通知(サーバー側)
  */
@@ -32,6 +34,8 @@ export const notificationTypes = [
 	'followRequestAccepted',
 	'roleAssigned',
 	'achievementEarned',
+	'exportCompleted',
+	'login',
 	'app',
 	'test',
 ] as const;
@@ -51,6 +55,20 @@ export const mutedNoteReasons = ['word', 'manual', 'spam', 'other'] as const;
 export const followingVisibilities = ['public', 'followers', 'private'] as const;
 export const followersVisibilities = ['public', 'followers', 'private'] as const;
 
+/**
+ * ユーザーがエクスポートできるものの種類
+ *
+ * (主にエクスポート完了通知で使用するものであり、既存のDBの名称等と必ずしも一致しない)
+ */
+export const userExportableEntities = ['antenna', 'blocking', 'clip', 'customEmoji', 'favorite', 'following', 'muting', 'note', 'userList'] as const;
+
+/**
+ * ユーザーがインポートできるものの種類
+ *
+ * (主にインポート完了通知で使用するものであり、既存のDBの名称等と必ずしも一致しない)
+ */
+export const userImportableEntities = ['antenna', 'blocking', 'customEmoji', 'following', 'muting', 'userList'] as const;
+
 export const moderationLogTypes = [
 	'updateServerSettings',
 	'suspend',
@@ -81,6 +99,8 @@ export const moderationLogTypes = [
 	'markSensitiveDriveFile',
 	'unmarkSensitiveDriveFile',
 	'resolveAbuseReport',
+	'forwardAbuseReport',
+	'updateAbuseReportNote',
 	'createInvitation',
 	'createAd',
 	'updateAd',
@@ -249,7 +269,18 @@ export type ModerationLogPayloads = {
 	resolveAbuseReport: {
 		reportId: string;
 		report: any;
-		forwarded: boolean;
+		forwarded?: boolean;
+		resolvedAs?: string | null;
+	};
+	forwardAbuseReport: {
+		reportId: string;
+		report: any;
+	};
+	updateAbuseReportNote: {
+		reportId: string;
+		report: any;
+		before: string;
+		after: string;
 	};
 	createInvitation: {
 		invitations: any[];
diff --git a/packages/backend/test-federation/.config/example.conf b/packages/backend/test-federation/.config/example.conf
new file mode 100644
index 000000000000..83d04eb39d50
--- /dev/null
+++ b/packages/backend/test-federation/.config/example.conf
@@ -0,0 +1,70 @@
+# based on https://github.com/misskey-dev/misskey-hub/blob/7071f63a1c80ee35c71f0fd8a6d8722c118c7574/src/docs/admin/nginx.md
+
+# For WebSocket
+map $http_upgrade $connection_upgrade {
+	default upgrade;
+	'' close;
+}
+
+proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache1:16m max_size=1g inactive=720m use_temp_path=off;
+
+server {
+	listen 80;
+	listen [::]:80;
+	server_name ${HOST};
+
+	# For SSL domain validation
+	root /var/www/html;
+	location /.well-known/acme-challenge/ { allow all; }
+	location /.well-known/pki-validation/ { allow all; }
+	location / { return 301 https://$server_name$request_uri; }
+}
+
+server {
+	listen 443 ssl;
+	listen [::]:443 ssl;
+	http2 on;
+	server_name ${HOST};
+
+	ssl_session_timeout 1d;
+	ssl_session_cache shared:ssl_session_cache:10m;
+	ssl_session_tickets off;
+
+	ssl_trusted_certificate /etc/nginx/certificates/rootCA.crt;
+	ssl_certificate /etc/nginx/certificates/$server_name.crt;
+	ssl_certificate_key /etc/nginx/certificates/$server_name.key;
+
+	# SSL protocol settings
+	ssl_protocols TLSv1.2 TLSv1.3;
+	ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
+	ssl_prefer_server_ciphers off;
+	ssl_stapling on;
+	ssl_stapling_verify on;
+
+	# Change to your upload limit
+	client_max_body_size 80m;
+
+	# Proxy to Node
+	location / {
+		proxy_pass http://misskey.${HOST}:3000;
+		proxy_set_header Host $host;
+		proxy_http_version 1.1;
+		proxy_redirect off;
+
+		# If it's behind another reverse proxy or CDN, remove the following.
+		proxy_set_header X-Real-IP $remote_addr;
+		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+		proxy_set_header X-Forwarded-Proto https;
+
+		# For WebSocket
+		proxy_set_header Upgrade $http_upgrade;
+		proxy_set_header Connection $connection_upgrade;
+
+		# Cache settings
+		proxy_cache cache1;
+		proxy_cache_lock on;
+		proxy_cache_use_stale updating;
+		proxy_force_ranges on;
+		add_header X-Cache $upstream_cache_status;
+	}
+}
diff --git a/packages/backend/test-federation/.config/example.default.yml b/packages/backend/test-federation/.config/example.default.yml
new file mode 100644
index 000000000000..ff1760a5a65b
--- /dev/null
+++ b/packages/backend/test-federation/.config/example.default.yml
@@ -0,0 +1,25 @@
+url: https://${HOST}/
+port: 3000
+db:
+  host: db.${HOST}
+  port: 5432
+  db: misskey
+  user: postgres
+  pass: postgres
+dbReplications: false
+redis:
+  host: redis.test
+  port: 6379
+id: 'aidx'
+proxyBypassHosts:
+  - api.deepl.com
+  - api-free.deepl.com
+  - www.recaptcha.net
+  - hcaptcha.com
+  - challenges.cloudflare.com
+proxyRemoteFiles: true
+signToActivityPubGet: true
+allowedPrivateNetworks: [
+  '127.0.0.1/32',
+  '172.20.0.0/16'
+]
diff --git a/packages/backend/test-federation/.config/example.docker.env b/packages/backend/test-federation/.config/example.docker.env
new file mode 100644
index 000000000000..a8af7cce49fb
--- /dev/null
+++ b/packages/backend/test-federation/.config/example.docker.env
@@ -0,0 +1,5 @@
+NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
+POSTGRES_DB=misskey
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=postgres
+MK_VERBOSE=true
diff --git a/packages/backend/test-federation/.gitignore b/packages/backend/test-federation/.gitignore
new file mode 100644
index 000000000000..e00f952cb58a
--- /dev/null
+++ b/packages/backend/test-federation/.gitignore
@@ -0,0 +1,6 @@
+certificates
+volumes
+.env
+docker.env
+*.test.conf
+*.test.default.yml
diff --git a/packages/backend/test-federation/README.md b/packages/backend/test-federation/README.md
new file mode 100644
index 000000000000..967d51f085e4
--- /dev/null
+++ b/packages/backend/test-federation/README.md
@@ -0,0 +1,24 @@
+## test-federation
+Test federation between two Misskey servers: `a.test` and `b.test`.
+
+Before testing, you need to build the entire project, and change working directory to here:
+```sh
+pnpm build
+cd packages/backend/test-federation
+```
+
+First, you need to start servers by executing following commands:
+```sh
+bash ./setup.sh
+docker compose up --scale tester=0
+```
+
+Then you can run all tests by a following command:
+```sh
+docker compose run --no-deps --rm tester
+```
+
+For testing a specific file, run a following command:
+```sh
+docker compose run --no-deps --rm tester -- pnpm -F backend test:fed packages/backend/test-federation/test/user.test.ts
+```
diff --git a/packages/backend/test-federation/compose.a.yml b/packages/backend/test-federation/compose.a.yml
new file mode 100644
index 000000000000..6a305b404cfe
--- /dev/null
+++ b/packages/backend/test-federation/compose.a.yml
@@ -0,0 +1,64 @@
+services:
+  a.test:
+    extends:
+      file: ./compose.tpl.yml
+      service: nginx
+    depends_on:
+      misskey.a.test:
+        condition: service_healthy
+    networks:
+      - internal_network_a
+    volumes:
+      - type: bind
+        source: ./.config/a.test.conf
+        target: /etc/nginx/conf.d/a.test.conf
+        read_only: true
+      - type: bind
+        source: ./certificates/a.test.crt
+        target: /etc/nginx/certificates/a.test.crt
+        read_only: true
+      - type: bind
+        source: ./certificates/a.test.key
+        target: /etc/nginx/certificates/a.test.key
+        read_only: true
+
+  misskey.a.test:
+    extends:
+      file: ./compose.tpl.yml
+      service: misskey
+    depends_on:
+      db.a.test:
+        condition: service_healthy
+      redis.test:
+        condition: service_healthy
+      setup:
+        condition: service_completed_successfully
+    networks:
+      - internal_network_a
+    volumes:
+      - type: bind
+        source: ./.config/a.test.default.yml
+        target: /misskey/.config/default.yml
+        read_only: true
+
+  db.a.test:
+    extends:
+      file: ./compose.tpl.yml
+      service: db
+    networks:
+      - internal_network_a
+    volumes:
+      - type: bind
+        source: ./volumes/db.a
+        target: /var/lib/postgresql/data
+        bind:
+          create_host_path: true
+
+networks:
+  internal_network_a:
+    internal: true
+    driver: bridge
+    ipam:
+      config:
+        - subnet: 172.21.0.0/16
+          ip_range: 172.21.0.0/24
diff --git a/packages/backend/test-federation/compose.b.yml b/packages/backend/test-federation/compose.b.yml
new file mode 100644
index 000000000000..1158b53baefe
--- /dev/null
+++ b/packages/backend/test-federation/compose.b.yml
@@ -0,0 +1,64 @@
+services:
+  b.test:
+    extends:
+      file: ./compose.tpl.yml
+      service: nginx
+    depends_on:
+      misskey.b.test:
+        condition: service_healthy
+    networks:
+      - internal_network_b
+    volumes:
+      - type: bind
+        source: ./.config/b.test.conf
+        target: /etc/nginx/conf.d/b.test.conf
+        read_only: true
+      - type: bind
+        source: ./certificates/b.test.crt
+        target: /etc/nginx/certificates/b.test.crt
+        read_only: true
+      - type: bind
+        source: ./certificates/b.test.key
+        target: /etc/nginx/certificates/b.test.key
+        read_only: true
+
+  misskey.b.test:
+    extends:
+      file: ./compose.tpl.yml
+      service: misskey
+    depends_on:
+      db.b.test:
+        condition: service_healthy
+      redis.test:
+        condition: service_healthy
+      setup:
+        condition: service_completed_successfully
+    networks:
+      - internal_network_b
+    volumes:
+      - type: bind
+        source: ./.config/b.test.default.yml
+        target: /misskey/.config/default.yml
+        read_only: true
+
+  db.b.test:
+    extends:
+      file: ./compose.tpl.yml
+      service: db
+    networks:
+      - internal_network_b
+    volumes:
+      - type: bind
+        source: ./volumes/db.b
+        target: /var/lib/postgresql/data
+        bind:
+          create_host_path: true
+
+networks:
+  internal_network_b:
+    internal: true
+    driver: bridge
+    ipam:
+      config:
+        - subnet: 172.22.0.0/16
+          ip_range: 172.22.0.0/24
diff --git a/packages/backend/test-federation/compose.override.yaml b/packages/backend/test-federation/compose.override.yaml
new file mode 100644
index 000000000000..60a7631ab520
--- /dev/null
+++ b/packages/backend/test-federation/compose.override.yaml
@@ -0,0 +1,117 @@
+services:
+  setup:
+    volumes:
+      - type: volume
+        source: node_modules
+        target: /misskey/node_modules
+      - type: volume
+        source: node_modules_backend
+        target: /misskey/packages/backend/node_modules
+      - type: volume
+        source: node_modules_misskey-js
+        target: /misskey/packages/misskey-js/node_modules
+      - type: volume
+        source: node_modules_misskey-reversi
+        target: /misskey/packages/misskey-reversi/node_modules
+
+  tester:
+    networks:
+      external_network:
+      internal_network:
+        ipv4_address: 172.20.1.1
+    volumes:
+      - type: volume
+        source: node_modules_dev
+        target: /misskey/node_modules
+      - type: volume
+        source: node_modules_backend_dev
+        target: /misskey/packages/backend/node_modules
+      - type: volume
+        source: node_modules_misskey-js_dev
+        target: /misskey/packages/misskey-js/node_modules
+
+  daemon:
+    networks:
+      - external_network
+      - internal_network_a
+      - internal_network_b
+    volumes:
+      - type: volume
+        source: node_modules_dev
+        target: /misskey/node_modules
+      - type: volume
+        source: node_modules_backend_dev
+        target: /misskey/packages/backend/node_modules
+
+  redis.test:
+    networks:
+      - internal_network_a
+      - internal_network_b
+
+  a.test:
+    networks:
+      - internal_network
+
+  misskey.a.test:
+    networks:
+      - external_network
+      - internal_network
+    volumes:
+      - type: volume
+        source: node_modules
+        target: /misskey/node_modules
+      - type: volume
+        source: node_modules_backend
+        target: /misskey/packages/backend/node_modules
+      - type: volume
+        source: node_modules_misskey-js
+        target: /misskey/packages/misskey-js/node_modules
+      - type: volume
+        source: node_modules_misskey-reversi
+        target: /misskey/packages/misskey-reversi/node_modules
+
+  b.test:
+    networks:
+      - internal_network
+
+  misskey.b.test:
+    networks:
+      - external_network
+      - internal_network
+    volumes:
+      - type: volume
+        source: node_modules
+        target: /misskey/node_modules
+      - type: volume
+        source: node_modules_backend
+        target: /misskey/packages/backend/node_modules
+      - type: volume
+        source: node_modules_misskey-js
+        target: /misskey/packages/misskey-js/node_modules
+      - type: volume
+        source: node_modules_misskey-reversi
+        target: /misskey/packages/misskey-reversi/node_modules
+
+networks:
+  external_network:
+    driver: bridge
+    ipam:
+      config:
+        - subnet: 172.23.0.0/16
+          ip_range: 172.23.0.0/24
+  internal_network:
+    internal: true
+    driver: bridge
+    ipam:
+      config:
+        - subnet: 172.20.0.0/16
+          ip_range: 172.20.0.0/24
+
+volumes:
+  node_modules:
+  node_modules_dev:
+  node_modules_backend:
+  node_modules_backend_dev:
+  node_modules_misskey-js:
+  node_modules_misskey-js_dev:
+  node_modules_misskey-reversi:
diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml
new file mode 100644
index 000000000000..8c38f16919f8
--- /dev/null
+++ b/packages/backend/test-federation/compose.tpl.yml
@@ -0,0 +1,101 @@
+services:
+  nginx:
+    image: nginx:1.27
+    volumes:
+      - type: bind
+        source: ./certificates/rootCA.crt
+        target: /etc/nginx/certificates/rootCA.crt
+        read_only: true
+    healthcheck:
+      test: service nginx status
+      interval: 5s
+      retries: 20
+
+  misskey:
+    image: node:20
+    env_file:
+      - ./.config/docker.env
+    environment:
+      - NODE_ENV=production
+    volumes:
+      - type: bind
+        source: ../../../built
+        target: /misskey/built
+        read_only: true
+      - type: bind
+        source: ../assets
+        target: /misskey/packages/backend/assets
+        read_only: true
+      - type: bind
+        source: ../built
+        target: /misskey/packages/backend/built
+        read_only: true
+      - type: bind
+        source: ../migration
+        target: /misskey/packages/backend/migration
+        read_only: true
+      - type: bind
+        source: ../ormconfig.js
+        target: /misskey/packages/backend/ormconfig.js
+        read_only: true
+      - type: bind
+        source: ../package.json
+        target: /misskey/packages/backend/package.json
+        read_only: true
+      - type: bind
+        source: ../../misskey-js/built
+        target: /misskey/packages/misskey-js/built
+        read_only: true
+      - type: bind
+        source: ../../misskey-js/package.json
+        target: /misskey/packages/misskey-js/package.json
+        read_only: true
+      - type: bind
+        source: ../../misskey-reversi/built
+        target: /misskey/packages/misskey-reversi/built
+        read_only: true
+      - type: bind
+        source: ../../misskey-reversi/package.json
+        target: /misskey/packages/misskey-reversi/package.json
+        read_only: true
+      - type: bind
+        source: ../../../healthcheck.sh
+        target: /misskey/healthcheck.sh
+        read_only: true
+      - type: bind
+        source: ../../../package.json
+        target: /misskey/package.json
+        read_only: true
+      - type: bind
+        source: ../../../pnpm-lock.yaml
+        target: /misskey/pnpm-lock.yaml
+        read_only: true
+      - type: bind
+        source: ../../../pnpm-workspace.yaml
+        target: /misskey/pnpm-workspace.yaml
+        read_only: true
+      - type: bind
+        source: ./certificates/rootCA.crt
+        target: /usr/local/share/ca-certificates/rootCA.crt
+        read_only: true
+    working_dir: /misskey
+    command: >
+      bash -c "
+        corepack enable && corepack prepare
+        pnpm -F backend migrate
+        pnpm -F backend start
+      "
+    healthcheck:
+      test: bash /misskey/healthcheck.sh
+      interval: 5s
+      retries: 20
+
+  db:
+    image: postgres:15-alpine
+    env_file:
+      - ./.config/docker.env
+    volumes:
+    healthcheck:
+      test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB
+      interval: 5s
+      retries: 20
diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml
new file mode 100644
index 000000000000..62d7e977c04e
--- /dev/null
+++ b/packages/backend/test-federation/compose.yml
@@ -0,0 +1,133 @@
+include:
+  - ./compose.a.yml
+  - ./compose.b.yml
+
+services:
+  setup:
+    extends:
+      file: ./compose.tpl.yml
+      service: misskey
+    command: >
+      bash -c "
+        corepack enable && corepack prepare
+        pnpm -F backend i
+        pnpm -F misskey-js i
+        pnpm -F misskey-reversi i
+      "
+
+  tester:
+    image: node:20
+    depends_on:
+      a.test:
+        condition: service_healthy
+      b.test:
+        condition: service_healthy
+    environment:
+      - NODE_ENV=development
+      - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt
+    volumes:
+      - type: bind
+        source: ../package.json
+        target: /misskey/packages/backend/package.json
+        read_only: true
+      - type: bind
+        source: ../test/resources
+        target: /misskey/packages/backend/test/resources
+        read_only: true
+      - type: bind
+        source: ./test
+        target: /misskey/packages/backend/test-federation/test
+        read_only: true
+      - type: bind
+        source: ../jest.config.cjs
+        target: /misskey/packages/backend/jest.config.cjs
+        read_only: true
+      - type: bind
+        source: ../jest.config.fed.cjs
+        target: /misskey/packages/backend/jest.config.fed.cjs
+        read_only: true
+      - type: bind
+        source: ../../misskey-js/built
+        target: /misskey/packages/misskey-js/built
+        read_only: true
+      - type: bind
+        source: ../../misskey-js/package.json
+        target: /misskey/packages/misskey-js/package.json
+        read_only: true
+      - type: bind
+        source: ../../../package.json
+        target: /misskey/package.json
+        read_only: true
+      - type: bind
+        source: ../../../pnpm-lock.yaml
+        target: /misskey/pnpm-lock.yaml
+        read_only: true
+      - type: bind
+        source: ../../../pnpm-workspace.yaml
+        target: /misskey/pnpm-workspace.yaml
+        read_only: true
+      - type: bind
+        source: ./certificates/rootCA.crt
+        target: /usr/local/share/ca-certificates/rootCA.crt
+        read_only: true
+    working_dir: /misskey
+    entrypoint: >
+      bash -c '
+        corepack enable && corepack prepare
+        pnpm -F misskey-js i --frozen-lockfile
+        pnpm -F backend i --frozen-lockfile
+        exec "$0" "$@"
+      '
+    command: pnpm -F backend test:fed
+
+  daemon:
+    image: node:20
+    depends_on:
+      redis.test:
+        condition: service_healthy
+    volumes:
+      - type: bind
+        source: ../package.json
+        target: /misskey/packages/backend/package.json
+        read_only: true
+      - type: bind
+        source: ./daemon.ts
+        target: /misskey/packages/backend/test-federation/daemon.ts
+        read_only: true
+      - type: bind
+        source: ./tsconfig.json
+        target: /misskey/packages/backend/test-federation/tsconfig.json
+        read_only: true
+      - type: bind
+        source: ../../../package.json
+        target: /misskey/package.json
+        read_only: true
+      - type: bind
+        source: ../../../pnpm-lock.yaml
+        target: /misskey/pnpm-lock.yaml
+        read_only: true
+      - type: bind
+        source: ../../../pnpm-workspace.yaml
+        target: /misskey/pnpm-workspace.yaml
+        read_only: true
+    working_dir: /misskey
+    command: >
+      bash -c "
+        corepack enable && corepack prepare
+        pnpm -F backend i --frozen-lockfile
+        pnpm exec tsc -p ./packages/backend/test-federation
+        node ./packages/backend/test-federation/built/daemon.js
+      "
+
+  redis.test:
+    image: redis:7-alpine
+    volumes:
+      - type: bind
+        source: ./volumes/redis
+        target: /data
+        bind:
+          create_host_path: true
+    healthcheck:
+      test: redis-cli ping
+      interval: 5s
+      retries: 20
diff --git a/packages/backend/test-federation/daemon.ts b/packages/backend/test-federation/daemon.ts
new file mode 100644
index 000000000000..46b6963c79ac
--- /dev/null
+++ b/packages/backend/test-federation/daemon.ts
@@ -0,0 +1,38 @@
+import IPCIDR from 'ip-cidr';
+import { Redis } from 'ioredis';
+
+const TESTER_IP_ADDRESS = '172.20.1.1';
+
+/**
+ * This should be same as {@link file://./../src/misc/get-ip-hash.ts}.
+ */
+function getIpHash(ip: string) {
+	const prefix = IPCIDR.createAddress(ip).mask(64);
+	return `ip-${BigInt('0b' + prefix).toString(36)}`;
+}
+
+/**
+ * This prevents hitting rate limit when login.
+ */
+export async function purgeLimit(host: string, client: Redis) {
+	const ipHash = getIpHash(TESTER_IP_ADDRESS);
+	const key = `${host}:limit:${ipHash}:signin`;
+	const res = await client.zrange(key, 0, -1);
+	if (res.length !== 0) {
+		console.log(`${key} - ${JSON.stringify(res)}`);
+		await client.del(key);
+	}
+}
+
+console.log('Daemon started running');
+
+{
+	const redisClient = new Redis({
+		host: 'redis.test',
+	});
+
+	setInterval(() => {
+		purgeLimit('a.test', redisClient);
+		purgeLimit('b.test', redisClient);
+	}, 200);
+}
diff --git a/packages/backend/test-federation/eslint.config.js b/packages/backend/test-federation/eslint.config.js
new file mode 100644
index 000000000000..e3bcf4c0fe22
--- /dev/null
+++ b/packages/backend/test-federation/eslint.config.js
@@ -0,0 +1,21 @@
+import globals from 'globals';
+import tsParser from '@typescript-eslint/parser';
+import sharedConfig from '../../shared/eslint.config.js';
+
+export default [
+	...sharedConfig,
+	{
+		files: ['**/*.ts', '**/*.tsx'],
+		languageOptions: {
+			globals: {
+				...globals.node,
+			},
+			parserOptions: {
+				parser: tsParser,
+				project: ['./tsconfig.json'],
+				sourceType: 'module',
+				tsconfigRootDir: import.meta.dirname,
+			},
+		},
+	},
+];
diff --git a/packages/backend/test-federation/setup.sh b/packages/backend/test-federation/setup.sh
new file mode 100644
index 000000000000..1bc3a2a87c0d
--- /dev/null
+++ b/packages/backend/test-federation/setup.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+mkdir certificates
+
+# rootCA
+openssl genrsa -des3 \
+  -passout pass:rootCA \
+  -out certificates/rootCA.key 4096
+openssl req -x509 -new -nodes -batch \
+  -key certificates/rootCA.key \
+  -sha256 \
+  -days 1024 \
+  -passin pass:rootCA \
+  -out certificates/rootCA.crt
+
+# domain
+function generate {
+  openssl req -new -newkey rsa:2048 -sha256 -nodes \
+    -keyout certificates/$1.key \
+    -subj "/CN=$1/emailAddress=admin@$1/C=JP/ST=/L=/O=Misskey Tester/OU=Some Unit" \
+    -out certificates/$1.csr
+  openssl x509 -req -sha256 \
+    -in certificates/$1.csr \
+    -CA certificates/rootCA.crt \
+    -CAkey certificates/rootCA.key \
+    -CAcreateserial \
+    -passin pass:rootCA \
+    -out certificates/$1.crt \
+    -days 500
+  if [ ! -f .config/docker.env ]; then cp .config/example.docker.env .config/docker.env; fi
+  if [ ! -f .config/$1.conf ]; then sed "s/\${HOST}/$1/g" .config/example.conf > .config/$1.conf; fi
+  if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.default.yml > .config/$1.default.yml; fi
+}
+
+generate a.test
+generate b.test
diff --git a/packages/backend/test-federation/test/abuse-report.test.ts b/packages/backend/test-federation/test/abuse-report.test.ts
new file mode 100644
index 000000000000..b54d6222b4af
--- /dev/null
+++ b/packages/backend/test-federation/test/abuse-report.test.ts
@@ -0,0 +1,52 @@
+import { rejects, strictEqual } from 'node:assert';
+import * as Misskey from 'misskey-js';
+import { createAccount, createModerator, resolveRemoteUser, sleep, type LoginUser } from './utils.js';
+
+describe('Abuse report', () => {
+	describe('Forwarding report', () => {
+		let alice: LoginUser, bob: LoginUser, aModerator: LoginUser, bModerator: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[aModerator, bModerator] = await Promise.all([
+				createModerator('a.test'),
+				createModerator('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+		});
+
+		test('Alice reports Bob, moderator in A forwards it, and B moderator receives it', async () => {
+			const comment = crypto.randomUUID();
+			await alice.client.request('users/report-abuse', { userId: bobInA.id, comment });
+			const reports = await aModerator.client.request('admin/abuse-user-reports', {});
+			const report = reports.filter(report => report.comment === comment)[0];
+			await aModerator.client.request('admin/forward-abuse-user-report', { reportId: report.id });
+			await sleep();
+
+			const reportsInB = await bModerator.client.request('admin/abuse-user-reports', {});
+			const reportInB = reportsInB.filter(report => report.comment.includes(comment))[0];
+			// NOTE: reporter is not Alice, and is not moderator in A
+			strictEqual(reportInB.reporter.url, 'https://a.test/@instance.actor');
+			strictEqual(reportInB.targetUserId, bob.id);
+
+			// NOTE: cannot forward multiple times
+			await rejects(
+				async () => await aModerator.client.request('admin/forward-abuse-user-report', { reportId: report.id }),
+				(err: any) => {
+					strictEqual(err.code, 'INTERNAL_ERROR');
+					strictEqual(err.info.e.message, 'The report has already been forwarded.');
+					return true;
+				},
+			);
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/block.test.ts b/packages/backend/test-federation/test/block.test.ts
new file mode 100644
index 000000000000..ef910eeaead7
--- /dev/null
+++ b/packages/backend/test-federation/test/block.test.ts
@@ -0,0 +1,224 @@
+import { deepStrictEqual, rejects, strictEqual } from 'node:assert';
+import * as Misskey from 'misskey-js';
+import { assertNotificationReceived, createAccount, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep } from './utils.js';
+
+describe('Block', () => {
+	describe('Check follow', () => {
+		let alice: LoginUser, bob: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+		});
+
+		test('Cannot follow if blocked', async () => {
+			await alice.client.request('blocking/create', { userId: bobInA.id });
+			await sleep();
+			await rejects(
+				async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+				(err: any) => {
+					strictEqual(err.code, 'BLOCKED');
+					return true;
+				},
+			);
+
+			const following = await bob.client.request('users/following', { userId: bob.id });
+			strictEqual(following.length, 0);
+			const followers = await alice.client.request('users/followers', { userId: alice.id });
+			strictEqual(followers.length, 0);
+		});
+
+		// FIXME: this is invalid case
+		test('Cannot follow even if unblocked', async () => {
+			// unblock here
+			await alice.client.request('blocking/delete', { userId: bobInA.id });
+			await sleep();
+
+			// TODO: why still being blocked?
+			await rejects(
+				async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+				(err: any) => {
+					strictEqual(err.code, 'BLOCKED');
+					return true;
+				},
+			);
+		});
+
+		test.skip('Can follow if unblocked', async () => {
+			await alice.client.request('blocking/delete', { userId: bobInA.id });
+			await sleep();
+
+			await bob.client.request('following/create', { userId: aliceInB.id });
+			await sleep();
+
+			const following = await bob.client.request('users/following', { userId: bob.id });
+			strictEqual(following.length, 1);
+			const followers = await alice.client.request('users/followers', { userId: alice.id });
+			strictEqual(followers.length, 1);
+		});
+
+		test.skip('Remove follower when block them', async () => {
+			test('before block', async () => {
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 1);
+				const followers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(followers.length, 1);
+			});
+
+			await alice.client.request('blocking/create', { userId: bobInA.id });
+			await sleep();
+
+			test('after block', async () => {
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 0);
+				const followers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(followers.length, 0);
+			});
+		});
+	});
+
+	describe('Check reply', () => {
+		let alice: LoginUser, bob: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+		});
+
+		test('Cannot reply if blocked', async () => {
+			await alice.client.request('blocking/create', { userId: bobInA.id });
+			await sleep();
+
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			await rejects(
+				async () => await bob.client.request('notes/create', { text: 'b', replyId: resolvedNote.id }),
+				(err: any) => {
+					strictEqual(err.code, 'YOU_HAVE_BEEN_BLOCKED');
+					return true;
+				},
+			);
+		});
+
+		test('Can reply if unblocked', async () => {
+			await alice.client.request('blocking/delete', { userId: bobInA.id });
+			await sleep();
+
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			const reply = (await bob.client.request('notes/create', { text: 'b', replyId: resolvedNote.id })).createdNote;
+
+			await resolveRemoteNote('b.test', reply.id, alice);
+		});
+	});
+
+	describe('Check reaction', () => {
+		let alice: LoginUser, bob: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+		});
+
+		test('Cannot reaction if blocked', async () => {
+			await alice.client.request('blocking/create', { userId: bobInA.id });
+			await sleep();
+
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			await rejects(
+				async () => await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: '😅' }),
+				(err: any) => {
+					strictEqual(err.code, 'YOU_HAVE_BEEN_BLOCKED');
+					return true;
+				},
+			);
+		});
+
+		// FIXME: this is invalid case
+		test('Cannot reaction even if unblocked', async () => {
+			// unblock here
+			await alice.client.request('blocking/delete', { userId: bobInA.id });
+			await sleep();
+
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+
+			// TODO: why still being blocked?
+			await rejects(
+				async () => await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: '😅' }),
+				(err: any) => {
+					strictEqual(err.code, 'YOU_HAVE_BEEN_BLOCKED');
+					return true;
+				},
+			);
+		});
+
+		test.skip('Can reaction if unblocked', async () => {
+			await alice.client.request('blocking/delete', { userId: bobInA.id });
+			await sleep();
+
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: '😅' });
+
+			const _note = await alice.client.request('notes/show', { noteId: note.id });
+			deepStrictEqual(_note.reactions, { '😅': 1 });
+		});
+	});
+
+	describe('Check mention', () => {
+		let alice: LoginUser, bob: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+		});
+
+		/** NOTE: You should mute the target to stop receiving notifications */
+		test('Can mention and notified even if blocked', async () => {
+			await alice.client.request('blocking/create', { userId: bobInA.id });
+			await sleep();
+
+			const text = `@${alice.username}@a.test plz unblock me!`;
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('notes/create', { text }),
+				notification => notification.type === 'mention' && notification.userId === bobInA.id && notification.note.text === text,
+				true,
+			);
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/drive.test.ts b/packages/backend/test-federation/test/drive.test.ts
new file mode 100644
index 000000000000..f755183b4dbe
--- /dev/null
+++ b/packages/backend/test-federation/test/drive.test.ts
@@ -0,0 +1,175 @@
+import assert, { strictEqual } from 'node:assert';
+import * as Misskey from 'misskey-js';
+import { createAccount, deepStrictEqualWithExcludedFields, fetchAdmin, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep, uploadFile } from './utils.js';
+
+const bAdmin = await fetchAdmin('b.test');
+
+describe('Drive', () => {
+	describe('Upload image in a.test and resolve from b.test', () => {
+		let uploader: LoginUser;
+
+		beforeAll(async () => {
+			uploader = await createAccount('a.test');
+		});
+
+		let image: Misskey.entities.DriveFile, imageInB: Misskey.entities.DriveFile;
+
+		describe('Upload', () => {
+			beforeAll(async () => {
+				image = await uploadFile('a.test', uploader);
+				const noteWithImage = (await uploader.client.request('notes/create', { fileIds: [image.id] })).createdNote;
+				const noteInB = await resolveRemoteNote('a.test', noteWithImage.id, bAdmin);
+				assert(noteInB.files != null);
+				strictEqual(noteInB.files.length, 1);
+				imageInB = noteInB.files[0];
+			});
+
+			test('Check consistency of DriveFile', () => {
+				// console.log(`a.test: ${JSON.stringify(image, null, '\t')}`);
+				// console.log(`b.test: ${JSON.stringify(imageInB, null, '\t')}`);
+
+				deepStrictEqualWithExcludedFields(image, imageInB, [
+					'id',
+					'createdAt',
+					'size',
+					'url',
+					'thumbnailUrl',
+					'userId',
+				]);
+			});
+		});
+
+		let updatedImage: Misskey.entities.DriveFile, updatedImageInB: Misskey.entities.DriveFile;
+
+		describe('Update', () => {
+			beforeAll(async () => {
+				updatedImage = await uploader.client.request('drive/files/update', {
+					fileId: image.id,
+					name: 'updated_192.jpg',
+					isSensitive: true,
+				});
+
+				updatedImageInB = await bAdmin.client.request('drive/files/show', {
+					fileId: imageInB.id,
+				});
+			});
+
+			test('Check consistency', () => {
+				// console.log(`a.test: ${JSON.stringify(updatedImage, null, '\t')}`);
+				// console.log(`b.test: ${JSON.stringify(updatedImageInB, null, '\t')}`);
+
+				// FIXME: not updated with `drive/files/update`
+				strictEqual(updatedImage.isSensitive, true);
+				strictEqual(updatedImage.name, 'updated_192.jpg');
+				strictEqual(updatedImageInB.isSensitive, false);
+				strictEqual(updatedImageInB.name, '192.jpg');
+			});
+		});
+
+		let reupdatedImageInB: Misskey.entities.DriveFile;
+
+		describe('Re-update with attaching to Note', () => {
+			beforeAll(async () => {
+				const noteWithUpdatedImage = (await uploader.client.request('notes/create', { fileIds: [updatedImage.id] })).createdNote;
+				const noteWithUpdatedImageInB = await resolveRemoteNote('a.test', noteWithUpdatedImage.id, bAdmin);
+				assert(noteWithUpdatedImageInB.files != null);
+				strictEqual(noteWithUpdatedImageInB.files.length, 1);
+				reupdatedImageInB = noteWithUpdatedImageInB.files[0];
+			});
+
+			test('Check consistency', () => {
+				// console.log(`b.test: ${JSON.stringify(reupdatedImageInB, null, '\t')}`);
+
+				// `isSensitive` is updated
+				strictEqual(reupdatedImageInB.isSensitive, true);
+				// FIXME: but `name` is not updated
+				strictEqual(reupdatedImageInB.name, '192.jpg');
+			});
+		});
+	});
+
+	describe('Sensitive flag', () => {
+		describe('isSensitive is federated in delivering to followers', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				[bobInA, aliceInB] = await Promise.all([
+					resolveRemoteUser('b.test', bob.id, alice),
+					resolveRemoteUser('a.test', alice.id, bob),
+				]);
+
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+			});
+
+			test('Alice uploads sensitive image and it is shown as sensitive from Bob', async () => {
+				const file = await uploadFile('a.test', alice);
+				await alice.client.request('drive/files/update', { fileId: file.id, isSensitive: true });
+				await alice.client.request('notes/create', { text: 'sensitive', fileIds: [file.id] });
+				await sleep();
+
+				const notes = await bob.client.request('notes/timeline', {});
+				strictEqual(notes.length, 1);
+				const noteInB = notes[0];
+				assert(noteInB.files != null);
+				strictEqual(noteInB.files.length, 1);
+				strictEqual(noteInB.files[0].isSensitive, true);
+			});
+		});
+
+		describe('isSensitive is federated in resolving', () => {
+			let alice: LoginUser, bob: LoginUser;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+			});
+
+			test('Alice uploads sensitive image and it is shown as sensitive from Bob', async () => {
+				const file = await uploadFile('a.test', alice);
+				await alice.client.request('drive/files/update', { fileId: file.id, isSensitive: true });
+				const note = (await alice.client.request('notes/create', { text: 'sensitive', fileIds: [file.id] })).createdNote;
+
+				const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+				assert(noteInB.files != null);
+				strictEqual(noteInB.files.length, 1);
+				strictEqual(noteInB.files[0].isSensitive, true);
+			});
+		});
+
+		/** @see https://github.com/misskey-dev/misskey/issues/12208 */
+		describe('isSensitive is federated in replying', () => {
+			let alice: LoginUser, bob: LoginUser;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+			});
+
+			test('Alice uploads sensitive image and it is shown as sensitive from Bob', async () => {
+				const bobNote = (await bob.client.request('notes/create', { text: 'I\'m Bob' })).createdNote;
+
+				const file = await uploadFile('a.test', alice);
+				await alice.client.request('drive/files/update', { fileId: file.id, isSensitive: true });
+				const bobNoteInA = await resolveRemoteNote('b.test', bobNote.id, alice);
+				const note = (await alice.client.request('notes/create', { text: 'sensitive', fileIds: [file.id], replyId: bobNoteInA.id })).createdNote;
+				await sleep();
+
+				const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+				assert(noteInB.files != null);
+				strictEqual(noteInB.files.length, 1);
+				strictEqual(noteInB.files[0].isSensitive, true);
+			});
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/emoji.test.ts b/packages/backend/test-federation/test/emoji.test.ts
new file mode 100644
index 000000000000..3119ca6e4de5
--- /dev/null
+++ b/packages/backend/test-federation/test/emoji.test.ts
@@ -0,0 +1,97 @@
+import assert, { deepStrictEqual, strictEqual } from 'assert';
+import * as Misskey from 'misskey-js';
+import { addCustomEmoji, createAccount, type LoginUser, resolveRemoteUser, sleep } from './utils.js';
+
+describe('Emoji', () => {
+	let alice: LoginUser, bob: LoginUser;
+	let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+	beforeAll(async () => {
+		[alice, bob] = await Promise.all([
+			createAccount('a.test'),
+			createAccount('b.test'),
+		]);
+
+		[bobInA, aliceInB] = await Promise.all([
+			resolveRemoteUser('b.test', bob.id, alice),
+			resolveRemoteUser('a.test', alice.id, bob),
+		]);
+
+		await bob.client.request('following/create', { userId: aliceInB.id });
+		await sleep();
+	});
+
+	test('Custom emoji are delivered with Note delivery', async () => {
+		const emoji = await addCustomEmoji('a.test');
+		await alice.client.request('notes/create', { text: `I love :${emoji.name}:` });
+		await sleep();
+
+		const notes = await bob.client.request('notes/timeline', {});
+		const noteInB = notes[0];
+
+		strictEqual(noteInB.text, `I love \u200b:${emoji.name}:\u200b`);
+		assert(noteInB.emojis != null);
+		assert(emoji.name in noteInB.emojis);
+		strictEqual(noteInB.emojis[emoji.name], emoji.url);
+	});
+
+	test('Custom emoji are delivered with Reaction delivery', async () => {
+		const emoji = await addCustomEmoji('a.test');
+		const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+		await sleep();
+
+		await alice.client.request('notes/reactions/create', { noteId: note.id, reaction: `:${emoji.name}:` });
+		await sleep();
+
+		const noteInB = (await bob.client.request('notes/timeline', {}))[0];
+		deepStrictEqual(noteInB.reactions[`:${emoji.name}@a.test:`], 1);
+		deepStrictEqual(noteInB.reactionEmojis[`${emoji.name}@a.test`], emoji.url);
+	});
+
+	test('Custom emoji are delivered with Profile delivery', async () => {
+		const emoji = await addCustomEmoji('a.test');
+		const renewedAlice = await alice.client.request('i/update', { name: `:${emoji.name}:` });
+		await sleep();
+
+		const renewedaliceInB = await bob.client.request('users/show', { userId: aliceInB.id });
+		strictEqual(renewedaliceInB.name, renewedAlice.name);
+		assert(emoji.name in renewedaliceInB.emojis);
+		strictEqual(renewedaliceInB.emojis[emoji.name], emoji.url);
+	});
+
+	test('Local-only custom emoji aren\'t delivered with Note delivery', async () => {
+		const emoji = await addCustomEmoji('a.test', { localOnly: true });
+		await alice.client.request('notes/create', { text: `I love :${emoji.name}:` });
+		await sleep();
+
+		const notes = await bob.client.request('notes/timeline', {});
+		const noteInB = notes[0];
+
+		strictEqual(noteInB.text, `I love \u200b:${emoji.name}:\u200b`);
+		// deepStrictEqual(noteInB.emojis, {}); // TODO: this fails (why?)
+		deepStrictEqual({ ...noteInB.emojis }, {});
+	});
+
+	test('Local-only custom emoji aren\'t delivered with Reaction delivery', async () => {
+		const emoji = await addCustomEmoji('a.test', { localOnly: true });
+		const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+		await sleep();
+
+		await alice.client.request('notes/reactions/create', { noteId: note.id, reaction: `:${emoji.name}:` });
+		await sleep();
+
+		const noteInB = (await bob.client.request('notes/timeline', {}))[0];
+		deepStrictEqual({ ...noteInB.reactions }, { '❤': 1 });
+		deepStrictEqual({ ...noteInB.reactionEmojis }, {});
+	});
+
+	test('Local-only custom emoji aren\'t delivered with Profile delivery', async () => {
+		const emoji = await addCustomEmoji('a.test', { localOnly: true });
+		const renewedAlice = await alice.client.request('i/update', { name: `:${emoji.name}:` });
+		await sleep();
+
+		const renewedaliceInB = await bob.client.request('users/show', { userId: aliceInB.id });
+		strictEqual(renewedaliceInB.name, renewedAlice.name);
+		deepStrictEqual({ ...renewedaliceInB.emojis }, {});
+	});
+});
diff --git a/packages/backend/test-federation/test/move.test.ts b/packages/backend/test-federation/test/move.test.ts
new file mode 100644
index 000000000000..56a57de8a4e5
--- /dev/null
+++ b/packages/backend/test-federation/test/move.test.ts
@@ -0,0 +1,52 @@
+import assert, { strictEqual } from 'node:assert';
+import { createAccount, type LoginUser, sleep } from './utils.js';
+
+describe('Move', () => {
+	test('Minimum move', async () => {
+		const [alice, bob] = await Promise.all([
+			createAccount('a.test'),
+			createAccount('b.test'),
+		]);
+
+		await bob.client.request('i/update', { alsoKnownAs: [`@${alice.username}@a.test`] });
+		await alice.client.request('i/move', { moveToAccount: `@${bob.username}@b.test` });
+	});
+
+	/** @see https://github.com/misskey-dev/misskey/issues/11320 */
+	describe('Following relation is transferred after move', () => {
+		let alice: LoginUser, bob: LoginUser, carol: LoginUser;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+			carol = await createAccount('a.test');
+
+			// Follow @carol@a.test ==> @alice@a.test
+			await carol.client.request('following/create', { userId: alice.id });
+
+			// Move @alice@a.test ==> @bob@b.test
+			await bob.client.request('i/update', { alsoKnownAs: [`@${alice.username}@a.test`] });
+			await alice.client.request('i/move', { moveToAccount: `@${bob.username}@b.test` });
+			await sleep();
+		});
+
+		test('Check from follower', async () => {
+			const following = await carol.client.request('users/following', { userId: carol.id });
+			strictEqual(following.length, 2);
+			const followees = following.map(({ followee }) => followee);
+			assert(followees.every(followee => followee != null));
+			assert(followees.some(({ id, url }) => id === alice.id && url === null));
+			assert(followees.some(({ url }) => url === `https://b.test/@${bob.username}`));
+		});
+
+		test('Check from followee', async () => {
+			const followers = await bob.client.request('users/followers', { userId: bob.id });
+			strictEqual(followers.length, 1);
+			const follower = followers[0].follower;
+			assert(follower != null);
+			strictEqual(follower.url, `https://a.test/@${carol.username}`);
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts
new file mode 100644
index 000000000000..bacc4cc54f18
--- /dev/null
+++ b/packages/backend/test-federation/test/note.test.ts
@@ -0,0 +1,317 @@
+import assert, { rejects, strictEqual } from 'node:assert';
+import * as Misskey from 'misskey-js';
+import { addCustomEmoji, createAccount, createModerator, deepStrictEqualWithExcludedFields, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep, uploadFile } from './utils.js';
+
+describe('Note', () => {
+	let alice: LoginUser, bob: LoginUser;
+	let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+	beforeAll(async () => {
+		[alice, bob] = await Promise.all([
+			createAccount('a.test'),
+			createAccount('b.test'),
+		]);
+
+		[bobInA, aliceInB] = await Promise.all([
+			resolveRemoteUser('b.test', bob.id, alice),
+			resolveRemoteUser('a.test', alice.id, bob),
+		]);
+	});
+
+	describe('Note content', () => {
+		test('Consistency of Public Note', async () => {
+			const image = await uploadFile('a.test', alice);
+			const note = (await alice.client.request('notes/create', {
+				text: 'I am Alice!',
+				fileIds: [image.id],
+				poll: {
+					choices: ['neko', 'inu'],
+					multiple: false,
+					expiredAfter: 60 * 60 * 1000,
+				},
+			})).createdNote;
+
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			deepStrictEqualWithExcludedFields(note, resolvedNote, [
+				'id',
+				'emojis',
+				/** Consistency of files is checked at {@link file://./drive.test.ts}, so let's skip. */
+				'fileIds',
+				'files',
+				/** @see https://github.com/misskey-dev/misskey/issues/12409 */
+				'reactionAcceptance',
+				'userId',
+				'user',
+				'uri',
+			]);
+			strictEqual(aliceInB.id, resolvedNote.userId);
+		});
+
+		test('Consistency of reply', async () => {
+			const _replyedNote = (await alice.client.request('notes/create', {
+				text: 'a',
+			})).createdNote;
+			const note = (await alice.client.request('notes/create', {
+				text: 'b',
+				replyId: _replyedNote.id,
+			})).createdNote;
+			// NOTE: the repliedCount is incremented, so fetch again
+			const replyedNote = await alice.client.request('notes/show', { noteId: _replyedNote.id });
+			strictEqual(replyedNote.repliesCount, 1);
+
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			deepStrictEqualWithExcludedFields(note, resolvedNote, [
+				'id',
+				'emojis',
+				'reactionAcceptance',
+				'replyId',
+				'reply',
+				'userId',
+				'user',
+				'uri',
+			]);
+			assert(resolvedNote.replyId != null);
+			assert(resolvedNote.reply != null);
+			deepStrictEqualWithExcludedFields(replyedNote, resolvedNote.reply, [
+				'id',
+				// TODO: why clippedCount loses consistency?
+				'clippedCount',
+				'emojis',
+				'userId',
+				'user',
+				'uri',
+				// flaky because this is parallelly incremented, so let's check it below
+				'repliesCount',
+			]);
+			strictEqual(aliceInB.id, resolvedNote.userId);
+
+			await sleep();
+
+			const resolvedReplyedNote = await bob.client.request('notes/show', { noteId: resolvedNote.replyId });
+			strictEqual(resolvedReplyedNote.repliesCount, 1);
+		});
+
+		test('Consistency of Renote', async () => {
+			// NOTE: the renoteCount is not incremented, so no need to fetch again
+			const renotedNote = (await alice.client.request('notes/create', {
+				text: 'a',
+			})).createdNote;
+			const note = (await alice.client.request('notes/create', {
+				text: 'b',
+				renoteId: renotedNote.id,
+			})).createdNote;
+
+			const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+			deepStrictEqualWithExcludedFields(note, resolvedNote, [
+				'id',
+				'emojis',
+				'reactionAcceptance',
+				'renoteId',
+				'renote',
+				'userId',
+				'user',
+				'uri',
+			]);
+			assert(resolvedNote.renoteId != null);
+			assert(resolvedNote.renote != null);
+			deepStrictEqualWithExcludedFields(renotedNote, resolvedNote.renote, [
+				'id',
+				'emojis',
+				'userId',
+				'user',
+				'uri',
+			]);
+			strictEqual(aliceInB.id, resolvedNote.userId);
+		});
+	});
+
+	describe('Other props', () => {
+		test('localOnly', async () => {
+			const note = (await alice.client.request('notes/create', { text: 'a', localOnly: true })).createdNote;
+			rejects(
+				async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }),
+				(err: any) => {
+					/**
+					 * FIXME: this error is not handled
+					 * @see https://github.com/misskey-dev/misskey/issues/12736
+					 */
+					strictEqual(err.code, 'INTERNAL_ERROR');
+					return true;
+				},
+			);
+		});
+	});
+
+	describe('Deletion', () => {
+		describe('Check Delete consistency', () => {
+			let carol: LoginUser;
+
+			beforeAll(async () => {
+				carol = await createAccount('a.test');
+
+				await carol.client.request('following/create', { userId: bobInA.id });
+				await sleep();
+			});
+
+			test('Delete is derivered to followers', async () => {
+				const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote;
+				const noteInA = await resolveRemoteNote('b.test', note.id, carol);
+				await bob.client.request('notes/delete', { noteId: note.id });
+				await sleep();
+
+				await rejects(
+					async () => await carol.client.request('notes/show', { noteId: noteInA.id }),
+					(err: any) => {
+						strictEqual(err.code, 'NO_SUCH_NOTE');
+						return true;
+					},
+				);
+			});
+		});
+
+		describe('Deletion of remote user\'s note for moderation', () => {
+			let note: Misskey.entities.Note;
+
+			test('Alice post is deleted in B', async () => {
+				note = (await alice.client.request('notes/create', { text: 'Hello' })).createdNote;
+				const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+				const bMod = await createModerator('b.test');
+				await bMod.client.request('notes/delete', { noteId: noteInB.id });
+				await rejects(
+					async () => await bob.client.request('notes/show', { noteId: noteInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'NO_SUCH_NOTE');
+						return true;
+					},
+				);
+			});
+
+			/**
+			 * FIXME: implement soft deletion as well as user?
+			 *        @see https://github.com/misskey-dev/misskey/issues/11437
+			 */
+			test.failing('Not found even if resolve again', async () => {
+				const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+				await rejects(
+					async () => await bob.client.request('notes/show', { noteId: noteInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'NO_SUCH_NOTE');
+						return true;
+					},
+				);
+			});
+		});
+	});
+
+	describe('Reaction', () => {
+		describe('Consistency', () => {
+			test('Unicode reaction', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+				const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+				const reaction = '😅';
+				await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction });
+				await sleep();
+
+				const reactions = await alice.client.request('notes/reactions', { noteId: note.id });
+				strictEqual(reactions.length, 1);
+				strictEqual(reactions[0].type, reaction);
+				strictEqual(reactions[0].user.id, bobInA.id);
+			});
+
+			test('Custom emoji reaction', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+				const resolvedNote = await resolveRemoteNote('a.test', note.id, bob);
+				const emoji = await addCustomEmoji('b.test');
+				await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: `:${emoji.name}:` });
+				await sleep();
+
+				const reactions = await alice.client.request('notes/reactions', { noteId: note.id });
+				strictEqual(reactions.length, 1);
+				strictEqual(reactions[0].type, `:${emoji.name}@b.test:`);
+				strictEqual(reactions[0].user.id, bobInA.id);
+			});
+		});
+
+		describe('Acceptance', () => {
+			test('Even if likeOnly, remote users can react with custom emoji, but it is converted to like', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a', reactionAcceptance: 'likeOnly' })).createdNote;
+				const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+				const emoji = await addCustomEmoji('b.test');
+				await bob.client.request('notes/reactions/create', { noteId: noteInB.id, reaction: `:${emoji.name}:` });
+				await sleep();
+
+				const reactions = await alice.client.request('notes/reactions', { noteId: note.id });
+				strictEqual(reactions.length, 1);
+				strictEqual(reactions[0].type, '❤');
+			});
+
+			/**
+			 * TODO: this may be unexpected behavior?
+			 *       @see https://github.com/misskey-dev/misskey/issues/12409
+			 */
+			test('Even if nonSensitiveOnly, remote users can react with sensitive emoji, and it is not converted', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a', reactionAcceptance: 'nonSensitiveOnly' })).createdNote;
+				const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+				const emoji = await addCustomEmoji('b.test', { isSensitive: true });
+				await bob.client.request('notes/reactions/create', { noteId: noteInB.id, reaction: `:${emoji.name}:` });
+				await sleep();
+
+				const reactions = await alice.client.request('notes/reactions', { noteId: note.id });
+				strictEqual(reactions.length, 1);
+				strictEqual(reactions[0].type, `:${emoji.name}@b.test:`);
+			});
+		});
+	});
+
+	describe('Poll', () => {
+		describe('Any remote user\'s vote is delivered to the author', () => {
+			let carol: LoginUser;
+
+			beforeAll(async () => {
+				carol = await createAccount('a.test');
+			});
+
+			test('Bob creates poll and receives a vote from Carol', async () => {
+				const note = (await bob.client.request('notes/create', { poll: { choices: ['inu', 'neko'] } })).createdNote;
+				const noteInA = await resolveRemoteNote('b.test', note.id, carol);
+				await carol.client.request('notes/polls/vote', { noteId: noteInA.id, choice: 0 });
+				await sleep();
+
+				const noteAfterVote = await bob.client.request('notes/show', { noteId: note.id });
+				assert(noteAfterVote.poll != null);
+				strictEqual(noteAfterVote.poll.choices[0].votes, 1);
+				strictEqual(noteAfterVote.poll.choices[1].votes, 0);
+			});
+		});
+
+		describe('Local user\'s vote is delivered to the author\'s remote followers', () => {
+			let bobRemoteFollower: LoginUser, localVoter: LoginUser;
+
+			beforeAll(async () => {
+				[
+					bobRemoteFollower,
+					localVoter,
+				] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				await bobRemoteFollower.client.request('following/create', { userId: bobInA.id });
+				await sleep();
+			});
+
+			test('A vote in Bob\'s server is delivered to Bob\'s remote followers', async () => {
+				const note = (await bob.client.request('notes/create', { poll: { choices: ['inu', 'neko'] } })).createdNote;
+				// NOTE: resolve before voting
+				const noteInA = await resolveRemoteNote('b.test', note.id, bobRemoteFollower);
+				await localVoter.client.request('notes/polls/vote', { noteId: note.id, choice: 0 });
+				await sleep();
+
+				const noteAfterVote = await bobRemoteFollower.client.request('notes/show', { noteId: noteInA.id });
+				assert(noteAfterVote.poll != null);
+				strictEqual(noteAfterVote.poll.choices[0].votes, 1);
+				strictEqual(noteAfterVote.poll.choices[1].votes, 0);
+			});
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/notification.test.ts b/packages/backend/test-federation/test/notification.test.ts
new file mode 100644
index 000000000000..6d55353653e6
--- /dev/null
+++ b/packages/backend/test-federation/test/notification.test.ts
@@ -0,0 +1,107 @@
+import * as Misskey from 'misskey-js';
+import { assertNotificationReceived, createAccount, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep } from './utils.js';
+
+describe('Notification', () => {
+	let alice: LoginUser, bob: LoginUser;
+	let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+	beforeAll(async () => {
+		[alice, bob] = await Promise.all([
+			createAccount('a.test'),
+			createAccount('b.test'),
+		]);
+
+		[bobInA, aliceInB] = await Promise.all([
+			resolveRemoteUser('b.test', bob.id, alice),
+			resolveRemoteUser('a.test', alice.id, bob),
+		]);
+	});
+
+	describe('Follow', () => {
+		test('Get notification when follow', async () => {
+			await assertNotificationReceived(
+				'b.test', bob,
+				async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+				notification => notification.type === 'followRequestAccepted' && notification.userId === aliceInB.id,
+				true,
+			);
+
+			await bob.client.request('following/delete', { userId: aliceInB.id });
+			await sleep();
+		});
+
+		test('Get notification when get followed', async () => {
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+				notification => notification.type === 'follow' && notification.userId === bobInA.id,
+				true,
+			);
+		});
+
+		afterAll(async () => await bob.client.request('following/delete', { userId: aliceInB.id }));
+	});
+
+	describe('Note', () => {
+		test('Get notification when get a reaction', async () => {
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+			const reaction = '😅';
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('notes/reactions/create', { noteId: noteInB.id, reaction }),
+				notification =>
+					notification.type === 'reaction' && notification.note.id === note.id && notification.userId === bobInA.id && notification.reaction === reaction,
+				true,
+			);
+		});
+
+		test('Get notification when replied', async () => {
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+			const text = crypto.randomUUID();
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('notes/create', { text, replyId: noteInB.id }),
+				notification =>
+					notification.type === 'reply' && notification.note.reply!.id === note.id && notification.userId === bobInA.id && notification.note.text === text,
+				true,
+			);
+		});
+
+		test('Get notification when renoted', async () => {
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('notes/create', { renoteId: noteInB.id }),
+				notification =>
+					notification.type === 'renote' && notification.note.renote!.id === note.id && notification.userId === bobInA.id,
+				true,
+			);
+		});
+
+		test('Get notification when quoted', async () => {
+			const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+			const noteInB = await resolveRemoteNote('a.test', note.id, bob);
+			const text = crypto.randomUUID();
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('notes/create', { text, renoteId: noteInB.id }),
+				notification =>
+					notification.type === 'quote' && notification.note.renote!.id === note.id && notification.userId === bobInA.id && notification.note.text === text,
+				true,
+			);
+		});
+
+		test('Get notification when mentioned', async () => {
+			const text = `@${alice.username}@a.test`;
+			await assertNotificationReceived(
+				'a.test', alice,
+				async () => await bob.client.request('notes/create', { text }),
+				notification => notification.type === 'mention' && notification.userId === bobInA.id && notification.note.text === text,
+				true,
+			);
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/timeline.test.ts b/packages/backend/test-federation/test/timeline.test.ts
new file mode 100644
index 000000000000..2250bf4a426d
--- /dev/null
+++ b/packages/backend/test-federation/test/timeline.test.ts
@@ -0,0 +1,328 @@
+import { strictEqual } from 'assert';
+import * as Misskey from 'misskey-js';
+import { createAccount, fetchAdmin, isNoteUpdatedEventFired, isFired, type LoginUser, type Request, resolveRemoteUser, sleep, createRole } from './utils.js';
+
+const bAdmin = await fetchAdmin('b.test');
+
+describe('Timeline', () => {
+	let alice: LoginUser, bob: LoginUser;
+	let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+	beforeAll(async () => {
+		[alice, bob] = await Promise.all([
+			createAccount('a.test'),
+			createAccount('b.test'),
+		]);
+
+		[bobInA, aliceInB] = await Promise.all([
+			resolveRemoteUser('b.test', bob.id, alice),
+			resolveRemoteUser('a.test', alice.id, bob),
+		]);
+
+		await bob.client.request('following/create', { userId: aliceInB.id });
+		await sleep();
+	});
+
+	type TimelineChannel = keyof Misskey.Channels & (`${string}Timeline` | 'antenna' | 'userList' | 'hashtag');
+	type TimelineEndpoint = keyof Misskey.Endpoints & (`${string}timeline` | 'antennas/notes' | 'roles/notes' | 'notes/search-by-tag');
+	const timelineMap = new Map<TimelineChannel, TimelineEndpoint>([
+		['antenna', 'antennas/notes'],
+		['globalTimeline', 'notes/global-timeline'],
+		['homeTimeline', 'notes/timeline'],
+		['hybridTimeline', 'notes/hybrid-timeline'],
+		['localTimeline', 'notes/local-timeline'],
+		['roleTimeline', 'roles/notes'],
+		['hashtag', 'notes/search-by-tag'],
+		['userList', 'notes/user-list-timeline'],
+	]);
+
+	async function postAndCheckReception<C extends TimelineChannel>(
+		timelineChannel: C,
+		expect: boolean,
+		noteParams: Misskey.entities.NotesCreateRequest = {},
+		channelParams: Misskey.Channels[C]['params'] = {},
+	) {
+		let note: Misskey.entities.Note | undefined;
+		const text = noteParams.text ?? crypto.randomUUID();
+		const streamingFired = await isFired(
+			'b.test', bob, timelineChannel,
+			async () => {
+				note = (await alice.client.request('notes/create', { text, ...noteParams })).createdNote;
+			},
+			'note', msg => msg.text === text,
+			channelParams,
+		);
+		strictEqual(streamingFired, expect);
+
+		const endpoint = timelineMap.get(timelineChannel)!;
+		const params: Misskey.Endpoints[typeof endpoint]['req'] =
+			endpoint === 'antennas/notes' ? { antennaId: (channelParams as Misskey.Channels['antenna']['params']).antennaId } :
+			endpoint === 'notes/user-list-timeline' ? { listId: (channelParams as Misskey.Channels['userList']['params']).listId } :
+			endpoint === 'notes/search-by-tag' ? { query: (channelParams as Misskey.Channels['hashtag']['params']).q } :
+			endpoint === 'roles/notes' ? { roleId: (channelParams as Misskey.Channels['roleTimeline']['params']).roleId } :
+			{};
+
+		await sleep();
+		const notes = await (bob.client.request as Request)(endpoint, params);
+		const noteInB = notes.filter(({ uri }) => uri === `https://a.test/notes/${note!.id}`).pop();
+		const endpointFired = noteInB != null;
+		strictEqual(endpointFired, expect);
+
+		// Let's check Delete reception
+		if (expect) {
+			const streamingFired = await isNoteUpdatedEventFired(
+				'b.test', bob, noteInB!.id,
+				async () => await alice.client.request('notes/delete', { noteId: note!.id }),
+				msg => msg.type === 'deleted' && msg.id === noteInB!.id,
+			);
+			strictEqual(streamingFired, true);
+
+			await sleep();
+			const notes = await (bob.client.request as Request)(endpoint, params);
+			const endpointFired = notes.every(({ uri }) => uri !== `https://a.test/notes/${note!.id}`);
+			strictEqual(endpointFired, true);
+		}
+	}
+
+	describe('homeTimeline', () => {
+		// NOTE: narrowing scope intentionally to prevent mistakes by copy-and-paste
+		const homeTimeline = 'homeTimeline';
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				await postAndCheckReception(homeTimeline, true);
+			});
+
+			test('Receive remote followee\'s home-only Note', async () => {
+				await postAndCheckReception(homeTimeline, true, { visibility: 'home' });
+			});
+
+			test('Receive remote followee\'s followers-only Note', async () => {
+				await postAndCheckReception(homeTimeline, true, { visibility: 'followers' });
+			});
+
+			test('Receive remote followee\'s visible specified-only Note', async () => {
+				await postAndCheckReception(homeTimeline, true, { visibility: 'specified', visibleUserIds: [bobInA.id] });
+			});
+
+			test('Don\'t receive remote followee\'s localOnly Note', async () => {
+				await postAndCheckReception(homeTimeline, false, { localOnly: true });
+			});
+
+			test('Don\'t receive remote followee\'s invisible specified-only Note', async () => {
+				await postAndCheckReception(homeTimeline, false, { visibility: 'specified' });
+			});
+
+			/**
+			 * FIXME: can receive this
+			 * @see https://github.com/misskey-dev/misskey/issues/14083
+			 */
+			test.failing('Don\'t receive remote followee\'s invisible and mentioned specified-only Note', async () => {
+				await postAndCheckReception(homeTimeline, false, { text: `@${bob.username}@b.test Hello`, visibility: 'specified' });
+			});
+
+			/**
+			 * FIXME: cannot receive this
+			 * @see https://github.com/misskey-dev/misskey/issues/14084
+			 */
+			test.failing('Receive remote followee\'s visible specified-only reply to invisible specified-only Note', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a', visibility: 'specified' })).createdNote;
+				await postAndCheckReception(homeTimeline, true, { replyId: note.id, visibility: 'specified', visibleUserIds: [bobInA.id] });
+			});
+		});
+	});
+
+	describe('localTimeline', () => {
+		const localTimeline = 'localTimeline';
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Don\'t receive remote followee\'s Note', async () => {
+				await postAndCheckReception(localTimeline, false);
+			});
+		});
+	});
+
+	describe('hybridTimeline', () => {
+		const hybridTimeline = 'hybridTimeline';
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				await postAndCheckReception(hybridTimeline, true);
+			});
+
+			test('Receive remote followee\'s home-only Note', async () => {
+				await postAndCheckReception(hybridTimeline, true, { visibility: 'home' });
+			});
+
+			test('Receive remote followee\'s followers-only Note', async () => {
+				await postAndCheckReception(hybridTimeline, true, { visibility: 'followers' });
+			});
+
+			test('Receive remote followee\'s visible specified-only Note', async () => {
+				await postAndCheckReception(hybridTimeline, true, { visibility: 'specified', visibleUserIds: [bobInA.id] });
+			});
+		});
+	});
+
+	describe('globalTimeline', () => {
+		const globalTimeline = 'globalTimeline';
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				await postAndCheckReception(globalTimeline, true);
+			});
+
+			test('Don\'t receive remote followee\'s home-only Note', async () => {
+				await postAndCheckReception(globalTimeline, false, { visibility: 'home' });
+			});
+
+			test('Don\'t receive remote followee\'s followers-only Note', async () => {
+				await postAndCheckReception(globalTimeline, false, { visibility: 'followers' });
+			});
+
+			test('Don\'t receive remote followee\'s visible specified-only Note', async () => {
+				await postAndCheckReception(globalTimeline, false, { visibility: 'specified', visibleUserIds: [bobInA.id] });
+			});
+		});
+	});
+
+	describe('userList', () => {
+		const userList = 'userList';
+
+		let list: Misskey.entities.UserList;
+
+		beforeAll(async () => {
+			list = await bob.client.request('users/lists/create', { name: 'Bob\'s List' });
+			await bob.client.request('users/lists/push', { listId: list.id, userId: aliceInB.id });
+			await sleep();
+		});
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				await postAndCheckReception(userList, true, {}, { listId: list.id });
+			});
+
+			test('Receive remote followee\'s home-only Note', async () => {
+				await postAndCheckReception(userList, true, { visibility: 'home' }, { listId: list.id });
+			});
+
+			test('Receive remote followee\'s followers-only Note', async () => {
+				await postAndCheckReception(userList, true, { visibility: 'followers' }, { listId: list.id });
+			});
+
+			test('Receive remote followee\'s visible specified-only Note', async () => {
+				await postAndCheckReception(userList, true, { visibility: 'specified', visibleUserIds: [bobInA.id] }, { listId: list.id });
+			});
+		});
+	});
+
+	describe('hashtag', () => {
+		const hashtag = 'hashtag';
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				const tag = crypto.randomUUID();
+				await postAndCheckReception(hashtag, true, { text: `#${tag}` }, { q: [[tag]] });
+			});
+
+			test('Receive remote followee\'s home-only Note', async () => {
+				const tag = crypto.randomUUID();
+				await postAndCheckReception(hashtag, true, { text: `#${tag}`, visibility: 'home' }, { q: [[tag]] });
+			});
+
+			test('Receive remote followee\'s followers-only Note', async () => {
+				const tag = crypto.randomUUID();
+				await postAndCheckReception(hashtag, true, { text: `#${tag}`, visibility: 'followers' }, { q: [[tag]] });
+			});
+
+			test('Receive remote followee\'s visible specified-only Note', async () => {
+				const tag = crypto.randomUUID();
+				await postAndCheckReception(hashtag, true, { text: `#${tag}`, visibility: 'specified', visibleUserIds: [bobInA.id] }, { q: [[tag]] });
+			});
+		});
+	});
+
+	describe('roleTimeline', () => {
+		const roleTimeline = 'roleTimeline';
+
+		let role: Misskey.entities.Role;
+
+		beforeAll(async () => {
+			role = await createRole('b.test', {
+				name: 'Remote Users',
+				description: 'Remote users are assigned to this role.',
+				condFormula: {
+					/** TODO: @see https://github.com/misskey-dev/misskey/issues/14169 */
+					type: 'isRemote' as never,
+				},
+			});
+			await sleep();
+		});
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				await postAndCheckReception(roleTimeline, true, {}, { roleId: role.id });
+			});
+
+			test('Don\'t receive remote followee\'s home-only Note', async () => {
+				await postAndCheckReception(roleTimeline, false, { visibility: 'home' }, { roleId: role.id });
+			});
+
+			test('Don\'t receive remote followee\'s followers-only Note', async () => {
+				await postAndCheckReception(roleTimeline, false, { visibility: 'followers' }, { roleId: role.id });
+			});
+
+			test('Don\'t receive remote followee\'s visible specified-only Note', async () => {
+				await postAndCheckReception(roleTimeline, false, { visibility: 'specified', visibleUserIds: [bobInA.id] }, { roleId: role.id });
+			});
+		});
+
+		afterAll(async () => {
+			await bAdmin.client.request('admin/roles/delete', { roleId: role.id });
+		});
+	});
+
+	// TODO: Cannot test
+	describe.skip('antenna', () => {
+		const antenna = 'antenna';
+
+		let bobAntenna: Misskey.entities.Antenna;
+
+		beforeAll(async () => {
+			bobAntenna = await bob.client.request('antennas/create', {
+				name: 'Bob\'s Egosurfing Antenna',
+				src: 'all',
+				keywords: [['Bob']],
+				excludeKeywords: [],
+				users: [],
+				caseSensitive: false,
+				localOnly: false,
+				withReplies: true,
+				withFile: true,
+			});
+			await sleep();
+		});
+
+		describe('Check reception of remote followee\'s Note', () => {
+			test('Receive remote followee\'s Note', async () => {
+				await postAndCheckReception(antenna, true, { text: 'I love Bob (1)' }, { antennaId: bobAntenna.id });
+			});
+
+			test('Don\'t receive remote followee\'s home-only Note', async () => {
+				await postAndCheckReception(antenna, false, { text: 'I love Bob (2)', visibility: 'home' }, { antennaId: bobAntenna.id });
+			});
+
+			test('Don\'t receive remote followee\'s followers-only Note', async () => {
+				await postAndCheckReception(antenna, false, { text: 'I love Bob (3)', visibility: 'followers' }, { antennaId: bobAntenna.id });
+			});
+
+			test('Don\'t receive remote followee\'s visible specified-only Note', async () => {
+				await postAndCheckReception(antenna, false, { text: 'I love Bob (4)', visibility: 'specified', visibleUserIds: [bobInA.id] }, { antennaId: bobAntenna.id });
+			});
+		});
+
+		afterAll(async () => {
+			await bob.client.request('antennas/delete', { antennaId: bobAntenna.id });
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/user.test.ts b/packages/backend/test-federation/test/user.test.ts
new file mode 100644
index 000000000000..76605e61d427
--- /dev/null
+++ b/packages/backend/test-federation/test/user.test.ts
@@ -0,0 +1,560 @@
+import assert, { rejects, strictEqual } from 'node:assert';
+import * as Misskey from 'misskey-js';
+import { createAccount, deepStrictEqualWithExcludedFields, fetchAdmin, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep } from './utils.js';
+
+const [aAdmin, bAdmin] = await Promise.all([
+	fetchAdmin('a.test'),
+	fetchAdmin('b.test'),
+]);
+
+describe('User', () => {
+	describe('Profile', () => {
+		describe('Consistency of profile', () => {
+			let alice: LoginUser;
+			let aliceWatcher: LoginUser;
+			let aliceWatcherInB: LoginUser;
+
+			beforeAll(async () => {
+				alice = await createAccount('a.test');
+				[
+					aliceWatcher,
+					aliceWatcherInB,
+				] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+			});
+
+			test('Check consistency', async () => {
+				const aliceInA = await aliceWatcher.client.request('users/show', { userId: alice.id });
+				const resolved = await resolveRemoteUser('a.test', aliceInA.id, aliceWatcherInB);
+				const aliceInB = await aliceWatcherInB.client.request('users/show', { userId: resolved.id });
+
+				// console.log(`a.test: ${JSON.stringify(aliceInA, null, '\t')}`);
+				// console.log(`b.test: ${JSON.stringify(aliceInB, null, '\t')}`);
+
+				deepStrictEqualWithExcludedFields(aliceInA, aliceInB, [
+					'id',
+					'host',
+					'avatarUrl',
+					'instance',
+					'badgeRoles',
+					'url',
+					'uri',
+					'createdAt',
+					'lastFetchedAt',
+					'publicReactions',
+				]);
+			});
+		});
+
+		describe('ffVisibility is federated', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				[bobInA, aliceInB] = await Promise.all([
+					resolveRemoteUser('b.test', bob.id, alice),
+					resolveRemoteUser('a.test', alice.id, bob),
+				]);
+
+				// NOTE: follow each other
+				await Promise.all([
+					alice.client.request('following/create', { userId: bobInA.id }),
+					bob.client.request('following/create', { userId: aliceInB.id }),
+				]);
+				await sleep();
+			});
+
+			test('Visibility set public by default', async () => {
+				for (const user of await Promise.all([
+					alice.client.request('users/show', { userId: bobInA.id }),
+					bob.client.request('users/show', { userId: aliceInB.id }),
+				])) {
+					strictEqual(user.followersVisibility, 'public');
+					strictEqual(user.followingVisibility, 'public');
+				}
+			});
+
+			/** FIXME: not working */
+			test.skip('Setting private for followersVisibility is federated', async () => {
+				await Promise.all([
+					alice.client.request('i/update', { followersVisibility: 'private' }),
+					bob.client.request('i/update', { followersVisibility: 'private' }),
+				]);
+				await sleep();
+
+				for (const user of await Promise.all([
+					alice.client.request('users/show', { userId: bobInA.id }),
+					bob.client.request('users/show', { userId: aliceInB.id }),
+				])) {
+					strictEqual(user.followersVisibility, 'private');
+					strictEqual(user.followingVisibility, 'public');
+				}
+			});
+
+			test.skip('Setting private for followingVisibility is federated', async () => {
+				await Promise.all([
+					alice.client.request('i/update', { followingVisibility: 'private' }),
+					bob.client.request('i/update', { followingVisibility: 'private' }),
+				]);
+				await sleep();
+
+				for (const user of await Promise.all([
+					alice.client.request('users/show', { userId: bobInA.id }),
+					bob.client.request('users/show', { userId: aliceInB.id }),
+				])) {
+					strictEqual(user.followersVisibility, 'private');
+					strictEqual(user.followingVisibility, 'private');
+				}
+			});
+		});
+
+		describe('isCat is federated', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				[bobInA, aliceInB] = await Promise.all([
+					resolveRemoteUser('b.test', bob.id, alice),
+					resolveRemoteUser('a.test', alice.id, bob),
+				]);
+			});
+
+			test('Not isCat for default', () => {
+				strictEqual(aliceInB.isCat, false);
+			});
+
+			test('Becoming a cat is sent to their followers', async () => {
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+
+				await alice.client.request('i/update', { isCat: true });
+				await sleep();
+
+				const res = await bob.client.request('users/show', { userId: aliceInB.id });
+				strictEqual(res.isCat, true);
+			});
+		});
+
+		describe('Pinning Notes', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+				aliceInB = await resolveRemoteUser('a.test', alice.id, bob);
+
+				await bob.client.request('following/create', { userId: aliceInB.id });
+			});
+
+			test('Pinning localOnly Note is not delivered', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a', localOnly: true })).createdNote;
+				await alice.client.request('i/pin', { noteId: note.id });
+				await sleep();
+
+				const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id });
+				strictEqual(_aliceInB.pinnedNoteIds.length, 0);
+			});
+
+			test('Pinning followers-only Note is not delivered', async () => {
+				const note = (await alice.client.request('notes/create', { text: 'a', visibility: 'followers' })).createdNote;
+				await alice.client.request('i/pin', { noteId: note.id });
+				await sleep();
+
+				const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id });
+				strictEqual(_aliceInB.pinnedNoteIds.length, 0);
+			});
+
+			let pinnedNote: Misskey.entities.Note;
+
+			test('Pinning normal Note is delivered', async () => {
+				pinnedNote = (await alice.client.request('notes/create', { text: 'a' })).createdNote;
+				await alice.client.request('i/pin', { noteId: pinnedNote.id });
+				await sleep();
+
+				const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id });
+				strictEqual(_aliceInB.pinnedNoteIds.length, 1);
+				const pinnedNoteInB = await resolveRemoteNote('a.test', pinnedNote.id, bob);
+				strictEqual(_aliceInB.pinnedNotes[0].id, pinnedNoteInB.id);
+			});
+
+			test('Unpinning normal Note is delivered', async () => {
+				await alice.client.request('i/unpin', { noteId: pinnedNote.id });
+				await sleep();
+
+				const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id });
+				strictEqual(_aliceInB.pinnedNoteIds.length, 0);
+			});
+		});
+	});
+
+	describe('Follow / Unfollow', () => {
+		let alice: LoginUser, bob: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+		});
+
+		describe('Follow a.test ==> b.test', () => {
+			beforeAll(async () => {
+				await alice.client.request('following/create', { userId: bobInA.id });
+
+				await sleep();
+			});
+
+			test('Check consistency with `users/following` and `users/followers` endpoints', async () => {
+				await Promise.all([
+					strictEqual(
+						(await alice.client.request('users/following', { userId: alice.id }))
+							.some(v => v.followeeId === bobInA.id),
+						true,
+					),
+					strictEqual(
+						(await bob.client.request('users/followers', { userId: bob.id }))
+							.some(v => v.followerId === aliceInB.id),
+						true,
+					),
+				]);
+			});
+		});
+
+		describe('Unfollow a.test ==> b.test', () => {
+			beforeAll(async () => {
+				await alice.client.request('following/delete', { userId: bobInA.id });
+
+				await sleep();
+			});
+
+			test('Check consistency with `users/following` and `users/followers` endpoints', async () => {
+				await Promise.all([
+					strictEqual(
+						(await alice.client.request('users/following', { userId: alice.id }))
+							.some(v => v.followeeId === bobInA.id),
+						false,
+					),
+					strictEqual(
+						(await bob.client.request('users/followers', { userId: bob.id }))
+							.some(v => v.followerId === aliceInB.id),
+						false,
+					),
+				]);
+			});
+		});
+	});
+
+	describe('Follow requests', () => {
+		let alice: LoginUser, bob: LoginUser;
+		let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+		beforeAll(async () => {
+			[alice, bob] = await Promise.all([
+				createAccount('a.test'),
+				createAccount('b.test'),
+			]);
+
+			[bobInA, aliceInB] = await Promise.all([
+				resolveRemoteUser('b.test', bob.id, alice),
+				resolveRemoteUser('a.test', alice.id, bob),
+			]);
+
+			await alice.client.request('i/update', { isLocked: true });
+		});
+
+		describe('Send follow request from Bob to Alice and cancel', () => {
+			describe('Bob sends follow request to Alice', () => {
+				beforeAll(async () => {
+					await bob.client.request('following/create', { userId: aliceInB.id });
+					await sleep();
+				});
+
+				test('Alice should have a request', async () => {
+					const requests = await alice.client.request('following/requests/list', {});
+					strictEqual(requests.length, 1);
+					strictEqual(requests[0].followee.id, alice.id);
+					strictEqual(requests[0].follower.id, bobInA.id);
+				});
+			});
+
+			describe('Alice cancels it', () => {
+				beforeAll(async () => {
+					await bob.client.request('following/requests/cancel', { userId: aliceInB.id });
+					await sleep();
+				});
+
+				test('Alice should have no requests', async () => {
+					const requests = await alice.client.request('following/requests/list', {});
+					strictEqual(requests.length, 0);
+				});
+			});
+		});
+
+		describe('Send follow request from Bob to Alice and reject', () => {
+			beforeAll(async () => {
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+
+				await alice.client.request('following/requests/reject', { userId: bobInA.id });
+				await sleep();
+			});
+
+			test('Bob should have no requests', async () => {
+				await rejects(
+					async () => await bob.client.request('following/requests/cancel', { userId: aliceInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'FOLLOW_REQUEST_NOT_FOUND');
+						return true;
+					},
+				);
+			});
+
+			test('Bob doesn\'t follow Alice', async () => {
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 0);
+			});
+		});
+
+		describe('Send follow request from Bob to Alice and accept', () => {
+			beforeAll(async () => {
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+
+				await alice.client.request('following/requests/accept', { userId: bobInA.id });
+				await sleep();
+			});
+
+			test('Bob follows Alice', async () => {
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 1);
+				strictEqual(following[0].followeeId, aliceInB.id);
+				strictEqual(following[0].followerId, bob.id);
+			});
+		});
+	});
+
+	describe('Deletion', () => {
+		describe('Check Delete consistency', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				[bobInA, aliceInB] = await Promise.all([
+					resolveRemoteUser('b.test', bob.id, alice),
+					resolveRemoteUser('a.test', alice.id, bob),
+				]);
+			});
+
+			test('Bob follows Alice, and Alice deleted themself', async () => {
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+
+				const followers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(followers.length, 1); // followed by Bob
+
+				await alice.client.request('i/delete-account', { password: alice.password });
+				await sleep();
+
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 0); // no following relation
+
+				await rejects(
+					async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'NO_SUCH_USER');
+						return true;
+					},
+				);
+			});
+		});
+
+		describe('Deletion of remote user for moderation', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				[bobInA, aliceInB] = await Promise.all([
+					resolveRemoteUser('b.test', bob.id, alice),
+					resolveRemoteUser('a.test', alice.id, bob),
+				]);
+			});
+
+			test('Bob follows Alice, then Alice gets deleted in B server', async () => {
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+
+				const followers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(followers.length, 1); // followed by Bob
+
+				await bAdmin.client.request('admin/delete-account', { userId: aliceInB.id });
+				await sleep();
+
+				/**
+				 * FIXME: remote account is not deleted!
+				 *        @see https://github.com/misskey-dev/misskey/issues/14728
+				 */
+				const deletedAlice = await bob.client.request('users/show', { userId: aliceInB.id });
+				assert(deletedAlice.id, aliceInB.id);
+
+				// TODO: why still following relation?
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 1);
+				await rejects(
+					async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'ALREADY_FOLLOWING');
+						return true;
+					},
+				);
+			});
+
+			test('Alice tries to follow Bob, but it is not processed', async () => {
+				await alice.client.request('following/create', { userId: bobInA.id });
+				await sleep();
+
+				const following = await alice.client.request('users/following', { userId: alice.id });
+				strictEqual(following.length, 0); // Not following Bob because B server doesn't return Accept
+
+				const followers = await bob.client.request('users/followers', { userId: bob.id });
+				strictEqual(followers.length, 0); // Alice's Follow is not processed
+			});
+		});
+	});
+
+	describe('Suspension', () => {
+		describe('Check suspend/unsuspend consistency', () => {
+			let alice: LoginUser, bob: LoginUser;
+			let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe;
+
+			beforeAll(async () => {
+				[alice, bob] = await Promise.all([
+					createAccount('a.test'),
+					createAccount('b.test'),
+				]);
+
+				[bobInA, aliceInB] = await Promise.all([
+					resolveRemoteUser('b.test', bob.id, alice),
+					resolveRemoteUser('a.test', alice.id, bob),
+				]);
+			});
+
+			test('Bob follows Alice, and Alice gets suspended, there is no following relation, and Bob fails to follow again', async () => {
+				await bob.client.request('following/create', { userId: aliceInB.id });
+				await sleep();
+
+				const followers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(followers.length, 1); // followed by Bob
+
+				await aAdmin.client.request('admin/suspend-user', { userId: alice.id });
+				await sleep();
+
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 0); // no following relation
+
+				await rejects(
+					async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'NO_SUCH_USER');
+						return true;
+					},
+				);
+			});
+
+			test('Alice gets unsuspended, Bob succeeds in following Alice', async () => {
+				await aAdmin.client.request('admin/unsuspend-user', { userId: alice.id });
+				await sleep();
+
+				const followers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(followers.length, 1); // FIXME: followers are not deleted??
+
+				/**
+				 * FIXME: still rejected!
+				 *        seems to can't process Undo Delete activity because it is not implemented
+				 *        related @see https://github.com/misskey-dev/misskey/issues/13273
+				 */
+				await rejects(
+					async () => await bob.client.request('following/create', { userId: aliceInB.id }),
+					(err: any) => {
+						strictEqual(err.code, 'NO_SUCH_USER');
+						return true;
+					},
+				);
+
+				// FIXME: resolving also fails
+				await rejects(
+					async () => await resolveRemoteUser('a.test', alice.id, bob),
+					(err: any) => {
+						strictEqual(err.code, 'INTERNAL_ERROR');
+						return true;
+					},
+				);
+			});
+
+			/**
+			 * instead of simple unsuspension, let's tell existence by following from Alice
+			 */
+			test('Alice can follow Bob', async () => {
+				await alice.client.request('following/create', { userId: bobInA.id });
+				await sleep();
+
+				const bobFollowers = await bob.client.request('users/followers', { userId: bob.id });
+				strictEqual(bobFollowers.length, 1); // followed by Alice
+				assert(bobFollowers[0].follower != null);
+				const renewedaliceInB = bobFollowers[0].follower;
+				assert(aliceInB.username === renewedaliceInB.username);
+				assert(aliceInB.host === renewedaliceInB.host);
+				assert(aliceInB.id !== renewedaliceInB.id); // TODO: Same username and host, but their ids are different! Is it OK?
+
+				const following = await bob.client.request('users/following', { userId: bob.id });
+				strictEqual(following.length, 0); // following are deleted
+
+				// Bob tries to follow Alice
+				await bob.client.request('following/create', { userId: renewedaliceInB.id });
+				await sleep();
+
+				const aliceFollowers = await alice.client.request('users/followers', { userId: alice.id });
+				strictEqual(aliceFollowers.length, 1);
+
+				// FIXME: but resolving still fails ...
+				await rejects(
+					async () => await resolveRemoteUser('a.test', alice.id, bob),
+					(err: any) => {
+						strictEqual(err.code, 'INTERNAL_ERROR');
+						return true;
+					},
+				);
+			});
+		});
+	});
+});
diff --git a/packages/backend/test-federation/test/utils.ts b/packages/backend/test-federation/test/utils.ts
new file mode 100644
index 000000000000..093277cdb498
--- /dev/null
+++ b/packages/backend/test-federation/test/utils.ts
@@ -0,0 +1,307 @@
+import { deepStrictEqual, strictEqual } from 'assert';
+import { readFile } from 'fs/promises';
+import { dirname, join } from 'path';
+import { fileURLToPath } from 'url';
+import * as Misskey from 'misskey-js';
+import { WebSocket } from 'ws';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+export const ADMIN_PARAMS = { username: 'admin', password: 'admin' };
+const ADMIN_CACHE = new Map<Host, SigninResponse>();
+
+await Promise.all([
+	fetchAdmin('a.test'),
+	fetchAdmin('b.test'),
+]);
+
+type SigninResponse = Omit<Misskey.entities.SigninFlowResponse & { finished: true }, 'finished'>;
+
+export type LoginUser = SigninResponse & {
+	client: Misskey.api.APIClient;
+	username: string;
+	password: string;
+}
+
+/** used for avoiding overload and some endpoints */
+export type Request = <
+	E extends keyof Misskey.Endpoints,
+	P extends Misskey.Endpoints[E]['req'],
+>(
+	endpoint: E,
+	params: P,
+	credential?: string | null,
+) => Promise<Misskey.api.SwitchCaseResponseType<E, P>>;
+
+type Host = 'a.test' | 'b.test';
+
+export async function sleep(ms = 200): Promise<void> {
+	return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+async function signin(
+	host: Host,
+	params: Misskey.entities.SigninFlowRequest,
+): Promise<SigninResponse> {
+	// wait for a second to prevent hit rate limit
+	await sleep(1000);
+
+	return await (new Misskey.api.APIClient({ origin: `https://${host}` }).request as Request)('signin-flow', params)
+		.then(res => {
+			strictEqual(res.finished, true);
+			if (params.username === ADMIN_PARAMS.username) ADMIN_CACHE.set(host, res);
+			return res;
+		})
+		.then(({ id, i }) => ({ id, i }))
+		.catch(async err => {
+			if (err.code === 'TOO_MANY_AUTHENTICATION_FAILURES') {
+				await sleep(Math.random() * 2000);
+				return await signin(host, params);
+			}
+			throw err;
+		});
+}
+
+async function createAdmin(host: Host): Promise<Misskey.entities.SignupResponse | undefined> {
+	const client = new Misskey.api.APIClient({ origin: `https://${host}` });
+	return await client.request('admin/accounts/create', ADMIN_PARAMS).then(res => {
+		ADMIN_CACHE.set(host, {
+			id: res.id,
+			// @ts-expect-error FIXME: openapi-typescript generates incorrect response type for this endpoint, so ignore this
+			i: res.token,
+		});
+		return res as Misskey.entities.SignupResponse;
+	}).then(async res => {
+		await client.request('admin/roles/update-default-policies', {
+			policies: {
+				/** TODO: @see https://github.com/misskey-dev/misskey/issues/14169 */
+				rateLimitFactor: 0 as never,
+			},
+		}, res.token);
+		return res;
+	}).catch(err => {
+		if (err.info.e.message === 'access denied') return undefined;
+		throw err;
+	});
+}
+
+export async function fetchAdmin(host: Host): Promise<LoginUser> {
+	const admin = ADMIN_CACHE.get(host) ?? await signin(host, ADMIN_PARAMS)
+		.catch(async err => {
+			if (err.id === '6cc579cc-885d-43d8-95c2-b8c7fc963280') {
+				await createAdmin(host);
+				return await signin(host, ADMIN_PARAMS);
+			}
+			throw err;
+		});
+
+	return {
+		...admin,
+		client: new Misskey.api.APIClient({ origin: `https://${host}`, credential: admin.i }),
+		...ADMIN_PARAMS,
+	};
+}
+
+export async function createAccount(host: Host): Promise<LoginUser> {
+	const username = crypto.randomUUID().replaceAll('-', '').substring(0, 20);
+	const password = crypto.randomUUID().replaceAll('-', '');
+	const admin = await fetchAdmin(host);
+	await admin.client.request('admin/accounts/create', { username, password });
+	const signinRes = await signin(host, { username, password });
+
+	return {
+		...signinRes,
+		client: new Misskey.api.APIClient({ origin: `https://${host}`, credential: signinRes.i }),
+		username,
+		password,
+	};
+}
+
+export async function createModerator(host: Host): Promise<LoginUser> {
+	const user = await createAccount(host);
+	const role = await createRole(host, {
+		name: 'Moderator',
+		isModerator: true,
+	});
+	const admin = await fetchAdmin(host);
+	await admin.client.request('admin/roles/assign', { roleId: role.id, userId: user.id });
+	return user;
+}
+
+export async function createRole(
+	host: Host,
+	params: Partial<Misskey.entities.AdminRolesCreateRequest> = {},
+): Promise<Misskey.entities.Role> {
+	const admin = await fetchAdmin(host);
+	return await admin.client.request('admin/roles/create', {
+		name: 'Some role',
+		description: 'Role for testing',
+		color: null,
+		iconUrl: null,
+		target: 'conditional',
+		condFormula: {},
+		isPublic: true,
+		isModerator: false,
+		isAdministrator: false,
+		isExplorable: true,
+		asBadge: false,
+		canEditMembersByModerator: false,
+		displayOrder: 0,
+		policies: {},
+		...params,
+	});
+}
+
+export async function resolveRemoteUser(
+	host: Host,
+	id: string,
+	from: LoginUser,
+): Promise<Misskey.entities.UserDetailedNotMe> {
+	const uri = `https://${host}/users/${id}`;
+	return await from.client.request('ap/show', { uri })
+		.then(res => {
+			strictEqual(res.type, 'User');
+			strictEqual(res.object.uri, uri);
+			return res.object;
+		});
+}
+
+export async function resolveRemoteNote(
+	host: Host,
+	id: string,
+	from: LoginUser,
+): Promise<Misskey.entities.Note> {
+	const uri = `https://${host}/notes/${id}`;
+	return await from.client.request('ap/show', { uri })
+		.then(res => {
+			strictEqual(res.type, 'Note');
+			strictEqual(res.object.uri, uri);
+			return res.object;
+		});
+}
+
+export async function uploadFile(
+	host: Host,
+	user: { i: string },
+	path = '../../test/resources/192.jpg',
+): Promise<Misskey.entities.DriveFile> {
+	const filename = path.split('/').pop() ?? 'untitled';
+	const blob = new Blob([await readFile(join(__dirname, path))]);
+
+	const body = new FormData();
+	body.append('i', user.i);
+	body.append('force', 'true');
+	body.append('file', blob);
+	body.append('name', filename);
+
+	return await fetch(`https://${host}/api/drive/files/create`, { method: 'POST', body })
+		.then(async res => await res.json());
+}
+
+export async function addCustomEmoji(
+	host: Host,
+	param?: Partial<Misskey.entities.AdminEmojiAddRequest>,
+	path?: string,
+): Promise<Misskey.entities.EmojiDetailed> {
+	const admin = await fetchAdmin(host);
+	const name = crypto.randomUUID().replaceAll('-', '');
+	const file = await uploadFile(host, admin, path);
+	return await admin.client.request('admin/emoji/add', { name, fileId: file.id, ...param });
+}
+
+export function deepStrictEqualWithExcludedFields<T>(actual: T, expected: T, excludedFields: (keyof T)[]) {
+	const _actual = structuredClone(actual);
+	const _expected = structuredClone(expected);
+	for (const obj of [_actual, _expected]) {
+		for (const field of excludedFields) {
+			delete obj[field];
+		}
+	}
+	deepStrictEqual(_actual, _expected);
+}
+
+export async function isFired<C extends keyof Misskey.Channels, T extends keyof Misskey.Channels[C]['events']>(
+	host: Host,
+	user: { i: string },
+	channel: C,
+	trigger: () => Promise<unknown>,
+	type: T,
+	// @ts-expect-error TODO: why getting error here?
+	cond: (msg: Parameters<Misskey.Channels[C]['events'][T]>[0]) => boolean,
+	params?: Misskey.Channels[C]['params'],
+): Promise<boolean> {
+	return new Promise<boolean>(async (resolve, reject) => {
+		const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket });
+		const connection = stream.useChannel(channel, params);
+		connection.on(type as any, ((msg: any) => {
+			if (cond(msg)) {
+				stream.close();
+				clearTimeout(timer);
+				resolve(true);
+			}
+		}) as any);
+
+		let timer: NodeJS.Timeout | undefined;
+
+		await trigger().then(() => {
+			timer = setTimeout(() => {
+				stream.close();
+				resolve(false);
+			}, 500);
+		}).catch(err => {
+			stream.close();
+			clearTimeout(timer);
+			reject(err);
+		});
+	});
+};
+
+export async function isNoteUpdatedEventFired(
+	host: Host,
+	user: { i: string },
+	noteId: string,
+	trigger: () => Promise<unknown>,
+	cond: (msg: Parameters<Misskey.StreamEvents['noteUpdated']>[0]) => boolean,
+): Promise<boolean> {
+	return new Promise<boolean>(async (resolve, reject) => {
+		const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket });
+		stream.send('s', { id: noteId });
+		stream.on('noteUpdated', msg => {
+			if (cond(msg)) {
+				stream.close();
+				clearTimeout(timer);
+				resolve(true);
+			}
+		});
+
+		let timer: NodeJS.Timeout | undefined;
+
+		await trigger().then(() => {
+			timer = setTimeout(() => {
+				stream.close();
+				resolve(false);
+			}, 500);
+		}).catch(err => {
+			stream.close();
+			clearTimeout(timer);
+			reject(err);
+		});
+	});
+};
+
+export async function assertNotificationReceived(
+	receiverHost: Host,
+	receiver: LoginUser,
+	trigger: () => Promise<unknown>,
+	cond: (notification: Misskey.entities.Notification) => boolean,
+	expect: boolean,
+) {
+	const streamingFired = await isFired(receiverHost, receiver, 'main', trigger, 'notification', cond);
+	strictEqual(streamingFired, expect);
+
+	const endpointFired = await receiver.client.request('i/notifications', {})
+		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+		.then(([notification]) => notification != null ? cond(notification) : false);
+	strictEqual(endpointFired, expect);
+}
diff --git a/packages/backend/test-federation/tsconfig.json b/packages/backend/test-federation/tsconfig.json
new file mode 100644
index 000000000000..3a1cb3b9f3ba
--- /dev/null
+++ b/packages/backend/test-federation/tsconfig.json
@@ -0,0 +1,114 @@
+{
+	"compilerOptions": {
+		/* Visit https://aka.ms/tsconfig to read more about this file */
+
+		/* Projects */
+		// "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+		// "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
+		// "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
+		// "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
+		// "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
+		// "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
+
+		/* Language and Environment */
+		"target": "ESNext",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+		// "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+		// "jsx": "preserve",                                /* Specify what JSX code is generated. */
+		// "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
+		// "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
+		// "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+		// "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+		// "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+		// "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+		// "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
+		// "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
+		// "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
+
+		/* Modules */
+		"module": "NodeNext",                                /* Specify what module code is generated. */
+		// "rootDir": "./",                                  /* Specify the root folder within your source files. */
+		// "moduleResolution": "node10",                     /* Specify how TypeScript looks up a file from a given module specifier. */
+		// "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
+		// "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
+		// "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
+		// "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
+		// "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
+		// "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
+		// "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
+		// "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+		// "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
+		// "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
+		// "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+		// "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
+		// "resolveJsonModule": true,                        /* Enable importing .json files. */
+		// "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
+		// "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+
+		/* JavaScript Support */
+		// "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+		// "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
+		// "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+		/* Emit */
+		// "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+		// "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
+		// "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
+		// "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
+		// "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
+		// "noEmit": true,                                   /* Disable emitting files from a compilation. */
+		// "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+		"outDir": "./built",                                 /* Specify an output folder for all emitted files. */
+		// "removeComments": true,                           /* Disable emitting comments. */
+		// "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+		// "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+		// "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
+		// "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
+		// "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
+		// "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+		// "newLine": "crlf",                                /* Set the newline character for emitting files. */
+		// "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+		// "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
+		// "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
+		// "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
+		// "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
+
+		/* Interop Constraints */
+		// "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
+		// "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+		// "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
+		// "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
+		"esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+		// "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+		"forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */
+
+		/* Type Checking */
+		"strict": true,                                      /* Enable all strict type-checking options. */
+		// "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+		// "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
+		// "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+		// "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+		// "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
+		// "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
+		// "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
+		// "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
+		// "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
+		// "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
+		// "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
+		// "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
+		// "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
+		// "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
+		// "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
+		// "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
+		// "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
+		// "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
+		// "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
+
+		/* Completeness */
+		// "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
+		"skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
+	},
+	"include": [
+		"daemon.ts",
+		"./test/**/*.ts"
+	]
+}
diff --git a/packages/backend/test-server/entry.ts b/packages/backend/test-server/entry.ts
index 866a7e1f5bc5..04bf62d2096d 100644
--- a/packages/backend/test-server/entry.ts
+++ b/packages/backend/test-server/entry.ts
@@ -6,12 +6,16 @@ import { MainModule } from '@/MainModule.js';
 import { ServerService } from '@/server/ServerService.js';
 import { loadConfig } from '@/config.js';
 import { NestLogger } from '@/NestLogger.js';
+import { INestApplicationContext } from '@nestjs/common';
 
 const config = loadConfig();
 const originEnv = JSON.stringify(process.env);
 
 process.env.NODE_ENV = 'test';
 
+let app: INestApplicationContext;
+let serverService: ServerService;
+
 /**
  * テスト用のサーバインスタンスを起動する
  */
@@ -20,10 +24,10 @@ async function launch() {
 
 	console.log('starting application...');
 
-	const app = await NestFactory.createApplicationContext(MainModule, {
+	app = await NestFactory.createApplicationContext(MainModule, {
 		logger: new NestLogger(),
 	});
-	const serverService = app.get(ServerService);
+	serverService = app.get(ServerService);
 	await serverService.launch();
 
 	await startControllerEndpoints();
@@ -71,6 +75,20 @@ async function startControllerEndpoints(port = config.port + 1000) {
 
 	fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
 		process.env = JSON.parse(originEnv);
+
+		await serverService.dispose();
+		await app.close();
+
+		await killTestServer();
+
+		console.log('starting application...');
+
+		app = await NestFactory.createApplicationContext(MainModule, {
+			logger: new NestLogger(),
+		});
+		serverService = app.get(ServerService);
+		await serverService.launch();
+
 		res.code(200).send({ success: true });
 	});
 
diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts
index 06548fa7da03..48e1bababb7e 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -136,13 +136,7 @@ describe('2要素認証', () => {
 		keyName: string,
 		credentialId: Buffer,
 		requestOptions: PublicKeyCredentialRequestOptionsJSON,
-	}): {
-		username: string,
-		password: string,
-		credential: AuthenticationResponseJSON,
-		'g-recaptcha-response'?: string | null,
-		'hcaptcha-response'?: string | null,
-	} => {
+	}): misskey.entities.SigninFlowRequest => {
 		// AuthenticatorAssertionResponse.authenticatorData
 		// https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/authenticatorData
 		const authenticatorData = Buffer.concat([
@@ -202,17 +196,21 @@ describe('2要素認証', () => {
 		}, alice);
 		assert.strictEqual(doneResponse.status, 200);
 
-		const usersShowResponse = await api('users/show', {
-			username,
-		}, alice);
-		assert.strictEqual(usersShowResponse.status, 200);
-		assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true);
+		const signinWithoutTokenResponse = await api('signin-flow', {
+			...signinParam(),
+		});
+		assert.strictEqual(signinWithoutTokenResponse.status, 200);
+		assert.deepStrictEqual(signinWithoutTokenResponse.body, {
+			finished: false,
+			next: 'totp',
+		});
 
-		const signinResponse = await api('signin', {
+		const signinResponse = await api('signin-flow', {
 			...signinParam(),
 			token: otpToken(registerResponse.body.secret),
 		});
 		assert.strictEqual(signinResponse.status, 200);
+		assert.strictEqual(signinResponse.body.finished, true);
 		assert.notEqual(signinResponse.body.i, undefined);
 
 		// 後片付け
@@ -253,27 +251,23 @@ describe('2要素認証', () => {
 		assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url'));
 		assert.strictEqual(keyDoneResponse.body.name, keyName);
 
-		const usersShowResponse = await api('users/show', {
-			username,
-		});
-		assert.strictEqual(usersShowResponse.status, 200);
-		assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, true);
-
-		const signinResponse = await api('signin', {
+		const signinResponse = await api('signin-flow', {
 			...signinParam(),
 		});
 		assert.strictEqual(signinResponse.status, 200);
-		assert.strictEqual(signinResponse.body.i, undefined);
-		assert.notEqual((signinResponse.body as unknown as { challenge: unknown | undefined }).challenge, undefined);
-		assert.notEqual((signinResponse.body as unknown as { allowCredentials: unknown | undefined }).allowCredentials, undefined);
-		assert.strictEqual((signinResponse.body as unknown as { allowCredentials: {id: string}[] }).allowCredentials[0].id, credentialId.toString('base64url'));
+		assert.strictEqual(signinResponse.body.finished, false);
+		assert.strictEqual(signinResponse.body.next, 'passkey');
+		assert.notEqual(signinResponse.body.authRequest.challenge, undefined);
+		assert.notEqual(signinResponse.body.authRequest.allowCredentials, undefined);
+		assert.strictEqual(signinResponse.body.authRequest.allowCredentials && signinResponse.body.authRequest.allowCredentials[0]?.id, credentialId.toString('base64url'));
 
-		const signinResponse2 = await api('signin', signinWithSecurityKeyParam({
+		const signinResponse2 = await api('signin-flow', signinWithSecurityKeyParam({
 			keyName,
 			credentialId,
-			requestOptions: signinResponse.body,
-		} as any));
+			requestOptions: signinResponse.body.authRequest,
+		}));
 		assert.strictEqual(signinResponse2.status, 200);
+		assert.strictEqual(signinResponse2.body.finished, true);
 		assert.notEqual(signinResponse2.body.i, undefined);
 
 		// 後片付け
@@ -315,28 +309,30 @@ describe('2要素認証', () => {
 		}, alice);
 		assert.strictEqual(passwordLessResponse.status, 204);
 
-		const usersShowResponse = await api('users/show', {
-			username,
-		});
-		assert.strictEqual(usersShowResponse.status, 200);
-		assert.strictEqual((usersShowResponse.body as unknown as { usePasswordLessLogin: boolean }).usePasswordLessLogin, true);
+		const iResponse = await api('i', {}, alice);
+		assert.strictEqual(iResponse.status, 200);
+		assert.strictEqual(iResponse.body.usePasswordLessLogin, true);
 
-		const signinResponse = await api('signin', {
+		const signinResponse = await api('signin-flow', {
 			...signinParam(),
 			password: '',
 		});
 		assert.strictEqual(signinResponse.status, 200);
-		assert.strictEqual(signinResponse.body.i, undefined);
+		assert.strictEqual(signinResponse.body.finished, false);
+		assert.strictEqual(signinResponse.body.next, 'passkey');
+		assert.notEqual(signinResponse.body.authRequest.challenge, undefined);
+		assert.notEqual(signinResponse.body.authRequest.allowCredentials, undefined);
 
-		const signinResponse2 = await api('signin', {
+		const signinResponse2 = await api('signin-flow', {
 			...signinWithSecurityKeyParam({
 				keyName,
 				credentialId,
-				requestOptions: signinResponse.body,
+				requestOptions: signinResponse.body.authRequest,
 			} as any),
 			password: '',
 		});
 		assert.strictEqual(signinResponse2.status, 200);
+		assert.strictEqual(signinResponse2.body.finished, true);
 		assert.notEqual(signinResponse2.body.i, undefined);
 
 		// 後片付け
@@ -424,11 +420,11 @@ describe('2要素認証', () => {
 		assert.strictEqual(keyDoneResponse.status, 200);
 
 		// テストの実行順によっては複数残ってるので全部消す
-		const iResponse = await api('i', {
+		const beforeIResponse = await api('i', {
 		}, alice);
-		assert.strictEqual(iResponse.status, 200);
-		assert.ok(iResponse.body.securityKeysList);
-		for (const key of iResponse.body.securityKeysList) {
+		assert.strictEqual(beforeIResponse.status, 200);
+		assert.ok(beforeIResponse.body.securityKeysList);
+		for (const key of beforeIResponse.body.securityKeysList) {
 			const removeKeyResponse = await api('i/2fa/remove-key', {
 				token: otpToken(registerResponse.body.secret),
 				password,
@@ -437,17 +433,16 @@ describe('2要素認証', () => {
 			assert.strictEqual(removeKeyResponse.status, 200);
 		}
 
-		const usersShowResponse = await api('users/show', {
-			username,
-		});
-		assert.strictEqual(usersShowResponse.status, 200);
-		assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, false);
+		const afterIResponse = await api('i', {}, alice);
+		assert.strictEqual(afterIResponse.status, 200);
+		assert.strictEqual(afterIResponse.body.securityKeys, false);
 
-		const signinResponse = await api('signin', {
+		const signinResponse = await api('signin-flow', {
 			...signinParam(),
 			token: otpToken(registerResponse.body.secret),
 		});
 		assert.strictEqual(signinResponse.status, 200);
+		assert.strictEqual(signinResponse.body.finished, true);
 		assert.notEqual(signinResponse.body.i, undefined);
 
 		// 後片付け
@@ -468,11 +463,9 @@ describe('2要素認証', () => {
 		}, alice);
 		assert.strictEqual(doneResponse.status, 200);
 
-		const usersShowResponse = await api('users/show', {
-			username,
-		});
-		assert.strictEqual(usersShowResponse.status, 200);
-		assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true);
+		const iResponse = await api('i', {}, alice);
+		assert.strictEqual(iResponse.status, 200);
+		assert.strictEqual(iResponse.body.twoFactorEnabled, true);
 
 		const unregisterResponse = await api('i/2fa/unregister', {
 			token: otpToken(registerResponse.body.secret),
@@ -480,10 +473,11 @@ describe('2要素認証', () => {
 		}, alice);
 		assert.strictEqual(unregisterResponse.status, 204);
 
-		const signinResponse = await api('signin', {
+		const signinResponse = await api('signin-flow', {
 			...signinParam(),
 		});
 		assert.strictEqual(signinResponse.status, 200);
+		assert.strictEqual(signinResponse.body.finished, true);
 		assert.notEqual(signinResponse.body.i, undefined);
 
 		// 後片付け
diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts
index 6ac14cd8dcde..a544db955a07 100644
--- a/packages/backend/test/e2e/antennas.ts
+++ b/packages/backend/test/e2e/antennas.ts
@@ -228,6 +228,17 @@ describe('アンテナ', () => {
 		assert.deepStrictEqual(response, expected);
 	});
 
+	test('を作成する時キーワードが指定されていないとエラーになる', async () => {
+		await failedApiCall({
+			endpoint: 'antennas/create',
+			parameters: { ...defaultParam, keywords: [[]], excludeKeywords: [[]] },
+			user: alice
+		}, {
+			status: 400,
+			code: 'EMPTY_KEYWORD',
+			id: '53ee222e-1ddd-4f9a-92e5-9fb82ddb463a'
+		})
+	});
 	//#endregion
 	//#region 更新(antennas/update)
 
@@ -255,6 +266,18 @@ describe('アンテナ', () => {
 			id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
 		});
 	});
+	test('を変更する時キーワードが指定されていないとエラーになる', async () => {
+		const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
+		await failedApiCall({
+			endpoint: 'antennas/update',
+			parameters: { ...defaultParam, antennaId: antenna.id, keywords: [[]], excludeKeywords: [[]] },
+			user: alice
+		}, {
+			status: 400,
+			code: 'EMPTY_KEYWORD',
+			id: '721aaff6-4e1b-4d88-8de6-877fae9f68c4'
+		})
+	});
 
 	//#endregion
 	//#region 表示(antennas/show)
diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts
index 5aaec7f6f9d0..b91d77c398c8 100644
--- a/packages/backend/test/e2e/endpoints.ts
+++ b/packages/backend/test/e2e/endpoints.ts
@@ -66,9 +66,9 @@ describe('Endpoints', () => {
 		});
 	});
 
-	describe('signin', () => {
+	describe('signin-flow', () => {
 		test('間違ったパスワードでサインインできない', async () => {
-			const res = await api('signin', {
+			const res = await api('signin-flow', {
 				username: 'test1',
 				password: 'bar',
 			});
@@ -77,7 +77,7 @@ describe('Endpoints', () => {
 		});
 
 		test('クエリをインジェクションできない', async () => {
-			const res = await api('signin', {
+			const res = await api('signin-flow', {
 				username: 'test1',
 				// @ts-expect-error password must be string
 				password: {
@@ -89,7 +89,7 @@ describe('Endpoints', () => {
 		});
 
 		test('正しい情報でサインインできる', async () => {
-			const res = await api('signin', {
+			const res = await api('signin-flow', {
 				username: 'test1',
 				password: 'test1',
 			});
diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts
index 7efd688ec279..8ea4cb9800cd 100644
--- a/packages/backend/test/e2e/fetch-resource.ts
+++ b/packages/backend/test/e2e/fetch-resource.ts
@@ -230,6 +230,7 @@ describe('Webリソース', () => {
 				path: path('xxxxxxxxxx'),
 				type: HTML,
 			}));
+			test.todo('HTMLとしてGETできる。(リモートユーザーでもリダイレクトせず)');
 		});
 
 		describe.each([
@@ -249,6 +250,7 @@ describe('Webリソース', () => {
 				path: path('xxxxxxxxxx'),
 				accept,
 			}));
+			test.todo('はオリジナルにリダイレクトされる。(リモートユーザー)');
 		});
 	});
 
diff --git a/packages/backend/test/e2e/synalio/abuse-report.ts b/packages/backend/test/e2e/synalio/abuse-report.ts
index 6ce6e477812f..c98d199f35d7 100644
--- a/packages/backend/test/e2e/synalio/abuse-report.ts
+++ b/packages/backend/test/e2e/synalio/abuse-report.ts
@@ -157,7 +157,6 @@ describe('[シナリオ] ユーザ通報', () => {
 			const webhookBody2 = await captureWebhook(async () => {
 				await resolveAbuseReport({
 					reportId: webhookBody1.body.id,
-					forward: false,
 				}, admin);
 			});
 
@@ -214,7 +213,6 @@ describe('[シナリオ] ユーザ通報', () => {
 			const webhookBody2 = await captureWebhook(async () => {
 				await resolveAbuseReport({
 					reportId: abuseReportId,
-					forward: false,
 				}, admin);
 			});
 
@@ -257,7 +255,6 @@ describe('[シナリオ] ユーザ通報', () => {
 			const webhookBody2 = await captureWebhook(async () => {
 				await resolveAbuseReport({
 					reportId: webhookBody1.body.id,
-					forward: false,
 				}, admin);
 			}).catch(e => e.message);
 
@@ -288,7 +285,6 @@ describe('[シナリオ] ユーザ通報', () => {
 			const webhookBody2 = await captureWebhook(async () => {
 				await resolveAbuseReport({
 					reportId: abuseReportId,
-					forward: false,
 				}, admin);
 			}).catch(e => e.message);
 
@@ -319,7 +315,6 @@ describe('[シナリオ] ユーザ通報', () => {
 			const webhookBody2 = await captureWebhook(async () => {
 				await resolveAbuseReport({
 					reportId: abuseReportId,
-					forward: false,
 				}, admin);
 			}).catch(e => e.message);
 
@@ -350,7 +345,6 @@ describe('[シナリオ] ユーザ通報', () => {
 			const webhookBody2 = await captureWebhook(async () => {
 				await resolveAbuseReport({
 					reportId: abuseReportId,
-					forward: false,
 				}, admin);
 			}).catch(e => e.message);
 
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 61fd7599322a..822ca14ae6c3 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -7,9 +7,9 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import { inspect } from 'node:util';
-import { DEFAULT_POLICIES } from '@/core/RoleService.js';
 import { api, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
 import type * as misskey from 'misskey-js';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
 
 describe('ユーザー', () => {
 	// エンティティとしてのユーザーを主眼においたテストを記述する
@@ -83,9 +83,6 @@ describe('ユーザー', () => {
 			publicReactions: user.publicReactions,
 			followingVisibility: user.followingVisibility,
 			followersVisibility: user.followersVisibility,
-			twoFactorEnabled: user.twoFactorEnabled,
-			usePasswordLessLogin: user.usePasswordLessLogin,
-			securityKeys: user.securityKeys,
 			roles: user.roles,
 			memo: user.memo,
 		});
@@ -105,6 +102,7 @@ describe('ユーザー', () => {
 			isRenoteMuted: user.isRenoteMuted ?? false,
 			notify: user.notify ?? 'none',
 			withReplies: user.withReplies ?? false,
+			followedMessage: user.isFollowing ? (user.followedMessage ?? null) : undefined,
 		});
 	};
 
@@ -114,6 +112,7 @@ describe('ユーザー', () => {
 			...userDetailedNotMe(user),
 			avatarId: user.avatarId,
 			bannerId: user.bannerId,
+			followedMessage: user.followedMessage,
 			isModerator: user.isModerator,
 			isAdmin: user.isAdmin,
 			injectFeaturedNote: user.injectFeaturedNote,
@@ -147,6 +146,9 @@ describe('ユーザー', () => {
 			achievements: user.achievements,
 			loggedInDays: user.loggedInDays,
 			policies: user.policies,
+			twoFactorEnabled: user.twoFactorEnabled,
+			usePasswordLessLogin: user.usePasswordLessLogin,
+			securityKeys: user.securityKeys,
 			...(security ? {
 				email: user.email,
 				emailVerified: user.emailVerified,
@@ -341,15 +343,13 @@ describe('ユーザー', () => {
 		assert.strictEqual(response.publicReactions, true);
 		assert.strictEqual(response.followingVisibility, 'public');
 		assert.strictEqual(response.followersVisibility, 'public');
-		assert.strictEqual(response.twoFactorEnabled, false);
-		assert.strictEqual(response.usePasswordLessLogin, false);
-		assert.strictEqual(response.securityKeys, false);
 		assert.deepStrictEqual(response.roles, []);
 		assert.strictEqual(response.memo, null);
 
 		// MeDetailedOnly
 		assert.strictEqual(response.avatarId, null);
 		assert.strictEqual(response.bannerId, null);
+		assert.strictEqual(response.followedMessage, null);
 		assert.strictEqual(response.isModerator, false);
 		assert.strictEqual(response.isAdmin, false);
 		assert.strictEqual(response.injectFeaturedNote, true);
@@ -382,6 +382,9 @@ describe('ユーザー', () => {
 		assert.deepStrictEqual(response.achievements, []);
 		assert.deepStrictEqual(response.loggedInDays, 0);
 		assert.deepStrictEqual(response.policies, DEFAULT_POLICIES);
+		assert.strictEqual(response.twoFactorEnabled, false);
+		assert.strictEqual(response.usePasswordLessLogin, false);
+		assert.strictEqual(response.securityKeys, false);
 		assert.notStrictEqual(response.email, undefined);
 		assert.strictEqual(response.emailVerified, false);
 		assert.deepStrictEqual(response.securityKeysList, []);
@@ -413,6 +416,8 @@ describe('ユーザー', () => {
 		{ parameters: () => ({ description: 'x'.repeat(1500) }) },
 		{ parameters: () => ({ description: 'x' }) },
 		{ parameters: () => ({ description: 'My description' }) },
+		{ parameters: () => ({ followedMessage: null }) },
+		{ parameters: () => ({ followedMessage: 'Thank you' }) },
 		{ parameters: () => ({ location: null }) },
 		{ parameters: () => ({ location: 'x'.repeat(50) }) },
 		{ parameters: () => ({ location: 'x' }) },
@@ -613,6 +618,9 @@ describe('ユーザー', () => {
 		{ label: 'Moderatorになっている', user: () => userModerator, me: () => userModerator, selector: (user: misskey.entities.MeDetailed) => user.isModerator },
 		// @ts-expect-error UserDetailedNotMe doesn't include isModerator
 		{ label: '自分以外から見たときはModeratorか判定できない', user: () => userModerator, selector: (user: misskey.entities.UserDetailedNotMe) => user.isModerator, expected: () => undefined },
+		{ label: '自分から見た場合に二要素認証関連のプロパティがセットされている', user: () => alice, me: () => alice, selector: (user: misskey.entities.MeDetailed) => user.twoFactorEnabled, expected: () => false },
+		{ label: '自分以外から見た場合に二要素認証関連のプロパティがセットされていない', user: () => alice, me: () => bob, selector: (user: misskey.entities.UserDetailedNotMe) => user.twoFactorEnabled, expected: () => undefined },
+		{ label: 'モデレーターから見た場合に二要素認証関連のプロパティがセットされている', user: () => alice, me: () => userModerator, selector: (user: misskey.entities.UserDetailedNotMe) => user.twoFactorEnabled, expected: () => false },
 		{ label: 'サイレンスになっている', user: () => userSilenced, selector: (user: misskey.entities.UserDetailed) => user.isSilenced },
 		// FIXME: 落ちる
 		//{ label: 'サスペンドになっている', user: () => userSuspended, selector: (user: misskey.entities.UserDetailed) => user.isSuspended },
diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts
index 861bc6db669b..7c6dd6a55f20 100644
--- a/packages/backend/test/jest.setup.ts
+++ b/packages/backend/test/jest.setup.ts
@@ -6,8 +6,6 @@
 import { initTestDb, sendEnvResetRequest } from './utils.js';
 
 beforeAll(async () => {
-	await Promise.all([
-		initTestDb(false),
-		sendEnvResetRequest(),
-	]);
+		await initTestDb(false);
+		await sendEnvResetRequest();
 });
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 3c7e796700da..c8f3db8aac39 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -17,6 +17,7 @@ import type { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import type {
 	FollowRequestsRepository,
+	MiMeta,
 	NoteReactionsRepository,
 	NotesRepository,
 	PollsRepository,
@@ -35,6 +36,7 @@ export class MockResolver extends Resolver {
 	constructor(loggerService: LoggerService) {
 		super(
 			{} as Config,
+			{} as MiMeta,
 			{} as UsersRepository,
 			{} as NotesRepository,
 			{} as PollsRepository,
@@ -42,7 +44,6 @@ export class MockResolver extends Resolver {
 			{} as FollowRequestsRepository,
 			{} as UtilityService,
 			{} as InstanceActorService,
-			{} as MetaService,
 			{} as ApRequestService,
 			{} as HttpRequestService,
 			{} as ApRendererService,
diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts
index e971659070ea..235af29f0dc9 100644
--- a/packages/backend/test/unit/AbuseReportNotificationService.ts
+++ b/packages/backend/test/unit/AbuseReportNotificationService.ts
@@ -5,6 +5,7 @@
 
 import { jest } from '@jest/globals';
 import { Test, TestingModule } from '@nestjs/testing';
+import { randomString } from '../utils.js';
 import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js';
 import {
 	AbuseReportNotificationRecipientRepository,
@@ -25,7 +26,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js';
 import { SystemWebhookService } from '@/core/SystemWebhookService.js';
-import { randomString } from '../utils.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
 
 process.env.NODE_ENV = 'test';
 
@@ -110,6 +111,9 @@ describe('AbuseReportNotificationService', () => {
 					{
 						provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }),
 					},
+					{
+						provide: UserEntityService, useFactory: () => ({ pack: (v: any) => v }),
+					},
 					{
 						provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }),
 					},
diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts
index bf8f3ab0e306..1e3605aafc95 100644
--- a/packages/backend/test/unit/FetchInstanceMetadataService.ts
+++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts
@@ -8,6 +8,7 @@ process.env.NODE_ENV = 'test';
 import { jest } from '@jest/globals';
 import { Test } from '@nestjs/testing';
 import { Redis } from 'ioredis';
+import type { TestingModule } from '@nestjs/testing';
 import { GlobalModule } from '@/GlobalModule.js';
 import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
@@ -16,7 +17,6 @@ import { LoggerService } from '@/core/LoggerService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { IdService } from '@/core/IdService.js';
 import { DI } from '@/di-symbols.js';
-import type { TestingModule } from '@nestjs/testing';
 
 function mockRedis() {
 	const hash = {} as any;
@@ -52,7 +52,7 @@ describe('FetchInstanceMetadataService', () => {
 				if (token === HttpRequestService) {
 					return { getJson: jest.fn(), getHtml: jest.fn(), send: jest.fn() };
 				} else if (token === FederatedInstanceService) {
-					return { fetch: jest.fn() };
+					return { fetchOrRegister: jest.fn() };
 				} else if (token === DI.redis) {
 					return mockRedis;
 				}
@@ -75,7 +75,7 @@ describe('FetchInstanceMetadataService', () => {
 	test('Lock and update', async () => {
 		redisClient.set = mockRedis();
 		const now = Date.now();
-		federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any);
+		federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any);
 		httpRequestService.getJson.mockImplementation(() => { throw Error(); });
 		const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
 		const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
@@ -83,14 +83,14 @@ describe('FetchInstanceMetadataService', () => {
 		await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
 		expect(tryLockSpy).toHaveBeenCalledTimes(1);
 		expect(unlockSpy).toHaveBeenCalledTimes(1);
-		expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1);
+		expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(1);
 		expect(httpRequestService.getJson).toHaveBeenCalled();
 	});
 
 	test('Lock and don\'t update', async () => {
 		redisClient.set = mockRedis();
 		const now = Date.now();
-		federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any);
+		federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any);
 		httpRequestService.getJson.mockImplementation(() => { throw Error(); });
 		const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
 		const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock');
@@ -98,14 +98,14 @@ describe('FetchInstanceMetadataService', () => {
 		await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
 		expect(tryLockSpy).toHaveBeenCalledTimes(1);
 		expect(unlockSpy).toHaveBeenCalledTimes(1);
-		expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1);
+		expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(1);
 		expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
 	});
 
 	test('Do nothing when lock not acquired', async () => {
 		redisClient.set = mockRedis();
 		const now = Date.now();
-		federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
+		federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
 		httpRequestService.getJson.mockImplementation(() => { throw Error(); });
 		await fetchInstanceMetadataService.tryLock('example.com');
 		const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
@@ -114,14 +114,14 @@ describe('FetchInstanceMetadataService', () => {
 		await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any);
 		expect(tryLockSpy).toHaveBeenCalledTimes(1);
 		expect(unlockSpy).toHaveBeenCalledTimes(0);
-		expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0);
+		expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(0);
 		expect(httpRequestService.getJson).toHaveBeenCalledTimes(0);
 	});
 
 	test('Do when lock not acquired but forced', async () => {
 		redisClient.set = mockRedis();
 		const now = Date.now();
-		federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
+		federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any);
 		httpRequestService.getJson.mockImplementation(() => { throw Error(); });
 		await fetchInstanceMetadataService.tryLock('example.com');
 		const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock');
@@ -130,7 +130,7 @@ describe('FetchInstanceMetadataService', () => {
 		await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true);
 		expect(tryLockSpy).toHaveBeenCalledTimes(0);
 		expect(unlockSpy).toHaveBeenCalledTimes(1);
-		expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0);
+		expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(0);
 		expect(httpRequestService.getJson).toHaveBeenCalled();
 	});
 });
diff --git a/packages/backend/test/unit/FlashService.ts b/packages/backend/test/unit/FlashService.ts
new file mode 100644
index 000000000000..12ffaf34213a
--- /dev/null
+++ b/packages/backend/test/unit/FlashService.ts
@@ -0,0 +1,152 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Test, TestingModule } from '@nestjs/testing';
+import { FlashService } from '@/core/FlashService.js';
+import { IdService } from '@/core/IdService.js';
+import { FlashsRepository, MiFlash, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalModule } from '@/GlobalModule.js';
+
+describe('FlashService', () => {
+	let app: TestingModule;
+	let service: FlashService;
+
+	// --------------------------------------------------------------------------------------
+
+	let flashsRepository: FlashsRepository;
+	let usersRepository: UsersRepository;
+	let userProfilesRepository: UserProfilesRepository;
+	let idService: IdService;
+
+	// --------------------------------------------------------------------------------------
+
+	let root: MiUser;
+	let alice: MiUser;
+	let bob: MiUser;
+
+	// --------------------------------------------------------------------------------------
+
+	async function createFlash(data: Partial<MiFlash>) {
+		return flashsRepository.insert({
+			id: idService.gen(),
+			updatedAt: new Date(),
+			userId: root.id,
+			title: 'title',
+			summary: 'summary',
+			script: 'script',
+			permissions: [],
+			likedCount: 0,
+			...data,
+		}).then(x => flashsRepository.findOneByOrFail(x.identifiers[0]));
+	}
+
+	async function createUser(data: Partial<MiUser> = {}) {
+		const user = await usersRepository
+			.insert({
+				id: idService.gen(),
+				...data,
+			})
+			.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
+
+		await userProfilesRepository.insert({
+			userId: user.id,
+		});
+
+		return user;
+	}
+
+	// --------------------------------------------------------------------------------------
+
+	beforeEach(async () => {
+		app = await Test.createTestingModule({
+			imports: [
+				GlobalModule,
+			],
+			providers: [
+				FlashService,
+				IdService,
+			],
+		}).compile();
+
+		service = app.get(FlashService);
+
+		flashsRepository = app.get(DI.flashsRepository);
+		usersRepository = app.get(DI.usersRepository);
+		userProfilesRepository = app.get(DI.userProfilesRepository);
+		idService = app.get(IdService);
+
+		root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
+		alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
+		bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false });
+	});
+
+	afterEach(async () => {
+		await usersRepository.delete({});
+		await userProfilesRepository.delete({});
+		await flashsRepository.delete({});
+	});
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	// --------------------------------------------------------------------------------------
+
+	describe('featured', () => {
+		test('should return featured flashes', async () => {
+			const flash1 = await createFlash({ likedCount: 1 });
+			const flash2 = await createFlash({ likedCount: 2 });
+			const flash3 = await createFlash({ likedCount: 3 });
+
+			const result = await service.featured({
+				offset: 0,
+				limit: 10,
+			});
+
+			expect(result).toEqual([flash3, flash2, flash1]);
+		});
+
+		test('should return featured flashes public visibility only', async () => {
+			const flash1 = await createFlash({ likedCount: 1, visibility: 'public' });
+			const flash2 = await createFlash({ likedCount: 2, visibility: 'public' });
+			const flash3 = await createFlash({ likedCount: 3, visibility: 'private' });
+
+			const result = await service.featured({
+				offset: 0,
+				limit: 10,
+			});
+
+			expect(result).toEqual([flash2, flash1]);
+		});
+
+		test('should return featured flashes with offset', async () => {
+			const flash1 = await createFlash({ likedCount: 1 });
+			const flash2 = await createFlash({ likedCount: 2 });
+			const flash3 = await createFlash({ likedCount: 3 });
+
+			const result = await service.featured({
+				offset: 1,
+				limit: 10,
+			});
+
+			expect(result).toEqual([flash2, flash1]);
+		});
+
+		test('should return featured flashes with limit', async () => {
+			const flash1 = await createFlash({ likedCount: 1 });
+			const flash2 = await createFlash({ likedCount: 2 });
+			const flash3 = await createFlash({ likedCount: 3 });
+
+			const result = await service.featured({
+				offset: 0,
+				limit: 2,
+			});
+
+			expect(result).toEqual([flash3, flash2]);
+		});
+	});
+});
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index b6cbe4c520d1..9c1b1008d625 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -10,9 +10,12 @@ import { jest } from '@jest/globals';
 import { ModuleMocker } from 'jest-mock';
 import { Test } from '@nestjs/testing';
 import * as lolex from '@sinonjs/fake-timers';
+import type { TestingModule } from '@nestjs/testing';
+import type { MockFunctionMetadata } from 'jest-mock';
 import { GlobalModule } from '@/GlobalModule.js';
 import { RoleService } from '@/core/RoleService.js';
 import {
+	MiMeta,
 	MiRole,
 	MiRoleAssignment,
 	MiUser,
@@ -30,8 +33,6 @@ import { secureRndstr } from '@/misc/secure-rndstr.js';
 import { NotificationService } from '@/core/NotificationService.js';
 import { RoleCondFormulaValue } from '@/models/Role.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import type { TestingModule } from '@nestjs/testing';
-import type { MockFunctionMetadata } from 'jest-mock';
 
 const moduleMocker = new ModuleMocker(global);
 
@@ -41,7 +42,7 @@ describe('RoleService', () => {
 	let usersRepository: UsersRepository;
 	let rolesRepository: RolesRepository;
 	let roleAssignmentsRepository: RoleAssignmentsRepository;
-	let metaService: jest.Mocked<MetaService>;
+	let meta: jest.Mocked<MiMeta>;
 	let notificationService: jest.Mocked<NotificationService>;
 	let clock: lolex.InstalledClock;
 
@@ -142,7 +143,7 @@ describe('RoleService', () => {
 		rolesRepository = app.get<RolesRepository>(DI.rolesRepository);
 		roleAssignmentsRepository = app.get<RoleAssignmentsRepository>(DI.roleAssignmentsRepository);
 
-		metaService = app.get<MetaService>(MetaService) as jest.Mocked<MetaService>;
+		meta = app.get<MiMeta>(DI.meta) as jest.Mocked<MiMeta>;
 		notificationService = app.get<NotificationService>(NotificationService) as jest.Mocked<NotificationService>;
 
 		await roleService.onModuleInit();
@@ -164,11 +165,9 @@ describe('RoleService', () => {
 	describe('getUserPolicies', () => {
 		test('instance default policies', async () => {
 			const user = await createUser();
-			metaService.fetch.mockResolvedValue({
-				policies: {
-					canManageCustomEmojis: false,
-				},
-			} as any);
+			meta.policies = {
+				canManageCustomEmojis: false,
+			};
 
 			const result = await roleService.getUserPolicies(user.id);
 
@@ -177,11 +176,9 @@ describe('RoleService', () => {
 
 		test('instance default policies 2', async () => {
 			const user = await createUser();
-			metaService.fetch.mockResolvedValue({
-				policies: {
-					canManageCustomEmojis: true,
-				},
-			} as any);
+			meta.policies = {
+				canManageCustomEmojis: true,
+			};
 
 			const result = await roleService.getUserPolicies(user.id);
 
@@ -201,11 +198,9 @@ describe('RoleService', () => {
 				},
 			});
 			await roleService.assign(user.id, role.id);
-			metaService.fetch.mockResolvedValue({
-				policies: {
-					canManageCustomEmojis: false,
-				},
-			} as any);
+			meta.policies = {
+				canManageCustomEmojis: false,
+			};
 
 			const result = await roleService.getUserPolicies(user.id);
 
@@ -236,11 +231,9 @@ describe('RoleService', () => {
 			});
 			await roleService.assign(user.id, role1.id);
 			await roleService.assign(user.id, role2.id);
-			metaService.fetch.mockResolvedValue({
-				policies: {
-					driveCapacityMb: 50,
-				},
-			} as any);
+			meta.policies = {
+				driveCapacityMb: 50,
+			};
 
 			const result = await roleService.getUserPolicies(user.id);
 
@@ -260,11 +253,9 @@ describe('RoleService', () => {
 				},
 			});
 			await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24)));
-			metaService.fetch.mockResolvedValue({
-				policies: {
-					canManageCustomEmojis: false,
-				},
-			} as any);
+			meta.policies = {
+				canManageCustomEmojis: false,
+			};
 
 			const result = await roleService.getUserPolicies(user.id);
 			expect(result.canManageCustomEmojis).toBe(true);
@@ -286,9 +277,9 @@ describe('RoleService', () => {
 	});
 
 	describe('getModeratorIds', () => {
-		test('includeAdmins = false, excludeExpire = false', async () => {
-			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
-				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+		test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => {
+			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
 			]);
 
 			const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -304,13 +295,17 @@ describe('RoleService', () => {
 				assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
 			]);
 
-			const result = await roleService.getModeratorIds(false, false);
+			const result = await roleService.getModeratorIds({
+				includeAdmins: false,
+				includeRoot: false,
+				excludeExpire: false,
+			});
 			expect(result).toEqual([modeUser1.id, modeUser2.id]);
 		});
 
-		test('includeAdmins = false, excludeExpire = true', async () => {
-			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
-				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+		test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => {
+			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
 			]);
 
 			const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -326,13 +321,17 @@ describe('RoleService', () => {
 				assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
 			]);
 
-			const result = await roleService.getModeratorIds(false, true);
+			const result = await roleService.getModeratorIds({
+				includeAdmins: false,
+				includeRoot: false,
+				excludeExpire: true,
+			});
 			expect(result).toEqual([modeUser1.id]);
 		});
 
-		test('includeAdmins = true, excludeExpire = false', async () => {
-			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
-				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+		test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => {
+			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
 			]);
 
 			const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -348,13 +347,17 @@ describe('RoleService', () => {
 				assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
 			]);
 
-			const result = await roleService.getModeratorIds(true, false);
+			const result = await roleService.getModeratorIds({
+				includeAdmins: true,
+				includeRoot: false,
+				excludeExpire: false,
+			});
 			expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]);
 		});
 
-		test('includeAdmins = true, excludeExpire = true', async () => {
-			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([
-				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(),
+		test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => {
+			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
 			]);
 
 			const role1 = await createRole({ name: 'admin', isAdministrator: true });
@@ -370,9 +373,111 @@ describe('RoleService', () => {
 				assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
 			]);
 
-			const result = await roleService.getModeratorIds(true, true);
+			const result = await roleService.getModeratorIds({
+				includeAdmins: true,
+				includeRoot: false,
+				excludeExpire: true,
+			});
 			expect(result).toEqual([adminUser1.id, modeUser1.id]);
 		});
+
+		test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => {
+			const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+			]);
+
+			const role1 = await createRole({ name: 'admin', isAdministrator: true });
+			const role2 = await createRole({ name: 'moderator', isModerator: true });
+			const role3 = await createRole({ name: 'normal' });
+
+			await Promise.all([
+				assignRole({ userId: adminUser1.id, roleId: role1.id }),
+				assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }),
+				assignRole({ userId: modeUser1.id, roleId: role2.id }),
+				assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
+				assignRole({ userId: normalUser1.id, roleId: role3.id }),
+				assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }),
+			]);
+
+			const result = await roleService.getModeratorIds({
+				includeAdmins: false,
+				includeRoot: true,
+				excludeExpire: false,
+			});
+			expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]);
+		});
+
+		test('root has moderator role', async () => {
+			const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+			]);
+
+			const role1 = await createRole({ name: 'admin', isAdministrator: true });
+			const role2 = await createRole({ name: 'moderator', isModerator: true });
+			const role3 = await createRole({ name: 'normal' });
+
+			await Promise.all([
+				assignRole({ userId: adminUser1.id, roleId: role1.id }),
+				assignRole({ userId: modeUser1.id, roleId: role2.id }),
+				assignRole({ userId: rootUser.id, roleId: role2.id }),
+				assignRole({ userId: normalUser1.id, roleId: role3.id }),
+			]);
+
+			const result = await roleService.getModeratorIds({
+				includeAdmins: false,
+				includeRoot: true,
+				excludeExpire: false,
+			});
+			expect(result).toEqual([modeUser1.id, rootUser.id]);
+		});
+
+		test('root has administrator role', async () => {
+			const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+			]);
+
+			const role1 = await createRole({ name: 'admin', isAdministrator: true });
+			const role2 = await createRole({ name: 'moderator', isModerator: true });
+			const role3 = await createRole({ name: 'normal' });
+
+			await Promise.all([
+				assignRole({ userId: adminUser1.id, roleId: role1.id }),
+				assignRole({ userId: rootUser.id, roleId: role1.id }),
+				assignRole({ userId: modeUser1.id, roleId: role2.id }),
+				assignRole({ userId: normalUser1.id, roleId: role3.id }),
+			]);
+
+			const result = await roleService.getModeratorIds({
+				includeAdmins: true,
+				includeRoot: true,
+				excludeExpire: false,
+			});
+			expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]);
+		});
+
+		test('root has moderator role(expire)', async () => {
+			const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([
+				createUser(), createUser(), createUser(), createUser({ isRoot: true }),
+			]);
+
+			const role1 = await createRole({ name: 'admin', isAdministrator: true });
+			const role2 = await createRole({ name: 'moderator', isModerator: true });
+			const role3 = await createRole({ name: 'normal' });
+
+			await Promise.all([
+				assignRole({ userId: adminUser1.id, roleId: role1.id }),
+				assignRole({ userId: modeUser1.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
+				assignRole({ userId: rootUser.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }),
+				assignRole({ userId: normalUser1.id, roleId: role3.id }),
+			]);
+
+			const result = await roleService.getModeratorIds({
+				includeAdmins: false,
+				includeRoot: true,
+				excludeExpire: true,
+			});
+			expect(result).toEqual([rootUser.id]);
+		});
 	});
 
 	describe('conditional role', () => {
diff --git a/packages/backend/test/unit/SigninWithPasskeyApiService.ts b/packages/backend/test/unit/SigninWithPasskeyApiService.ts
new file mode 100644
index 000000000000..bae2b88c6008
--- /dev/null
+++ b/packages/backend/test/unit/SigninWithPasskeyApiService.ts
@@ -0,0 +1,182 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { IncomingHttpHeaders } from 'node:http';
+import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { FastifyReply, FastifyRequest } from 'fastify';
+import { AuthenticationResponseJSON } from '@simplewebauthn/types';
+import { HttpHeader } from 'fastify/types/utils.js';
+import { MockFunctionMetadata, ModuleMocker } from 'jest-mock';
+import { MiUser } from '@/models/User.js';
+import { MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { DI } from '@/di-symbols.js';
+import { CoreModule } from '@/core/CoreModule.js';
+import { SigninWithPasskeyApiService } from '@/server/api/SigninWithPasskeyApiService.js';
+import { RateLimiterService } from '@/server/api/RateLimiterService.js';
+import { WebAuthnService } from '@/core/WebAuthnService.js';
+import { SigninService } from '@/server/api/SigninService.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+
+const moduleMocker = new ModuleMocker(global);
+
+class FakeLimiter {
+	public async limit() {
+		return;
+	}
+}
+
+class FakeSigninService {
+	public signin(..._args: any): any {
+		return true;
+	}
+}
+
+class DummyFastifyReply {
+	public statusCode: number;
+	code(num: number): void {
+		this.statusCode = num;
+	}
+	header(_key: HttpHeader, _value: any): void {
+	}
+}
+class DummyFastifyRequest {
+	public ip: string;
+	public body: {credential: any, context: string};
+	public headers: IncomingHttpHeaders = { 'accept': 'application/json' };
+	constructor(body?: any) {
+		this.ip = '0.0.0.0';
+		this.body = body;
+	}
+}
+
+type ApiFastifyRequestType = FastifyRequest<{
+	Body: {
+		credential?: AuthenticationResponseJSON;
+		context?: string;
+	};
+}>;
+
+describe('SigninWithPasskeyApiService', () => {
+	let app: TestingModule;
+	let passkeyApiService: SigninWithPasskeyApiService;
+	let usersRepository: UsersRepository;
+	let userProfilesRepository: UserProfilesRepository;
+	let webAuthnService: WebAuthnService;
+	let idService: IdService;
+	let FakeWebauthnVerify: ()=>Promise<string>;
+
+	async function createUser(data: Partial<MiUser> = {}) {
+		const user = await usersRepository
+			.save({
+				...data,
+			});
+		return user;
+	}
+
+	async function createUserProfile(data: Partial<MiUserProfile> = {}) {
+		const userProfile = await userProfilesRepository
+			.save({ ...data },
+			);
+		return userProfile;
+	}
+
+	beforeAll(async () => {
+		app = await Test.createTestingModule({
+			imports: [GlobalModule, CoreModule],
+			providers: [
+				SigninWithPasskeyApiService, 
+				{ provide: RateLimiterService, useClass: FakeLimiter }, 
+				{ provide: SigninService, useClass: FakeSigninService },
+			],
+		}).useMocker((token) => {
+			if (typeof token === 'function') {
+				const mockMetadata = moduleMocker.getMetadata(token) as MockFunctionMetadata<any, any>;
+				const Mock = moduleMocker.generateFromMetadata(mockMetadata);
+				return new Mock();
+			}
+		}).compile();
+		passkeyApiService = app.get<SigninWithPasskeyApiService>(SigninWithPasskeyApiService);
+		usersRepository = app.get<UsersRepository>(DI.usersRepository);
+		userProfilesRepository = app.get<UserProfilesRepository>(DI.userProfilesRepository);
+		webAuthnService = app.get<WebAuthnService>(WebAuthnService);
+		idService = app.get<IdService>(IdService);
+	});
+
+	beforeEach(async () => {
+		const uid = idService.gen();
+		FakeWebauthnVerify = async () => {
+			return uid;
+		};
+		jest.spyOn(webAuthnService, 'verifySignInWithPasskeyAuthentication').mockImplementation(FakeWebauthnVerify);
+
+		const dummyUser = {
+			id: uid, username: uid, usernameLower: uid.toLocaleLowerCase(), uri: null, host: null,
+		 };
+		const dummyProfile = {
+			userId: uid,
+			password: 'qwerty',
+			usePasswordLessLogin: true,
+		};
+		await createUser(dummyUser);
+		await createUserProfile(dummyProfile);
+	});
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	describe('Get Passkey Options', () => {
+		it('Should return passkey Auth Options', async () => {
+			const req = new DummyFastifyRequest({}) as ApiFastifyRequestType;
+			const res = new DummyFastifyReply() as unknown as FastifyReply;
+			const res_body = await passkeyApiService.signin(req, res);
+			expect(res.statusCode).toBe(200);
+			expect((res_body as any).option).toBeDefined();
+			expect(typeof (res_body as any).context).toBe('string');
+		});
+	});
+	describe('Try Passkey Auth', () => {
+		it('Should Success', async () => {
+			const req = new DummyFastifyRequest({ context: 'auth-context', credential: { dummy: [] } }) as ApiFastifyRequestType;
+			const res = new DummyFastifyReply() as FastifyReply;
+			const res_body = await passkeyApiService.signin(req, res);
+			expect((res_body as any).signinResponse).toBeDefined();
+		});
+
+		it('Should return 400 Without Auth Context', async () => {
+			const req = new DummyFastifyRequest({ credential: { dummy: [] } }) as ApiFastifyRequestType;
+			const res = new DummyFastifyReply() as FastifyReply;
+			const res_body = await passkeyApiService.signin(req, res);
+			expect(res.statusCode).toBe(400);
+			expect((res_body as any).error?.id).toStrictEqual('1658cc2e-4495-461f-aee4-d403cdf073c1');
+		});
+
+		it('Should return 403 When Challenge Verify fail', async () => {
+			const req = new DummyFastifyRequest({ context: 'misskey-1234', credential: { dummy: [] } }) as ApiFastifyRequestType;
+			const res = new DummyFastifyReply() as FastifyReply;
+			jest.spyOn(webAuthnService, 'verifySignInWithPasskeyAuthentication')
+				.mockImplementation(async () => {
+					throw new IdentifiableError('THIS_ERROR_CODE_SHOULD_BE_FORWARDED');
+				});
+			const res_body = await passkeyApiService.signin(req, res);
+			expect(res.statusCode).toBe(403);
+			expect((res_body as any).error?.id).toStrictEqual('THIS_ERROR_CODE_SHOULD_BE_FORWARDED');
+		});
+
+		it('Should return 403 When The user not Enabled Passwordless login', async () => {
+			const req = new DummyFastifyRequest({ context: 'misskey-1234', credential: { dummy: [] } }) as ApiFastifyRequestType;
+			const res = new DummyFastifyReply() as FastifyReply;
+			const userId = await FakeWebauthnVerify();
+			const data = { userId: userId, usePasswordLessLogin: false };
+			await userProfilesRepository.update({ userId: userId }, data);
+			const res_body = await passkeyApiService.signin(req, res);
+			expect(res.statusCode).toBe(403);
+			expect((res_body as any).error?.id).toStrictEqual('2d84773e-f7b7-4d0b-8f72-bb69b584c912');
+		});
+	});
+});
diff --git a/packages/backend/test/unit/SystemWebhookService.ts b/packages/backend/test/unit/SystemWebhookService.ts
index 790cd1490eb5..5401dd74d80b 100644
--- a/packages/backend/test/unit/SystemWebhookService.ts
+++ b/packages/backend/test/unit/SystemWebhookService.ts
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
 /*
  * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
@@ -6,6 +7,7 @@
 import { setTimeout } from 'node:timers/promises';
 import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
 import { Test, TestingModule } from '@nestjs/testing';
+import { randomString } from '../utils.js';
 import { MiUser } from '@/models/User.js';
 import { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
 import { SystemWebhooksRepository, UsersRepository } from '@/models/_.js';
@@ -17,7 +19,6 @@ import { DI } from '@/di-symbols.js';
 import { QueueService } from '@/core/QueueService.js';
 import { LoggerService } from '@/core/LoggerService.js';
 import { SystemWebhookService } from '@/core/SystemWebhookService.js';
-import { randomString } from '../utils.js';
 
 describe('SystemWebhookService', () => {
 	let app: TestingModule;
@@ -313,7 +314,7 @@ describe('SystemWebhookService', () => {
 					isActive: true,
 					on: ['abuseReport'],
 				});
-				await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' });
+				await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
 
 				expect(queueService.systemWebhookDeliver).toHaveBeenCalled();
 			});
@@ -323,7 +324,7 @@ describe('SystemWebhookService', () => {
 					isActive: false,
 					on: ['abuseReport'],
 				});
-				await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' });
+				await service.enqueueSystemWebhook(webhook.id, 'abuseReport', { foo: 'bar' } as any);
 
 				expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
 			});
@@ -337,8 +338,8 @@ describe('SystemWebhookService', () => {
 					isActive: true,
 					on: ['abuseReportResolved'],
 				});
-				await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' });
-				await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' });
+				await service.enqueueSystemWebhook(webhook1.id, 'abuseReport', { foo: 'bar' } as any);
+				await service.enqueueSystemWebhook(webhook2.id, 'abuseReport', { foo: 'bar' } as any);
 
 				expect(queueService.systemWebhookDeliver).not.toHaveBeenCalled();
 			});
diff --git a/packages/backend/test/unit/UserWebhookService.ts b/packages/backend/test/unit/UserWebhookService.ts
new file mode 100644
index 000000000000..0e88835a0243
--- /dev/null
+++ b/packages/backend/test/unit/UserWebhookService.ts
@@ -0,0 +1,245 @@
+
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { afterEach, beforeEach, describe, expect, jest } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import { randomString } from '../utils.js';
+import { MiUser } from '@/models/User.js';
+import { MiWebhook, UsersRepository, WebhooksRepository } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
+import { QueueService } from '@/core/QueueService.js';
+import { LoggerService } from '@/core/LoggerService.js';
+import { UserWebhookService } from '@/core/UserWebhookService.js';
+
+describe('UserWebhookService', () => {
+	let app: TestingModule;
+	let service: UserWebhookService;
+
+	// --------------------------------------------------------------------------------------
+
+	let usersRepository: UsersRepository;
+	let userWebhooksRepository: WebhooksRepository;
+	let idService: IdService;
+	let queueService: jest.Mocked<QueueService>;
+
+	// --------------------------------------------------------------------------------------
+
+	let root: MiUser;
+
+	// --------------------------------------------------------------------------------------
+
+	async function createUser(data: Partial<MiUser> = {}) {
+		return await usersRepository
+			.insert({
+				id: idService.gen(),
+				...data,
+			})
+			.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
+	}
+
+	async function createWebhook(data: Partial<MiWebhook> = {}) {
+		return userWebhooksRepository
+			.insert({
+				id: idService.gen(),
+				name: randomString(),
+				on: ['mention'],
+				url: 'https://example.com',
+				secret: randomString(),
+				userId: root.id,
+				...data,
+			})
+			.then(x => userWebhooksRepository.findOneByOrFail(x.identifiers[0]));
+	}
+
+	// --------------------------------------------------------------------------------------
+
+	async function beforeAllImpl() {
+		app = await Test
+			.createTestingModule({
+				imports: [
+					GlobalModule,
+				],
+				providers: [
+					UserWebhookService,
+					IdService,
+					LoggerService,
+					GlobalEventService,
+					{
+						provide: QueueService, useFactory: () => ({ systemWebhookDeliver: jest.fn() }),
+					},
+				],
+			})
+			.compile();
+
+		usersRepository = app.get(DI.usersRepository);
+		userWebhooksRepository = app.get(DI.webhooksRepository);
+
+		service = app.get(UserWebhookService);
+		idService = app.get(IdService);
+		queueService = app.get(QueueService) as jest.Mocked<QueueService>;
+
+		app.enableShutdownHooks();
+	}
+
+	async function afterAllImpl() {
+		await app.close();
+	}
+
+	async function beforeEachImpl() {
+		root = await createUser({ isRoot: true, username: 'root', usernameLower: 'root' });
+	}
+
+	async function afterEachImpl() {
+		await usersRepository.delete({});
+		await userWebhooksRepository.delete({});
+	}
+
+	// --------------------------------------------------------------------------------------
+
+	describe('アプリを毎回作り直す必要のないグループ', () => {
+		beforeAll(beforeAllImpl);
+		afterAll(afterAllImpl);
+		beforeEach(beforeEachImpl);
+		afterEach(afterEachImpl);
+
+		describe('fetchSystemWebhooks', () => {
+			test('フィルタなし', async () => {
+				const webhook1 = await createWebhook({
+					active: true,
+					on: ['mention'],
+				});
+				const webhook2 = await createWebhook({
+					active: false,
+					on: ['mention'],
+				});
+				const webhook3 = await createWebhook({
+					active: true,
+					on: ['reply'],
+				});
+				const webhook4 = await createWebhook({
+					active: false,
+					on: [],
+				});
+
+				const fetchedWebhooks = await service.fetchWebhooks();
+				expect(fetchedWebhooks).toEqual([webhook1, webhook2, webhook3, webhook4]);
+			});
+
+			test('activeのみ', async () => {
+				const webhook1 = await createWebhook({
+					active: true,
+					on: ['mention'],
+				});
+				const webhook2 = await createWebhook({
+					active: false,
+					on: ['mention'],
+				});
+				const webhook3 = await createWebhook({
+					active: true,
+					on: ['reply'],
+				});
+				const webhook4 = await createWebhook({
+					active: false,
+					on: [],
+				});
+
+				const fetchedWebhooks = await service.fetchWebhooks({ isActive: true });
+				expect(fetchedWebhooks).toEqual([webhook1, webhook3]);
+			});
+
+			test('特定のイベントのみ', async () => {
+				const webhook1 = await createWebhook({
+					active: true,
+					on: ['mention'],
+				});
+				const webhook2 = await createWebhook({
+					active: false,
+					on: ['mention'],
+				});
+				const webhook3 = await createWebhook({
+					active: true,
+					on: ['reply'],
+				});
+				const webhook4 = await createWebhook({
+					active: false,
+					on: [],
+				});
+
+				const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'] });
+				expect(fetchedWebhooks).toEqual([webhook1, webhook2]);
+			});
+
+			test('activeな特定のイベントのみ', async () => {
+				const webhook1 = await createWebhook({
+					active: true,
+					on: ['mention'],
+				});
+				const webhook2 = await createWebhook({
+					active: false,
+					on: ['mention'],
+				});
+				const webhook3 = await createWebhook({
+					active: true,
+					on: ['reply'],
+				});
+				const webhook4 = await createWebhook({
+					active: false,
+					on: [],
+				});
+
+				const fetchedWebhooks = await service.fetchWebhooks({ on: ['mention'], isActive: true });
+				expect(fetchedWebhooks).toEqual([webhook1]);
+			});
+
+			test('ID指定', async () => {
+				const webhook1 = await createWebhook({
+					active: true,
+					on: ['mention'],
+				});
+				const webhook2 = await createWebhook({
+					active: false,
+					on: ['mention'],
+				});
+				const webhook3 = await createWebhook({
+					active: true,
+					on: ['reply'],
+				});
+				const webhook4 = await createWebhook({
+					active: false,
+					on: [],
+				});
+
+				const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id] });
+				expect(fetchedWebhooks).toEqual([webhook1, webhook4]);
+			});
+
+			test('ID指定(他条件とANDになるか見たい)', async () => {
+				const webhook1 = await createWebhook({
+					active: true,
+					on: ['mention'],
+				});
+				const webhook2 = await createWebhook({
+					active: false,
+					on: ['mention'],
+				});
+				const webhook3 = await createWebhook({
+					active: true,
+					on: ['reply'],
+				});
+				const webhook4 = await createWebhook({
+					active: false,
+					on: [],
+				});
+
+				const fetchedWebhooks = await service.fetchWebhooks({ ids: [webhook1.id, webhook4.id], isActive: false });
+				expect(fetchedWebhooks).toEqual([webhook4]);
+			});
+		});
+	});
+});
diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts
new file mode 100644
index 000000000000..5e63b86f8fb3
--- /dev/null
+++ b/packages/backend/test/unit/WebhookTestService.ts
@@ -0,0 +1,225 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Test, TestingModule } from '@nestjs/testing';
+import { beforeAll, describe, jest } from '@jest/globals';
+import { WebhookTestService } from '@/core/WebhookTestService.js';
+import { UserWebhookService } from '@/core/UserWebhookService.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { DI } from '@/di-symbols.js';
+import { QueueService } from '@/core/QueueService.js';
+
+describe('WebhookTestService', () => {
+	let app: TestingModule;
+	let service: WebhookTestService;
+
+	// --------------------------------------------------------------------------------------
+
+	let usersRepository: UsersRepository;
+	let userProfilesRepository: UserProfilesRepository;
+	let queueService: jest.Mocked<QueueService>;
+	let userWebhookService: jest.Mocked<UserWebhookService>;
+	let systemWebhookService: jest.Mocked<SystemWebhookService>;
+	let idService: IdService;
+
+	let root: MiUser;
+	let alice: MiUser;
+
+	async function createUser(data: Partial<MiUser> = {}) {
+		const user = await usersRepository
+			.insert({
+				id: idService.gen(),
+				...data,
+			})
+			.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
+
+		await userProfilesRepository.insert({
+			userId: user.id,
+		});
+
+		return user;
+	}
+
+	// --------------------------------------------------------------------------------------
+
+	beforeAll(async () => {
+		app = await Test.createTestingModule({
+			imports: [
+				GlobalModule,
+			],
+			providers: [
+				WebhookTestService,
+				IdService,
+				{
+					provide: QueueService, useFactory: () => ({
+						systemWebhookDeliver: jest.fn(),
+						userWebhookDeliver: jest.fn(),
+					}),
+				},
+				{
+					provide: UserWebhookService, useFactory: () => ({
+						fetchWebhooks: jest.fn(),
+					}),
+				},
+				{
+					provide: SystemWebhookService, useFactory: () => ({
+						fetchSystemWebhooks: jest.fn(),
+					}),
+				},
+			],
+		}).compile();
+
+		usersRepository = app.get(DI.usersRepository);
+		userProfilesRepository = app.get(DI.userProfilesRepository);
+
+		service = app.get(WebhookTestService);
+		idService = app.get(IdService);
+		queueService = app.get(QueueService) as jest.Mocked<QueueService>;
+		userWebhookService = app.get(UserWebhookService) as jest.Mocked<UserWebhookService>;
+		systemWebhookService = app.get(SystemWebhookService) as jest.Mocked<SystemWebhookService>;
+
+		app.enableShutdownHooks();
+	});
+
+	beforeEach(async () => {
+		root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true });
+		alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false });
+
+		userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
+			{ id: 'dummy-webhook', active: true, userId: alice.id } as MiWebhook,
+		]));
+		systemWebhookService.fetchSystemWebhooks.mockReturnValue(Promise.resolve([
+			{ id: 'dummy-webhook', isActive: true } as MiSystemWebhook,
+		]));
+	});
+
+	afterEach(async () => {
+		queueService.systemWebhookDeliver.mockClear();
+		queueService.userWebhookDeliver.mockClear();
+		userWebhookService.fetchWebhooks.mockClear();
+		systemWebhookService.fetchSystemWebhooks.mockClear();
+
+		await usersRepository.delete({});
+		await userProfilesRepository.delete({});
+	});
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	// --------------------------------------------------------------------------------------
+
+	describe('testUserWebhook', () => {
+		test('note', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'note' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('note');
+			expect((calls[2] as any).id).toBe('dummy-note-1');
+		});
+
+		test('reply', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'reply' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('reply');
+			expect((calls[2] as any).id).toBe('dummy-reply-1');
+		});
+
+		test('renote', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'renote' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('renote');
+			expect((calls[2] as any).id).toBe('dummy-renote-1');
+		});
+
+		test('mention', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'mention' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('mention');
+			expect((calls[2] as any).id).toBe('dummy-mention-1');
+		});
+
+		test('follow', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'follow' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('follow');
+			expect((calls[2] as any).id).toBe('dummy-user-1');
+		});
+
+		test('followed', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'followed' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('followed');
+			expect((calls[2] as any).id).toBe('dummy-user-2');
+		});
+
+		test('unfollow', async () => {
+			await service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'unfollow' }, alice);
+
+			const calls = queueService.userWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('unfollow');
+			expect((calls[2] as any).id).toBe('dummy-user-3');
+		});
+
+		describe('NoSuchWebhookError', () => {
+			test('user not match', async () => {
+				userWebhookService.fetchWebhooks.mockClear();
+				userWebhookService.fetchWebhooks.mockReturnValue(Promise.resolve([
+					{ id: 'dummy-webhook', active: true } as MiWebhook,
+				]));
+
+				await expect(service.testUserWebhook({ webhookId: 'dummy-webhook', type: 'note' }, root))
+					.rejects.toThrow(WebhookTestService.NoSuchWebhookError);
+			});
+		});
+	});
+
+	describe('testSystemWebhook', () => {
+		test('abuseReport', async () => {
+			await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'abuseReport' });
+
+			const calls = queueService.systemWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('abuseReport');
+			expect((calls[2] as any).id).toBe('dummy-abuse-report1');
+			expect((calls[2] as any).resolved).toBe(false);
+		});
+
+		test('abuseReportResolved', async () => {
+			await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'abuseReportResolved' });
+
+			const calls = queueService.systemWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('abuseReportResolved');
+			expect((calls[2] as any).id).toBe('dummy-abuse-report1');
+			expect((calls[2] as any).resolved).toBe(true);
+		});
+
+		test('userCreated', async () => {
+			await service.testSystemWebhook({ webhookId: 'dummy-webhook', type: 'userCreated' });
+
+			const calls = queueService.systemWebhookDeliver.mock.calls[0];
+			expect((calls[0] as any).id).toBe('dummy-webhook');
+			expect(calls[1]).toBe('userCreated');
+			expect((calls[2] as any).id).toBe('dummy-user-1');
+		});
+	});
+});
diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts
index 763ce2b336b6..2fc08aec9148 100644
--- a/packages/backend/test/unit/activitypub.ts
+++ b/packages/backend/test/unit/activitypub.ts
@@ -24,7 +24,6 @@ import { MiMeta, MiNote, UserProfilesRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { secureRndstr } from '@/misc/secure-rndstr.js';
 import { DownloadService } from '@/core/DownloadService.js';
-import { MetaService } from '@/core/MetaService.js';
 import type { MiRemoteUser } from '@/models/User.js';
 import { genAidx } from '@/misc/id/aidx.js';
 import { MockResolver } from '../misc/mock-resolver.js';
@@ -107,7 +106,14 @@ describe('ActivityPub', () => {
 		sensitiveWords: [] as string[],
 		prohibitedWords: [] as string[],
 	} as MiMeta;
-	let meta = metaInitial;
+	const meta = { ...metaInitial };
+
+	function updateMeta(newMeta: Partial<MiMeta>): void {
+		for (const key in meta) {
+			delete (meta as any)[key];
+		}
+		Object.assign(meta, newMeta);
+	}
 
 	beforeAll(async () => {
 		const app = await Test.createTestingModule({
@@ -120,11 +126,8 @@ describe('ActivityPub', () => {
 					};
 				},
 			})
-			.overrideProvider(MetaService).useValue({
-				async fetch(): Promise<MiMeta> {
-					return meta;
-				},
-			}).compile();
+			.overrideProvider(DI.meta).useFactory({ factory: () => meta })
+			.compile();
 
 		await app.init();
 		app.enableShutdownHooks();
@@ -367,7 +370,7 @@ describe('ActivityPub', () => {
 		});
 
 		test('cacheRemoteFiles=false disables caching', async () => {
-			meta = { ...metaInitial, cacheRemoteFiles: false };
+			updateMeta({ ...metaInitial, cacheRemoteFiles: false });
 
 			const imageObject: IApDocument = {
 				type: 'Document',
@@ -396,7 +399,7 @@ describe('ActivityPub', () => {
 		});
 
 		test('cacheRemoteSensitiveFiles=false only affects sensitive files', async () => {
-			meta = { ...metaInitial, cacheRemoteSensitiveFiles: false };
+			updateMeta({ ...metaInitial, cacheRemoteSensitiveFiles: false });
 
 			const imageObject: IApDocument = {
 				type: 'Document',
diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts
index ee16d421c4c9..e4f42809f8ec 100644
--- a/packages/backend/test/unit/entities/UserEntityService.ts
+++ b/packages/backend/test/unit/entities/UserEntityService.ts
@@ -4,10 +4,10 @@
  */
 
 import { Test, TestingModule } from '@nestjs/testing';
+import type { MiUser } from '@/models/User.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { GlobalModule } from '@/GlobalModule.js';
 import { CoreModule } from '@/core/CoreModule.js';
-import type { MiUser } from '@/models/User.js';
 import { secureRndstr } from '@/misc/secure-rndstr.js';
 import { genAidx } from '@/misc/id/aidx.js';
 import {
@@ -49,6 +49,7 @@ import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
 import { AccountMoveService } from '@/core/AccountMoveService.js';
 import { ReactionService } from '@/core/ReactionService.js';
 import { NotificationService } from '@/core/NotificationService.js';
+import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
 
 process.env.NODE_ENV = 'test';
 
@@ -169,6 +170,7 @@ describe('UserEntityService', () => {
 				ApLoggerService,
 				AccountMoveService,
 				ReactionService,
+				ReactionsBufferingService,
 				NotificationService,
 			];
 
diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
new file mode 100644
index 000000000000..1506283a3ccd
--- /dev/null
+++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts
@@ -0,0 +1,379 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { jest } from '@jest/globals';
+import { Test, TestingModule } from '@nestjs/testing';
+import * as lolex from '@sinonjs/fake-timers';
+import { addHours, addSeconds, subDays, subHours, subSeconds } from 'date-fns';
+import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
+import { MiSystemWebhook, MiUser, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { IdService } from '@/core/IdService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { MetaService } from '@/core/MetaService.js';
+import { DI } from '@/di-symbols.js';
+import { QueueLoggerService } from '@/queue/QueueLoggerService.js';
+import { EmailService } from '@/core/EmailService.js';
+import { SystemWebhookService } from '@/core/SystemWebhookService.js';
+import { AnnouncementService } from '@/core/AnnouncementService.js';
+
+const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0));
+
+describe('CheckModeratorsActivityProcessorService', () => {
+	let app: TestingModule;
+	let clock: lolex.InstalledClock;
+	let service: CheckModeratorsActivityProcessorService;
+
+	// --------------------------------------------------------------------------------------
+
+	let usersRepository: UsersRepository;
+	let userProfilesRepository: UserProfilesRepository;
+	let idService: IdService;
+	let roleService: jest.Mocked<RoleService>;
+	let announcementService: jest.Mocked<AnnouncementService>;
+	let emailService: jest.Mocked<EmailService>;
+	let systemWebhookService: jest.Mocked<SystemWebhookService>;
+
+	let systemWebhook1: MiSystemWebhook;
+	let systemWebhook2: MiSystemWebhook;
+	let systemWebhook3: MiSystemWebhook;
+
+	// --------------------------------------------------------------------------------------
+
+	async function createUser(data: Partial<MiUser> = {}, profile: Partial<MiUserProfile> = {}): Promise<MiUser> {
+		const id = idService.gen();
+		const user = await usersRepository
+			.insert({
+				id: id,
+				username: `user_${id}`,
+				usernameLower: `user_${id}`.toLowerCase(),
+				...data,
+			})
+			.then(x => usersRepository.findOneByOrFail(x.identifiers[0]));
+
+		await userProfilesRepository.insert({
+			userId: user.id,
+			...profile,
+		});
+
+		return user;
+	}
+
+	function crateSystemWebhook(data: Partial<MiSystemWebhook> = {}): MiSystemWebhook {
+		return {
+			id: idService.gen(),
+			isActive: true,
+			updatedAt: new Date(),
+			latestSentAt: null,
+			latestStatus: null,
+			name: 'test',
+			url: 'https://example.com',
+			secret: 'test',
+			on: [],
+			...data,
+		};
+	}
+
+	function mockModeratorRole(users: MiUser[]) {
+		roleService.getModerators.mockReset();
+		roleService.getModerators.mockResolvedValue(users);
+	}
+
+	// --------------------------------------------------------------------------------------
+
+	beforeAll(async () => {
+		app = await Test
+			.createTestingModule({
+				imports: [
+					GlobalModule,
+				],
+				providers: [
+					CheckModeratorsActivityProcessorService,
+					IdService,
+					{
+						provide: RoleService, useFactory: () => ({ getModerators: jest.fn() }),
+					},
+					{
+						provide: MetaService, useFactory: () => ({ fetch: jest.fn() }),
+					},
+					{
+						provide: AnnouncementService, useFactory: () => ({ create: jest.fn() }),
+					},
+					{
+						provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }),
+					},
+					{
+						provide: SystemWebhookService, useFactory: () => ({
+							fetchActiveSystemWebhooks: jest.fn(),
+							enqueueSystemWebhook: jest.fn(),
+						}),
+					},
+					{
+						provide: QueueLoggerService, useFactory: () => ({
+							logger: ({
+								createSubLogger: () => ({
+									info: jest.fn(),
+									warn: jest.fn(),
+									succ: jest.fn(),
+								}),
+							}),
+						}),
+					},
+				],
+			})
+			.compile();
+
+		usersRepository = app.get(DI.usersRepository);
+		userProfilesRepository = app.get(DI.userProfilesRepository);
+
+		service = app.get(CheckModeratorsActivityProcessorService);
+		idService = app.get(IdService);
+		roleService = app.get(RoleService) as jest.Mocked<RoleService>;
+		announcementService = app.get(AnnouncementService) as jest.Mocked<AnnouncementService>;
+		emailService = app.get(EmailService) as jest.Mocked<EmailService>;
+		systemWebhookService = app.get(SystemWebhookService) as jest.Mocked<SystemWebhookService>;
+
+		app.enableShutdownHooks();
+	});
+
+	beforeEach(async () => {
+		clock = lolex.install({
+			now: new Date(baseDate),
+			shouldClearNativeTimers: true,
+		});
+
+		systemWebhook1 = crateSystemWebhook({ on: ['inactiveModeratorsWarning'] });
+		systemWebhook2 = crateSystemWebhook({ on: ['inactiveModeratorsWarning', 'inactiveModeratorsInvitationOnlyChanged'] });
+		systemWebhook3 = crateSystemWebhook({ on: ['abuseReport'] });
+
+		emailService.sendEmail.mockReturnValue(Promise.resolve());
+		announcementService.create.mockReturnValue(Promise.resolve({} as never));
+		systemWebhookService.fetchActiveSystemWebhooks.mockResolvedValue([systemWebhook1, systemWebhook2, systemWebhook3]);
+		systemWebhookService.enqueueSystemWebhook.mockReturnValue(Promise.resolve({} as never));
+	});
+
+	afterEach(async () => {
+		clock.uninstall();
+		await usersRepository.delete({});
+		await userProfilesRepository.delete({});
+		roleService.getModerators.mockReset();
+		announcementService.create.mockReset();
+		emailService.sendEmail.mockReset();
+		systemWebhookService.enqueueSystemWebhook.mockReset();
+	});
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	// --------------------------------------------------------------------------------------
+
+	describe('evaluateModeratorsInactiveDays', () => {
+		test('[isModeratorsInactive] inactiveなモデレーターがいても他のモデレーターがアクティブなら"運営が非アクティブ"としてみなされない', async () => {
+			const [user1, user2, user3, user4] = await Promise.all([
+				// 期限よりも1秒新しいタイミングでアクティブ化(セーフ)
+				createUser({ lastActiveDate: subDays(addSeconds(baseDate, 1), 7) }),
+				// 期限ちょうどにアクティブ化(セーフ)
+				createUser({ lastActiveDate: subDays(baseDate, 7) }),
+				// 期限よりも1秒古いタイミングでアクティブ化(アウト)
+				createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }),
+				// 対象外
+				createUser({ lastActiveDate: null }),
+			]);
+
+			mockModeratorRole([user1, user2, user3, user4]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(false);
+			expect(result.inactiveModerators).toEqual([user3]);
+		});
+
+		test('[isModeratorsInactive] 全員非アクティブなら"運営が非アクティブ"としてみなされる', async () => {
+			const [user1, user2] = await Promise.all([
+				// 期限よりも1秒古いタイミングでアクティブ化(アウト)
+				createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }),
+				// 対象外
+				createUser({ lastActiveDate: null }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(true);
+			expect(result.inactiveModerators).toEqual([user1]);
+		});
+
+		test('[remainingTime] 猶予まで24時間ある場合、猶予1日として計算される', async () => {
+			const [user1, user2] = await Promise.all([
+				createUser({ lastActiveDate: subDays(baseDate, 8) }),
+				// 猶予はこのユーザ基準で計算される想定。
+				// 期限まで残り24時間->猶予1日として計算されるはずである
+				createUser({ lastActiveDate: subDays(baseDate, 6) }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(false);
+			expect(result.inactiveModerators).toEqual([user1]);
+			expect(result.remainingTime.asDays).toBe(1);
+			expect(result.remainingTime.asHours).toBe(24);
+		});
+
+		test('[remainingTime] 猶予まで25時間ある場合、猶予1日として計算される', async () => {
+			const [user1, user2] = await Promise.all([
+				createUser({ lastActiveDate: subDays(baseDate, 8) }),
+				// 猶予はこのユーザ基準で計算される想定。
+				// 期限まで残り25時間->猶予1日として計算されるはずである
+				createUser({ lastActiveDate: subDays(addHours(baseDate, 1), 6) }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(false);
+			expect(result.inactiveModerators).toEqual([user1]);
+			expect(result.remainingTime.asDays).toBe(1);
+			expect(result.remainingTime.asHours).toBe(25);
+		});
+
+		test('[remainingTime] 猶予まで23時間ある場合、猶予0日として計算される', async () => {
+			const [user1, user2] = await Promise.all([
+				createUser({ lastActiveDate: subDays(baseDate, 8) }),
+				// 猶予はこのユーザ基準で計算される想定。
+				// 期限まで残り23時間->猶予0日として計算されるはずである
+				createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 6) }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(false);
+			expect(result.inactiveModerators).toEqual([user1]);
+			expect(result.remainingTime.asDays).toBe(0);
+			expect(result.remainingTime.asHours).toBe(23);
+		});
+
+		test('[remainingTime] 期限ちょうどの場合、猶予0日として計算される', async () => {
+			const [user1, user2] = await Promise.all([
+				createUser({ lastActiveDate: subDays(baseDate, 8) }),
+				// 猶予はこのユーザ基準で計算される想定。
+				// 期限ちょうど->猶予0日として計算されるはずである
+				createUser({ lastActiveDate: subDays(baseDate, 7) }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(false);
+			expect(result.inactiveModerators).toEqual([user1]);
+			expect(result.remainingTime.asDays).toBe(0);
+			expect(result.remainingTime.asHours).toBe(0);
+		});
+
+		test('[remainingTime] 期限より1時間超過している場合、猶予-1日として計算される', async () => {
+			const [user1, user2] = await Promise.all([
+				createUser({ lastActiveDate: subDays(baseDate, 8) }),
+				// 猶予はこのユーザ基準で計算される想定。
+				// 期限より1時間超過->猶予-1日として計算されるはずである
+				createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 7) }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(true);
+			expect(result.inactiveModerators).toEqual([user1, user2]);
+			expect(result.remainingTime.asDays).toBe(-1);
+			expect(result.remainingTime.asHours).toBe(-1);
+		});
+
+		test('[remainingTime] 期限より25時間超過している場合、猶予-2日として計算される', async () => {
+			const [user1, user2] = await Promise.all([
+				createUser({ lastActiveDate: subDays(baseDate, 10) }),
+				// 猶予はこのユーザ基準で計算される想定。
+				// 期限より1時間超過->猶予-1日として計算されるはずである
+				createUser({ lastActiveDate: subDays(subHours(baseDate, 25), 7) }),
+			]);
+
+			mockModeratorRole([user1, user2]);
+
+			const result = await service.evaluateModeratorsInactiveDays();
+			expect(result.isModeratorsInactive).toBe(true);
+			expect(result.inactiveModerators).toEqual([user1, user2]);
+			expect(result.remainingTime.asDays).toBe(-2);
+			expect(result.remainingTime.asHours).toBe(-25);
+		});
+	});
+
+	describe('notifyInactiveModeratorsWarning', () => {
+		test('[notification + mail] 通知はモデレータ全員に発信され、メールはメールアドレスが存在+認証済みの場合のみ', async () => {
+			const [user1, user2, user3, user4, root] = await Promise.all([
+				createUser({}, { email: 'user1@example.com', emailVerified: true }),
+				createUser({}, { email: 'user2@example.com', emailVerified: false }),
+				createUser({}, { email: null, emailVerified: false }),
+				createUser({}, { email: 'user4@example.com', emailVerified: true }),
+				createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }),
+			]);
+
+			mockModeratorRole([user1, user2, user3, root]);
+			await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 });
+
+			expect(emailService.sendEmail).toHaveBeenCalledTimes(2);
+			expect(emailService.sendEmail.mock.calls[0][0]).toBe('user1@example.com');
+			expect(emailService.sendEmail.mock.calls[1][0]).toBe('root@example.com');
+		});
+
+		test('[systemWebhook] "inactiveModeratorsWarning"が有効なSystemWebhookに対して送信される', async () => {
+			const [user1] = await Promise.all([
+				createUser({}, { email: 'user1@example.com', emailVerified: true }),
+			]);
+
+			mockModeratorRole([user1]);
+			await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 });
+
+			expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(2);
+			expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook1);
+			expect(systemWebhookService.enqueueSystemWebhook.mock.calls[1][0]).toEqual(systemWebhook2);
+		});
+	});
+
+	describe('notifyChangeToInvitationOnly', () => {
+		test('[notification + mail] 通知はモデレータ全員に発信され、メールはメールアドレスが存在+認証済みの場合のみ', async () => {
+			const [user1, user2, user3, user4, root] = await Promise.all([
+				createUser({}, { email: 'user1@example.com', emailVerified: true }),
+				createUser({}, { email: 'user2@example.com', emailVerified: false }),
+				createUser({}, { email: null, emailVerified: false }),
+				createUser({}, { email: 'user4@example.com', emailVerified: true }),
+				createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }),
+			]);
+
+			mockModeratorRole([user1, user2, user3, root]);
+			await service.notifyChangeToInvitationOnly();
+
+			expect(announcementService.create).toHaveBeenCalledTimes(4);
+			expect(announcementService.create.mock.calls[0][0].userId).toBe(user1.id);
+			expect(announcementService.create.mock.calls[1][0].userId).toBe(user2.id);
+			expect(announcementService.create.mock.calls[2][0].userId).toBe(user3.id);
+			expect(announcementService.create.mock.calls[3][0].userId).toBe(root.id);
+
+			expect(emailService.sendEmail).toHaveBeenCalledTimes(2);
+			expect(emailService.sendEmail.mock.calls[0][0]).toBe('user1@example.com');
+			expect(emailService.sendEmail.mock.calls[1][0]).toBe('root@example.com');
+		});
+
+		test('[systemWebhook] "inactiveModeratorsInvitationOnlyChanged"が有効なSystemWebhookに対して送信される', async () => {
+			const [user1] = await Promise.all([
+				createUser({}, { email: 'user1@example.com', emailVerified: true }),
+			]);
+
+			mockModeratorRole([user1]);
+			await service.notifyChangeToInvitationOnly();
+
+			expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1);
+			expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook2);
+		});
+	});
+});
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
index a65d6ab657b8..cb62191c3b12 100644
--- a/packages/frontend-embed/package.json
+++ b/packages/frontend-embed/package.json
@@ -11,75 +11,62 @@
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
 	"dependencies": {
-		"@discordapp/twemoji": "15.0.3",
-		"@github/webauthn-json": "2.1.1",
+		"@discordapp/twemoji": "15.1.0",
 		"@rollup/plugin-json": "6.1.0",
 		"@rollup/plugin-replace": "5.0.7",
-		"@rollup/pluginutils": "5.1.0",
+		"@rollup/pluginutils": "5.1.2",
 		"@tabler/icons-webfont": "3.3.0",
 		"@twemoji/parser": "15.1.1",
-		"@vitejs/plugin-vue": "5.1.0",
-		"@vue/compiler-sfc": "3.4.37",
-		"astring": "1.8.6",
+		"@vitejs/plugin-vue": "5.1.4",
+		"@vue/compiler-sfc": "3.5.11",
+		"astring": "1.9.0",
 		"buraha": "0.0.1",
-		"compare-versions": "6.1.1",
-		"date-fns": "2.30.0",
-		"escape-regexp": "0.0.1",
 		"estree-walker": "3.0.3",
-		"eventemitter3": "5.0.1",
-		"idb-keyval": "6.2.1",
-		"is-file-animated": "1.0.2",
 		"mfm-js": "0.24.0",
 		"misskey-js": "workspace:*",
 		"frontend-shared": "workspace:*",
 		"punycode": "2.3.1",
-		"rollup": "4.19.1",
-		"sanitize-html": "2.13.0",
-		"sass": "1.77.8",
-		"shiki": "1.12.0",
-		"strict-event-emitter-types": "2.0.0",
-		"throttle-debounce": "5.0.2",
+		"rollup": "4.22.5",
+		"sass": "1.79.4",
+		"shiki": "1.21.0",
 		"tinycolor2": "1.6.0",
 		"tsc-alias": "1.8.10",
 		"tsconfig-paths": "4.2.0",
-		"typescript": "5.5.4",
+		"typescript": "5.6.2",
 		"uuid": "10.0.0",
 		"json5": "2.2.3",
-		"vite": "5.3.5",
-		"vue": "3.4.37"
+		"vite": "5.4.8",
+		"vue": "3.5.11"
 	},
 	"devDependencies": {
 		"@misskey-dev/summaly": "5.1.0",
 		"@testing-library/vue": "8.1.0",
-		"@types/escape-regexp": "0.0.3",
-		"@types/estree": "1.0.5",
+		"@types/estree": "1.0.6",
 		"@types/micromatch": "4.0.9",
 		"@types/node": "20.14.12",
 		"@types/punycode": "2.1.4",
-		"@types/sanitize-html": "2.11.0",
-		"@types/throttle-debounce": "5.0.2",
 		"@types/tinycolor2": "1.4.6",
 		"@types/uuid": "10.0.0",
-		"@types/ws": "8.5.11",
+		"@types/ws": "8.5.12",
 		"@typescript-eslint/eslint-plugin": "7.17.0",
 		"@typescript-eslint/parser": "7.17.0",
 		"@vitest/coverage-v8": "1.6.0",
-		"@vue/runtime-core": "3.4.37",
+		"@vue/runtime-core": "3.5.11",
 		"acorn": "8.12.1",
 		"cross-env": "7.0.3",
-		"eslint-plugin-import": "2.29.1",
-		"eslint-plugin-vue": "9.27.0",
+		"eslint-plugin-import": "2.31.0",
+		"eslint-plugin-vue": "9.28.0",
 		"fast-glob": "3.3.2",
 		"happy-dom": "10.0.3",
 		"intersection-observer": "0.12.2",
-		"micromatch": "4.0.7",
+		"micromatch": "4.0.8",
 		"msw": "2.3.4",
-		"nodemon": "3.1.4",
+		"nodemon": "3.1.7",
 		"prettier": "3.3.3",
-		"start-server-and-test": "2.0.4",
+		"start-server-and-test": "2.0.8",
 		"vite-plugin-turbosnap": "1.0.3",
-		"vue-component-type-helpers": "2.0.29",
+		"vue-component-type-helpers": "2.1.6",
 		"vue-eslint-parser": "9.4.3",
-		"vue-tsc": "2.0.29"
+		"vue-tsc": "2.1.6"
 	}
 }
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
index fcea7d32eac7..8ab4ab32e6f8 100644
--- a/packages/frontend-embed/src/boot.ts
+++ b/packages/frontend-embed/src/boot.ts
@@ -20,16 +20,20 @@ import { serverMetadata } from '@/server-metadata.js';
 import { url } from '@@/js/config.js';
 import { parseEmbedParams } from '@@/js/embed-page.js';
 import { postMessageToParentWindow, setIframeId } from '@/post-message.js';
+import { serverContext } from '@/server-context.js';
+import { i18n } from '@/i18n.js';
 
 import type { Theme } from '@/theme.js';
 
 console.log('Misskey Embed');
 
+//#region Embedパラメータの取得・パース
 const params = new URLSearchParams(location.search);
 const embedParams = parseEmbedParams(params);
-
 if (_DEV_) console.log(embedParams);
+//#endregion
 
+//#region テーマ
 function parseThemeOrNull(theme: string | null): Theme | null {
 	if (theme == null) return null;
 	try {
@@ -65,6 +69,7 @@ if (embedParams.colorMode === 'dark') {
 		}
 	});
 }
+//#endregion
 
 // サイズの制限
 document.documentElement.style.maxWidth = '500px';
@@ -89,6 +94,10 @@ const app = createApp(
 
 app.provide(DI.mediaProxy, new MediaProxy(serverMetadata, url));
 
+app.provide(DI.serverMetadata, serverMetadata);
+
+app.provide(DI.serverContext, serverContext);
+
 app.provide(DI.embedParams, embedParams);
 
 // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
@@ -119,6 +128,27 @@ window.onunhandledrejection = null;
 
 removeSplash();
 
+//#region Self-XSS 対策メッセージ
+console.log(
+	`%c${i18n.ts._selfXssPrevention.warning}`,
+	'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
+);
+console.log(
+	`%c${i18n.ts._selfXssPrevention.title}`,
+	'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
+);
+console.log(
+	`%c${i18n.ts._selfXssPrevention.description1}`,
+	'font-size: 16px; font-weight: 700;',
+);
+console.log(
+	`%c${i18n.ts._selfXssPrevention.description2}`,
+	'font-size: 16px;',
+	'font-size: 20px; font-weight: 700; color: #f00;',
+);
+console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
+//#endregion
+
 function removeSplash() {
 	const splash = document.getElementById('splash');
 	if (splash) {
diff --git a/packages/frontend-embed/src/components/EmCustomEmoji.vue b/packages/frontend-embed/src/components/EmCustomEmoji.vue
index e4149cf363f3..59b670cdc682 100644
--- a/packages/frontend-embed/src/components/EmCustomEmoji.vue
+++ b/packages/frontend-embed/src/components/EmCustomEmoji.vue
@@ -38,8 +38,6 @@ const props = defineProps<{
 	host?: string | null;
 	url?: string;
 	useOriginalSize?: boolean;
-	menu?: boolean;
-	menuReaction?: boolean;
 	fallbackToImage?: boolean;
 }>();
 
diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue
index eeeaee528e75..4a116e317aca 100644
--- a/packages/frontend-embed/src/components/EmInstanceTicker.vue
+++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue
@@ -29,12 +29,12 @@ const props = defineProps<{
 // if no instance data is given, this is for the local instance
 const instance = props.instance ?? {
 	name: serverMetadata.name,
-	themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content,
+	themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement)?.content,
 };
 
 const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico');
 
-const themeColor = serverMetadata.themeColor ?? '#777777';
+const themeColor = props.instance?.themeColor ?? serverMetadata.themeColor ?? '#777777';
 
 const bg = {
 	background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`,
diff --git a/packages/frontend-embed/src/components/EmLoading.vue b/packages/frontend-embed/src/components/EmLoading.vue
index 49d8ace37bee..47d797606bd8 100644
--- a/packages/frontend-embed/src/components/EmLoading.vue
+++ b/packages/frontend-embed/src/components/EmLoading.vue
@@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{
 	--size: 38px;
 
 	&.colored {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 
 	&.inline {
diff --git a/packages/frontend-embed/src/components/EmMediaBanner.vue b/packages/frontend-embed/src/components/EmMediaBanner.vue
index 435da238a4e6..cf4a4c53b58b 100644
--- a/packages/frontend-embed/src/components/EmMediaBanner.vue
+++ b/packages/frontend-embed/src/components/EmMediaBanner.vue
@@ -31,17 +31,17 @@ defineProps<{
 	display: flex;
 	align-items: center;
 	width: 100%;
-	padding: var(--margin);
+	padding: var(--MI-margin);
 	margin-top: 4px;
-	border: 1px solid var(--inputBorder);
-	border-radius: var(--radius);
-	background-color: var(--panel);
+	border: 1px solid var(--MI_THEME-inputBorder);
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-panel);
 	transition: background-color .1s, border-color .1s;
 
 	&:hover {
 		text-decoration: none;
-		border-color: var(--inputBorderHover);
-		background-color: var(--buttonHoverBg);
+		border-color: var(--MI_THEME-inputBorderHover);
+		background-color: var(--MI_THEME-buttonHoverBg);
 	}
 }
 
diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue
index fe1aa5a877be..d711020a74a6 100644
--- a/packages/frontend-embed/src/components/EmMediaImage.vue
+++ b/packages/frontend-embed/src/components/EmMediaImage.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="[hide ? $style.hidden : $style.visible]" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick">
+<div :class="[hide ? $style.hidden : $style.visible]" @click="onclick">
 	<a
 		:title="image.name"
 		:class="$style.imageContainer"
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div :class="$style.indicators">
 		<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
 		<div v-if="image.comment" :class="$style.indicator">ALT</div>
-		<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
+		<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
 	</div>
 	<i v-if="!hide" class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i>
 </div>
@@ -58,7 +58,6 @@ const props = withDefaults(defineProps<{
 });
 
 const hide = ref(props.image.isSensitive);
-const darkMode = ref<boolean>(false); // TODO
 
 const url = computed(() => (props.raw)
 	? props.image.url
@@ -95,8 +94,8 @@ async function onclick(ev: MouseEvent) {
 	display: block;
 	position: absolute;
 	border-radius: 6px;
-	background-color: var(--fg);
-	color: var(--accentLighten);
+	background-color: var(--MI_THEME-fg);
+	color: var(--MI_THEME-accentLighten);
 	font-size: 12px;
 	opacity: .5;
 	padding: 5px 8px;
@@ -115,12 +114,21 @@ async function onclick(ev: MouseEvent) {
 
 .visible {
 	position: relative;
-	//box-shadow: 0 0 0 1px var(--divider) inset;
-	background: var(--bg);
-	background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%);
+	//box-shadow: 0 0 0 1px var(--MI_THEME-divider) inset;
+	background: var(--MI_THEME-bg);
 	background-size: 16px 16px;
 }
 
+html[data-color-scheme=dark] .visible {
+	--c: rgb(255 255 255 / 2%);
+	background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%);
+}
+
+html[data-color-scheme=light] .visible {
+	--c: rgb(0 0 0 / 2%);
+	background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%);
+}
+
 .imageContainer {
 	display: block;
 	overflow: hidden;
@@ -142,10 +150,10 @@ async function onclick(ev: MouseEvent) {
 }
 
 .indicator {
-	/* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */
+	/* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */
 	background-color: black;
 	border-radius: 6px;
-	color: var(--accentLighten);
+	color: var(--MI_THEME-accentLighten);
 	display: inline-block;
 	font-weight: bold;
 	font-size: 0.8em;
diff --git a/packages/frontend-embed/src/components/EmMediaVideo.vue b/packages/frontend-embed/src/components/EmMediaVideo.vue
index ce751f9acdf8..e2779bdee4e6 100644
--- a/packages/frontend-embed/src/components/EmMediaVideo.vue
+++ b/packages/frontend-embed/src/components/EmMediaVideo.vue
@@ -29,9 +29,9 @@ defineProps<{
 	width: 100%;
 	height: auto;
 	aspect-ratio: 16 / 9;
-	padding: var(--margin);
-	border: 1px solid var(--divider);
-	border-radius: var(--radius);
+	padding: var(--MI-margin);
+	border: 1px solid var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	background-color: #000;
 
 	&:hover {
@@ -49,7 +49,7 @@ defineProps<{
 }
 
 .videoOverlayPlayButton {
-	background: var(--accent);
+	background: var(--MI_THEME-accent);
 	color: #fff;
 	padding: 1rem;
 	border-radius: 99rem;
diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue
index 777033bd3ec1..a71364237d47 100644
--- a/packages/frontend-embed/src/components/EmMention.vue
+++ b/packages/frontend-embed/src/components/EmMention.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkA v-user-preview="canonical" :class="[$style.root]" :to="url" :style="{ background: bgCss }">
+<MkA :class="[$style.root]" :to="url" :style="{ background: bgCss }">
 	<span>
 		<span>@{{ username }}</span>
 		<span v-if="(host != localHost)" :class="$style.host">@{{ toUnicode(host) }}</span>
@@ -27,7 +27,7 @@ const canonical = props.host === localHost ? `@${props.username}` : `@${props.us
 
 const url = `/${canonical}`;
 
-const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--mention'));
+const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention'));
 bg.setAlpha(0.1);
 const bgCss = bg.toRgbString();
 </script>
@@ -37,7 +37,7 @@ const bgCss = bg.toRgbString();
 	display: inline-block;
 	padding: 4px 8px 4px 4px;
 	border-radius: 999px;
-	color: var(--mention);
+	color: var(--MI_THEME-mention);
 }
 
 .host {
diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts
index b2bcf4597e35..cae2feb8fb28 100644
--- a/packages/frontend-embed/src/components/EmMfm.ts
+++ b/packages/frontend-embed/src/components/EmMfm.ts
@@ -6,6 +6,7 @@
 import { VNode, h, SetupContext, provide } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
+import { host } from '@@/js/config.js';
 import EmUrl from '@/components/EmUrl.vue';
 import EmTime from '@/components/EmTime.vue';
 import EmLink from '@/components/EmLink.vue';
@@ -13,7 +14,6 @@ import EmMention from '@/components/EmMention.vue';
 import EmEmoji from '@/components/EmEmoji.vue';
 import EmCustomEmoji from '@/components/EmCustomEmoji.vue';
 import EmA from '@/components/EmA.vue';
-import { host } from '@@/js/config.js';
 
 function safeParseFloat(str: unknown): number | null {
 	if (typeof str !== 'string' || str === '') return null;
@@ -26,8 +26,8 @@ const QUOTE_STYLE = `
 display: block;
 margin: 8px;
 padding: 6px 0 6px 12px;
-color: var(--fg);
-border-left: solid 3px var(--fg);
+color: var(--MI_THEME-fg);
+border-left: solid 3px var(--MI_THEME-fg);
 opacity: 0.7;
 `.split('\n').join(' ');
 
@@ -41,9 +41,6 @@ type MfmProps = {
 	rootScale?: number;
 	nyaize?: boolean | 'respect';
 	parsedNodes?: mfm.MfmNode[] | null;
-	enableEmojiMenu?: boolean;
-	enableEmojiMenuReaction?: boolean;
-	linkNavigationBehavior?: string;
 };
 
 type MfmEvents = {
@@ -52,8 +49,6 @@ type MfmEvents = {
 
 // eslint-disable-next-line import/no-default-export
 export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) {
-	provide('linkNavigationBehavior', props.linkNavigationBehavior);
-
 	const isNote = props.isNote ?? true;
 	const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false;
 
@@ -256,7 +251,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 					}
 					case 'border': {
 						let color = validColor(token.props.args.color);
-						color = color ? `#${color}` : 'var(--accent)';
+						color = color ? `#${color}` : 'var(--MI_THEME-accent)';
 						let b_style = token.props.args.style;
 						if (
 							typeof b_style !== 'string' ||
@@ -289,7 +284,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 						const child = token.children[0];
 						const unixtime = parseInt(child.type === 'text' ? child.props.text : '');
 						return h('span', {
-							style: 'display: inline-block; font-size: 90%; border: solid 1px var(--divider); border-radius: 999px; padding: 4px 10px 4px 6px;',
+							style: 'display: inline-block; font-size: 90%; border: solid 1px var(--MI_THEME-divider); border-radius: 999px; padding: 4px 10px 4px 6px;',
 						}, [
 							h('i', {
 								class: 'ti ti-clock',
@@ -360,7 +355,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 				return [h(EmA, {
 					key: Math.random(),
 					to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
-					style: 'color:var(--hashtag);',
+					style: 'color:var(--MI_THEME-hashtag);',
 				}, `#${token.props.hashtag}`)];
 			}
 
@@ -397,8 +392,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 						normal: props.plain,
 						host: null,
 						useOriginalSize: scale >= 2.5,
-						menu: props.enableEmojiMenu,
-						menuReaction: props.enableEmojiMenuReaction,
 						fallbackToImage: false,
 					})];
 				} else {
diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue
index 02475898c518..f5b064c29330 100644
--- a/packages/frontend-embed/src/components/EmNote.vue
+++ b/packages/frontend-embed/src/components/EmNote.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
 		<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
 			<template #user>
-				<EmA v-user-preview="true ? undefined : note.userId" :class="$style.renoteUserName" :to="userPage(note.user)">
+				<EmA :class="$style.renoteUserName" :to="userPage(note.user)">
 					<EmUserName :user="note.user"/>
 				</EmA>
 			</template>
@@ -44,6 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<EmAvatar :class="$style.avatar" :user="appearNote.user" link/>
 		<div :class="$style.main">
 			<EmNoteHeader :note="appearNote" :mini="true"/>
+			<EmInstanceTicker v-if="appearNote.user.instance != null" :instance="appearNote.user.instance"/>
 			<div style="container-type: inline-size;">
 				<p v-if="appearNote.cw != null" :class="$style.cw">
 					<EmMfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
@@ -107,10 +108,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, ref, shallowRef } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
+import { url } from '@@/js/config.js';
 import I18n from '@/components/I18n.vue';
 import EmNoteSub from '@/components/EmNoteSub.vue';
 import EmNoteHeader from '@/components/EmNoteHeader.vue';
 import EmNoteSimple from '@/components/EmNoteSimple.vue';
+import EmInstanceTicker from '@/components/EmInstanceTicker.vue';
 import EmReactionsViewer from '@/components/EmReactionsViewer.vue';
 import EmMediaList from '@/components/EmMediaList.vue';
 import EmPoll from '@/components/EmPoll.vue';
@@ -121,8 +125,6 @@ import EmUserName from '@/components/EmUserName.vue';
 import EmTime from '@/components/EmTime.vue';
 import { userPage } from '@/utils.js';
 import { i18n } from '@/i18n.js';
-import { shouldCollapsed } from '@@/js/collapsed.js';
-import { url } from '@@/js/config.js';
 
 function getAppearNote(note: Misskey.entities.Note) {
 	return Misskey.note.isPureRenote(note) ? note.renote : note;
@@ -162,14 +164,8 @@ const isDeleted = ref(false);
 	font-size: 1.05em;
 	overflow: clip;
 	contain: content;
-
-	// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
-	// 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう
-	// ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、
-	// 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる
-	// 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?)
-	//content-visibility: auto;
-  //contain-intrinsic-size: 0 128px;
+	content-visibility: auto;
+  contain-intrinsic-size: 0 150px;
 
 	&:focus-visible {
 		outline: none;
@@ -187,8 +183,8 @@ const isDeleted = ref(false);
 			margin: auto;
 			width: calc(100% - 8px);
 			height: calc(100% - 8px);
-			border: dashed 2px var(--focus);
-			border-radius: var(--radius);
+			border: dashed 2px var(--MI_THEME-focus);
+			border-radius: var(--MI-radius);
 			box-sizing: border-box;
 		}
 	}
@@ -210,9 +206,9 @@ const isDeleted = ref(false);
 			right: 12px;
 			padding: 0 4px;
 			margin-bottom: 0 !important;
-			background: var(--popup);
+			background: var(--MI_THEME-popup);
 			border-radius: 8px;
-			box-shadow: 0px 4px 32px var(--shadow);
+			box-shadow: 0px 4px 32px var(--MI_THEME-shadow);
 		}
 
 		.footerButton {
@@ -257,7 +253,7 @@ const isDeleted = ref(false);
 	padding: 16px 32px 8px 32px;
 	line-height: 28px;
 	white-space: pre;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 
 	& + .article {
 		padding-top: 8px;
@@ -354,7 +350,7 @@ const isDeleted = ref(false);
 	width: 58px;
 	height: 58px;
 	position: sticky !important;
-	top: calc(22px + var(--stickyTop, 0px));
+	top: calc(22px + var(--MI-stickyTop, 0px));
 	left: 0;
 }
 
@@ -375,12 +371,12 @@ const isDeleted = ref(false);
 	width: 100%;
 	margin-top: 14px;
 	position: sticky;
-	bottom: calc(var(--stickyBottom, 0px) + 14px);
+	bottom: calc(var(--MI-stickyBottom, 0px) + 14px);
 }
 
 .showLessLabel {
 	display: inline-block;
-	background: var(--popup);
+	background: var(--MI_THEME-popup);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
@@ -401,16 +397,16 @@ const isDeleted = ref(false);
 	z-index: 2;
 	width: 100%;
 	height: 64px;
-	background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+	background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 	&:hover > .collapsedLabel {
-		background: var(--panelHighlight);
+		background: var(--MI_THEME-panelHighlight);
 	}
 }
 
 .collapsedLabel {
 	display: inline-block;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
@@ -422,13 +418,13 @@ const isDeleted = ref(false);
 }
 
 .replyIcon {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	margin-right: 0.5em;
 }
 
 .translation {
-	border: solid 0.5px var(--divider);
-	border-radius: var(--radius);
+	border: solid 0.5px var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	padding: 12px;
 	margin-top: 8px;
 }
@@ -447,7 +443,7 @@ const isDeleted = ref(false);
 
 .quoteNote {
 	padding: 16px;
-	border: dashed 1px var(--renote);
+	border: dashed 1px var(--MI_THEME-renote);
 	border-radius: 8px;
 	overflow: clip;
 }
@@ -471,7 +467,7 @@ const isDeleted = ref(false);
 	}
 
 	&:hover {
-		color: var(--fgHighlighted);
+		color: var(--MI_THEME-fgHighlighted);
 	}
 }
 
@@ -548,7 +544,7 @@ const isDeleted = ref(false);
 		margin: 0 10px 0 0;
 		width: 46px;
 		height: 46px;
-		top: calc(14px + var(--stickyTop, 0px));
+		top: calc(14px + var(--MI-stickyTop, 0px));
 	}
 }
 
diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue
index 8169f500a9ae..b39b47c06509 100644
--- a/packages/frontend-embed/src/components/EmNoteDetailed.vue
+++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue
@@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</a>
 					</div>
 				</div>
+				<EmInstanceTicker v-if="appearNote.user.instance != null" :instance="appearNote.user.instance"/>
 			</div>
 		</header>
 		<div :class="[$style.noteContent, { [$style.contentCollapsed]: collapsed }]">
@@ -132,6 +133,7 @@ import I18n from '@/components/I18n.vue';
 import EmMediaList from '@/components/EmMediaList.vue';
 import EmNoteSub from '@/components/EmNoteSub.vue';
 import EmNoteSimple from '@/components/EmNoteSimple.vue';
+import EmInstanceTicker from '@/components/EmInstanceTicker.vue';
 import EmReactionsViewer from '@/components/EmReactionsViewer.vue';
 import EmPoll from '@/components/EmPoll.vue';
 import EmA from '@/components/EmA.vue';
@@ -142,8 +144,8 @@ import EmAcct from '@/components/EmAcct.vue';
 import { userPage } from '@/utils.js';
 import { notePage } from '@/utils.js';
 import { i18n } from '@/i18n.js';
+import { DI } from '@/di.js';
 import { shouldCollapsed } from '@@/js/collapsed.js';
-import { serverMetadata } from '@/server-metadata.js';
 import { url } from '@@/js/config.js';
 import EmMfm from '@/components/EmMfm.js';
 
@@ -151,6 +153,8 @@ const props = defineProps<{
 	note: Misskey.entities.Note;
 }>();
 
+const serverMetadata = inject(DI.serverMetadata)!;
+
 const inChannel = inject('inChannel', null);
 
 const note = ref(props.note);
@@ -191,7 +195,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 	padding: 16px 32px 8px 32px;
 	line-height: 28px;
 	white-space: pre;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 }
 
 .renoteAvatar {
@@ -277,7 +281,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 	padding: 4px 6px;
 	font-size: 80%;
 	line-height: 1;
-	border: solid 0.5px var(--divider);
+	border: solid 0.5px var(--MI_THEME-divider);
 	border-radius: 4px;
 }
 
@@ -319,14 +323,14 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 }
 
 .noteReplyTarget {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	margin-right: 0.5em;
 }
 
 .rn {
 	margin-left: 4px;
 	font-style: oblique;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 }
 
 .reactionOmitted {
@@ -346,7 +350,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 
 .quoteNote {
 	padding: 16px;
-	border: dashed 1px var(--renote);
+	border: dashed 1px var(--MI_THEME-renote);
 	border-radius: 8px;
 	overflow: clip;
 }
@@ -360,12 +364,12 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 	width: 100%;
 	margin-top: 14px;
 	position: sticky;
-	bottom: calc(var(--stickyBottom, 0px) + 14px);
+	bottom: calc(var(--MI-stickyBottom, 0px) + 14px);
 }
 
 .showLessLabel {
 	display: inline-block;
-	background: var(--popup);
+	background: var(--MI_THEME-popup);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
@@ -386,16 +390,16 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 	z-index: 2;
 	width: 100%;
 	height: 64px;
-	background: linear-gradient(0deg, var(--panel), var(--X15));
+	background: linear-gradient(0deg, var(--MI_THEME-panel), var(--MI_THEME-X15));
 
 	&:hover > .collapsedLabel {
-		background: var(--panelHighlight);
+		background: var(--MI_THEME-panelHighlight);
 	}
 }
 
 .collapsedLabel {
 	display: inline-block;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
@@ -418,7 +422,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 	}
 
 	&:hover {
-		color: var(--fgHighlighted);
+		color: var(--MI_THEME-fgHighlighted);
 	}
 }
 
@@ -434,7 +438,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong);
 	opacity: 0.7;
 
 	&.reacted {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
diff --git a/packages/frontend-embed/src/components/EmNoteHeader.vue b/packages/frontend-embed/src/components/EmNoteHeader.vue
index e4add9501f41..85b4aac07130 100644
--- a/packages/frontend-embed/src/components/EmNoteHeader.vue
+++ b/packages/frontend-embed/src/components/EmNoteHeader.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
 	<div :class="$style.username"><EmAcct :user="note.user"/></div>
 	<div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
-		<img v-for="(role, i) in note.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl!"/>
+		<img v-for="(role, i) in note.user.badgeRoles" :key="i" :class="$style.badgeRole" :src="role.iconUrl!"/>
 	</div>
 	<div :class="$style.info">
 		<EmA :to="notePage(note)">
@@ -72,7 +72,7 @@ defineProps<{
 	margin: 0 .5em 0 0;
 	padding: 1px 6px;
 	font-size: 80%;
-	border: solid 0.5px var(--divider);
+	border: solid 0.5px var(--MI_THEME-divider);
 	border-radius: 3px;
 }
 
diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue
index 704a876e5921..b9aaf3fa4ac0 100644
--- a/packages/frontend-embed/src/components/EmNoteSimple.vue
+++ b/packages/frontend-embed/src/components/EmNoteSimple.vue
@@ -53,7 +53,7 @@ const showContent = ref(false);
 	height: 34px;
 	border-radius: 8px;
 	position: sticky !important;
-	top: calc(16px + var(--stickyTop, 0px));
+	top: calc(16px + var(--MI-stickyTop, 0px));
 	left: 0;
 }
 
diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue
index f60aea3e7e88..59be8608e056 100644
--- a/packages/frontend-embed/src/components/EmNoteSub.vue
+++ b/packages/frontend-embed/src/components/EmNoteSub.vue
@@ -123,7 +123,7 @@ if (props.detail) {
 }
 
 .reply, .more {
-	border-left: solid 0.5px var(--divider);
+	border-left: solid 0.5px var(--MI_THEME-divider);
 	margin-top: 10px;
 }
 
@@ -144,7 +144,7 @@ if (props.detail) {
 .muted {
 	text-align: center;
 	padding: 8px !important;
-	border: 1px solid var(--divider);
+	border: 1px solid var(--MI_THEME-divider);
 	margin: 8px 8px 0 8px;
 	border-radius: 8px;
 }
diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue
index 6370f4aeae52..4211261e1974 100644
--- a/packages/frontend-embed/src/components/EmNotes.vue
+++ b/packages/frontend-embed/src/components/EmNotes.vue
@@ -20,12 +20,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import EmNote from '@/components/EmNote.vue';
 import EmPagination, { Paging } from '@/components/EmPagination.vue';
 import { i18n } from '@/i18n.js';
 
-const props = withDefaults(defineProps<{
+withDefaults(defineProps<{
 	pagination: Paging;
 	noGap?: boolean;
 	disableAutoLoad?: boolean;
@@ -34,7 +34,7 @@ const props = withDefaults(defineProps<{
 	ad: true,
 });
 
-const pagingComponent = shallowRef<InstanceType<typeof EmPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
 
 defineExpose({
 	pagingComponent,
@@ -43,10 +43,10 @@ defineExpose({
 
 <style lang="scss" module>
 .root {
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 }
 
 .note {
-	border-bottom: 0.5px solid var(--divider);
+	border-bottom: 0.5px solid var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend-embed/src/components/EmPoll.vue b/packages/frontend-embed/src/components/EmPoll.vue
index a2b120344940..d197e094c6d2 100644
--- a/packages/frontend-embed/src/components/EmPoll.vue
+++ b/packages/frontend-embed/src/components/EmPoll.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<li v-for="(choice, i) in poll.choices" :key="i" :class="$style.choice">
 			<div :class="$style.bg" :style="{ 'width': `${choice.votes / total * 100}%` }"></div>
 			<span :class="$style.fg">
-				<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template>
+				<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--MI_THEME-accent);"></i></template>
 				<EmMfm :text="choice.text" :plain="true"/>
 				<span style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span>
 			</span>
@@ -52,8 +52,8 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes)));
 	position: relative;
 	margin: 4px 0;
 	padding: 4px;
-	//border: solid 0.5px var(--divider);
-	background: var(--accentedBg);
+	//border: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-accentedBg);
 	border-radius: 4px;
 	overflow: clip;
 }
@@ -63,8 +63,8 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes)));
 	top: 0;
 	left: 0;
 	height: 100%;
-	background: var(--accent);
-	background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
+	background: var(--MI_THEME-accent);
+	background: linear-gradient(90deg,var(--MI_THEME-buttonGradateA),var(--MI_THEME-buttonGradateB));
 	transition: width 1s ease;
 }
 
@@ -72,11 +72,11 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes)));
 	position: relative;
 	display: inline-block;
 	padding: 3px 5px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 3px;
 }
 
 .info {
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 </style>
diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue
index 2e43eb8d170b..2ebff489fd7b 100644
--- a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue
+++ b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue
@@ -38,7 +38,7 @@ const props = defineProps<{
 	justify-content: center;
 
 	&.canToggle {
-		background: var(--buttonBg);
+		background: var(--MI_THEME-buttonBg);
 
 		&:hover {
 			background: rgba(0, 0, 0, 0.1);
@@ -72,12 +72,12 @@ const props = defineProps<{
 	}
 
 	&.reacted, &.reacted:hover {
-		background: var(--accentedBg);
-		color: var(--accent);
-		box-shadow: 0 0 0 1px var(--accent) inset;
+		background: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
+		box-shadow: 0 0 0 1px var(--MI_THEME-accent) inset;
 
 		> .count {
-			color: var(--accent);
+			color: var(--MI_THEME-accent);
 		}
 
 		> .icon {
diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue
index db2666a45f47..61815ddfd82f 100644
--- a/packages/frontend-embed/src/components/EmSubNoteContent.vue
+++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue
@@ -65,11 +65,11 @@ const collapsed = ref(isLong);
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+			background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
-				background: var(--panel);
+				background: var(--MI_THEME-panel);
 				padding: 6px 10px;
 				font-size: 0.8em;
 				border-radius: 999px;
@@ -78,7 +78,7 @@ const collapsed = ref(isLong);
 
 			&:hover {
 				> .fadeLabel {
-					background: var(--panelHighlight);
+					background: var(--MI_THEME-panelHighlight);
 				}
 			}
 		}
@@ -87,25 +87,25 @@ const collapsed = ref(isLong);
 
 .reply {
 	margin-right: 6px;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 .rp {
 	margin-left: 4px;
 	font-style: oblique;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 }
 
 .showLess {
 	width: 100%;
 	margin-top: 14px;
 	position: sticky;
-	bottom: calc(var(--stickyBottom, 0px) + 14px);
+	bottom: calc(var(--MI-stickyBottom, 0px) + 14px);
 }
 
 .showLessLabel {
 	display: inline-block;
-	background: var(--popup);
+	background: var(--MI_THEME-popup);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
diff --git a/packages/frontend-embed/src/components/EmTime.vue b/packages/frontend-embed/src/components/EmTime.vue
index c3986f7d7031..7902e1848323 100644
--- a/packages/frontend-embed/src/components/EmTime.vue
+++ b/packages/frontend-embed/src/components/EmTime.vue
@@ -98,10 +98,10 @@ if (!invalid && props.origin === null && (props.mode === 'relative' || props.mod
 
 <style lang="scss" module>
 .old1 {
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 }
 
 .old1.old2 {
-	color: var(--error);
+	color: var(--MI_THEME-error);
 }
 </style>
diff --git a/packages/frontend-embed/src/components/EmTimelineContainer.vue b/packages/frontend-embed/src/components/EmTimelineContainer.vue
index 6c30b1102d9e..60fd67ced918 100644
--- a/packages/frontend-embed/src/components/EmTimelineContainer.vue
+++ b/packages/frontend-embed/src/components/EmTimelineContainer.vue
@@ -20,7 +20,7 @@ withDefaults(defineProps<{
 
 <style module lang="scss">
 .timelineRoot {
-	background-color: var(--panel);
+	background-color: var(--MI_THEME-panel);
 	height: 100%;
 	max-height: var(--embedMaxHeight, none);
 	display: flex;
@@ -29,7 +29,7 @@ withDefaults(defineProps<{
 
 .header {
 	flex-shrink: 0;
-	border-bottom: 1px solid var(--divider);
+	border-bottom: 1px solid var(--MI_THEME-divider);
 }
 
 .body {
diff --git a/packages/frontend-embed/src/di.ts b/packages/frontend-embed/src/di.ts
index 799bbed598a1..22f62766309a 100644
--- a/packages/frontend-embed/src/di.ts
+++ b/packages/frontend-embed/src/di.ts
@@ -7,9 +7,11 @@ import type { InjectionKey } from 'vue';
 import * as Misskey from 'misskey-js';
 import { MediaProxy } from '@@/js/media-proxy.js';
 import type { ParsedEmbedParams } from '@@/js/embed-page.js';
+import type { ServerContext } from '@/server-context.js';
 
 export const DI = {
 	serverMetadata: Symbol() as InjectionKey<Misskey.entities.MetaDetailed>,
 	embedParams: Symbol() as InjectionKey<ParsedEmbedParams>,
+	serverContext: Symbol() as InjectionKey<ServerContext>,
 	mediaProxy: Symbol() as InjectionKey<MediaProxy>,
 };
diff --git a/packages/frontend-embed/src/pages/clip.vue b/packages/frontend-embed/src/pages/clip.vue
index 957d425d9384..f4d4e8cf6f02 100644
--- a/packages/frontend-embed/src/pages/clip.vue
+++ b/packages/frontend-embed/src/pages/clip.vue
@@ -5,8 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div>
-	<EmLoading v-if="loading"/>
-	<EmTimelineContainer v-else-if="clip" :showHeader="embedParams.header">
+	<EmTimelineContainer v-if="clip" :showHeader="embedParams.header">
 		<template #header>
 			<div :class="$style.clipHeader">
 				<div :class="$style.headerClipIconRoot">
@@ -39,20 +38,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { ref, computed, shallowRef, inject } from 'vue';
+import { ref, computed, inject, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import { scrollToTop } from '@@/js/scroll.js';
+import { url, instanceName } from '@@/js/config.js';
+import { isLink } from '@@/js/is-link.js';
+import { defaultEmbedParams } from '@@/js/embed-page.js';
 import type { Paging } from '@/components/EmPagination.vue';
-import EmLoading from '@/components/EmLoading.vue';
 import EmNotes from '@/components/EmNotes.vue';
 import XNotFound from '@/pages/not-found.vue';
 import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
 import { misskeyApi } from '@/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { serverMetadata } from '@/server-metadata.js';
-import { url, instanceName } from '@@/js/config.js';
-import { isLink } from '@@/js/is-link.js';
-import { defaultEmbedParams } from '@@/js/embed-page.js';
+import { assertServerContext } from '@/server-context.js';
 import { DI } from '@/di.js';
 
 const props = defineProps<{
@@ -61,16 +59,30 @@ const props = defineProps<{
 
 const embedParams = inject(DI.embedParams, defaultEmbedParams);
 
-const clip = ref<Misskey.entities.Clip | null>(null);
+const serverMetadata = inject(DI.serverMetadata)!;
+
+const serverContext = inject(DI.serverContext)!;
+
+const clip = ref<Misskey.entities.Clip | null>();
+
+if (assertServerContext(serverContext, 'clip')) {
+	clip.value = serverContext.clip;
+} else {
+	clip.value = await misskeyApi('clips/show', {
+		clipId: props.clipId,
+	}).catch(() => {
+		return null;
+	});
+}
+
 const pagination = computed(() => ({
 	endpoint: 'clips/notes',
 	params: {
 		clipId: props.clipId,
 	},
 } as Paging));
-const loading = ref(true);
 
-const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
+const notesEl = useTemplateRef('notesEl');
 
 function top(ev: MouseEvent) {
 	const target = ev.target as HTMLElement | null;
@@ -80,16 +92,6 @@ function top(ev: MouseEvent) {
 		scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' });
 	}
 }
-
-misskeyApi('clips/show', {
-	clipId: props.clipId,
-}).then(res => {
-	clip.value = res;
-	loading.value = false;
-}).catch(err => {
-	console.error(err);
-	loading.value = false;
-});
 </script>
 
 <style lang="scss" module>
@@ -98,7 +100,7 @@ misskeyApi('clips/show', {
 	display: flex;
 	min-width: 0;
 	align-items: center;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 	overflow: hidden;
 
 	.headerClipIconRoot {
@@ -108,8 +110,8 @@ misskeyApi('clips/show', {
 		line-height: 32px;
 		font-size: 14px;
 		text-align: center;
-		background-color: var(--accentedBg);
-		color: var(--accent);
+		background-color: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
 		border-radius: 50%;
 	}
 
diff --git a/packages/frontend-embed/src/pages/note.vue b/packages/frontend-embed/src/pages/note.vue
index 86aebe072a12..e8794302869c 100644
--- a/packages/frontend-embed/src/pages/note.vue
+++ b/packages/frontend-embed/src/pages/note.vue
@@ -5,44 +5,48 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div :class="$style.noteEmbedRoot">
-	<EmLoading v-if="loading"/>
-	<EmNoteDetailed v-else-if="note" :note="note"/>
+	<EmNoteDetailed v-if="note && !prohibited" :note="note"/>
 	<XNotFound v-else/>
 </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { inject, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import EmNoteDetailed from '@/components/EmNoteDetailed.vue';
-import EmLoading from '@/components/EmLoading.vue';
 import XNotFound from '@/pages/not-found.vue';
+import { DI } from '@/di.js';
 import { misskeyApi } from '@/misskey-api.js';
+import { assertServerContext } from '@/server-context';
 
 const props = defineProps<{
 	noteId: string;
 }>();
 
+const serverContext = inject(DI.serverContext)!;
+
 const note = ref<Misskey.entities.Note | null>(null);
-const loading = ref(true);
-
-// TODO: クライアント側でAPIを叩くのは二度手間なので予めHTMLに埋め込んでおく
-misskeyApi('notes/show', {
-	noteId: props.noteId,
-}).then(res => {
-	// リモートのノートは埋め込ませない
-	if (res.url == null && res.uri == null) {
-		note.value = res;
-	}
-	loading.value = false;
-}).catch(err => {
-	console.error(err);
-	loading.value = false;
-});
+
+const prohibited = ref(false);
+
+if (assertServerContext(serverContext, 'note')) {
+	note.value = serverContext.note;
+} else {
+	note.value = await misskeyApi('notes/show', {
+		noteId: props.noteId,
+	}).catch(() => {
+		return null;
+	});
+}
+
+if (note.value?.url != null || note.value?.uri != null) {
+	// リモートサーバーのノートは弾く
+	prohibited.value = true;
+}
 </script>
 
 <style lang="scss" module>
 .noteEmbedRoot {
-	background-color: var(--panel);
+	background-color: var(--MI_THEME-panel);
 }
 </style>
diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue
index d9759a47e7ca..4b00ae7c2d3e 100644
--- a/packages/frontend-embed/src/pages/tag.vue
+++ b/packages/frontend-embed/src/pages/tag.vue
@@ -38,14 +38,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, shallowRef, inject } from 'vue';
+import { computed, inject, useTemplateRef } from 'vue';
 import { scrollToTop } from '@@/js/scroll.js';
 import type { Paging } from '@/components/EmPagination.vue';
 import EmNotes from '@/components/EmNotes.vue';
 import XNotFound from '@/pages/not-found.vue';
 import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
 import { i18n } from '@/i18n.js';
-import { serverMetadata } from '@/server-metadata.js';
 import { url, instanceName } from '@@/js/config.js';
 import { isLink } from '@@/js/is-link.js';
 import { DI } from '@/di.js';
@@ -55,6 +54,8 @@ const props = defineProps<{
 	tag: string;
 }>();
 
+const serverMetadata = inject(DI.serverMetadata)!;
+
 const embedParams = inject(DI.embedParams, defaultEmbedParams);
 
 const pagination = computed(() => ({
@@ -64,7 +65,7 @@ const pagination = computed(() => ({
 	},
 } as Paging));
 
-const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
+const notesEl = useTemplateRef('notesEl');
 
 function top(ev: MouseEvent) {
 	const target = ev.target as HTMLElement | null;
@@ -82,7 +83,7 @@ function top(ev: MouseEvent) {
 	display: flex;
 	min-width: 0;
 	align-items: center;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 	overflow: hidden;
 
 	.headerClipIconRoot {
@@ -92,8 +93,8 @@ function top(ev: MouseEvent) {
 		line-height: 32px;
 		font-size: 14px;
 		text-align: center;
-		background-color: var(--accentedBg);
-		color: var(--accent);
+		background-color: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
 		border-radius: 50%;
 	}
 
diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue
index 8f587d26040d..348b1a762213 100644
--- a/packages/frontend-embed/src/pages/user-timeline.vue
+++ b/packages/frontend-embed/src/pages/user-timeline.vue
@@ -5,14 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div>
-	<EmLoading v-if="loading"/>
-	<EmTimelineContainer v-else-if="user" :showHeader="embedParams.header">
+	<EmTimelineContainer v-if="user && !prohibited" :showHeader="embedParams.header">
 		<template #header>
 			<div :class="$style.userHeader">
 				<a :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer" :class="$style.avatarLink">
 					<EmAvatar :class="$style.avatar" :user="user"/>
 				</a>
-				<div :class="$style.headerTitle">
+				<div :class="$style.headerTitle" @click="top">
 					<I18n :src="i18n.ts.noteOf" tag="div" class="_nowrap">
 						<template #user>
 							<a v-if="user != null" :href="`/@${user.username}`" target="_blank" rel="noopener noreferrer">
@@ -46,21 +45,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { ref, computed, shallowRef, inject } from 'vue';
+import { ref, computed, inject, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
+import { url, instanceName } from '@@/js/config.js';
+import { defaultEmbedParams } from '@@/js/embed-page.js';
 import type { Paging } from '@/components/EmPagination.vue';
 import EmNotes from '@/components/EmNotes.vue';
 import EmAvatar from '@/components/EmAvatar.vue';
-import EmLoading from '@/components/EmLoading.vue';
 import EmUserName from '@/components/EmUserName.vue';
 import I18n from '@/components/I18n.vue';
 import XNotFound from '@/pages/not-found.vue';
 import EmTimelineContainer from '@/components/EmTimelineContainer.vue';
+import { scrollToTop } from '@@/js/scroll.js';
+import { isLink } from '@@/js/is-link.js';
 import { misskeyApi } from '@/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { serverMetadata } from '@/server-metadata.js';
-import { url, instanceName } from '@@/js/config.js';
-import { defaultEmbedParams } from '@@/js/embed-page.js';
+import { assertServerContext } from '@/server-context.js';
 import { DI } from '@/di.js';
 
 const props = defineProps<{
@@ -69,26 +69,46 @@ const props = defineProps<{
 
 const embedParams = inject(DI.embedParams, defaultEmbedParams);
 
-const user = ref<Misskey.entities.UserLite | null>(null);
+const serverMetadata = inject(DI.serverMetadata)!;
+
+const serverContext = inject(DI.serverContext)!;
+
+const user = ref<Misskey.entities.UserLite | null>();
+
+const prohibited = ref(false);
+
+if (assertServerContext(serverContext, 'user')) {
+	user.value = serverContext.user;
+} else {
+	user.value = await misskeyApi('users/show', {
+		userId: props.userId,
+	}).catch(() => {
+		return null;
+	});
+}
+
+if (user.value?.host != null) {
+	// リモートサーバーのユーザーは弾く
+	prohibited.value = true;
+}
+
 const pagination = computed(() => ({
 	endpoint: 'users/notes',
 	params: {
 		userId: user.value?.id,
 	},
 } as Paging));
-const loading = ref(true);
-
-const notesEl = shallowRef<InstanceType<typeof EmNotes> | null>(null);
-
-misskeyApi('users/show', {
-	userId: props.userId,
-}).then(res => {
-	user.value = res;
-	loading.value = false;
-}).catch(err => {
-	console.error(err);
-	loading.value = false;
-});
+
+const notesEl = useTemplateRef('notesEl');
+
+function top(ev: MouseEvent) {
+	const target = ev.target as HTMLElement | null;
+	if (target && isLink(target)) return;
+
+	if (notesEl.value) {
+		scrollToTop(notesEl.value.$el as HTMLElement, { behavior: 'smooth' });
+	}
+}
 </script>
 
 <style lang="scss" module>
@@ -97,7 +117,7 @@ misskeyApi('users/show', {
 	display: flex;
 	min-width: 0;
 	align-items: center;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 	overflow: hidden;
 
 	.avatarLink {
diff --git a/packages/frontend-embed/src/server-context.ts b/packages/frontend-embed/src/server-context.ts
new file mode 100644
index 000000000000..a84a1a726a03
--- /dev/null
+++ b/packages/frontend-embed/src/server-context.ts
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import * as Misskey from 'misskey-js';
+
+const providedContextEl = document.getElementById('misskey_embedCtx');
+
+export type ServerContext = {
+	clip?: Misskey.entities.Clip;
+	note?: Misskey.entities.Note;
+	user?: Misskey.entities.UserLite;
+} | null;
+
+// NOTE: devモードのときしか embedCtx が null になることは無い
+export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
+
+export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> {
+	if (ctx == null) return false;
+	return entity in ctx;
+}
diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss
index 02008ddbd05b..2e43cfd20adc 100644
--- a/packages/frontend-embed/src/style.scss
+++ b/packages/frontend-embed/src/style.scss
@@ -7,18 +7,18 @@
  */
 
 :root {
-	--radius: 12px;
-	--marginFull: 14px;
-	--marginHalf: 10px;
+	--MI-radius: 12px;
+	--MI-marginFull: 14px;
+	--MI-marginHalf: 10px;
 
-	--margin: var(--marginFull);
+	--MI-margin: var(--MI-marginFull);
 }
 
 html {
 	background-color: transparent;
 	color-scheme: light dark;
-	color: var(--fg);
-	accent-color: var(--accent);
+	color: var(--MI_THEME-fg);
+	accent-color: var(--MI_THEME-accent);
 	overflow: clip;
 	overflow-wrap: break-word;
 	font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif;
@@ -29,7 +29,7 @@ html {
 	-webkit-text-size-adjust: 100%;
 
 	&, * {
-		scrollbar-color: var(--scrollbarHandle) transparent;
+		scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
 		scrollbar-width: thin;
 
 		&::-webkit-scrollbar {
@@ -42,14 +42,14 @@ html {
 		}
 
 		&::-webkit-scrollbar-thumb {
-			background: var(--scrollbarHandle);
+			background: var(--MI_THEME-scrollbarHandle);
 
 			&:hover {
-				background: var(--scrollbarHandleHover);
+				background: var(--MI_THEME-scrollbarHandleHover);
 			}
 
 			&:active {
-				background: var(--accent);
+				background: var(--MI_THEME-accent);
 			}
 		}
 	}
@@ -93,7 +93,7 @@ rt {
 }
 
 :focus-visible {
-	outline: var(--focus) solid 2px;
+	outline: var(--MI_THEME-focus) solid 2px;
 	outline-offset: -2px;
 
 	&:hover {
@@ -151,38 +151,38 @@ rt {
 
 ._buttonGray {
 	@extend ._button;
-	background: var(--buttonBg);
+	background: var(--MI_THEME-buttonBg);
 
 	&:not(:disabled):hover {
-		background: var(--buttonHoverBg);
+		background: var(--MI_THEME-buttonHoverBg);
 	}
 }
 
 ._buttonPrimary {
 	@extend ._button;
-	color: var(--fgOnAccent);
-	background: var(--accent);
+	color: var(--MI_THEME-fgOnAccent);
+	background: var(--MI_THEME-accent);
 
 	&:not(:disabled):hover {
-		background: hsl(from var(--accent) h s calc(l + 5));
+		background: hsl(from var(--MI_THEME-accent) h s calc(l + 5));
 	}
 
 	&:not(:disabled):active {
-		background: hsl(from var(--accent) h s calc(l - 5));
+		background: hsl(from var(--MI_THEME-accent) h s calc(l - 5));
 	}
 }
 
 ._buttonGradate {
 	@extend ._buttonPrimary;
-	color: var(--fgOnAccent);
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+	color: var(--MI_THEME-fgOnAccent);
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 
 	&:not(:disabled):hover {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 
 	&:not(:disabled):active {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 }
 
@@ -199,13 +199,13 @@ rt {
 }
 
 ._help {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	cursor: help;
 }
 
 ._textButton {
 	@extend ._button;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 
 	&:focus-visible {
 		outline-offset: 2px;
@@ -217,13 +217,13 @@ rt {
 }
 
 ._panel {
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 
 ._margin {
-	margin: var(--margin) 0;
+	margin: var(--MI-margin) 0;
 }
 
 ._gaps_m {
@@ -241,7 +241,7 @@ rt {
 ._gaps {
 	display: flex;
 	flex-direction: column;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 }
 
 ._buttons {
@@ -263,24 +263,24 @@ rt {
 	padding: 10px;
 	box-sizing: border-box;
 	text-align: center;
-	border: solid 0.5px var(--divider);
-	border-radius: var(--radius);
+	border: solid 0.5px var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 
 	&:active {
-		border-color: var(--accent);
+		border-color: var(--MI_THEME-accent);
 	}
 }
 
 ._popup {
-	background: var(--popup);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-popup);
+	border-radius: var(--MI-radius);
 	contain: content;
 }
 
 ._acrylic {
-	background: var(--acrylicPanel);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	background: var(--MI_THEME-acrylicPanel);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 
 ._fullinfo {
@@ -296,7 +296,7 @@ rt {
 }
 
 ._link {
-	color: var(--link);
+	color: var(--MI_THEME-link);
 }
 
 ._caption {
diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts
index ee633fae9451..4664ad48804e 100644
--- a/packages/frontend-embed/src/theme.ts
+++ b/packages/frontend-embed/src/theme.ts
@@ -39,6 +39,10 @@ export function applyTheme(theme: Theme, persist = true) {
 		document.documentElement.classList.remove('_themeChanging_');
 	}, 1000);
 
+	const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
+
+	document.documentElement.dataset.colorScheme = colorScheme;
+
 	// Deep copy
 	const _theme = JSON.parse(JSON.stringify(theme));
 
@@ -57,7 +61,7 @@ export function applyTheme(theme: Theme, persist = true) {
 	}
 
 	for (const [k, v] of Object.entries(props)) {
-		document.documentElement.style.setProperty(`--${k}`, v.toString());
+		document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
 	}
 
 	// iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照
diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue
index 35d9946b1250..4ba5968a91f1 100644
--- a/packages/frontend-embed/src/ui.vue
+++ b/packages/frontend-embed/src/ui.vue
@@ -18,11 +18,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div
 		:class="$style.routerViewContainer"
 	>
-		<EmNotePage v-if="page === 'notes'" :noteId="contentId"/>
-		<EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/>
-		<EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/>
-		<EmTagPage v-else-if="page === 'tags'" :tag="contentId"/>
-		<XNotFound v-else/>
+		<Suspense :timeout="0">
+			<EmNotePage v-if="page === 'notes'" :noteId="contentId"/>
+			<EmUserTimelinePage v-else-if="page === 'user-timeline'" :userId="contentId"/>
+			<EmClipPage v-else-if="page === 'clips'" :clipId="contentId"/>
+			<EmTagPage v-else-if="page === 'tags'" :tag="contentId"/>
+			<XNotFound v-else/>
+			<template #fallback>
+				<EmLoading/>
+			</template>
+		</Suspense>
 	</div>
 </div>
 </template>
@@ -37,9 +42,18 @@ import EmUserTimelinePage from '@/pages/user-timeline.vue';
 import EmClipPage from '@/pages/clip.vue';
 import EmTagPage from '@/pages/tag.vue';
 import XNotFound from '@/pages/not-found.vue';
+import EmLoading from '@/components/EmLoading.vue';
+
+function safeURIDecode(str: string): string {
+	try {
+		return decodeURIComponent(str);
+	} catch {
+		return str;
+	}
+}
 
 const page = location.pathname.split('/')[2];
-const contentId = location.pathname.split('/')[3];
+const contentId = safeURIDecode(location.pathname.split('/')[3]);
 if (_DEV_) console.log(page, contentId);
 
 const embedParams = inject(DI.embedParams, defaultEmbedParams);
@@ -74,14 +88,14 @@ onUnmounted(() => {
 <style lang="scss" module>
 .rootForEmbedPage {
 	box-sizing: border-box;
-	border: 1px solid var(--divider);
-	background-color: var(--bg);
+	border: 1px solid var(--MI_THEME-divider);
+	background-color: var(--MI_THEME-bg);
 	overflow: hidden;
 	position: relative;
 	height: auto;
 
 	&.rounded {
-		border-radius: var(--radius);
+		border-radius: var(--MI-radius);
 	}
 
 	&.noBorder {
diff --git a/packages/frontend-embed/src/utils.ts b/packages/frontend-embed/src/utils.ts
index 48e06b21efe2..939648aa383d 100644
--- a/packages/frontend-embed/src/utils.ts
+++ b/packages/frontend-embed/src/utils.ts
@@ -18,6 +18,6 @@ export const userPage = (user: Misskey.Acct, path?: string, absolute = false) =>
 	return `${absolute ? url : ''}/@${acct(user)}${(path ? `/${path}` : '')}`;
 };
 
-export const notePage = note => {
+export const notePage = (note: Misskey.entities.Note) => {
 	return `/notes/${note.id}`;
 };
diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts
index 64e67401c2f8..2dbee488c5f0 100644
--- a/packages/frontend-embed/vite.config.ts
+++ b/packages/frontend-embed/vite.config.ts
@@ -91,6 +91,11 @@ export function getConfig(): UserConfig {
 					}
 				},
 			},
+			preprocessorOptions: {
+				scss: {
+					api: 'modern-compiler',
+				},
+			},
 		},
 
 		define: {
diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts
index 8391fb638c5e..4fe5cbb205a7 100644
--- a/packages/frontend-shared/js/const.ts
+++ b/packages/frontend-shared/js/const.ts
@@ -67,6 +67,9 @@ export const notificationTypes = [
 	'followRequestAccepted',
 	'roleAssigned',
 	'achievementEarned',
+	'exportCompleted',
+	'login',
+	'test',
 	'app',
 ] as const;
 export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
@@ -98,6 +101,11 @@ export const ROLE_POLICIES = [
 	'userEachUserListsLimit',
 	'rateLimitFactor',
 	'avatarDecorationLimit',
+	'canImportAntennas',
+	'canImportBlocking',
+	'canImportFollowing',
+	'canImportMuting',
+	'canImportUserLists',
 ] as const;
 
 // なんか動かない
diff --git a/packages/frontend-shared/js/scroll.ts b/packages/frontend-shared/js/scroll.ts
index 1062e5252feb..4f2e9105c340 100644
--- a/packages/frontend-shared/js/scroll.ts
+++ b/packages/frontend-shared/js/scroll.ts
@@ -36,19 +36,27 @@ export function getScrollPosition(el: HTMLElement | null): number {
 	return container == null ? window.scrollY : container.scrollTop;
 }
 
-export function onScrollTop(el: HTMLElement, cb: () => unknown, tolerance = 1, once = false) {
+export function onScrollTop(el: HTMLElement, cb: (topVisible: boolean) => unknown, tolerance = 1, once = false) {
 	// とりあえず評価してみる
-	if (el.isConnected && isTopVisible(el)) {
-		cb();
+	const firstTopVisible = isTopVisible(el);
+	if (el.isConnected && firstTopVisible) {
+		cb(firstTopVisible);
 		if (once) return null;
 	}
 
 	const container = getScrollContainer(el) ?? window;
 
+	// 以下のケースにおいて、cbが何度も呼び出されてしまって具合が悪いので1回呼んだら以降は無視するようにする
+	// - スクロールイベントは1回のスクロールで複数回発生することがある
+	// - toleranceの範囲内に収まる程度の微量なスクロールが発生した
+	let prevTopVisible = firstTopVisible;
 	const onScroll = () => {
 		if (!document.body.contains(el)) return;
-		if (isTopVisible(el, tolerance)) {
-			cb();
+
+		const topVisible = isTopVisible(el, tolerance);
+		if (topVisible !== prevTopVisible) {
+			prevTopVisible = topVisible;
+			cb(topVisible);
 			if (once) removeListener();
 		}
 	};
@@ -126,6 +134,7 @@ export function scrollToBottom(
 
 export function isTopVisible(el: HTMLElement, tolerance = 1): boolean {
 	const scrollTop = getScrollPosition(el);
+	if (_DEV_) console.log(scrollTop, tolerance, scrollTop <= tolerance);
 	return scrollTop <= tolerance;
 }
 
diff --git a/packages/frontend-shared/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5
index 17fb98e4eeed..5271785e62d3 100644
--- a/packages/frontend-shared/themes/_dark.json5
+++ b/packages/frontend-shared/themes/_dark.json5
@@ -13,6 +13,7 @@
 		accentDarken: ':darken<10<@accent',
 		accentLighten: ':lighten<10<@accent',
 		accentedBg: ':alpha<0.15<@accent',
+		love: '#dd2e44',
 		focus: ':alpha<0.3<@accent',
 		bg: '#000',
 		acrylicBg: ':alpha<0.5<@bg',
@@ -29,7 +30,7 @@
 		panelHeaderBg: ':lighten<3<@panel',
 		panelHeaderFg: '@fg',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
-		panelBorder: '" solid 1px var(--divider)',
+		panelBorder: '" solid 1px var(--MI_THEME-divider)',
 		acrylicPanel: ':alpha<0.5<@panel',
 		windowHeader: ':alpha<0.85<@panel',
 		popup: ':lighten<3<@panel',
@@ -53,18 +54,19 @@
 		infoFg: '#fff',
 		infoWarnBg: '#42321c',
 		infoWarnFg: '#ffbd3e',
-		switchBg: 'rgba(255, 255, 255, 0.15)',
-		buttonBg: 'rgba(255, 255, 255, 0.05)',
-		buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
+		folderHeaderBg: 'rgba(255, 255, 255, 0.05)',
+		folderHeaderHoverBg: 'rgba(255, 255, 255, 0.1)',
+		buttonBg: ':lighten<5<@panel',
+		buttonHoverBg: ':lighten<10<@panel',
 		buttonGradateA: '@accent',
 		buttonGradateB: ':hue<20<@accent',
+		switchBg: 'rgba(255, 255, 255, 0.15)',
 		switchOffBg: 'rgba(255, 255, 255, 0.1)',
 		switchOffFg: ':alpha<0.8<@fg',
 		switchOnBg: '@accentedBg',
 		switchOnFg: '@accent',
 		inputBorder: 'rgba(255, 255, 255, 0.1)',
 		inputBorderHover: 'rgba(255, 255, 255, 0.2)',
-		listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
 		driveFolderBg: ':alpha<0.3<@accent',
 		wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
 		badge: '#31b1ce',
diff --git a/packages/frontend-shared/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5
index ca6c059e16e3..be331ce58fc7 100644
--- a/packages/frontend-shared/themes/_light.json5
+++ b/packages/frontend-shared/themes/_light.json5
@@ -13,6 +13,7 @@
 		accentDarken: ':darken<10<@accent',
 		accentLighten: ':lighten<10<@accent',
 		accentedBg: ':alpha<0.15<@accent',
+		love: '#dd2e44',
 		focus: ':alpha<0.3<@accent',
 		bg: '#fff',
 		acrylicBg: ':alpha<0.5<@bg',
@@ -29,7 +30,7 @@
 		panelHeaderBg: ':lighten<3<@panel',
 		panelHeaderFg: '@fg',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
-		panelBorder: '" solid 1px var(--divider)',
+		panelBorder: '" solid 1px var(--MI_THEME-divider)',
 		acrylicPanel: ':alpha<0.5<@panel',
 		windowHeader: ':alpha<0.85<@panel',
 		popup: ':lighten<3<@panel',
@@ -53,18 +54,19 @@
 		infoFg: '#72818a',
 		infoWarnBg: '#fff0db',
 		infoWarnFg: '#8f6e31',
-		switchBg: 'rgba(0, 0, 0, 0.15)',
-		buttonBg: 'rgba(0, 0, 0, 0.05)',
-		buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
+		folderHeaderBg: 'rgba(0, 0, 0, 0.05)',
+		folderHeaderHoverBg: 'rgba(0, 0, 0, 0.1)',
+		buttonBg: ':darken<5<@panel',
+		buttonHoverBg: ':darken<10<@panel',
 		buttonGradateA: '@accent',
 		buttonGradateB: ':hue<20<@accent',
+		switchBg: 'rgba(0, 0, 0, 0.15)',
 		switchOffBg: 'rgba(0, 0, 0, 0.1)',
 		switchOffFg: '@panel',
 		switchOnBg: '@accent',
 		switchOnFg: '@fgOnAccent',
 		inputBorder: 'rgba(0, 0, 0, 0.1)',
 		inputBorderHover: 'rgba(0, 0, 0, 0.2)',
-		listItemHoverBg: 'rgba(0, 0, 0, 0.03)',
 		driveFolderBg: ':alpha<0.3<@accent',
 		wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
 		badge: '#31b1ce',
diff --git a/packages/frontend-shared/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5
index 1cbb4e519d97..4422526a3394 100644
--- a/packages/frontend-shared/themes/d-astro.json5
+++ b/packages/frontend-shared/themes/d-astro.json5
@@ -25,7 +25,6 @@
 		mention: '#ffd152',
 		modalBg: 'rgba(0, 0, 0, 0.5)',
 		success: '#86b300',
-		buttonBg: 'rgba(255, 255, 255, 0.05)',
 		acrylicBg: ':alpha<0.5<@bg',
 		indicator: '@accent',
 		mentionMe: '#fb5d38',
@@ -37,12 +36,11 @@
 		dateLabelFg: '@fg',
 		inputBorder: 'rgba(255, 255, 255, 0.1)',
 		inputBorderHover: 'rgba(255, 255, 255, 0.2)',
-		panelBorder: '" solid 1px var(--divider)',
+		panelBorder: '" solid 1px var(--MI_THEME-divider)',
 		accentDarken: ':darken<10<@accent',
 		acrylicPanel: ':alpha<0.5<@panel',
 		navIndicator: '@accent',
 		accentLighten: ':lighten<10<@accent',
-		buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
 		buttonGradateA: '@accent',
 		buttonGradateB: ':hue<-20<@accent',
 		driveFolderBg: ':alpha<0.3<@accent',
@@ -52,7 +50,6 @@
 		htmlThemeColor: '@bg',
 		fgOnWhite: '@accent',
 		panelHighlight: ':lighten<3<@panel',
-		listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
 		scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
 		wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
diff --git a/packages/frontend-shared/themes/d-u0.json5 b/packages/frontend-shared/themes/d-u0.json5
index c8a31bb1a78e..fb707c74c333 100644
--- a/packages/frontend-shared/themes/d-u0.json5
+++ b/packages/frontend-shared/themes/d-u0.json5
@@ -38,7 +38,6 @@
 		mention: '@accent',
 		modalBg: 'rgba(0, 0, 0, 0.5)',
 		success: '#86b300',
-		buttonBg: 'rgba(255, 255, 255, 0.05)',
 		switchBg: 'rgba(255, 255, 255, 0.15)',
 		acrylicBg: ':alpha<0.5<@bg',
 		indicator: '@accent',
@@ -56,12 +55,11 @@
 		codeBoolean: '#c59eff',
 		dateLabelFg: '@fg',
 		inputBorder: 'rgba(255, 255, 255, 0.1)',
-		panelBorder: '" solid 1px var(--divider)',
+		panelBorder: '" solid 1px var(--MI_THEME-divider)',
 		accentDarken: ':darken<10<@accent',
 		acrylicPanel: ':alpha<0.5<@panel',
 		navIndicator: '@indicator',
 		accentLighten: ':lighten<10<@accent',
-		buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
 		driveFolderBg: ':alpha<0.3<@accent',
 		fgHighlighted: ':lighten<3<@fg',
 		fgTransparent: ':alpha<0.5<@fg',
@@ -71,7 +69,6 @@
 		buttonGradateB: ':hue<20<@accent',
 		htmlThemeColor: '@bg',
 		panelHighlight: ':lighten<3<@panel',
-		listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
 		scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
 		inputBorderHover: 'rgba(255, 255, 255, 0.2)',
 		wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
diff --git a/packages/frontend-shared/themes/l-u0.json5 b/packages/frontend-shared/themes/l-u0.json5
index 0b952b003ae5..7062e7fe5b14 100644
--- a/packages/frontend-shared/themes/l-u0.json5
+++ b/packages/frontend-shared/themes/l-u0.json5
@@ -56,7 +56,7 @@
 		codeBoolean: '#c59eff',
 		dateLabelFg: '@fg',
 		inputBorder: 'rgba(255, 255, 255, 0.1)',
-		panelBorder: '" solid 1px var(--divider)',
+		panelBorder: '" solid 1px var(--MI_THEME-divider)',
 		accentDarken: ':darken<10<@accent',
 		acrylicPanel: ':alpha<0.5<@panel',
 		navIndicator: '@indicator',
@@ -71,7 +71,6 @@
 		buttonGradateB: ':hue<20<@accent',
 		htmlThemeColor: '@bg',
 		panelHighlight: ':lighten<3<@panel',
-		listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
 		scrollbarHandle: '#74747433',
 		inputBorderHover: 'rgba(255, 255, 255, 0.2)',
 		wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
diff --git a/packages/frontend-shared/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5
index 3da2ca28fbc9..39768d4ac6c7 100644
--- a/packages/frontend-shared/themes/l-vivid.json5
+++ b/packages/frontend-shared/themes/l-vivid.json5
@@ -28,7 +28,6 @@
 		mention: '@accent',
 		modalBg: 'rgba(0, 0, 0, 0.3)',
 		success: '#86b300',
-		buttonBg: 'rgba(0, 0, 0, 0.05)',
 		acrylicBg: ':alpha<0.5<@bg',
 		indicator: '@accent',
 		mentionMe: '@mention',
@@ -40,12 +39,11 @@
 		dateLabelFg: '@fg',
 		inputBorder: 'rgba(0, 0, 0, 0.1)',
 		inputBorderHover: 'rgba(0, 0, 0, 0.2)',
-		panelBorder: '" solid 1px var(--divider)',
+		panelBorder: '" solid 1px var(--MI_THEME-divider)',
 		accentDarken: ':darken<10<@accent',
 		acrylicPanel: ':alpha<0.5<@panel',
 		navIndicator: '@accent',
 		accentLighten: ':lighten<10<@accent',
-		buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
 		driveFolderBg: ':alpha<0.3<@accent',
 		fgHighlighted: ':darken<3<@fg',
 		fgTransparent: ':alpha<0.5<@fg',
@@ -54,7 +52,6 @@
 		panelHeaderFg: '@fg',
 		htmlThemeColor: '@bg',
 		panelHighlight: ':darken<3<@panel',
-		listItemHoverBg: 'rgba(0, 0, 0, 0.03)',
 		scrollbarHandle: 'rgba(0, 0, 0, 0.2)',
 		wallpaperOverlay: 'rgba(255, 255, 255, 0.5)',
 		fgTransparentWeak: ':alpha<0.75<@fg',
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index 490a441b7055..f2bdc631d2b3 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -397,7 +397,18 @@ function toStories(component: string): Promise<string> {
 	const globs = await Promise.all([
 		glob('src/components/global/Mk*.vue'),
 		glob('src/components/global/RouterView.vue'),
-		glob('src/components/Mk[A-E]*.vue'),
+		glob('src/components/MkAbuseReportWindow.vue'),
+		glob('src/components/MkAccountMoved.vue'),
+		glob('src/components/MkAchievements.vue'),
+		glob('src/components/MkAnalogClock.vue'),
+		glob('src/components/MkAnimBg.vue'),
+		glob('src/components/MkAnnouncementDialog.vue'),
+		glob('src/components/MkAntennaEditor.vue'),
+		glob('src/components/MkAntennaEditorDialog.vue'),
+		glob('src/components/MkAsUi.vue'),
+		glob('src/components/MkAutocomplete.vue'),
+		glob('src/components/MkAvatars.vue'),
+		glob('src/components/Mk[B-E]*.vue'),
 		glob('src/components/MkFlashPreview.vue'),
 		glob('src/components/MkGalleryPostPreview.vue'),
 		glob('src/components/MkSignupServerRules.vue'),
@@ -405,8 +416,9 @@ function toStories(component: string): Promise<string> {
 		glob('src/components/MkUserSetupDialog.*.vue'),
 		glob('src/components/MkInstanceCardMini.vue'),
 		glob('src/components/MkInviteCode.vue'),
-		glob('src/pages/search.vue'),
+		glob('src/pages/admin/overview.ap-requests.vue'),
 		glob('src/pages/user/home.vue'),
+		glob('src/pages/search.vue'),
 	]);
 	const components = globs.flat();
 	await Promise.all(components.map(async (component) => {
diff --git a/packages/frontend/assets/testcaptcha.png b/packages/frontend/assets/testcaptcha.png
new file mode 100644
index 000000000000..9bfd252b51c6
Binary files /dev/null and b/packages/frontend/assets/testcaptcha.png differ
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 67be7f0598a0..3226a554a93b 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -17,35 +17,35 @@
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
 	"dependencies": {
-		"@discordapp/twemoji": "15.0.3",
+		"@discordapp/twemoji": "15.1.0",
 		"@github/webauthn-json": "2.1.1",
 		"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
 		"@misskey-dev/browser-image-resizer": "2024.1.0",
 		"@rollup/plugin-json": "6.1.0",
 		"@rollup/plugin-replace": "5.0.7",
-		"@rollup/pluginutils": "5.1.0",
+		"@rollup/pluginutils": "5.1.2",
 		"@syuilo/aiscript": "0.19.0",
 		"@tabler/icons-webfont": "3.3.0",
 		"@twemoji/parser": "15.1.1",
-		"@vitejs/plugin-vue": "5.1.0",
-		"@vue/compiler-sfc": "3.4.37",
+		"@vitejs/plugin-vue": "5.1.4",
+		"@vue/compiler-sfc": "3.5.11",
 		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11",
-		"astring": "1.8.6",
+		"astring": "1.9.0",
 		"broadcast-channel": "7.0.0",
 		"buraha": "0.0.1",
 		"canvas-confetti": "1.9.3",
-		"chart.js": "4.4.3",
+		"chart.js": "4.4.4",
 		"chartjs-adapter-date-fns": "3.0.0",
 		"chartjs-chart-matrix": "2.0.1",
 		"chartjs-plugin-gradient": "0.6.1",
 		"chartjs-plugin-zoom": "2.0.1",
-		"chromatic": "11.5.6",
+		"chromatic": "11.11.0",
 		"compare-versions": "6.1.1",
-		"cropperjs": "2.0.0-rc.1",
+		"cropperjs": "2.0.0-rc.2",
 		"date-fns": "2.30.0",
-		"escape-regexp": "0.0.1",
 		"estree-walker": "3.0.3",
 		"eventemitter3": "5.0.1",
+		"frontend-shared": "workspace:*",
 		"idb-keyval": "6.2.1",
 		"insert-text-at-cursor": "0.3.0",
 		"is-file-animated": "1.0.2",
@@ -55,88 +55,87 @@
 		"misskey-bubble-game": "workspace:*",
 		"misskey-js": "workspace:*",
 		"misskey-reversi": "workspace:*",
-		"frontend-shared": "workspace:*",
 		"photoswipe": "5.4.4",
 		"punycode": "2.3.1",
-		"rollup": "4.19.1",
-		"sanitize-html": "2.13.0",
-		"sass": "1.77.8",
-		"shiki": "1.12.0",
+		"rollup": "4.22.5",
+		"sanitize-html": "2.13.1",
+		"sass": "1.79.3",
+		"shiki": "1.21.0",
 		"strict-event-emitter-types": "2.0.0",
 		"textarea-caret": "3.1.0",
-		"three": "0.167.0",
+		"three": "0.169.0",
 		"throttle-debounce": "5.0.2",
 		"tinycolor2": "1.6.0",
 		"tsc-alias": "1.8.10",
 		"tsconfig-paths": "4.2.0",
-		"typescript": "5.5.4",
+		"typescript": "5.6.2",
 		"uuid": "10.0.0",
-		"v-code-diff": "1.12.0",
-		"vite": "5.3.5",
-		"vue": "3.4.37",
+		"v-code-diff": "1.13.1",
+		"vite": "5.4.8",
+		"vue": "3.5.11",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
 		"@misskey-dev/summaly": "5.1.0",
-		"@storybook/addon-actions": "8.2.6",
-		"@storybook/addon-essentials": "8.2.6",
-		"@storybook/addon-interactions": "8.2.6",
-		"@storybook/addon-links": "8.2.6",
-		"@storybook/addon-mdx-gfm": "8.2.6",
-		"@storybook/addon-storysource": "8.2.6",
-		"@storybook/blocks": "8.2.6",
-		"@storybook/components": "8.2.6",
-		"@storybook/core-events": "8.2.6",
-		"@storybook/manager-api": "8.2.6",
-		"@storybook/preview-api": "8.2.6",
-		"@storybook/react": "8.2.6",
-		"@storybook/react-vite": "8.2.6",
-		"@storybook/test": "8.2.6",
-		"@storybook/theming": "8.2.6",
-		"@storybook/types": "8.2.6",
-		"@storybook/vue3": "8.2.6",
-		"@storybook/vue3-vite": "8.1.11",
+		"@storybook/addon-actions": "8.3.4",
+		"@storybook/addon-essentials": "8.3.4",
+		"@storybook/addon-interactions": "8.3.4",
+		"@storybook/addon-links": "8.3.4",
+		"@storybook/addon-mdx-gfm": "8.3.4",
+		"@storybook/addon-storysource": "8.3.4",
+		"@storybook/blocks": "8.3.4",
+		"@storybook/components": "8.3.4",
+		"@storybook/core-events": "8.3.4",
+		"@storybook/manager-api": "8.3.4",
+		"@storybook/preview-api": "8.3.4",
+		"@storybook/react": "8.3.4",
+		"@storybook/react-vite": "8.3.4",
+		"@storybook/test": "8.3.4",
+		"@storybook/theming": "8.3.4",
+		"@storybook/types": "8.3.4",
+		"@storybook/vue3": "8.3.4",
+		"@storybook/vue3-vite": "8.3.4",
 		"@testing-library/vue": "8.1.0",
-		"@types/escape-regexp": "0.0.3",
-		"@types/estree": "1.0.5",
+		"@types/canvas-confetti": "^1.6.4",
+		"@types/estree": "1.0.6",
 		"@types/matter-js": "0.19.7",
 		"@types/micromatch": "4.0.9",
 		"@types/node": "20.14.12",
 		"@types/punycode": "2.1.4",
-		"@types/sanitize-html": "2.11.0",
+		"@types/sanitize-html": "2.13.0",
 		"@types/seedrandom": "3.0.8",
 		"@types/throttle-debounce": "5.0.2",
 		"@types/tinycolor2": "1.4.6",
 		"@types/uuid": "10.0.0",
-		"@types/ws": "8.5.11",
+		"@types/ws": "8.5.12",
 		"@typescript-eslint/eslint-plugin": "7.17.0",
 		"@typescript-eslint/parser": "7.17.0",
 		"@vitest/coverage-v8": "1.6.0",
-		"@vue/runtime-core": "3.4.37",
+		"@vue/runtime-core": "3.5.11",
 		"acorn": "8.12.1",
 		"cross-env": "7.0.3",
-		"cypress": "13.13.1",
-		"eslint-plugin-import": "2.29.1",
-		"eslint-plugin-vue": "9.27.0",
+		"cypress": "13.15.0",
+		"eslint-plugin-import": "2.31.0",
+		"eslint-plugin-vue": "9.28.0",
 		"fast-glob": "3.3.2",
 		"happy-dom": "10.0.3",
 		"intersection-observer": "0.12.2",
-		"micromatch": "4.0.7",
-		"msw": "2.3.4",
+		"micromatch": "4.0.8",
+		"msw": "2.4.9",
 		"msw-storybook-addon": "2.0.3",
-		"nodemon": "3.1.4",
+		"nodemon": "3.1.7",
 		"prettier": "3.3.3",
 		"react": "18.3.1",
 		"react-dom": "18.3.1",
 		"seedrandom": "3.0.5",
-		"start-server-and-test": "2.0.4",
-		"storybook": "8.2.6",
+		"start-server-and-test": "2.0.8",
+		"storybook": "8.3.4",
 		"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
 		"vite-plugin-turbosnap": "1.0.3",
 		"vitest": "1.6.0",
 		"vitest-fetch-mock": "0.2.2",
-		"vue-component-type-helpers": "2.0.29",
+		"vue-component-type-helpers": "2.1.6",
 		"vue-eslint-parser": "9.4.3",
-		"vue-tsc": "2.0.29"
+		"vue-tsc": "2.1.6"
 	}
 }
diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts
index 13a97e433ca1..c90cc6bdd016 100644
--- a/packages/frontend/src/_boot_.ts
+++ b/packages/frontend/src/_boot_.ts
@@ -12,7 +12,7 @@ import '@/style.scss';
 import { mainBoot } from '@/boot/main-boot.js';
 import { subBoot } from '@/boot/sub-boot.js';
 
-const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete'];
+const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete'];
 
 if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
 	subBoot();
diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts
index 1601f247d759..f312765dcf40 100644
--- a/packages/frontend/src/_dev_boot_.ts
+++ b/packages/frontend/src/_dev_boot_.ts
@@ -43,7 +43,7 @@ async function main() {
 	const theme = localStorage.getItem('theme');
 	if (theme) {
 		for (const [k, v] of Object.entries(JSON.parse(theme))) {
-			document.documentElement.style.setProperty(`--${k}`, v.toString());
+			document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
 
 			// HTMLの theme-color 適用
 			if (k === 'htmlThemeColor') {
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index f3883974668b..36186ecac193 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -5,12 +5,12 @@
 
 import { defineAsyncComponent, reactive, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { apiUrl } from '@@/js/config.js';
+import type { MenuItem, MenuButton } from '@/types/menu.js';
 import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
-import { MenuButton } from '@/types/menu.js';
 import { del, get, set } from '@/scripts/idb-proxy.js';
-import { apiUrl } from '@@/js/config.js';
 import { waiting, popup, popupMenu, success, alert } from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
@@ -165,7 +165,18 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr
 	});
 }
 
-export function updateAccount(accountData: Partial<Account>) {
+export function updateAccount(accountData: Account) {
+	if (!$i) return;
+	for (const key of Object.keys($i)) {
+		delete $i[key];
+	}
+	for (const [key, value] of Object.entries(accountData)) {
+		$i[key] = value;
+	}
+	miLocalStorage.setItem('account', JSON.stringify($i));
+}
+
+export function updateAccountPartial(accountData: Partial<Account>) {
 	if (!$i) return;
 	for (const [key, value] of Object.entries(accountData)) {
 		$i[key] = value;
@@ -224,26 +235,6 @@ export async function openAccountMenu(opts: {
 }, ev: MouseEvent) {
 	if (!$i) return;
 
-	function showSigninDialog() {
-		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
-			done: res => {
-				addAccount(res.id, res.i);
-				success();
-			},
-			closed: () => dispose(),
-		});
-	}
-
-	function createAccount() {
-		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
-			done: res => {
-				addAccount(res.id, res.i);
-				switchAccountWithToken(res.i);
-			},
-			closed: () => dispose(),
-		});
-	}
-
 	async function switchAccount(account: Misskey.entities.UserDetailed) {
 		const storedAccounts = await getAccounts();
 		const found = storedAccounts.find(x => x.id === account.id);
@@ -288,36 +279,98 @@ export async function openAccountMenu(opts: {
 		});
 	}));
 
+	const menuItems: MenuItem[] = [];
+
 	if (opts.withExtraOperation) {
-		popupMenu([...[{
-			type: 'link' as const,
+		menuItems.push({
+			type: 'link',
 			text: i18n.ts.profile,
-			to: `/@${ $i.username }`,
+			to: `/@${$i.username}`,
 			avatar: $i,
-		}, { type: 'divider' as const }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
-			type: 'parent' as const,
+		}, {
+			type: 'divider',
+		});
+
+		if (opts.includeCurrentAccount) {
+			menuItems.push(createItem($i));
+		}
+
+		menuItems.push(...accountItemPromises);
+
+		menuItems.push({
+			type: 'parent',
 			icon: 'ti ti-plus',
 			text: i18n.ts.addAccount,
 			children: [{
 				text: i18n.ts.existingAccount,
-				action: () => { showSigninDialog(); },
+				action: () => {
+					getAccountWithSigninDialog().then(res => {
+						if (res != null) {
+							success();
+						}
+					});
+				},
 			}, {
 				text: i18n.ts.createAccount,
-				action: () => { createAccount(); },
+				action: () => {
+					getAccountWithSignupDialog().then(res => {
+						if (res != null) {
+							switchAccountWithToken(res.token);
+						}
+					});
+				},
 			}],
 		}, {
-			type: 'link' as const,
+			type: 'link',
 			icon: 'ti ti-users',
 			text: i18n.ts.manageAccounts,
 			to: '/settings/accounts',
-		}]], ev.currentTarget ?? ev.target, {
-			align: 'left',
 		});
 	} else {
-		popupMenu([...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises], ev.currentTarget ?? ev.target, {
-			align: 'left',
-		});
+		if (opts.includeCurrentAccount) {
+			menuItems.push(createItem($i));
+		}
+
+		menuItems.push(...accountItemPromises);
 	}
+
+	popupMenu(menuItems, ev.currentTarget ?? ev.target, {
+		align: 'left',
+	});
+}
+
+export function getAccountWithSigninDialog(): Promise<{ id: string, token: string } | null> {
+	return new Promise((resolve) => {
+		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
+			done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => {
+				await addAccount(res.id, res.i);
+				resolve({ id: res.id, token: res.i });
+			},
+			cancelled: () => {
+				resolve(null);
+			},
+			closed: () => {
+				dispose();
+			},
+		});
+	});
+}
+
+export function getAccountWithSignupDialog(): Promise<{ id: string, token: string } | null> {
+	return new Promise((resolve) => {
+		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
+			done: async (res: Misskey.entities.SignupResponse) => {
+				await addAccount(res.id, res.token);
+				resolve({ id: res.id, token: res.token });
+			},
+			cancelled: () => {
+				resolve(null);
+			},
+			closed: () => {
+				dispose();
+			},
+		});
+	});
 }
 
 if (_DEV_) {
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 287788bc8e12..bfe5c4f5f708 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -5,17 +5,17 @@
 
 import { computed, watch, version as vueVersion, App } from 'vue';
 import { compareVersions } from 'compare-versions';
+import { version, lang, updateLocale, locale } from '@@/js/config.js';
 import widgets from '@/widgets/index.js';
 import directives from '@/directives/index.js';
 import components from '@/components/index.js';
-import { version, lang, updateLocale, locale } from '@@/js/config.js';
 import { applyTheme } from '@/scripts/theme.js';
 import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
-import { updateI18n } from '@/i18n.js';
+import { updateI18n, i18n } from '@/i18n.js';
 import { $i, refreshAccount, login } from '@/account.js';
 import { defaultStore, ColdDeviceStorage } from '@/store.js';
 import { fetchInstance, instance } from '@/instance.js';
-import { deviceKind } from '@/scripts/device-kind.js';
+import { deviceKind, updateDeviceKind } from '@/scripts/device-kind.js';
 import { reloadChannel } from '@/scripts/unison-reload.js';
 import { getUrlWithoutLoginId } from '@/scripts/login-id.js';
 import { getAccountFromId } from '@/scripts/get-account-from-id.js';
@@ -146,10 +146,9 @@ export async function common(createVue: () => App<Element>) {
 	// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
 	watch(defaultStore.reactiveState.darkMode, (darkMode) => {
 		applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
-		document.documentElement.dataset.colorMode = darkMode ? 'dark' : 'light';
 	}, { immediate: miLocalStorage.getItem('theme') == null });
 
-	document.documentElement.dataset.colorMode = defaultStore.state.darkMode ? 'dark' : 'light';
+	document.documentElement.dataset.colorScheme = defaultStore.state.darkMode ? 'dark' : 'light';
 
 	const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
 	const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
@@ -183,24 +182,22 @@ export async function common(createVue: () => App<Element>) {
 			if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme));
 			if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme));
 			defaultStore.set('themeInitial', false);
-		} else {
-			if (defaultStore.state.darkMode) {
-				applyTheme(darkTheme.value);
-			} else {
-				applyTheme(lightTheme.value);
-			}
 		}
 	});
 
+	watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => {
+		updateDeviceKind(kind);
+	}, { immediate: true });
+
 	watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
-		document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none');
+		document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
 	}, { immediate: true });
 
 	watch(defaultStore.reactiveState.useBlurEffect, v => {
 		if (v) {
-			document.documentElement.style.removeProperty('--blur');
+			document.documentElement.style.removeProperty('--MI-blur');
 		} else {
-			document.documentElement.style.setProperty('--blur', 'none');
+			document.documentElement.style.setProperty('--MI-blur', 'none');
 		}
 	}, { immediate: true });
 
@@ -276,6 +273,27 @@ export async function common(createVue: () => App<Element>) {
 
 	removeSplash();
 
+	//#region Self-XSS 対策メッセージ
+	console.log(
+		`%c${i18n.ts._selfXssPrevention.warning}`,
+		'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
+	);
+	console.log(
+		`%c${i18n.ts._selfXssPrevention.title}`,
+		'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
+	);
+	console.log(
+		`%c${i18n.ts._selfXssPrevention.description1}`,
+		'font-size: 16px; font-weight: 700;',
+	);
+	console.log(
+		`%c${i18n.ts._selfXssPrevention.description2}`,
+		'font-size: 16px;',
+		'font-size: 20px; font-weight: 700; color: #f00;',
+	);
+	console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
+	//#endregion
+
 	return {
 		isClientUpdated,
 		app,
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index ddd47ca448b8..2bf902947992 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -4,14 +4,14 @@
  */
 
 import { createApp, defineAsyncComponent, markRaw } from 'vue';
+import { ui } from '@@/js/config.js';
 import { common } from './common.js';
 import type * as Misskey from 'misskey-js';
-import { ui } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { alert, confirm, popup, post, toast } from '@/os.js';
 import { useStream } from '@/stream.js';
 import * as sound from '@/scripts/sound.js';
-import { $i, signout, updateAccount } from '@/account.js';
+import { $i, signout, updateAccountPartial } from '@/account.js';
 import { instance } from '@/instance.js';
 import { ColdDeviceStorage, defaultStore } from '@/store.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
@@ -230,19 +230,55 @@ export async function mainBoot() {
 			claimAchievement('collectAchievements30');
 		}
 
-		window.setInterval(() => {
-			if (Math.floor(Math.random() * 20000) === 0) {
-				claimAchievement('justPlainLucky');
+		if (!claimedAchievements.includes('justPlainLucky')) {
+			let justPlainLuckyTimer: number | null = null;
+			let lastVisibilityChangedAt = Date.now();
+
+			function claimPlainLucky() {
+				if (document.visibilityState !== 'visible') {
+					if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer);
+					return;
+				}
+
+				if (Math.floor(Math.random() * 20000) === 0) {
+					claimAchievement('justPlainLucky');
+				} else {
+					justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10);
+				}
 			}
-		}, 1000 * 10);
 
-		window.setTimeout(() => {
-			claimAchievement('client30min');
-		}, 1000 * 60 * 30);
+			window.addEventListener('visibilitychange', () => {
+				const now = Date.now();
+
+				if (document.visibilityState === 'visible') {
+					// タブを高速で切り替えたら取得処理が何度も走るのを防ぐ
+					if ((now - lastVisibilityChangedAt) < 1000 * 10) {
+						justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10);
+					} else {
+						claimPlainLucky();
+					}
+				} else if (justPlainLuckyTimer != null) {
+					window.clearTimeout(justPlainLuckyTimer);
+					justPlainLuckyTimer = null;
+				}
 
-		window.setTimeout(() => {
-			claimAchievement('client60min');
-		}, 1000 * 60 * 60);
+				lastVisibilityChangedAt = now;
+			}, { passive: true });
+
+			claimPlainLucky();
+		}
+
+		if (!claimedAchievements.includes('client30min')) {
+			window.setTimeout(() => {
+				claimAchievement('client30min');
+			}, 1000 * 60 * 30);
+		}
+
+		if (!claimedAchievements.includes('client60min')) {
+			window.setTimeout(() => {
+				claimAchievement('client60min');
+			}, 1000 * 60 * 60);
+		}
 
 		// 邪魔
 		//const lastUsed = miLocalStorage.getItem('lastUsed');
@@ -285,11 +321,11 @@ export async function mainBoot() {
 
 		// 自分の情報が更新されたとき
 		main.on('meUpdated', i => {
-			updateAccount(i);
+			updateAccountPartial(i);
 		});
 
 		main.on('readAllNotifications', () => {
-			updateAccount({
+			updateAccountPartial({
 				hasUnreadNotification: false,
 				unreadNotificationsCount: 0,
 			});
@@ -297,39 +333,39 @@ export async function mainBoot() {
 
 		main.on('unreadNotification', () => {
 			const unreadNotificationsCount = ($i?.unreadNotificationsCount ?? 0) + 1;
-			updateAccount({
+			updateAccountPartial({
 				hasUnreadNotification: true,
 				unreadNotificationsCount,
 			});
 		});
 
 		main.on('unreadMention', () => {
-			updateAccount({ hasUnreadMentions: true });
+			updateAccountPartial({ hasUnreadMentions: true });
 		});
 
 		main.on('readAllUnreadMentions', () => {
-			updateAccount({ hasUnreadMentions: false });
+			updateAccountPartial({ hasUnreadMentions: false });
 		});
 
 		main.on('unreadSpecifiedNote', () => {
-			updateAccount({ hasUnreadSpecifiedNotes: true });
+			updateAccountPartial({ hasUnreadSpecifiedNotes: true });
 		});
 
 		main.on('readAllUnreadSpecifiedNotes', () => {
-			updateAccount({ hasUnreadSpecifiedNotes: false });
+			updateAccountPartial({ hasUnreadSpecifiedNotes: false });
 		});
 
 		main.on('readAllAntennas', () => {
-			updateAccount({ hasUnreadAntenna: false });
+			updateAccountPartial({ hasUnreadAntenna: false });
 		});
 
 		main.on('unreadAntenna', () => {
-			updateAccount({ hasUnreadAntenna: true });
+			updateAccountPartial({ hasUnreadAntenna: true });
 			sound.playMisskeySfx('antenna');
 		});
 
 		main.on('readAllAnnouncements', () => {
-			updateAccount({ hasUnreadAnnouncement: false });
+			updateAccountPartial({ hasUnreadAnnouncement: false });
 		});
 
 		// 個人宛てお知らせが発行されたとき
diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index a28e7c25592d..e48b6ef781fa 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -4,112 +4,151 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div class="bcekxzvu _margin _panel">
-	<div class="target">
-		<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
-			<MkAvatar class="avatar" :user="report.targetUser" indicator/>
-			<div class="names">
-				<MkUserName class="name" :user="report.targetUser"/>
-				<MkAcct class="acct" :user="report.targetUser" style="display: block;"/>
-			</div>
-		</MkA>
-		<MkKeyValue>
-			<template #key>{{ i18n.ts.registeredDate }}</template>
-			<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
-		</MkKeyValue>
-	</div>
-	<div class="detail">
-		<div>
-			<Mfm :text="report.comment" :linkNavigationBehavior="'window'"/>
+<MkFolder>
+	<template #icon>
+		<i v-if="report.resolved && report.resolvedAs === 'accept'" class="ti ti-check" style="color: var(--MI_THEME-success)"></i>
+		<i v-else-if="report.resolved && report.resolvedAs === 'reject'" class="ti ti-x" style="color: var(--MI_THEME-error)"></i>
+		<i v-else-if="report.resolved" class="ti ti-slash"></i>
+		<i v-else class="ti ti-exclamation-circle" style="color: var(--MI_THEME-warn)"></i>
+	</template>
+	<template #label><MkAcct :user="report.targetUser"/> (by <MkAcct :user="report.reporter"/>)</template>
+	<template #caption>{{ report.comment }}</template>
+	<template #suffix><MkTime :time="report.createdAt"/></template>
+	<template #footer>
+		<div class="_buttons">
+			<template v-if="!report.resolved">
+				<MkButton @click="resolve('accept')"><i class="ti ti-check" style="color: var(--MI_THEME-success)"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts._abuseUserReport.accept }})</MkButton>
+				<MkButton @click="resolve('reject')"><i class="ti ti-x" style="color: var(--MI_THEME-error)"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts._abuseUserReport.reject }})</MkButton>
+				<MkButton @click="resolve(null)"><i class="ti ti-slash"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts.other }})</MkButton>
+			</template>
+			<template v-if="report.targetUser.host != null">
+				<MkButton :disabled="report.forwarded" primary @click="forward"><i class="ti ti-corner-up-right"></i> {{ i18n.ts._abuseUserReport.forward }}</MkButton>
+				<div v-tooltip:dialog="i18n.ts._abuseUserReport.forwardDescription" class="_button _help"><i class="ti ti-help-circle"></i></div>
+			</template>
+			<button class="_button" style="margin-left: auto; width: 34px;" @click="showMenu"><i class="ti ti-dots"></i></button>
 		</div>
-		<hr/>
-		<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></div>
+	</template>
+
+	<div class="_gaps_s">
+		<MkFolder :withSpacer="false">
+			<template #icon><MkAvatar :user="report.targetUser" style="width: 18px; height: 18px;"/></template>
+			<template #label>{{ i18n.ts.target }}: <MkAcct :user="report.targetUser"/></template>
+			<template #suffix>#{{ report.targetUserId.toUpperCase() }}</template>
+
+			<div style="container-type: inline-size;">
+				<RouterView :router="targetRouter"/>
+			</div>
+		</MkFolder>
+
+		<MkFolder :defaultOpen="true">
+			<template #icon><i class="ti ti-message-2"></i></template>
+			<template #label>{{ i18n.ts.details }}</template>
+			<div class="_gaps_s">
+				<Mfm :text="report.comment" :linkNavigationBehavior="'window'"/>
+			</div>
+		</MkFolder>
+
+		<MkFolder :withSpacer="false">
+			<template #icon><MkAvatar :user="report.reporter" style="width: 18px; height: 18px;"/></template>
+			<template #label>{{ i18n.ts.reporter }}: <MkAcct :user="report.reporter"/></template>
+			<template #suffix>#{{ report.reporterId.toUpperCase() }}</template>
+
+			<div style="container-type: inline-size;">
+				<RouterView :router="reporterRouter"/>
+			</div>
+		</MkFolder>
+
+		<MkFolder :defaultOpen="false">
+			<template #icon><i class="ti ti-message-2"></i></template>
+			<template #label>{{ i18n.ts.moderationNote }}</template>
+			<template #suffix>{{ moderationNote.length > 0 ? '...' : i18n.ts.none }}</template>
+			<div class="_gaps_s">
+				<MkTextarea v-model="moderationNote" manualSave>
+					<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
+				</MkTextarea>
+			</div>
+		</MkFolder>
+
 		<div v-if="report.assignee">
 			{{ i18n.ts.moderator }}:
 			<MkAcct :user="report.assignee"/>
 		</div>
-		<div><MkTime :time="report.createdAt"/></div>
-		<div class="action">
-			<MkSwitch v-model="forward" :disabled="report.targetUser.host == null || report.resolved">
-				{{ i18n.ts.forwardReport }}
-				<template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template>
-			</MkSwitch>
-			<MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton>
-		</div>
 	</div>
-</div>
+</MkFolder>
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { provide, ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { dateString } from '@/filters/date.js';
+import MkFolder from '@/components/MkFolder.vue';
+import RouterView from '@/components/global/RouterView.vue';
+import { useRouterFactory } from '@/router/supplier';
+import MkTextarea from '@/components/MkTextarea.vue';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 
 const props = defineProps<{
-	report: any;
+	report: Misskey.entities.AdminAbuseUserReportsResponse[number];
 }>();
 
 const emit = defineEmits<{
 	(ev: 'resolved', reportId: string): void;
 }>();
 
-const forward = ref(props.report.forwarded);
+const routerFactory = useRouterFactory();
+const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
+targetRouter.init();
+const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
+reporterRouter.init();
 
-function resolve() {
+const moderationNote = ref(props.report.moderationNote ?? '');
+
+watch(moderationNote, async () => {
+	os.apiWithDialog('admin/update-abuse-user-report', {
+		reportId: props.report.id,
+		moderationNote: moderationNote.value,
+	}).then(() => {
+	});
+});
+
+function resolve(resolvedAs) {
 	os.apiWithDialog('admin/resolve-abuse-user-report', {
-		forward: forward.value,
 		reportId: props.report.id,
+		resolvedAs,
 	}).then(() => {
 		emit('resolved', props.report.id);
 	});
 }
-</script>
 
-<style lang="scss" scoped>
-.bcekxzvu {
-	display: flex;
-
-	> .target {
-		width: 35%;
-		box-sizing: border-box;
-		text-align: left;
-		padding: 24px;
-		border-right: solid 1px var(--divider);
-
-		> .info {
-			display: flex;
-			box-sizing: border-box;
-			align-items: center;
-			padding: 14px;
-			border-radius: 8px;
-			--c: rgb(255 196 0 / 15%);
-			background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
-			background-size: 16px 16px;
-
-			> .avatar {
-				width: 42px;
-				height: 42px;
-			}
-
-			> .names {
-				margin-left: 0.3em;
-				padding: 0 8px;
-				flex: 1;
-
-				> .name {
-					font-weight: bold;
-				}
-			}
-		}
-	}
-
-	> .detail {
-		flex: 1;
-		padding: 24px;
-	}
+function forward() {
+	os.apiWithDialog('admin/forward-abuse-user-report', {
+		reportId: props.report.id,
+	}).then(() => {
+
+	});
 }
+
+function showMenu(ev: MouseEvent) {
+	os.popupMenu([{
+		icon: 'ti ti-id',
+		text: 'Copy ID',
+		action: () => {
+			copyToClipboard(props.report.id);
+		},
+	}, {
+		icon: 'ti ti-json',
+		text: 'Copy JSON',
+		action: () => {
+			copyToClipboard(JSON.stringify(props.report, null, '\t'));
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+</script>
+
+<style lang="scss" module>
 </style>
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index 796524fce949..0839955d9d93 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -32,9 +32,9 @@ misskeyApi('users/show', { userId: props.movedTo }).then(u => user.value = u);
 .root {
 	padding: 16px;
 	font-size: 90%;
-	background: var(--infoWarnBg);
-	color: var(--error);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-infoWarnBg);
+	color: var(--MI_THEME-error);
+	border-radius: var(--MI-radius);
 }
 
 .link {
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index 835efbd6cd71..c8fa6246e01a 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -193,12 +193,12 @@ tick();
 
 function calcColors() {
 	const computedStyle = getComputedStyle(document.documentElement);
-	const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark();
-	const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+	const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark();
+	const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
 	majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
 	//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 	sHandColor.value = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
-	mHandColor.value = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
+	mHandColor.value = tinycolor(computedStyle.getPropertyValue('--MI_THEME-fg')).toHexString();
 	hHandColor.value = accent;
 	nowColor.value = accent;
 }
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue
index f27694658ef0..3045a4758590 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.vue
+++ b/packages/frontend/src/components/MkAnnouncementDialog.vue
@@ -9,9 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.header">
 			<span :class="$style.icon">
 				<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
-				<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
-				<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
-				<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
+				<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
+				<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
+				<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
 			</span>
 			<span :class="$style.title">{{ announcement.title }}</span>
 		</div>
@@ -29,7 +29,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
-import { $i, updateAccount } from '@/account.js';
+import { $i, updateAccountPartial } from '@/account.js';
 
 const props = withDefaults(defineProps<{
 	announcement: Misskey.entities.Announcement;
@@ -51,7 +51,7 @@ async function ok() {
 
 	modal.value?.close();
 	misskeyApi('i/read-announcement', { announcementId: props.announcement.id });
-	updateAccount({
+	updateAccountPartial({
 		unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id),
 	});
 }
@@ -83,8 +83,8 @@ onMounted(() => {
 	min-width: 320px;
 	max-width: 480px;
 	box-sizing: border-box;
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 }
 
 .header {
diff --git a/packages/frontend/src/components/MkAntennaEditor.vue b/packages/frontend/src/components/MkAntennaEditor.vue
index cb7ee3d6ca38..e622d57f1ecf 100644
--- a/packages/frontend/src/components/MkAntennaEditor.vue
+++ b/packages/frontend/src/components/MkAntennaEditor.vue
@@ -160,7 +160,7 @@ async function deleteAntenna() {
 function addUser() {
 	os.selectUser({ includeSelf: true }).then(user => {
 		users.value = users.value.trim();
-		users.value += '\n@' + Misskey.acct.toString(user as any);
+		users.value += '\n@' + Misskey.acct.toString(user);
 		users.value = users.value.trim();
 	});
 }
@@ -170,6 +170,6 @@ function addUser() {
 .actions {
 	margin-top: 16px;
 	padding: 24px 0;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 18e8e7542e3f..e52ab5ccad15 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
 		</template>
 	</MkFolder>
-	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }]" :style="{ textAlign: c.align, backgroundColor: c.bgColor, color: c.fgColor, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }">
+	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }]" :style="containerStyle">
 		<template v-for="child in c.children" :key="child">
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size" :align="c.align"/>
 		</template>
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { Ref, ref } from 'vue';
+import { Ref, ref, computed } from 'vue';
 import * as os from '@/os.js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -97,6 +97,29 @@ function g(id) {
 	} as AsUiRoot;
 }
 
+const containerStyle = computed(() => {
+	if (c.type !== 'container') return undefined;
+
+	// width, color, styleのうち一つでも指定があれば、枠線がちゃんと表示されるようにwidthとstyleのデフォルト値を設定
+	// radiusは単に角を丸める用途もあるため除外
+	const isBordered = c.borderWidth ?? c.borderColor ?? c.borderStyle;
+
+	const border = isBordered ? {
+		borderWidth: c.borderWidth ?? '1px',
+		borderColor: c.borderColor ?? 'var(--MI_THEME-divider)',
+		borderStyle: c.borderStyle ?? 'solid',
+	} : undefined;
+
+	return {
+		textAlign: c.align,
+		backgroundColor: c.bgColor,
+		color: c.fgColor,
+		padding: c.padding ? `${c.padding}px` : 0,
+		borderRadius: (c.borderRadius ?? (c.rounded ? 8 : 0)) + 'px',
+		...border,
+	};
+});
+
 const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false);
 
 function onSwitchUpdate(v) {
@@ -142,7 +165,7 @@ function openPostForm() {
 }
 
 .postForm {
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 	border-radius: 8px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts b/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts
new file mode 100644
index 000000000000..0adc44e204d7
--- /dev/null
+++ b/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts
@@ -0,0 +1,7 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import MkAuthConfirm from './MkAuthConfirm.vue';
+void MkAuthConfirm;
diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue
new file mode 100644
index 000000000000..f78d2d38f024
--- /dev/null
+++ b/packages/frontend/src/components/MkAuthConfirm.vue
@@ -0,0 +1,450 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.wrapper">
+	<Transition
+		mode="out-in"
+		:enterActiveClass="$style.transition_enterActive"
+		:leaveActiveClass="$style.transition_leaveActive"
+		:enterFromClass="$style.transition_enterFrom"
+		:leaveToClass="$style.transition_leaveTo"
+
+		:inert="_waiting"
+	>
+		<div v-if="phase === 'accountSelect'" key="accountSelect" :class="$style.root" class="_gaps">
+			<div :class="$style.header" class="_gaps_s">
+				<div :class="$style.iconFallback">
+					<i class="ti ti-user"></i>
+				</div>
+				<div :class="$style.headerText">{{ i18n.ts.pleaseSelectAccount }}</div>
+			</div>
+			<div>
+				<div :class="$style.accountSelectorLabel">{{ i18n.ts.selectAccount }}</div>
+				<div :class="$style.accountSelectorList">
+					<template v-for="[id, user] in users">
+						<input :id="'account-' + id" v-model="selectedUser" type="radio" name="accountSelector" :value="id" :class="$style.accountSelectorRadio"/>
+						<label :for="'account-' + id" :class="$style.accountSelectorItem">
+							<MkAvatar :user="user" :class="$style.accountSelectorAvatar"/>
+							<div :class="$style.accountSelectorBody">
+								<MkUserName :user="user" :class="$style.accountSelectorName"/>
+								<MkAcct :user="user" :class="$style.accountSelectorAcct"/>
+							</div>
+						</label>
+					</template>
+					<button class="_button" :class="[$style.accountSelectorItem, $style.accountSelectorAddAccountRoot]" @click="clickAddAccount">
+						<div :class="[$style.accountSelectorAvatar, $style.accountSelectorAddAccountAvatar]">
+							<i class="ti ti-user-plus"></i>
+						</div>
+						<div :class="[$style.accountSelectorBody, $style.accountSelectorName]">{{ i18n.ts.addAccount }}</div>
+					</button>
+				</div>
+			</div>
+			<div class="_buttonsCenter">
+				<MkButton rounded gradate :disabled="selectedUser === null" @click="clickChooseAccount">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+			</div>
+		</div>
+		<div v-else-if="phase === 'consent'" key="consent" :class="$style.root" class="_gaps">
+			<div :class="$style.header" class="_gaps_s">
+				<img v-if="icon" :class="$style.icon" :src="getProxiedImageUrl(icon, 'preview')"/>
+				<div v-else :class="$style.iconFallback">
+					<i class="ti ti-apps"></i>
+				</div>
+				<div :class="$style.headerText">{{ name ? i18n.tsx._auth.shareAccess({ name }) : i18n.ts._auth.shareAccessAsk }}</div>
+			</div>
+			<div v-if="permissions && permissions.length > 0" class="_gaps_s" :class="$style.permissionRoot">
+				<div>{{ name ? i18n.tsx._auth.permission({ name }) : i18n.ts._auth.permissionAsk }}</div>
+				<div :class="$style.permissionListWrapper">
+					<ul :class="$style.permissionList">
+						<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
+					</ul>
+				</div>
+			</div>
+			<slot name="consentAdditionalInfo"></slot>
+			<div>
+				<div :class="$style.accountSelectorLabel">
+					{{ i18n.ts._auth.scopeUser }} <button class="_textButton" @click="clickBackToAccountSelect">{{ i18n.ts.switchAccount }}</button>
+				</div>
+				<div :class="$style.accountSelectorList">
+					<div :class="[$style.accountSelectorItem, $style.static]">
+						<MkAvatar :user="users.get(selectedUser!)!" :class="$style.accountSelectorAvatar"/>
+						<div :class="$style.accountSelectorBody">
+							<MkUserName :user="users.get(selectedUser!)!" :class="$style.accountSelectorName"/>
+							<MkAcct :user="users.get(selectedUser!)!" :class="$style.accountSelectorAcct"/>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="_buttonsCenter">
+				<MkButton rounded @click="clickCancel">{{ i18n.ts.reject }}</MkButton>
+				<MkButton rounded gradate @click="clickAccept">{{ i18n.ts.accept }}</MkButton>
+			</div>
+		</div>
+		<div v-else-if="phase === 'success'" key="success" :class="$style.root" class="_gaps_s">
+			<div :class="$style.header" class="_gaps_s">
+				<div :class="$style.iconFallback">
+					<i class="ti ti-check"></i>
+				</div>
+				<div :class="$style.headerText">{{ i18n.ts._auth.accepted }}</div>
+				<div :class="$style.headerTextSub">{{ i18n.ts._auth.pleaseGoBack }}</div>
+			</div>
+		</div>
+		<div v-else-if="phase === 'denied'" key="denied" :class="$style.root" class="_gaps_s">
+			<div :class="$style.header" class="_gaps_s">
+				<div :class="$style.iconFallback">
+					<i class="ti ti-x"></i>
+				</div>
+				<div :class="$style.headerText">{{ i18n.ts._auth.denied }}</div>
+			</div>
+		</div>
+		<div v-else-if="phase === 'failed'" key="failed" :class="$style.root" class="_gaps_s">
+			<div :class="$style.header" class="_gaps_s">
+				<div :class="$style.iconFallback">
+					<i class="ti ti-x"></i>
+				</div>
+				<div :class="$style.headerText">{{ i18n.ts.somethingHappened }}</div>
+			</div>
+		</div>
+	</Transition>
+	<div v-if="_waiting" :class="$style.waitingRoot">
+		<MkLoading/>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue';
+import * as Misskey from 'misskey-js';
+
+import MkButton from '@/components/MkButton.vue';
+
+import { $i, getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+
+const props = defineProps<{
+	name?: string;
+	icon?: string;
+	permissions?: (typeof Misskey.permissions[number])[];
+	manualWaiting?: boolean;
+	waitOnDeny?: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'accept', token: string): void;
+	(ev: 'deny', token: string): void;
+}>();
+
+const waiting = ref(true);
+const _waiting = computed(() => waiting.value || props.manualWaiting);
+const phase = ref<'accountSelect' | 'consent' | 'success' | 'denied' | 'failed'>('accountSelect');
+
+const selectedUser = ref<string | null>(null);
+
+const users = ref(new Map<string, Misskey.entities.UserDetailed & { token: string; }>());
+
+async function init() {
+	waiting.value = true;
+
+	users.value.clear();
+
+	if ($i) {
+		users.value.set($i.id, $i);
+	}
+
+	const accounts = await getAccounts();
+
+	const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id));
+
+	if (accountIdsToFetch.length > 0) {
+		const usersRes = await misskeyApi('users/show', {
+			userIds: accountIdsToFetch,
+		});
+
+		for (const user of usersRes) {
+			if (users.value.has(user.id)) continue;
+
+			users.value.set(user.id, {
+				...user,
+				token: accounts.find(a => a.id === user.id)!.token,
+			});
+		}
+	}
+
+	waiting.value = false;
+}
+
+init();
+
+function clickAddAccount(ev: MouseEvent) {
+	selectedUser.value = null;
+
+	os.popupMenu([{
+		text: i18n.ts.existingAccount,
+		action: () => {
+			getAccountWithSigninDialog().then(async (res) => {
+				if (res != null) {
+					os.success();
+					await init();
+					if (users.value.has(res.id)) {
+						selectedUser.value = res.id;
+					}
+				}
+			});
+		},
+	}, {
+		text: i18n.ts.createAccount,
+		action: () => {
+			getAccountWithSignupDialog().then(async (res) => {
+				if (res != null) {
+					os.success();
+					await init();
+					if (users.value.has(res.id)) {
+						selectedUser.value = res.id;
+					}
+				}
+			});
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
+function clickChooseAccount() {
+	if (selectedUser.value === null) return;
+
+	phase.value = 'consent';
+}
+
+function clickBackToAccountSelect() {
+	selectedUser.value = null;
+	phase.value = 'accountSelect';
+}
+
+function clickCancel() {
+	if (selectedUser.value === null) return;
+
+	const user = users.value.get(selectedUser.value)!;
+
+	const token = user.token;
+
+	if (props.waitOnDeny) {
+		waiting.value = true;
+	}
+	emit('deny', token);
+}
+
+async function clickAccept() {
+	if (selectedUser.value === null) return;
+
+	const user = users.value.get(selectedUser.value)!;
+
+	const token = user.token;
+
+	waiting.value = true;
+	emit('accept', token);
+}
+
+function showUI(state: 'success' | 'denied' | 'failed') {
+	phase.value = state;
+	waiting.value = false;
+}
+
+defineExpose({
+	showUI,
+});
+</script>
+
+<style lang="scss" module>
+.transition_enterActive,
+.transition_leaveActive {
+	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
+}
+.transition_enterFrom {
+	opacity: 0;
+	transform: translateX(50px);
+}
+.transition_leaveTo {
+	opacity: 0;
+	transform: translateX(-50px);
+}
+
+.wrapper {
+	overflow-x: hidden;
+	overflow-x: clip;
+
+	position: relative;
+	width: 100%;
+	height: 100%;
+}
+
+.waitingRoot {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background-color: color-mix(in srgb, var(--MI_THEME-panel), transparent 50%);
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	z-index: 1;
+	cursor: wait;
+}
+
+.root {
+	position: relative;
+	box-sizing: border-box;
+	width: 100%;
+	padding: 48px 24px;
+}
+
+.header {
+	margin: 0 auto;
+	max-width: 320px;
+}
+
+.icon,
+.iconFallback {
+	display: block;
+	margin: 0 auto;
+	width: 54px;
+	height: 54px;
+}
+
+.icon {
+	border-radius: 50%;
+	border: 1px solid var(--MI_THEME-divider);
+	background-color: #fff;
+	object-fit: contain;
+}
+
+.iconFallback {
+	border-radius: 50%;
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
+	text-align: center;
+	line-height: 54px;
+	font-size: 18px;
+}
+
+.headerText,
+.headerTextSub {
+	text-align: center;
+	word-break: normal;
+	word-break: auto-phrase;
+}
+
+.headerText {
+	font-size: 16px;
+	font-weight: 700;
+}
+
+.permissionRoot {
+	padding: 16px;
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-bg);
+}
+
+.permissionListWrapper {
+	max-height: 350px;
+	overflow-y: auto;
+	padding: 12px;
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-panel);
+}
+
+.permissionList {
+	margin: 0 0 0 1.5em;
+	padding: 0;
+	font-size: 90%;
+}
+
+.accountSelectorLabel {
+	font-size: 0.85em;
+	opacity: 0.7;
+	margin-bottom: 8px;
+}
+
+.accountSelectorList {
+	border-radius: var(--MI-radius);
+	border: 1px solid var(--MI_THEME-divider);
+	overflow: hidden;
+	overflow: clip;
+}
+
+.accountSelectorRadio {
+	position: absolute;
+	clip: rect(0, 0, 0, 0);
+	pointer-events: none;
+
+	&:focus-visible + .accountSelectorItem {
+		outline: 2px solid var(--MI_THEME-accent);
+		outline-offset: -4px;
+	}
+
+	&:checked:focus-visible + .accountSelectorItem {
+		outline-color: #fff;
+	}
+
+	&:checked + .accountSelectorItem {
+		background: var(--MI_THEME-accent);
+		color: #fff;
+	}
+}
+
+.accountSelectorItem {
+	display: flex;
+	align-items: center;
+	padding: 8px;
+	font-size: 14px;
+	-webkit-tap-highlight-color: transparent;
+	cursor: pointer;
+
+	&:hover {
+		background: var(--MI_THEME-buttonHoverBg);
+	}
+
+	&.static {
+		cursor: unset;
+
+		&:hover {
+			background: none;
+		}
+	}
+}
+
+.accountSelectorAddAccountRoot {
+	width: 100%;
+}
+
+.accountSelectorBody {
+	padding: 0 8px;
+	min-width: 0;
+}
+
+.accountSelectorAvatar {
+	width: 45px;
+	height: 45px;
+}
+
+.accountSelectorAddAccountAvatar {
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
+	font-size: 16px;
+	line-height: 45px;
+	text-align: center;
+	border-radius: 50%;
+}
+
+.accountSelectorName {
+	display: block;
+	font-weight: bold;
+}
+
+.accountSelectorAcct {
+	opacity: 0.5;
+}
+</style>
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index f54799136937..0ea4566d4e6a 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -407,16 +407,16 @@ onBeforeUnmount(() => {
 	text-overflow: ellipsis;
 
 	&:hover {
-		background: var(--X3);
+		background: var(--MI_THEME-X3);
 	}
 
 	&[data-selected='true'] {
-		background: var(--accent);
+		background: var(--MI_THEME-accent);
 		color: #fff !important;
 	}
 
 	&:active {
-		background: var(--accentDarken);
+		background: var(--MI_THEME-accentDarken);
 		color: #fff !important;
 	}
 }
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index fb9c036fbcfc..311facb4aaf1 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -129,7 +129,7 @@ function onMousedown(evt: MouseEvent): void {
 	font-size: 95%;
 	box-shadow: none;
 	text-decoration: none;
-	background: var(--buttonBg);
+	background: var(--MI_THEME-buttonBg);
 	border-radius: 5px;
 	overflow: clip;
 	box-sizing: border-box;
@@ -140,11 +140,11 @@ function onMousedown(evt: MouseEvent): void {
 	}
 
 	&:not(:disabled):hover {
-		background: var(--buttonHoverBg);
+		background: var(--MI_THEME-buttonHoverBg);
 	}
 
 	&:not(:disabled):active {
-		background: var(--buttonHoverBg);
+		background: var(--MI_THEME-buttonHoverBg);
 	}
 
 	&.small {
@@ -167,15 +167,15 @@ function onMousedown(evt: MouseEvent): void {
 
 	&.primary {
 		font-weight: bold;
-		color: var(--fgOnAccent) !important;
-		background: var(--accent);
+		color: var(--MI_THEME-fgOnAccent) !important;
+		background: var(--MI_THEME-accent);
 
 		&:not(:disabled):hover {
-			background: hsl(from var(--accent) h s calc(l + 5));
+			background: hsl(from var(--MI_THEME-accent) h s calc(l + 5));
 		}
 
 		&:not(:disabled):active {
-			background: hsl(from var(--accent) h s calc(l + 5));
+			background: hsl(from var(--MI_THEME-accent) h s calc(l + 5));
 		}
 	}
 
@@ -216,19 +216,20 @@ function onMousedown(evt: MouseEvent): void {
 
 	&.gradate {
 		font-weight: bold;
-		color: var(--fgOnAccent) !important;
-		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		color: var(--MI_THEME-fgOnAccent) !important;
+		background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 
 		&:not(:disabled):hover {
-			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+			background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 		}
 
 		&:not(:disabled):active {
-			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+			background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 		}
 	}
 
 	&.danger {
+		font-weight: bold;
 		color: #ff2a2a;
 
 		&.primary {
@@ -246,7 +247,7 @@ function onMousedown(evt: MouseEvent): void {
 	}
 
 	&:disabled {
-		opacity: 0.7;
+		opacity: 0.5;
 	}
 
 	&:focus-visible {
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index c5b6e0caede7..264cf9af069b 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -10,6 +10,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div id="mcaptcha__widget-container" class="m-captcha-style"></div>
 		<div ref="captchaEl"></div>
 	</div>
+	<div v-if="props.provider == 'testcaptcha'" style="background: #eee; border: solid 1px #888; padding: 8px; color: #000; max-width: 320px; display: flex; gap: 10px; align-items: center; box-shadow: 2px 2px 6px #0004; border-radius: 4px;">
+		<img src="/client-assets/testcaptcha.png" style="width: 60px; height: 60px; "/>
+		<div v-if="testcaptchaPassed">
+			<div style="color: green;">Test captcha passed!</div>
+		</div>
+		<div v-else>
+			<div style="font-size: 13px; margin-bottom: 4px;">Type "ai-chan-kawaii" to pass captcha</div>
+			<input v-model="testcaptchaInput" data-cy-testcaptcha-input/>
+			<button type="button" data-cy-testcaptcha-submit @click="testcaptchaSubmit">Submit</button>
+		</div>
+	</div>
 	<div v-else ref="captchaEl"></div>
 </div>
 </template>
@@ -29,7 +40,7 @@ export type Captcha = {
 	getResponse(id: string): string;
 };
 
-export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
+export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha' | 'testcaptcha';
 
 type CaptchaContainer = {
 	readonly [_ in CaptchaProvider]?: Captcha;
@@ -54,12 +65,16 @@ const available = ref(false);
 
 const captchaEl = shallowRef<HTMLDivElement | undefined>();
 
+const testcaptchaInput = ref('');
+const testcaptchaPassed = ref(false);
+
 const variable = computed(() => {
 	switch (props.provider) {
 		case 'hcaptcha': return 'hcaptcha';
 		case 'recaptcha': return 'grecaptcha';
 		case 'turnstile': return 'turnstile';
 		case 'mcaptcha': return 'mcaptcha';
+		case 'testcaptcha': return 'testcaptcha';
 	}
 });
 
@@ -71,6 +86,7 @@ const src = computed(() => {
 		case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit';
 		case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
 		case 'mcaptcha': return null;
+		case 'testcaptcha': return null;
 	}
 });
 
@@ -78,7 +94,7 @@ const scriptId = computed(() => `script-${props.provider}`);
 
 const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
 
-if (loaded || props.provider === 'mcaptcha') {
+if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') {
 	available.value = true;
 } else if (src.value !== null) {
 	(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), {
@@ -91,6 +107,8 @@ if (loaded || props.provider === 'mcaptcha') {
 
 function reset() {
 	if (captcha.value.reset) captcha.value.reset();
+	testcaptchaPassed.value = false;
+	testcaptchaInput.value = '';
 }
 
 async function requestRender() {
@@ -99,8 +117,8 @@ async function requestRender() {
 			sitekey: props.sitekey,
 			theme: defaultStore.state.darkMode ? 'dark' : 'light',
 			callback: callback,
-			'expired-callback': callback,
-			'error-callback': callback,
+			'expired-callback': () => callback(undefined),
+			'error-callback': () => callback(undefined),
 		});
 	} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
 		const { default: Widget } = await import('@mcaptcha/vanilla-glue');
@@ -127,6 +145,12 @@ function onReceivedMessage(message: MessageEvent) {
 	}
 }
 
+function testcaptchaSubmit() {
+	testcaptchaPassed.value = testcaptchaInput.value === 'ai-chan-kawaii';
+	callback(testcaptchaPassed.value ? 'testcaptcha-passed' : undefined);
+	if (!testcaptchaPassed.value) testcaptchaInput.value = '';
+}
+
 onMounted(() => {
 	if (available.value) {
 		window.addEventListener('message', onReceivedMessage);
diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue
index 35dc3ad4bf83..d4e4f6179a83 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.vue
+++ b/packages/frontend/src/components/MkChannelFollowButton.vue
@@ -68,9 +68,9 @@ async function onClick() {
 	position: relative;
 	display: inline-block;
 	font-weight: bold;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	background: transparent;
-	border: solid 1px var(--accent);
+	border: solid 1px var(--MI_THEME-accent);
 	padding: 0;
 	height: 31px;
 	font-size: 16px;
@@ -99,17 +99,17 @@ async function onClick() {
 	}
 
 	&.active {
-		color: var(--fgOnAccent);
-		background: var(--accent);
+		color: var(--MI_THEME-fgOnAccent);
+		background: var(--MI_THEME-accent);
 
 		&:hover {
-			background: var(--accentLighten);
-			border-color: var(--accentLighten);
+			background: var(--MI_THEME-accentLighten);
+			border-color: var(--MI_THEME-accentLighten);
 		}
 
 		&:active {
-			background: var(--accentDarken);
-			border-color: var(--accentDarken);
+			background: var(--MI_THEME-accentDarken);
+			border-color: var(--MI_THEME-accentDarken);
 		}
 	}
 
diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue
index 3c0874a1eb76..c470042b79ac 100644
--- a/packages/frontend/src/components/MkChannelPreview.vue
+++ b/packages/frontend/src/components/MkChannelPreview.vue
@@ -47,11 +47,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
 
 const props = defineProps<{
-	channel: Record<string, any>;
+	channel: Misskey.entities.Channel;
 }>();
 
 const getLastReadedAt = (): number | null => {
@@ -100,7 +101,7 @@ const bannerStyle = computed(() => {
 			height: 100%;
 			border-radius: inherit;
 			pointer-events: none;
-			box-shadow: inset 0 0 0 2px var(--focus);
+			box-shadow: inset 0 0 0 2px var(--MI_THEME-focus);
 		}
 	}
 
@@ -117,7 +118,7 @@ const bannerStyle = computed(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+			background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 		}
 
 		> .name {
@@ -148,7 +149,7 @@ const bannerStyle = computed(() => {
 			bottom: 16px;
 			left: 16px;
 			background: rgba(0, 0, 0, 0.7);
-			color: var(--warn);
+			color: var(--MI_THEME-warn);
 			border-radius: 6px;
 			font-weight: bold;
 			font-size: 1em;
@@ -167,7 +168,7 @@ const bannerStyle = computed(() => {
 
 	> footer {
 		padding: 12px 16px;
-		border-top: solid 0.5px var(--divider);
+		border-top: solid 0.5px var(--MI_THEME-divider);
 
 		> span {
 			opacity: 0.7;
@@ -213,8 +214,8 @@ const bannerStyle = computed(() => {
 	top: 0;
 	right: 0;
 	transform: translate(25%, -25%);
-	background-color: var(--accent);
-	border: solid var(--bg) 4px;
+	background-color: var(--MI_THEME-accent);
+	border: solid var(--MI_THEME-bg) 4px;
 	border-radius: 100%;
 	width: 1.5rem;
 	height: 1.5rem;
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index 57d325b11ad3..d05f4921f68e 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -863,8 +863,8 @@ onMounted(() => {
 	left: 0;
 	width: 100%;
 	height: 100%;
-	-webkit-backdrop-filter: var(--blur, blur(12px));
-	backdrop-filter: var(--blur, blur(12px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(12px));
+	backdrop-filter: var(--MI-blur, blur(12px));
 	display: flex;
 	justify-content: center;
 	align-items: center;
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
index 6eb200978470..574cde9da40a 100644
--- a/packages/frontend/src/components/MkChartLegend.vue
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -53,11 +53,11 @@ defineExpose({
 		> .item {
 			font-size: 85%;
 			padding: 4px 12px 4px 8px;
-			border: solid 1px var(--divider);
+			border: solid 1px var(--MI_THEME-divider);
 			border-radius: 999px;
 
 			&:hover {
-				border-color: var(--inputBorderHover);
+				border-color: var(--MI_THEME-inputBorderHover);
 			}
 
 			&.disabled {
diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue
index dd550733cb21..5b09ec90dd3c 100644
--- a/packages/frontend/src/components/MkClipPreview.vue
+++ b/packages/frontend/src/components/MkClipPreview.vue
@@ -49,13 +49,13 @@ const remaining = computed(() => {
 		outline: none;
 
 		.root {
-			box-shadow: inset 0 0 0 2px var(--focus);
+			box-shadow: inset 0 0 0 2px var(--MI_THEME-focus);
 		}
 	}
 
 	&:hover {
 		text-decoration: none;
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
@@ -65,7 +65,7 @@ const remaining = computed(() => {
 
 .divider {
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 
 .description {
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index c0e7df5dac27..0d7a67eaec2a 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -77,7 +77,7 @@ watch(() => props.lang, (to) => {
 	margin: .5em 0;
 	overflow: auto;
 	border-radius: 8px;
-	border: 1px solid var(--divider);
+	border: 1px solid var(--MI_THEME-divider);
 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 
 	color: var(--shiki-fallback);
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index 716dd9267872..cb82bfd98bff 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -71,7 +71,7 @@ function copy() {
 .codeBlockFallbackRoot {
 	display: block;
 	overflow-wrap: anywhere;
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 	padding: 1em;
 	margin: .5em 0;
 	overflow: auto;
@@ -94,8 +94,8 @@ function copy() {
 	border-radius: 8px;
 	padding: 24px;
 	margin-top: 4px;
-	color: var(--fg);
-	background: var(--bg);
+	color: var(--MI_THEME-fg);
+	background: var(--MI_THEME-bg);
 }
 
 .codePlaceholderContainer {
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue
index afd9132a12fb..5bf2301e7260 100644
--- a/packages/frontend/src/components/MkCodeEditor.vue
+++ b/packages/frontend/src/components/MkCodeEditor.vue
@@ -140,7 +140,7 @@ watch(v, newValue => {
 .caption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
@@ -160,17 +160,17 @@ watch(v, newValue => {
 	margin: 0;
 	border-radius: 6px;
 	padding: 0;
-	color: var(--fg);
-	border: solid 1px var(--panel);
+	color: var(--MI_THEME-fg);
+	border: solid 1px var(--MI_THEME-panel);
 	transition: border-color 0.1s ease-out;
 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 	&:hover {
-		border-color: var(--inputBorderHover) !important;
+		border-color: var(--MI_THEME-inputBorderHover) !important;
 	}
 }
 
 .focused.codeEditorRoot {
-	border-color: var(--accent) !important;
+	border-color: var(--MI_THEME-accent) !important;
 	border-radius: 6px;
 }
 
@@ -196,7 +196,7 @@ watch(v, newValue => {
 	resize: none;
 	text-align: left;
 	color: transparent;
-	caret-color: var(--fg);
+	caret-color: var(--MI_THEME-fg);
 	background-color: transparent;
 	border: 0;
 	border-radius: 6px;
@@ -211,6 +211,6 @@ watch(v, newValue => {
 }
 
 .textarea::selection {
-	color: var(--bg);
+	color: var(--MI_THEME-bg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue
index 6add80d1bc13..04b6e54108b5 100644
--- a/packages/frontend/src/components/MkCodeInline.vue
+++ b/packages/frontend/src/components/MkCodeInline.vue
@@ -18,7 +18,7 @@ const props = defineProps<{
 	display: inline-block;
 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 	overflow-wrap: anywhere;
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 	padding: .1em;
 	border-radius: .3em;
 }
diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue
index f5c580789b56..55a32664de9d 100644
--- a/packages/frontend/src/components/MkColorInput.vue
+++ b/packages/frontend/src/components/MkColorInput.vue
@@ -60,7 +60,7 @@ const onInput = () => {
 .caption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
@@ -72,8 +72,8 @@ const onInput = () => {
 
 	&.focused {
 		> .inputCore {
-			border-color: var(--accent) !important;
-			//box-shadow: 0 0 0 4px var(--focus);
+			border-color: var(--MI_THEME-accent) !important;
+			//box-shadow: 0 0 0 4px var(--MI_THEME-focus);
 		}
 	}
 
@@ -98,9 +98,9 @@ const onInput = () => {
 	font: inherit;
 	font-weight: normal;
 	font-size: 1em;
-	color: var(--fg);
-	background: var(--panel);
-	border: solid 1px var(--panel);
+	color: var(--MI_THEME-fg);
+	background: var(--MI_THEME-panel);
+	border: solid 1px var(--MI_THEME-panel);
 	border-radius: 6px;
 	outline: none;
 	box-shadow: none;
@@ -108,7 +108,7 @@ const onInput = () => {
 	transition: border-color 0.1s ease-out;
 
 	&:hover {
-		border-color: var(--inputBorderHover) !important;
+		border-color: var(--MI_THEME-inputBorderHover) !important;
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index 8ad653a0bfcc..f513795c5619 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -64,26 +64,30 @@ const showBody = ref(props.expanded);
 const ignoreOmit = ref(false);
 const omitted = ref(false);
 
-function enter(el) {
+function enter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = 0;
+	el.style.height = '0';
 	el.offsetHeight; // reflow
-	el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px';
+	el.style.height = `${Math.min(elementHeight, props.maxHeight ?? Infinity)}px`;
 }
 
-function afterEnter(el) {
-	el.style.height = null;
+function afterEnter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	el.style.height = '';
 }
 
-function leave(el) {
+function leave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = elementHeight + 'px';
+	el.style.height = `${elementHeight}px`;
 	el.offsetHeight; // reflow
-	el.style.height = 0;
+	el.style.height = '0';
 }
 
-function afterLeave(el) {
-	el.style.height = null;
+function afterLeave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	el.style.height = '';
 }
 
 const calcOmit = () => {
@@ -165,11 +169,11 @@ onUnmounted(() => {
 
 .header {
 	position: sticky;
-	top: var(--stickyTop, 0px);
+	top: var(--MI-stickyTop, 0px);
 	left: 0;
-	color: var(--panelHeaderFg);
-	background: var(--panelHeaderBg);
-	border-bottom: solid 0.5px var(--panelHeaderDivider);
+	color: var(--MI_THEME-panelHeaderFg);
+	background: var(--MI_THEME-panelHeaderBg);
+	border-bottom: solid 0.5px var(--MI_THEME-panelHeaderDivider);
 	z-index: 2;
 	line-height: 1.4em;
 }
@@ -201,7 +205,7 @@ onUnmounted(() => {
 }
 
 .content {
-	--stickyTop: 0px;
+	--MI-stickyTop: 0px;
 
 	&.omitted {
 		position: relative;
@@ -216,11 +220,11 @@ onUnmounted(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+			background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
-				background: var(--panel);
+				background: var(--MI_THEME-panel);
 				padding: 6px 10px;
 				font-size: 0.8em;
 				border-radius: 999px;
@@ -229,7 +233,7 @@ onUnmounted(() => {
 
 			&:hover {
 				> .fadeLabel {
-					background: var(--panelHighlight);
+					background: var(--MI_THEME-panelHighlight);
 				}
 			}
 		}
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 8ea8fa6cf3a3..f51fefa0c077 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
 import MkMenu from './MkMenu.vue';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import contains from '@/scripts/contains.js';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 2e1e92cbdfee..0186cfc2c086 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:withOkButton="true"
 	@close="cancel()"
 	@ok="ok()"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.cropImage }}</template>
 	<template #default="{ width, height }">
@@ -125,7 +125,7 @@ onMounted(() => {
 	const computedStyle = getComputedStyle(document.documentElement);
 
 	const selection = cropper.getCropperSelection()!;
-	selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+	selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
 	selection.aspectRatio = props.aspectRatio;
 	selection.initialAspectRatio = props.aspectRatio;
 	selection.outlined = true;
@@ -170,8 +170,8 @@ onMounted(() => {
 		display: flex;
 		align-items: center;
 		justify-content: center;
-		-webkit-backdrop-filter: var(--blur, blur(10px));
-		backdrop-filter: var(--blur, blur(10px));
+		-webkit-backdrop-filter: var(--MI-blur, blur(10px));
+		backdrop-filter: var(--MI-blur, blur(10px));
 		background: rgba(0, 0, 0, 0.5);
 	}
 
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
index c7f128872929..ecbee864dc21 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
+<MkModalWindow ref="dialogEl" @close="cancel()" @closed="emit('closed')">
 	<template #header>:{{ emoji.name }}:</template>
 	<template #default>
 		<MkSpacer>
@@ -85,8 +85,8 @@ function cancel() {
 .emojiImgWrapper {
   max-width: 100%;
   height: 40cqh;
-  background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px);
-  border-radius: var(--radius);
+  background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-X5) 8px, var(--MI_THEME-X5) 14px);
+  border-radius: var(--MI-radius);
   margin: auto;
   overflow-y: hidden;
 }
@@ -101,8 +101,8 @@ function cancel() {
   display: inline-block;
   word-break: break-all;
   padding: 3px 10px;
-  background-color: var(--X5);
-  border: solid 1px var(--divider);
-  border-radius: var(--radius);
+  background-color: var(--MI_THEME-X5);
+  border: solid 1px var(--MI_THEME-divider);
+  border-radius: var(--MI-radius);
 }
 </style>
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index f16981716c88..9c75f91cb207 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -9,6 +9,7 @@ import MkAd from '@/components/global/MkAd.vue';
 import { isDebuggerEnabled, stackTraceInstances } from '@/debug.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { instance } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 import { MisskeyEntity } from '@/types/date-separated-list.js';
 
@@ -43,9 +44,9 @@ export default defineComponent({
 	setup(props, { slots, expose }) {
 		const $style = useCssModule(); // カスタムレンダラなので使っても大丈夫
 
-		function getDateText(time: string) {
-			const date = new Date(time).getDate();
-			const month = new Date(time).getMonth() + 1;
+		function getDateText(dateInstance: Date) {
+			const date = dateInstance.getDate();
+			const month = dateInstance.getMonth() + 1;
 			return i18n.tsx.monthAndDay({
 				month: month.toString(),
 				day: date.toString(),
@@ -62,9 +63,16 @@ export default defineComponent({
 			})[0];
 			if (el.key == null && item.id) el.key = item.id;
 
+			const date = new Date(item.createdAt);
+			const nextDate = props.items[i + 1] ? new Date(props.items[i + 1].createdAt) : null;
+
 			if (
 				i !== props.items.length - 1 &&
-				new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()
+				nextDate != null && (
+					date.getFullYear() !== nextDate.getFullYear() ||
+					date.getMonth() !== nextDate.getMonth() ||
+					date.getDate() !== nextDate.getDate()
+				)
 			) {
 				const separator = h('div', {
 					class: $style['separator'],
@@ -78,12 +86,12 @@ export default defineComponent({
 						h('i', {
 							class: `ti ti-chevron-up ${$style['date-1-icon']}`,
 						}),
-						getDateText(item.createdAt),
+						getDateText(date),
 					]),
 					h('span', {
 						class: $style['date-2'],
 					}, [
-						getDateText(props.items[i + 1].createdAt),
+						getDateText(nextDate),
 						h('i', {
 							class: `ti ti-chevron-down ${$style['date-2-icon']}`,
 						}),
@@ -92,11 +100,13 @@ export default defineComponent({
 
 				return [el, separator];
 			} else {
-				if (props.ad && item._shouldInsertAd_) {
-					return [h(MkAd, {
+				if (props.ad && instance.ads.length > 0 && item._shouldInsertAd_) {
+					return [h('div', {
 						key: item.id + ':ad',
+						class: $style['ad-wrapper'],
+					}, [h(MkAd, {
 						prefer: ['horizontal', 'horizontal-big'],
-					}), el];
+					})]), el];
 				} else {
 					return el;
 				}
@@ -118,14 +128,14 @@ export default defineComponent({
 			return children;
 		};
 
-		function onBeforeLeave(element: Element) {
-			const el = element as HTMLElement;
+		function onBeforeLeave(el: Element) {
+			if (!(el instanceof HTMLElement)) return;
 			el.style.top = `${el.offsetTop}px`;
 			el.style.left = `${el.offsetLeft}px`;
 		}
 
-		function onLeaveCancelled(element: Element) {
-			const el = element as HTMLElement;
+		function onLeaveCancelled(el: Element) {
+			if (!(el instanceof HTMLElement)) return;
 			el.style.top = '';
 			el.style.left = '';
 		}
@@ -175,7 +185,7 @@ export default defineComponent({
 	}
 
 	&:not(.date-separated-list-nogap) > *:not(:last-child) {
-		margin-bottom: var(--margin);
+		margin-bottom: var(--MI-margin);
 	}
 }
 
@@ -187,7 +197,7 @@ export default defineComponent({
 		box-shadow: none;
 
 		&:not(:last-child) {
-			border-bottom: solid 0.5px var(--divider);
+			border-bottom: solid 0.5px var(--MI_THEME-divider);
 		}
 	}
 }
@@ -228,7 +238,7 @@ export default defineComponent({
 	line-height: 32px;
 	text-align: center;
 	font-size: 12px;
-	color: var(--dateLabelFg);
+	color: var(--MI_THEME-dateLabelFg);
 }
 
 .date-1 {
@@ -246,5 +256,11 @@ export default defineComponent({
 .date-2-icon {
 	margin-left: 8px;
 }
+
+.ad-wrapper {
+	padding: 8px;
+	background-size: auto auto;
+	background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px);
+}
 </style>
 
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 16cf5b1b75c6..b095a1cd4a5d 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</template>
 		</MkSelect>
 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
-			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
+			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason != null" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
 			<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
 		</div>
 		<div v-if="actions" :class="$style.buttons">
@@ -98,7 +98,7 @@ const props = withDefaults(defineProps<{
 		text: string;
 		primary?: boolean,
 		danger?: boolean,
-		callback: (...args: any[]) => void;
+		callback: (...args: unknown[]) => void;
 	}[];
 	showOkButton?: boolean;
 	showCancelButton?: boolean;
@@ -184,7 +184,7 @@ function onInputKeydown(evt: KeyboardEvent) {
 	max-width: 480px;
 	box-sizing: border-box;
 	text-align: center;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 16px;
 }
 
@@ -206,15 +206,15 @@ function onInputKeydown(evt: KeyboardEvent) {
 }
 
 .type_success {
-	color: var(--success);
+	color: var(--MI_THEME-success);
 }
 
 .type_error {
-	color: var(--error);
+	color: var(--MI_THEME-error);
 }
 
 .type_warning {
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 }
 
 .title {
diff --git a/packages/frontend/src/components/MkDivider.vue b/packages/frontend/src/components/MkDivider.vue
index e4e3af99e429..f72f09138337 100644
--- a/packages/frontend/src/components/MkDivider.vue
+++ b/packages/frontend/src/components/MkDivider.vue
@@ -27,6 +27,6 @@ defineProps<{
 
 <style scoped lang="scss">
 .default {
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue
index 098be07a8ced..0e0da64750b5 100644
--- a/packages/frontend/src/components/MkDonation.vue
+++ b/packages/frontend/src/components/MkDonation.vue
@@ -65,12 +65,12 @@ function neverShow() {
 .root {
 	position: fixed;
 	z-index: v-bind(zIndex);
-	bottom: var(--margin);
+	bottom: var(--MI-margin);
 	left: 0;
 	right: 0;
 	margin: auto;
 	box-sizing: border-box;
-	width: calc(100% - (var(--margin) * 2));
+	width: calc(100% - (var(--MI-margin) * 2));
 	max-width: 500px;
 	display: flex;
 }
@@ -79,7 +79,7 @@ function neverShow() {
 	text-align: center;
 	padding-top: 25px;
 	width: 100px;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 @media (max-width: 500px) {
 	.icon {
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index 90284890a5df..e45c3bd9ce3a 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -148,14 +148,14 @@ function onDragend() {
 	}
 
 	&.isSelected {
-		background: var(--accent);
+		background: var(--MI_THEME-accent);
 
 		&:hover {
-			background: var(--accentLighten);
+			background: var(--MI_THEME-accentLighten);
 		}
 
 		&:active {
-			background: var(--accentDarken);
+			background: var(--MI_THEME-accentDarken);
 		}
 
 		> .label {
@@ -244,7 +244,7 @@ function onDragend() {
 	font-size: 0.8em;
 	text-align: center;
 	word-break: break-all;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	overflow: hidden;
 }
 </style>
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index d6dfaf34e58f..44e3b59aded4 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, defineAsyncComponent, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { MenuItem } from '@/types/menu.js';
 
 const props = withDefaults(defineProps<{
 	folder: Misskey.entities.DriveFolder;
@@ -313,7 +313,7 @@ function onContextmenu(ev: MouseEvent) {
 	position: relative;
 	padding: 8px;
 	height: 64px;
-	background: var(--driveFolderBg);
+	background: var(--MI_THEME-driveFolderBg);
 	border-radius: 4px;
 	cursor: pointer;
 
@@ -326,7 +326,7 @@ function onContextmenu(ev: MouseEvent) {
 			right: -4px;
 			bottom: -4px;
 			left: -4px;
-			border: 2px dashed var(--focus);
+			border: 2px dashed var(--MI_THEME-focus);
 			border-radius: 4px;
 		}
 	}
@@ -345,13 +345,13 @@ function onContextmenu(ev: MouseEvent) {
 		width: 18px;
 		height: 18px;
 		background: #fff;
-		border: solid 2px var(--divider);
+		border: solid 2px var(--MI_THEME-divider);
 		border-radius: 4px;
 		box-sizing: border-box;
 
 		&.checked {
-			border-color: var(--accent);
-			background: var(--accent);
+			border-color: var(--MI_THEME-accent);
+			background: var(--MI_THEME-accent);
 
 			&::after {
 				content: "\ea5e";
@@ -368,14 +368,13 @@ function onContextmenu(ev: MouseEvent) {
 	}
 
 	&:hover {
-		background: var(--accentedBg);
+		background: var(--MI_THEME-accentedBg);
 	}
 }
 
 .name {
 	margin: 0;
 	font-size: 0.9em;
-	color: var(--desktopDriveFolderFg);
 }
 
 .icon {
@@ -388,6 +387,5 @@ function onContextmenu(ev: MouseEvent) {
 	margin: 4px 4px;
 	font-size: 0.8em;
 	text-align: right;
-	color: var(--desktopDriveFolderFg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index dbb491706961..8be6d6f53de9 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -157,7 +157,12 @@ const ilFilesObserver = new IntersectionObserver(
 	(entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(),
 );
 
+const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt');
+
 watch(folder, () => emit('cd', folder.value));
+watch(sortModeSelect, () => {
+	fetch();
+});
 
 function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) {
 	addFile(file, true);
@@ -193,7 +198,7 @@ function onStreamDriveFolderDeleted(folderId: string) {
 	removeFolder(folderId);
 }
 
-function onDragover(ev: DragEvent): any {
+function onDragover(ev: DragEvent) {
 	if (!ev.dataTransfer) return;
 
 	// ドラッグ元が自分自身の所有するアイテムだったら
@@ -238,7 +243,7 @@ function onDragleave() {
 	draghover.value = false;
 }
 
-function onDrop(ev: DragEvent): any {
+function onDrop(ev: DragEvent) {
 	draghover.value = false;
 
 	if (!ev.dataTransfer) return;
@@ -327,7 +332,7 @@ function createFolder() {
 		title: i18n.ts.createFolder,
 		placeholder: i18n.ts.folderName,
 	}).then(({ canceled, result: name }) => {
-		if (canceled) return;
+		if (canceled || name == null) return;
 		misskeyApi('drive/folders/create', {
 			name: name,
 			parentId: folder.value ? folder.value.id : undefined,
@@ -558,6 +563,7 @@ async function fetch() {
 		folderId: folder.value ? folder.value.id : null,
 		type: props.type,
 		limit: filesMax + 1,
+		sort: sortModeSelect.value,
 	}).then(fetchedFiles => {
 		if (fetchedFiles.length === filesMax + 1) {
 			moreFiles.value = true;
@@ -607,6 +613,7 @@ function fetchMoreFiles() {
 		type: props.type,
 		untilId: files.value.at(-1)?.id,
 		limit: max + 1,
+		sort: sortModeSelect.value,
 	}).then(files => {
 		if (files.length === max + 1) {
 			moreFiles.value = true;
@@ -620,7 +627,9 @@ function fetchMoreFiles() {
 }
 
 function getMenu() {
-	const menu: MenuItem[] = [{
+	const menu: MenuItem[] = [];
+
+	menu.push({
 		type: 'switch',
 		text: i18n.ts.keepOriginalUploading,
 		ref: keepOriginal,
@@ -638,19 +647,62 @@ function getMenu() {
 	}, { type: 'divider' }, {
 		text: folder.value ? folder.value.name : i18n.ts.drive,
 		type: 'label',
-	}, folder.value ? {
-		text: i18n.ts.renameFolder,
-		icon: 'ti ti-forms',
-		action: () => { if (folder.value) renameFolder(folder.value); },
-	} : undefined, folder.value ? {
-		text: i18n.ts.deleteFolder,
-		icon: 'ti ti-trash',
-		action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
-	} : undefined, {
+	});
+
+	menu.push({
+		type: 'parent',
+		text: i18n.ts.sort,
+		icon: 'ti ti-arrows-sort',
+		children: [{
+			text: `${i18n.ts.registeredDate} (${i18n.ts.descendingOrder})`,
+			icon: 'ti ti-sort-descending-letters',
+			action: () => { sortModeSelect.value = '+createdAt'; },
+			active: sortModeSelect.value === '+createdAt',
+		}, {
+			text: `${i18n.ts.registeredDate} (${i18n.ts.ascendingOrder})`,
+			icon: 'ti ti-sort-ascending-letters',
+			action: () => { sortModeSelect.value = '-createdAt'; },
+			active: sortModeSelect.value === '-createdAt',
+		}, {
+			text: `${i18n.ts.size} (${i18n.ts.descendingOrder})`,
+			icon: 'ti ti-sort-descending-letters',
+			action: () => { sortModeSelect.value = '+size'; },
+			active: sortModeSelect.value === '+size',
+		}, {
+			text: `${i18n.ts.size} (${i18n.ts.ascendingOrder})`,
+			icon: 'ti ti-sort-ascending-letters',
+			action: () => { sortModeSelect.value = '-size'; },
+			active: sortModeSelect.value === '-size',
+		}, {
+			text: `${i18n.ts.name} (${i18n.ts.descendingOrder})`,
+			icon: 'ti ti-sort-descending-letters',
+			action: () => { sortModeSelect.value = '+name'; },
+			active: sortModeSelect.value === '+name',
+		}, {
+			text: `${i18n.ts.name} (${i18n.ts.ascendingOrder})`,
+			icon: 'ti ti-sort-ascending-letters',
+			action: () => { sortModeSelect.value = '-name'; },
+			active: sortModeSelect.value === '-name',
+		}],
+	});
+
+	if (folder.value) {
+		menu.push({
+			text: i18n.ts.renameFolder,
+			icon: 'ti ti-forms',
+			action: () => { if (folder.value) renameFolder(folder.value); },
+		}, {
+			text: i18n.ts.deleteFolder,
+			icon: 'ti ti-trash',
+			action: () => { deleteFolder(folder.value as Misskey.entities.DriveFolder); },
+		});
+	}
+
+	menu.push({
 		text: i18n.ts.createFolder,
 		icon: 'ti ti-folder-plus',
 		action: () => { createFolder(); },
-	}];
+	});
 
 	return menu;
 }
@@ -713,7 +765,7 @@ onBeforeUnmount(() => {
 	box-sizing: border-box;
 	overflow: auto;
 	font-size: 0.9em;
-	box-shadow: 0 1px 0 var(--divider);
+	box-shadow: 0 1px 0 var(--MI_THEME-divider);
 	user-select: none;
 }
 
@@ -760,7 +812,7 @@ onBeforeUnmount(() => {
 .main {
 	flex: 1;
 	overflow: auto;
-	padding: var(--margin);
+	padding: var(--MI-margin);
 	user-select: none;
 
 	&.fetching {
@@ -807,7 +859,7 @@ onBeforeUnmount(() => {
 	top: 38px;
 	width: 100%;
 	height: calc(100% - 38px);
-	border: dashed 2px var(--focus);
+	border: dashed 2px var(--MI_THEME-focus);
 	pointer-events: none;
 }
 </style>
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue
index 2c47a709709b..3410a915c300 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.vue
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue
@@ -4,7 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div ref="thumbnail" :class="$style.root">
+<div
+	ref="thumbnail"
+	:class="[
+		$style.root,
+		{ [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive },
+	]"
+>
 	<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
 	<i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i>
 	<i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i>
@@ -27,6 +33,7 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 const props = defineProps<{
 	file: Misskey.entities.DriveFile;
 	fit: 'cover' | 'contain';
+	highlightWhenSensitive?: boolean;
 }>();
 
 const is = computed(() => {
@@ -62,11 +69,23 @@ const isThumbnailAvailable = computed(() => {
 .root {
 	position: relative;
 	display: flex;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 8px;
 	overflow: clip;
 }
 
+.sensitiveHighlight::after {
+	content: "";
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	pointer-events: none;
+	border-radius: inherit;
+	box-shadow: inset 0 0 0 4px var(--MI_THEME-warn);
+}
+
 .iconSub {
 	position: absolute;
 	width: 30%;
diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
index 7bfdfbc20aa6..6e9eb75920a2 100644
--- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
+++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:scroll="false"
 	:withOkButton="false"
 	@close="cancel()"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 >
 	<template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template>
 
@@ -90,6 +90,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script setup lang="ts">
 import { shallowRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
+import { url } from '@@/js/config.js';
+import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
 import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 
@@ -103,10 +105,8 @@ import MkInfo from '@/components/MkInfo.vue';
 
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { url } from '@@/js/config.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { normalizeEmbedParams, getEmbedCode } from '@/scripts/get-embed-code.js';
-import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
 
 const emit = defineEmits<{
 	(ev: 'ok'): void;
@@ -306,7 +306,9 @@ onUnmounted(() => {
 
 .embedCodeGenPreviewRoot {
 	position: relative;
-	background-color: var(--bg);
+	background-color: var(--MI_THEME-bg);
+	background-size: auto auto;
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px);
 	cursor: not-allowed;
 }
 
@@ -379,8 +381,8 @@ onUnmounted(() => {
 
 .embedCodeGenResultHeadingIcon {
 	margin: 0 auto;
-	background-color: var(--accentedBg);
-	color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 	text-align: center;
 	height: 64px;
 	width: 64px;
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue
index fca7aa2f4ec1..b418ed3ae62c 100644
--- a/packages/frontend/src/components/MkEmojiPicker.section.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.section.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <!-- このコンポーネントの要素のclassは親から利用されるのでむやみに弄らないこと -->
 <!-- フォルダの中にはカスタム絵文字だけ(Unicode絵文字もこっち) -->
-<section v-if="!hasChildSection" v-panel style="border-radius: 6px; border-bottom: 0.5px solid var(--divider);">
+<section v-if="!hasChildSection" v-panel style="border-radius: 6px; border-bottom: 0.5px solid var(--MI_THEME-divider);">
 	<header class="_acrylic" @click="shown = !shown">
 		<i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ti ti-icons"></i>:{{ emojis.length }})
 	</header>
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 </section>
 <!-- フォルダの中にはカスタム絵文字やフォルダがある -->
-<section v-else v-panel style="border-radius: 6px; border-bottom: 0.5px solid var(--divider);">
+<section v-else v-panel style="border-radius: 6px; border-bottom: 0.5px solid var(--MI_THEME-divider);">
 	<header class="_acrylic" @click="shown = !shown">
 		<i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ti ti-folder ti-fw"></i>:{{ customEmojiTree?.length }} <i class="ti ti-icons ti-fw"></i>:{{ emojis.length }})
 	</header>
@@ -90,7 +90,7 @@ function computeButtonTitle(ev: MouseEvent): void {
 	elm.title = getEmojiName(emoji);
 }
 
-function nestedChosen(emoji: any, ev: MouseEvent) {
+function nestedChosen(emoji: string, ev: MouseEvent) {
 	emit('chosen', emoji, ev);
 }
 </script>
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 5ba175fc3504..8187d991e771 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -409,7 +409,7 @@ function computeButtonTitle(ev: MouseEvent): void {
 	elm.title = getEmojiName(emoji);
 }
 
-function chosen(emoji: any, ev?: MouseEvent) {
+function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) {
 	const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined;
 	if (el) {
 		const rect = el.getBoundingClientRect();
@@ -426,7 +426,7 @@ function chosen(emoji: any, ev?: MouseEvent) {
 	// 最近使った絵文字更新
 	if (!pinned.value?.includes(key)) {
 		let recents = defaultStore.state.recentlyUsedEmojis;
-		recents = recents.filter((emoji: any) => emoji !== key);
+		recents = recents.filter((emoji) => emoji !== key);
 		recents.unshift(key);
 		defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32));
 	}
@@ -580,7 +580,7 @@ defineExpose({
 
 						&:disabled {
 							cursor: not-allowed;
-							background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+							background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
 							opacity: 1;
 
 							> .emoji {
@@ -611,10 +611,11 @@ defineExpose({
 						width: auto;
 						height: auto;
 						min-width: 0;
+						padding: 0;
 
 						&:disabled {
 							cursor: not-allowed;
-							background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+							background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
 							opacity: 1;
 
 							> .emoji {
@@ -637,7 +638,7 @@ defineExpose({
 		outline: none;
 		border: none;
 		background: transparent;
-		color: var(--fg);
+		color: var(--MI_THEME-fg);
 
 		&:not(:focus):not(.filled) {
 			margin-bottom: env(safe-area-inset-bottom, 0px);
@@ -646,7 +647,7 @@ defineExpose({
 		&:not(.filled) {
 			order: 1;
 			z-index: 2;
-			box-shadow: 0px -1px 0 0px var(--divider);
+			box-shadow: 0px -1px 0 0px var(--MI_THEME-divider);
 		}
 	}
 
@@ -657,11 +658,11 @@ defineExpose({
 		> .tab {
 			flex: 1;
 			height: 38px;
-			border-top: solid 0.5px var(--divider);
+			border-top: solid 0.5px var(--MI_THEME-divider);
 
 			&.active {
-				border-top: solid 1px var(--accent);
-				color: var(--accent);
+				border-top: solid 1px var(--MI_THEME-accent);
+				color: var(--MI_THEME-accent);
 			}
 		}
 	}
@@ -680,7 +681,7 @@ defineExpose({
 		> .group {
 			&:not(.index) {
 				padding: 4px 0 8px 0;
-				border-top: solid 0.5px var(--divider);
+				border-top: solid 0.5px var(--MI_THEME-divider);
 			}
 
 			> header {
@@ -707,7 +708,7 @@ defineExpose({
 				cursor: pointer;
 
 				&:hover {
-					color: var(--accent);
+					color: var(--MI_THEME-accent);
 				}
 			}
 
@@ -717,7 +718,7 @@ defineExpose({
 
 				> .item {
 					position: relative;
-					padding: 0;
+					padding: 0 3px;
 					width: var(--eachSize);
 					height: var(--eachSize);
 					contain: strict;
@@ -729,13 +730,13 @@ defineExpose({
 					}
 
 					&:active {
-						background: var(--accent);
+						background: var(--MI_THEME-accent);
 						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
 					}
 
 					&:disabled {
 						cursor: not-allowed;
-						background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+						background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%);
 						opacity: 1;
 
 						> .emoji {
@@ -756,7 +757,7 @@ defineExpose({
 			}
 
 			&.result {
-				border-bottom: solid 0.5px var(--divider);
+				border-bottom: solid 0.5px var(--MI_THEME-divider);
 
 				&:empty {
 					display: none;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 7e1ffbfa9e4c..21c712b441fb 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	ref="modal"
 	v-slot="{ type, maxHeight }"
 	:zPriority="'middle'"
-	:preferType="defaultStore.state.emojiPickerUseDrawerForMobile === false ? 'popup' : 'auto'"
+	:preferType="defaultStore.state.emojiPickerStyle"
 	:hasInteractionWithOtherFocusTrappedEls="true"
 	:transparentBg="true"
 	:manualShowing="manualShowing"
diff --git a/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
new file mode 100644
index 000000000000..6763f7c54692
--- /dev/null
+++ b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts
@@ -0,0 +1,83 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { StoryObj } from '@storybook/vue3';
+import MkExtensionInstaller from './MkExtensionInstaller.vue';
+import lightTheme from '@@/themes/_light.json5';
+
+export const Plugin = {
+	render(args) {
+		return {
+			components: {
+				MkExtensionInstaller,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkExtensionInstaller v-bind="props" />',
+		};
+	},
+	args: {
+		extension: {
+			type: 'plugin',
+			raw: '"do nothing"',
+			meta: {
+				name: 'do nothing plugin',
+				version: '1.0',
+				author: 'syuilo and misskey-project',
+				description: 'a plugin that does nothing',
+				permissions: ['read:account'],
+				config: {
+					'doNothing': true,
+				},
+			},
+		},
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkExtensionInstaller>;
+
+export const Theme = {
+	render(args) {
+		return {
+			components: {
+				MkExtensionInstaller,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkExtensionInstaller v-bind="props" />',
+		};
+	},
+	args: {
+		extension: {
+			type: 'theme',
+			raw: JSON.stringify(lightTheme),
+			meta: lightTheme,
+		},
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkExtensionInstaller>;
diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue
new file mode 100644
index 000000000000..d59b20435e31
--- /dev/null
+++ b/packages/frontend/src/components/MkExtensionInstaller.vue
@@ -0,0 +1,146 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps_m" :class="$style.extInstallerRoot">
+	<div :class="$style.extInstallerIconWrapper">
+		<i v-if="isPlugin" class="ti ti-plug"></i>
+		<i v-else-if="isTheme" class="ti ti-palette"></i>
+		<!-- 拡張用? -->
+		<i v-else class="ti ti-download"></i>
+	</div>
+	<h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].title }}</h2>
+	<div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div>
+	<MkInfo v-if="isPlugin" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo>
+	<FormSection>
+		<template #label>{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].metaTitle }}</template>
+		<div class="_gaps_s">
+			<FormSplit>
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.name }}</template>
+					<template #value>{{ extension.meta.name }}</template>
+				</MkKeyValue>
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.author }}</template>
+					<template #value>{{ extension.meta.author }}</template>
+				</MkKeyValue>
+			</FormSplit>
+			<MkKeyValue v-if="isPlugin">
+				<template #key>{{ i18n.ts.description }}</template>
+				<template #value>{{ extension.meta.description ?? i18n.ts.none }}</template>
+			</MkKeyValue>
+			<MkKeyValue v-if="isPlugin">
+				<template #key>{{ i18n.ts.version }}</template>
+				<template #value>{{ extension.meta.version }}</template>
+			</MkKeyValue>
+			<MkKeyValue v-if="isPlugin">
+				<template #key>{{ i18n.ts.permission }}</template>
+				<template #value>
+					<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
+						<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
+					</ul>
+					<template v-else>{{ i18n.ts.none }}</template>
+				</template>
+			</MkKeyValue>
+			<MkKeyValue v-if="isTheme">
+				<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
+				<template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template>
+			</MkKeyValue>
+			<MkFolder>
+				<template #icon><i class="ti ti-code"></i></template>
+				<template #label>{{ i18n.ts._plugin.viewSource }}</template>
+
+				<MkCode :code="extension.raw"/>
+			</MkFolder>
+		</div>
+	</FormSection>
+	<slot name="additionalInfo"/>
+	<div class="_buttonsCenter">
+		<MkButton primary @click="emits('confirm')"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+export type Extension = {
+	type: 'plugin';
+	raw: string;
+	meta: {
+		name: string;
+		version: string;
+		author: string;
+		description?: string;
+		permissions?: string[];
+		config?: Record<string, unknown>;
+	};
+} | {
+	type: 'theme';
+	raw: string;
+	meta: {
+		name: string;
+		author: string;
+		base?: 'light' | 'dark';
+	};
+};
+</script>
+<script lang="ts" setup>
+import { computed } from 'vue';
+import MkButton from '@/components/MkButton.vue';
+import FormSection from '@/components/form/section.vue';
+import FormSplit from '@/components/form/split.vue';
+import MkCode from '@/components/MkCode.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import { i18n } from '@/i18n.js';
+
+const isPlugin = computed(() => props.extension.type === 'plugin');
+const isTheme = computed(() => props.extension.type === 'theme');
+
+const props = defineProps<{
+	extension: Extension;
+}>();
+
+const emits = defineEmits<{
+	(ev: 'confirm'): void;
+}>();
+</script>
+
+<style lang="scss" module>
+.extInstallerRoot {
+	border-radius: var(--MI-radius);
+	background: var(--MI_THEME-panel);
+	padding: 1.5rem;
+}
+
+.extInstallerIconWrapper {
+	width: 48px;
+	height: 48px;
+	font-size: 24px;
+	line-height: 48px;
+	text-align: center;
+	border-radius: 50%;
+	margin-left: auto;
+	margin-right: auto;
+
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
+}
+
+.extInstallerTitle {
+	font-size: 1.2rem;
+	text-align: center;
+	margin: 0;
+}
+
+.extInstallerNormDesc {
+	text-align: center;
+}
+
+.extInstallerKVList {
+	margin-top: 0;
+	margin-bottom: 0;
+}
+</style>
diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue
index 30822ef655bd..d5d32ebb285e 100644
--- a/packages/frontend/src/components/MkFileListForAdmin.vue
+++ b/packages/frontend/src/components/MkFileListForAdmin.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			class="file _button"
 		>
 			<div v-if="file.isSensitive" class="sensitive-label">{{ i18n.ts.sensitive }}</div>
-			<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
+			<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain" :highlightWhenSensitive="true"/>
 			<div v-if="viewMode === 'list'" class="body">
 				<div>
 					<small style="opacity: 0.7;">{{ file.name }}</small>
@@ -66,7 +66,7 @@ const props = defineProps<{
 			align-items: center;
 
 			&:hover {
-				color: var(--accent);
+				color: var(--MI_THEME-accent);
 			}
 
 			> .thumbnail {
diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue
index 8a2a4386240a..b7278ac742fb 100644
--- a/packages/frontend/src/components/MkFlashPreview.vue
+++ b/packages/frontend/src/components/MkFlashPreview.vue
@@ -36,7 +36,7 @@ const props = defineProps<{
 
 	&:hover {
 		text-decoration: none;
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 
 	&:focus-visible {
@@ -83,7 +83,6 @@ const props = defineProps<{
 			> p {
 				display: inline-block;
 				margin: 0;
-				color: var(--urlPreviewInfo);
 				font-size: 0.8em;
 				line-height: 16px;
 				vertical-align: top;
@@ -92,7 +91,7 @@ const props = defineProps<{
 	}
 
 	&:global(.gray) {
-		--c: var(--bg);
+		--c: var(--MI_THEME-bg);
 		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
 		background-size: 16px 16px;
 	}
diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue
index f10d58b38a62..fb1b5220fbe6 100644
--- a/packages/frontend/src/components/MkFoldableSection.vue
+++ b/packages/frontend/src/components/MkFoldableSection.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div ref="rootEl" :class="$style.root">
-	<header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody">
+	<header :class="$style.header" class="_button" @click="showBody = !showBody">
 		<div :class="$style.title"><div><slot name="header"></slot></div></div>
 		<div :class="$style.divider"></div>
 		<button class="_button" :class="$style.button">
@@ -32,21 +32,23 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { onMounted, ref, shallowRef, watch } from 'vue';
-import tinycolor from 'tinycolor2';
 import { miLocalStorage } from '@/local-storage.js';
 import { defaultStore } from '@/store.js';
+import { getBgColor } from '@/scripts/get-bg-color.js';
 
 const miLocalStoragePrefix = 'ui:folder:' as const;
 
 const props = withDefaults(defineProps<{
 	expanded?: boolean;
-	persistKey?: string;
+	persistKey?: string | null;
 }>(), {
 	expanded: true,
+	persistKey: null,
 });
 
-const rootEl = shallowRef<HTMLDivElement>();
-const bg = ref<string>();
+const rootEl = shallowRef<HTMLElement>();
+const parentBg = ref<string | null>(null);
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
 const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded);
 
 watch(showBody, () => {
@@ -55,47 +57,34 @@ watch(showBody, () => {
 	}
 });
 
-function enter(element: Element) {
-	const el = element as HTMLElement;
+function enter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementHeight = el.getBoundingClientRect().height;
 	el.style.height = '0';
 	el.offsetHeight; // reflow
-	el.style.height = elementHeight + 'px';
+	el.style.height = `${elementHeight}px`;
 }
 
-function afterEnter(element: Element) {
-	const el = element as HTMLElement;
-	el.style.height = 'unset';
+function afterEnter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	el.style.height = '';
 }
 
-function leave(element: Element) {
-	const el = element as HTMLElement;
+function leave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = elementHeight + 'px';
+	el.style.height = `${elementHeight}px`;
 	el.offsetHeight; // reflow
 	el.style.height = '0';
 }
 
-function afterLeave(element: Element) {
-	const el = element as HTMLElement;
-	el.style.height = 'unset';
+function afterLeave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	el.style.height = '';
 }
 
 onMounted(() => {
-	function getParentBg(el?: HTMLElement | null): string {
-		if (el == null || el.tagName === 'BODY') return 'var(--bg)';
-		const background = el.style.background || el.style.backgroundColor;
-		if (background) {
-			return background;
-		} else {
-			return getParentBg(el.parentElement);
-		}
-	}
-
-	const rawBg = getParentBg(rootEl.value);
-	const _bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
-	_bg.setAlpha(0.85);
-	bg.value = _bg.toRgbString();
+	parentBg.value = getBgColor(rootEl.value?.parentElement);
 });
 </script>
 
@@ -118,9 +107,10 @@ onMounted(() => {
 	position: relative;
 	z-index: 10;
 	position: sticky;
-	top: var(--stickyTop, 0px);
-	-webkit-backdrop-filter: var(--blur, blur(8px));
-	backdrop-filter: var(--blur, blur(20px));
+	top: var(--MI-stickyTop, 0px);
+	-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+	backdrop-filter: var(--MI-blur, blur(20px));
+	background-color: color(from v-bind("parentBg ?? 'var(--bg)'") srgb r g b / 0.85);
 }
 
 .title {
@@ -134,7 +124,7 @@ onMounted(() => {
 	flex: 1;
 	margin: auto;
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 
 .button {
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index f805be7b5726..7bdc06a8b410 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -38,9 +38,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 			>
 				<KeepAlive>
 					<div v-show="opened">
-						<MkSpacer :marginMin="14" :marginMax="22">
+						<MkSpacer v-if="withSpacer" :marginMin="14" :marginMax="22">
 							<slot></slot>
 						</MkSpacer>
+						<div v-else>
+							<slot></slot>
+						</div>
+						<div v-if="$slots.footer" :class="$style.footer">
+							<slot name="footer"></slot>
+						</div>
 					</div>
 				</KeepAlive>
 			</Transition>
@@ -50,51 +56,49 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, shallowRef, ref } from 'vue';
+import { nextTick, onMounted, ref, shallowRef } from 'vue';
 import { defaultStore } from '@/store.js';
+import { getBgColor } from '@/scripts/get-bg-color.js';
 
 const props = withDefaults(defineProps<{
 	defaultOpen?: boolean;
 	maxHeight?: number | null;
+	withSpacer?: boolean;
 }>(), {
 	defaultOpen: false,
 	maxHeight: null,
+	withSpacer: true,
 });
 
-const getBgColor = (el: HTMLElement) => {
-	const style = window.getComputedStyle(el);
-	if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
-		return style.backgroundColor;
-	} else {
-		return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
-	}
-};
-
 const rootEl = shallowRef<HTMLElement>();
 const bgSame = ref(false);
 const opened = ref(props.defaultOpen);
 const openedAtLeastOnce = ref(props.defaultOpen);
 
-function enter(el) {
+function enter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = 0;
+	el.style.height = '0';
 	el.offsetHeight; // reflow
-	el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px';
+	el.style.height = `${Math.min(elementHeight, props.maxHeight ?? Infinity)}px`;
 }
 
-function afterEnter(el) {
-	el.style.height = null;
+function afterEnter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	el.style.height = '';
 }
 
-function leave(el) {
+function leave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = elementHeight + 'px';
+	el.style.height = `${elementHeight}px`;
 	el.offsetHeight; // reflow
-	el.style.height = 0;
+	el.style.height = '0';
 }
 
-function afterLeave(el) {
-	el.style.height = null;
+function afterLeave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	el.style.height = '';
 }
 
 function toggle() {
@@ -109,8 +113,8 @@ function toggle() {
 
 onMounted(() => {
 	const computedStyle = getComputedStyle(document.documentElement);
-	const parentBg = getBgColor(rootEl.value!.parentElement!);
-	const myBg = computedStyle.getPropertyValue('--panel');
+	const parentBg = getBgColor(rootEl.value?.parentElement) ?? 'transparent';
+	const myBg = computedStyle.getPropertyValue('--MI_THEME-panel');
 	bgSame.value = parentBg === myBg;
 });
 </script>
@@ -136,15 +140,15 @@ onMounted(() => {
 	width: 100%;
 	box-sizing: border-box;
 	padding: 9px 12px 9px 12px;
-	background: var(--buttonBg);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	background: var(--MI_THEME-folderHeaderBg);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 	border-radius: 6px;
 	transition: border-radius 0.3s;
 
 	&:hover {
 		text-decoration: none;
-		background: var(--buttonHoverBg);
+		background: var(--MI_THEME-folderHeaderHoverBg);
 	}
 
 	&:focus-within {
@@ -152,8 +156,8 @@ onMounted(() => {
 	}
 
 	&.active {
-		color: var(--accent);
-		background: var(--buttonHoverBg);
+		color: var(--MI_THEME-accent);
+		background: var(--MI_THEME-folderHeaderHoverBg);
 	}
 
 	&.opened {
@@ -167,7 +171,7 @@ onMounted(() => {
 }
 
 .headerLower {
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 	font-size: .85em;
 	padding-left: 4px;
 }
@@ -201,13 +205,13 @@ onMounted(() => {
 }
 
 .headerTextSub {
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 	font-size: .85em;
 }
 
 .headerRight {
 	margin-left: auto;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 	white-space: nowrap;
 }
 
@@ -216,12 +220,26 @@ onMounted(() => {
 }
 
 .body {
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 0 0 6px 6px;
 	container-type: inline-size;
 
 	&.bgSame {
-		background: var(--bg);
+		background: var(--MI_THEME-bg);
 	}
 }
+
+.footer {
+	position: sticky !important;
+	z-index: 1;
+	bottom: var(--MI-stickyBottom, 0px);
+	left: 0;
+	padding: 12px;
+	background: var(--MI_THEME-acrylicBg);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	background-size: auto auto;
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--MI_THEME-panel) 5px, var(--MI_THEME-panel) 10px);
+	border-radius: 0 0 6px 6px;
+}
 </style>
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index 370d5f75c56e..cc0717590790 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
 		</template>
 		<template v-else-if="isFollowing">
-			<span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.youFollowing }}</span><i class="ti ti-minus"></i>
 		</template>
 		<template v-else-if="!isFollowing && user.isLocked">
 			<span v-if="full" :class="$style.text">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i>
@@ -37,13 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onBeforeUnmount, onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { host } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
-import { host } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
 
@@ -80,7 +80,7 @@ function onFollowChange(user: Misskey.entities.UserDetailed) {
 }
 
 async function onClick() {
-	pleaseLogin(undefined, { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` });
+	pleaseLogin({ openOnRemote: { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` } });
 
 	wait.value = true;
 
@@ -165,8 +165,8 @@ onBeforeUnmount(() => {
 	position: relative;
 	display: inline-block;
 	font-weight: bold;
-	color: var(--fgOnWhite);
-	border: solid 1px var(--accent);
+	color: var(--MI_THEME-fgOnWhite);
+	border: solid 1px var(--MI_THEME-accent);
 	padding: 0;
 	height: 31px;
 	font-size: 16px;
@@ -201,17 +201,17 @@ onBeforeUnmount(() => {
 	}
 
 	&.active {
-		color: var(--fgOnAccent);
-		background: var(--accent);
+		color: var(--MI_THEME-fgOnAccent);
+		background: var(--MI_THEME-accent);
 
 		&:hover {
-			background: var(--accentLighten);
-			border-color: var(--accentLighten);
+			background: var(--MI_THEME-accentLighten);
+			border-color: var(--MI_THEME-accentLighten);
 		}
 
 		&:active {
-			background: var(--accentDarken);
-			border-color: var(--accentDarken);
+			background: var(--MI_THEME-accentDarken);
+			border-color: var(--MI_THEME-accentDarken);
 		}
 	}
 
diff --git a/packages/frontend/src/components/MkFormDialog.file.vue b/packages/frontend/src/components/MkFormDialog.file.vue
index 936059423657..ecb6cf882be2 100644
--- a/packages/frontend/src/components/MkFormDialog.file.vue
+++ b/packages/frontend/src/components/MkFormDialog.file.vue
@@ -66,6 +66,6 @@ function selectButton(ev: MouseEvent) {
 <style module>
 .fileNotSelected {
 	font-weight: 700;
-	color: var(--infoWarnFg);
+	color: var(--MI_THEME-infoWarnFg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index 124f114111c2..a639eae208dd 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@click="cancel()"
 	@ok="ok()"
 	@close="cancel()"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 >
 	<template #header>
 		{{ title }}
diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue
new file mode 100644
index 000000000000..f409f6ce50a5
--- /dev/null
+++ b/packages/frontend/src/components/MkFormFooter.vue
@@ -0,0 +1,49 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+	<div :class="$style.text">{{ i18n.tsx.thereAreNChanges({ n: form.modifiedCount.value }) }}</div>
+	<div style="margin-left: auto;" class="_buttons">
+		<MkButton danger rounded @click="form.discard"><i class="ti ti-x"></i> {{ i18n.ts.discard }}</MkButton>
+		<MkButton primary rounded @click="form.save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import MkButton from './MkButton.vue';
+import { i18n } from '@/i18n.js';
+
+const props = defineProps<{
+	form: {
+		modifiedCount: {
+			value: number;
+		};
+		discard: () => void;
+		save: () => void;
+	};
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	display: flex;
+	align-items: center;
+}
+
+.text {
+	color: var(--MI_THEME-warn);
+	font-size: 90%;
+	animation: modified-blink 2s infinite;
+}
+
+@keyframes modified-blink {
+	0% { opacity: 1; }
+	50% { opacity: 0.5; }
+	100% { opacity: 1; }
+}
+</style>
diff --git a/packages/frontend/src/components/MkFukidashi.vue b/packages/frontend/src/components/MkFukidashi.vue
new file mode 100644
index 000000000000..8b1c56fca47f
--- /dev/null
+++ b/packages/frontend/src/components/MkFukidashi.vue
@@ -0,0 +1,100 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+	:class="[
+		$style.root,
+		tail === 'left' ? $style.left : $style.right,
+		negativeMargin === true && $style.negativeMargin,
+		shadow === true && $style.shadow,
+	]"
+>
+	<div :class="$style.bg">
+		<svg v-if="tail !== 'none'" :class="$style.tail" version="1.1" viewBox="0 0 14.597 14.58" xmlns="http://www.w3.org/2000/svg">
+			<g transform="translate(-173.71 -87.184)">
+				<path d="m188.19 87.657c-1.469 2.3218-3.9315 3.8312-6.667 4.0865-2.2309-1.7379-4.9781-2.6816-7.8061-2.6815h-5.1e-4v12.702h12.702v-5.1e-4c2e-5 -1.9998-0.47213-3.9713-1.378-5.754 2.0709-1.6834 3.2732-4.2102 3.273-6.8791-6e-5 -0.49375-0.0413-0.98662-0.1235-1.4735z" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-width=".33225" style="paint-order:stroke fill markers"/>
+			</g>
+		</svg>
+		<div :class="$style.content">
+			<slot></slot>
+		</div>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+withDefaults(defineProps<{
+	tail?: 'left' | 'right' | 'none';
+	negativeMargin?: boolean;
+	shadow?: boolean;
+}>(), {
+	tail: 'right',
+	negativeMargin: false,
+	shadow: false,
+});
+</script>
+
+<style module lang="scss">
+.root {
+	--fukidashi-radius: var(--MI-radius);
+	--fukidashi-bg: var(--MI_THEME-panel);
+
+	position: relative;
+	display: inline-block;
+	min-height: calc(var(--fukidashi-radius) * 2);
+	padding-top: calc(var(--fukidashi-radius) * .13);
+
+	&.shadow {
+		filter: drop-shadow(0 4px 32px var(--MI_THEME-shadow));
+	}
+
+	&.left {
+		padding-left: calc(var(--fukidashi-radius) * .13);
+
+		&.negativeMargin {
+			margin-left: calc(calc(var(--fukidashi-radius) * .13) * -1);
+		}
+	}
+
+	&.right {
+		padding-right: calc(var(--fukidashi-radius) * .13);
+
+		&.negativeMargin {
+			margin-right: calc(calc(var(--fukidashi-radius) * .13) * -1);
+		}
+	}
+}
+
+.bg {
+	width: 100%;
+	height: 100%;
+	background: var(--fukidashi-bg);
+	border-radius: var(--fukidashi-radius);
+}
+
+.content {
+	position: relative;
+	padding: 8px 12px;
+}
+
+.tail {
+	position: absolute;
+	top: 0;
+	display: block;
+	width: calc(var(--fukidashi-radius) * 1.13);
+	height: auto;
+	fill: var(--fukidashi-bg);
+}
+
+.left .tail {
+	left: 0;
+	transform: rotateY(180deg);
+}
+
+.right .tail {
+	right: 0;
+}
+</style>
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue
index 2bb5b8762a19..22f8355acf1a 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.vue
+++ b/packages/frontend/src/components/MkGalleryPostPreview.vue
@@ -75,7 +75,7 @@ function leaveHover(): void {
 
 	&:hover {
 		text-decoration: none;
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 
 		> .thumbnail {
 			transform: scale(1.1);
diff --git a/packages/frontend/src/components/MkGoogle.vue b/packages/frontend/src/components/MkGoogle.vue
index 2988d77fe3cd..2eaee5b11593 100644
--- a/packages/frontend/src/components/MkGoogle.vue
+++ b/packages/frontend/src/components/MkGoogle.vue
@@ -39,7 +39,7 @@ const search = () => {
 	width: 100%;
 	height: 40px;
 	font-size: 16px;
-	border: solid 1px var(--divider);
+	border: solid 1px var(--MI_THEME-divider);
 	border-radius: 4px 0 0 4px;
 	-webkit-appearance: textfield;
 }
@@ -48,7 +48,7 @@ const search = () => {
 	flex-shrink: 0;
 	margin: 0;
 	padding: 0 16px;
-	border: solid 1px var(--divider);
+	border: solid 1px var(--MI_THEME-divider);
 	border-left: none;
 	border-radius: 0 4px 4px 0;
 
diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue
index 33e65ccc4e10..90ca1b5a9d04 100644
--- a/packages/frontend/src/components/MkInfo.vue
+++ b/packages/frontend/src/components/MkInfo.vue
@@ -36,14 +36,14 @@ function close() {
   align-items: center;
 	padding: 12px 14px;
 	font-size: 90%;
-	background: var(--infoBg);
-	color: var(--infoFg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-infoBg);
+	color: var(--MI_THEME-infoFg);
+	border-radius: var(--MI-radius);
 	white-space: pre-wrap;
 
 	&.warn {
-		background: var(--infoWarnBg);
-		color: var(--infoWarnFg);
+		background: var(--MI_THEME-infoWarnBg);
+		color: var(--MI_THEME-infoWarnFg);
 	}
 }
 
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 4c2fc1ba00da..08817fd6a8fa 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs, InputHTMLAttributes } from 'vue';
 import { debounce } from 'throttle-debounce';
 import MkButton from '@/components/MkButton.vue';
 import { useInterval } from '@@/js/use-interval.js';
@@ -53,7 +53,7 @@ import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
 
 const props = defineProps<{
 	modelValue: string | number | null;
-	type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local';
+	type?: InputHTMLAttributes['type'];
 	required?: boolean;
 	readonly?: boolean;
 	disabled?: boolean;
@@ -64,8 +64,8 @@ const props = defineProps<{
 	mfmAutocomplete?: boolean | SuggestionType[],
 	autocapitalize?: string;
 	spellcheck?: boolean;
-	inputmode?: 'none' | 'text' | 'search' | 'email' | 'url' | 'numeric' | 'tel' | 'decimal';
-	step?: any;
+	inputmode?: InputHTMLAttributes['inputmode'];
+	step?: InputHTMLAttributes['step'];
 	datalist?: string[];
 	min?: number;
 	max?: number;
@@ -199,7 +199,7 @@ defineExpose({
 .caption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
@@ -216,8 +216,8 @@ defineExpose({
 
 	&.focused {
 		> .inputCore {
-			border-color: var(--accent) !important;
-			//box-shadow: 0 0 0 4px var(--focus);
+			border-color: var(--MI_THEME-accent) !important;
+			//box-shadow: 0 0 0 4px var(--MI_THEME-focus);
 		}
 	}
 
@@ -242,9 +242,9 @@ defineExpose({
 	font: inherit;
 	font-weight: normal;
 	font-size: 1em;
-	color: var(--fg);
-	background: var(--panel);
-	border: solid 1px var(--panel);
+	color: var(--MI_THEME-fg);
+	background: var(--MI_THEME-panel);
+	border: solid 1px var(--MI_THEME-panel);
 	border-radius: 6px;
 	outline: none;
 	box-shadow: none;
@@ -252,7 +252,7 @@ defineExpose({
 	transition: border-color 0.1s ease-out;
 
 	&:hover {
-		border-color: var(--inputBorderHover) !important;
+		border-color: var(--MI_THEME-inputBorderHover) !important;
 	}
 }
 
diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue
index 17c974dd04b9..b0601cf7f943 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.vue
+++ b/packages/frontend/src/components/MkInstanceCardMini.vue
@@ -46,7 +46,7 @@ function getInstanceIcon(instance): string {
 	display: flex;
 	align-items: center;
 	padding: 16px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 8px;
 
 	> :global(.icon) {
@@ -62,7 +62,7 @@ function getInstanceIcon(instance): string {
 		flex: 1;
 		overflow: hidden;
 		font-size: 0.9em;
-		color: var(--fg);
+		color: var(--MI_THEME-fg);
 		padding-right: 8px;
 
 		> :global(.host) {
@@ -109,7 +109,7 @@ function getInstanceIcon(instance): string {
 	}
 
 	&:global(.gray) {
-		--c: var(--bg);
+		--c: var(--MI_THEME-bg);
 		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
 		background-size: 16px 16px;
 	}
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index d74c885041f3..8ccbf61e48f3 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -121,7 +121,7 @@ function createDoughnut(chartEl, tooltip, data) {
 			labels: data.map(x => x.name),
 			datasets: [{
 				backgroundColor: data.map(x => x.color),
-				borderColor: getComputedStyle(document.documentElement).getPropertyValue('--panel'),
+				borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'),
 				borderWidth: 2,
 				hoverOffset: 0,
 				data: data.map(x => x.value),
@@ -256,8 +256,8 @@ onMounted(() => {
 				flex: 1;
 				min-width: 0;
 				position: relative;
-				background: var(--panel);
-				border-radius: var(--radius);
+				background: var(--MI_THEME-panel);
+				border-radius: var(--MI-radius);
 				padding: 24px;
 				max-height: 300px;
 
diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index de51a98789dc..1a71f6574ff6 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -8,11 +8,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #label>{{ invite.code }}</template>
 	<template #suffix>
 		<span v-if="invite.used">{{ i18n.ts.used }}</span>
-		<span v-else-if="isExpired" style="color: var(--error)">{{ i18n.ts.expired }}</span>
-		<span v-else style="color: var(--success)">{{ i18n.ts.unused }}</span>
+		<span v-else-if="isExpired" style="color: var(--MI_THEME-error)">{{ i18n.ts.expired }}</span>
+		<span v-else style="color: var(--MI_THEME-success)">{{ i18n.ts.unused }}</span>
+	</template>
+	<template #footer>
+		<div class="_buttons">
+			<MkButton v-if="!invite.used && !isExpired" primary rounded @click="copyInviteCode()"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
+			<MkButton v-if="!invite.used || moderator" danger rounded @click="deleteCode()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+		</div>
 	</template>
 
-	<div class="_gaps_s" :class="$style.root">
+	<div :class="$style.root">
 		<div :class="$style.items">
 			<div>
 				<div :class="$style.label">{{ i18n.ts.invitationCode }}</div>
@@ -49,10 +55,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div><MkTime :time="invite.createdAt" mode="absolute"/></div>
 			</div>
 		</div>
-		<div :class="$style.buttons">
-			<MkButton v-if="!invite.used && !isExpired" primary rounded @click="copyInviteCode()"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
-			<MkButton v-if="!invite.used || moderator" danger rounded @click="deleteCode()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
-		</div>
 	</div>
 </MkFolder>
 </template>
@@ -121,9 +123,4 @@ function copyInviteCode() {
 	width: var(--height);
 	height: var(--height);
 }
-
-.buttons {
-	display: flex;
-	gap: 8px;
-}
 </style>
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 8e3c19bd12be..32c1a2d17246 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -12,13 +12,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<i class="icon" :class="item.icon"></i>
 					<div class="text">{{ item.text }}</div>
 					<span v-if="item.indicate && item.indicateValue" class="_indicateCounter indicatorWithValue">{{ item.indicateValue }}</span>
-					<span v-else-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
+					<span v-else-if="item.indicate" class="indicator _blink"><i class="_indicatorCircle"></i></span>
 				</button>
 				<MkA v-else v-click-anime :to="item.to" class="item" @click.passive="close()">
 					<i class="icon" :class="item.icon"></i>
 					<div class="text">{{ item.text }}</div>
 					<span v-if="item.indicate && item.indicateValue" class="_indicateCounter indicatorWithValue">{{ item.indicateValue }}</span>
-					<span v-else-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span>
+					<span v-else-if="item.indicate" class="indicator _blink"><i class="_indicatorCircle"></i></span>
 				</MkA>
 			</template>
 		</div>
@@ -105,8 +105,8 @@ function close() {
 			box-sizing: border-box;
 
 			&:hover {
-				color: var(--accent);
-				background: var(--accentedBg);
+				color: var(--MI_THEME-accent);
+				background: var(--MI_THEME-accentedBg);
 				text-decoration: none;
 			}
 
@@ -137,9 +137,8 @@ function close() {
 				position: absolute;
 				top: 32px;
 				left: 32px;
-				color: var(--indicator);
+				color: var(--MI_THEME-indicator);
 				font-size: 8px;
-				animation: global-blink 1s infinite;
 
 				@media (max-width: 500px) {
 					top: 16px;
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue
index a080550ddfaf..8b713b27345a 100644
--- a/packages/frontend/src/components/MkMediaAudio.vue
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -172,9 +172,7 @@ async function show() {
 const menuShowing = ref(false);
 
 function showMenu(ev: MouseEvent) {
-	let menu: MenuItem[] = [];
-
-	menu = [
+	const menu: MenuItem[] = [
 		// TODO: 再生キューに追加
 		{
 			type: 'switch',
@@ -222,7 +220,7 @@ function showMenu(ev: MouseEvent) {
 		menu.push({
 			type: 'divider',
 		}, {
-			type: 'link' as const,
+			type: 'link',
 			text: i18n.ts._fileViewer.title,
 			icon: 'ti ti-info-circle',
 			to: `/my/drive/file/${props.audio.id}`,
@@ -393,8 +391,8 @@ onDeactivated(() => {
 .audioContainer {
 	container-type: inline-size;
 	position: relative;
-	border: .5px solid var(--divider);
-	border-radius: var(--radius);
+	border: .5px solid var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 
 	&:focus-visible {
@@ -414,7 +412,7 @@ onDeactivated(() => {
 		height: 100%;
 		pointer-events: none;
 		border-radius: inherit;
-		box-shadow: inset 0 0 0 4px var(--warn);
+		box-shadow: inset 0 0 0 4px var(--MI_THEME-warn);
 	}
 }
 
@@ -456,12 +454,12 @@ onDeactivated(() => {
 
 	.controlButton {
 		padding: 6px;
-		border-radius: calc(var(--radius) / 2);
+		border-radius: calc(var(--MI-radius) / 2);
 		font-size: 1.05rem;
 
 		&:hover {
-			color: var(--accent);
-			background-color: var(--accentedBg);
+			color: var(--MI_THEME-accent);
+			background-color: var(--MI_THEME-accentedBg);
 		}
 
 		&:focus-visible {
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index 11995e1f3b6a..3e521e0a03f6 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -68,7 +68,6 @@ async function show() {
 }
 
 .download {
-	background: var(--noteAttachedFile);
 }
 
 .sensitive {
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index 0d1409e2c82b..ec85569df5c0 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick">
+<div :class="[hide ? $style.hidden : $style.visible, (image.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive]" @click="onclick">
 	<component
 		:is="disableImageLink ? 'div' : 'a'"
 		v-bind="disableImageLink ? {
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.indicators">
 			<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
 			<div v-if="image.comment" :class="$style.indicator">ALT</div>
-			<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
+			<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
 		</div>
 		<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button>
 		<i class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i>
@@ -53,6 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { watch, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
 import bytes from '@/filters/bytes.js';
 import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
@@ -74,7 +75,6 @@ const props = withDefaults(defineProps<{
 });
 
 const hide = ref(true);
-const darkMode = ref<boolean>(defaultStore.state.darkMode);
 
 const url = computed(() => (props.raw || defaultStore.state.loadRawImages)
 	? props.image.url
@@ -111,27 +111,39 @@ watch(() => props.image, () => {
 });
 
 function showMenu(ev: MouseEvent) {
-	os.popupMenu([{
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
 		text: i18n.ts.hide,
 		icon: 'ti ti-eye-off',
 		action: () => {
 			hide.value = true;
 		},
-	}, ...(iAmModerator ? [{
-		text: i18n.ts.markAsSensitive,
-		icon: 'ti ti-eye-exclamation',
-		danger: true,
-		action: () => {
-			os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
-		},
-	}] : []), ...($i?.id === props.image.userId ? [{
-		type: 'divider' as const,
-	}, {
-		type: 'link' as const,
-		text: i18n.ts._fileViewer.title,
-		icon: 'ti ti-info-circle',
-		to: `/my/drive/file/${props.image.id}`,
-	}] : [])], ev.currentTarget ?? ev.target);
+	});
+
+	if (iAmModerator) {
+		menuItems.push({
+			text: i18n.ts.markAsSensitive,
+			icon: 'ti ti-eye-exclamation',
+			danger: true,
+			action: () => {
+				os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
+			},
+		});
+	}
+
+	if ($i?.id === props.image.userId) {
+		menuItems.push({
+			type: 'divider',
+		}, {
+			type: 'link',
+			text: i18n.ts._fileViewer.title,
+			icon: 'ti ti-info-circle',
+			to: `/my/drive/file/${props.image.id}`,
+		});
+	}
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 }
 
 </script>
@@ -153,7 +165,7 @@ function showMenu(ev: MouseEvent) {
 		height: 100%;
 		pointer-events: none;
 		border-radius: inherit;
-		box-shadow: inset 0 0 0 4px var(--warn);
+		box-shadow: inset 0 0 0 4px var(--MI_THEME-warn);
 	}
 }
 
@@ -174,8 +186,8 @@ function showMenu(ev: MouseEvent) {
 	display: block;
 	position: absolute;
 	border-radius: 6px;
-	background-color: var(--fg);
-	color: var(--accentLighten);
+	background-color: var(--MI_THEME-fg);
+	color: var(--MI_THEME-accentLighten);
 	font-size: 12px;
 	opacity: .5;
 	padding: 5px 8px;
@@ -194,19 +206,28 @@ function showMenu(ev: MouseEvent) {
 
 .visible {
 	position: relative;
-	//box-shadow: 0 0 0 1px var(--divider) inset;
-	background: var(--bg);
-	background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%);
+	//box-shadow: 0 0 0 1px var(--MI_THEME-divider) inset;
+	background: var(--MI_THEME-bg);
 	background-size: 16px 16px;
 }
 
+html[data-color-scheme=dark] .visible {
+	--c: rgb(255 255 255 / 2%);
+	background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%);
+}
+
+html[data-color-scheme=light] .visible {
+	--c: rgb(0 0 0 / 2%);
+	background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%);
+}
+
 .menu {
 	display: block;
 	position: absolute;
 	border-radius: 999px;
 	background-color: rgba(0, 0, 0, 0.3);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 	color: #fff;
 	font-size: 0.8em;
 	width: 28px;
@@ -237,10 +258,10 @@ function showMenu(ev: MouseEvent) {
 }
 
 .indicator {
-	/* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */
+	/* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */
 	background-color: black;
 	border-radius: 6px;
-	color: var(--accentLighten);
+	color: var(--MI_THEME-accentLighten);
 	display: inline-block;
 	font-weight: bold;
 	font-size: 0.8em;
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 4a4a99be253c..32766f2029d1 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -310,14 +310,14 @@ defineExpose({
 
 :global(.pswp) {
 	--pswp-root-z-index: var(--mk-pswp-root-z-index, 2000700) !important;
-	--pswp-bg: var(--modalBg) !important;
+	--pswp-bg: var(--MI_THEME-modalBg) !important;
 }
 </style>
 
 <style lang="scss">
 .pswp__bg {
-	background: var(--modalBg);
-	backdrop-filter: var(--modalBgFilter);
+	background: var(--MI_THEME-modalBg);
+	backdrop-filter: var(--MI-modalBgFilter);
 }
 
 .pswp__alt-text-container {
@@ -335,14 +335,14 @@ defineExpose({
 }
 
 .pswp__alt-text {
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	margin: 0 auto;
 	text-align: center;
-	padding: var(--margin);
-	border-radius: var(--radius);
+	padding: var(--MI-margin);
+	border-radius: var(--MI-radius);
 	max-height: 8em;
 	overflow-y: auto;
-	text-shadow: var(--bg) 0 0 10px, var(--bg) 0 0 3px, var(--bg) 0 0 3px;
+	text-shadow: var(--MI_THEME-bg) 0 0 10px, var(--MI_THEME-bg) 0 0 3px, var(--MI_THEME-bg) 0 0 3px;
 	white-space: pre-line;
 }
 </style>
diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue
index 86ed8ba2cf96..df7505b0c3cf 100644
--- a/packages/frontend/src/components/MkMediaRange.vue
+++ b/packages/frontend/src/components/MkMediaRange.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <!-- Media系専用のinput range -->
 <template>
-<div :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--scrollbarHandle);'">
+<div :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--MI_THEME-scrollbarHandle);'">
 	<div :class="$style.controlsSeekbar">
 		<progress v-if="buffer !== undefined" :class="$style.buffer" :value="isNaN(buffer) ? 0 : buffer" min="0" max="1">{{ Math.round(buffer * 100) }}% buffered</progress>
 		<input v-model="model" :class="$style.seek" :style="`--value: ${modelValue * 100}%;`" type="range" min="0" max="1" step="any" @change="emit('dragEnded', modelValue)"/>
@@ -48,7 +48,7 @@ const modelValue = computed({
 	background: transparent;
 	border: 0;
 	border-radius: 26px;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	display: block;
 	height: 19px;
 	margin: 0;
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 7c5a36514835..65e4a1eb12f2 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i>
 		<div :class="$style.indicators">
 			<div v-if="video.comment" :class="$style.indicator">ALT</div>
-			<div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
+			<div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
 		</div>
 	</div>
 
@@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i>
 		<div :class="$style.indicators">
 			<div v-if="video.comment" :class="$style.indicator">ALT</div>
-			<div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
+			<div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div>
 		</div>
 		<div :class="$style.videoControls" @click.self="togglePlayPause">
 			<div :class="[$style.controlsChild, $style.controlsLeft]">
@@ -118,7 +118,7 @@ import { hms } from '@/filters/hms.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
-import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
+import { exitFullscreen, requestFullscreen } from '@/scripts/fullscreen.js';
 import hasAudio from '@/scripts/media-has-audio.js';
 import MkMediaRange from '@/components/MkMediaRange.vue';
 import { $i, iAmModerator } from '@/account.js';
@@ -192,9 +192,7 @@ async function show() {
 const menuShowing = ref(false);
 
 function showMenu(ev: MouseEvent) {
-	let menu: MenuItem[] = [];
-
-	menu = [
+	const menu: MenuItem[] = [
 		// TODO: 再生キューに追加
 		{
 			type: 'switch',
@@ -247,7 +245,7 @@ function showMenu(ev: MouseEvent) {
 		menu.push({
 			type: 'divider',
 		}, {
-			type: 'link' as const,
+			type: 'link',
 			text: i18n.ts._fileViewer.title,
 			icon: 'ti ti-info-circle',
 			to: `/my/drive/file/${props.video.id}`,
@@ -336,26 +334,21 @@ function togglePlayPause() {
 }
 
 function toggleFullscreen() {
-	if (isFullscreenNotSupported && videoEl.value) {
-		if (isFullscreen.value) {
-			// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-			//@ts-ignore
-			videoEl.value.webkitExitFullscreen();
-			isFullscreen.value = false;
-		} else {
-			// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-			//@ts-ignore
-			videoEl.value.webkitEnterFullscreen();
-			isFullscreen.value = true;
-		}
-	} else if (playerEl.value) {
-		if (isFullscreen.value) {
-			document.exitFullscreen();
-			isFullscreen.value = false;
-		} else {
-			playerEl.value.requestFullscreen({ navigationUI: 'hide' });
-			isFullscreen.value = true;
-		}
+	if (playerEl.value == null || videoEl.value == null) return;
+	if (isFullscreen.value) {
+		exitFullscreen({
+			videoEl: videoEl.value,
+		});
+		isFullscreen.value = false;
+	} else {
+		requestFullscreen({
+			videoEl: videoEl.value,
+			playerEl: playerEl.value,
+			options: {
+				navigationUI: 'hide',
+			},
+		});
+		isFullscreen.value = true;
 	}
 }
 
@@ -456,8 +449,10 @@ watch(loop, (to) => {
 });
 
 watch(hide, (to) => {
-	if (to && isFullscreen.value) {
-		document.exitFullscreen();
+	if (videoEl.value && to && isFullscreen.value) {
+		exitFullscreen({
+			videoEl: videoEl.value,
+		});
 		isFullscreen.value = false;
 	}
 });
@@ -510,7 +505,7 @@ onDeactivated(() => {
 		height: 100%;
 		pointer-events: none;
 		border-radius: inherit;
-		box-shadow: inset 0 0 0 4px var(--warn);
+		box-shadow: inset 0 0 0 4px var(--MI_THEME-warn);
 	}
 }
 
@@ -525,10 +520,10 @@ onDeactivated(() => {
 }
 
 .indicator {
-	/* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */
+	/* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */
 	background-color: black;
 	border-radius: 6px;
-	color: var(--accentLighten);
+	color: var(--MI_THEME-accentLighten);
 	display: inline-block;
 	font-weight: bold;
 	font-size: 0.8em;
@@ -539,8 +534,8 @@ onDeactivated(() => {
 	display: block;
 	position: absolute;
 	border-radius: 6px;
-	background-color: var(--fg);
-	color: var(--accentLighten);
+	background-color: var(--MI_THEME-fg);
+	color: var(--MI_THEME-accentLighten);
 	font-size: 12px;
 	opacity: .5;
 	padding: 5px 8px;
@@ -594,7 +589,7 @@ onDeactivated(() => {
 	opacity: 0;
 	transition: opacity .4s ease-in-out;
 
-	background: var(--accent);
+	background: var(--MI_THEME-accent);
 	color: #fff;
 	padding: 1rem;
 	border-radius: 99rem;
@@ -660,12 +655,12 @@ onDeactivated(() => {
 
 	.controlButton {
 		padding: 6px;
-		border-radius: calc(var(--radius) / 2);
+		border-radius: calc(var(--MI-radius) / 2);
 		transition: background-color .2s ease-in-out;
 		font-size: 1.05rem;
 
 		&:hover {
-			background-color: var(--accent);
+			background-color: var(--MI_THEME-accent);
 		}
 
 		&:focus-visible {
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index 9d9661e8166f..ac2d3f43989e 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }" :behavior="navigationBehavior">
+<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :behavior="navigationBehavior">
 	<img :class="$style.icon" :src="avatarUrl" alt="">
 	<span>
 		<span>@{{ username }}</span>
@@ -16,7 +16,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { toUnicode } from 'punycode';
 import { computed } from 'vue';
-import tinycolor from 'tinycolor2';
 import { host as localHost } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
@@ -37,11 +36,7 @@ const isMe = $i && (
 	`@${props.username}@${toUnicode(props.host)}` === `@${$i.username}@${toUnicode(localHost)}`.toLowerCase()
 );
 
-const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention'));
-bg.setAlpha(0.1);
-const bgCss = bg.toRgbString();
-
-const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages
+const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar
 	? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`)
 	: `/avatar/@${props.username}@${props.host}`,
 );
@@ -52,10 +47,12 @@ const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages
 	display: inline-block;
 	padding: 4px 8px 4px 4px;
 	border-radius: 999px;
-	color: var(--mention);
+	color: var(--MI_THEME-mention);
+	background: color(from var(--MI_THEME-mention) srgb r g b / 0.1);
 
 	&.isMe {
-		color: var(--mentionMe);
+		color: var(--MI_THEME-mentionMe);
+		background: color(from var(--MI_THEME-mentionMe) srgb r g b / 0.1);
 	}
 }
 
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index 235790556c5b..086573ba6dd6 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue';
 import MkMenu from './MkMenu.vue';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 
 const props = defineProps<{
 	items: MenuItem[];
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index c0728d56fa0b..13a65e411f50 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -4,17 +4,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div role="menu" @focusin.passive.stop="() => {}">
+<div
+	role="menu"
+	:class="{
+		[$style.root]: true,
+		[$style.center]: align === 'center',
+		[$style.big]: big,
+		[$style.asDrawer]: asDrawer,
+	}"
+	@focusin.passive.stop="() => {}"
+>
 	<div
 		ref="itemsEl"
 		v-hotkey="keymap"
 		tabindex="0"
 		class="_popup _shadow"
-		:class="{
-			[$style.root]: true,
-			[$style.center]: align === 'center',
-			[$style.asDrawer]: asDrawer,
-		}"
+		:class="$style.menu"
 		:style="{
 			width: (width && !asDrawer) ? `${width}px` : '',
 			maxHeight: maxHeight ? `min(${maxHeight}px, calc(100dvh - 32px))` : 'calc(100dvh - 32px)',
@@ -44,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
 				<div :class="$style.item_content">
 					<span :class="$style.item_content_text">{{ item.text }}</span>
-					<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
+					<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
 				</div>
 			</MkA>
 			<a
@@ -63,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
 				<div :class="$style.item_content">
 					<span :class="$style.item_content_text">{{ item.text }}</span>
-					<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
+					<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
 				</div>
 			</a>
 			<button
@@ -77,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			>
 				<MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/>
 				<div v-if="item.indicate" :class="$style.item_content">
-					<span :class="$style.indicator"><i class="_indicatorCircle"></i></span>
+					<span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
 				</div>
 			</button>
 			<button
@@ -156,7 +161,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
 				<div :class="$style.item_content">
 					<span :class="$style.item_content_text">{{ item.text }}</span>
-					<span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span>
+					<span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span>
 				</div>
 			</button>
 		</template>
@@ -200,6 +205,8 @@ const emit = defineEmits<{
 	(ev: 'hide'): void;
 }>();
 
+const big = isTouchUsing;
+
 const isNestingMenu = inject<boolean>('isNestingMenu', false);
 
 const itemsEl = shallowRef<HTMLElement>();
@@ -297,6 +304,8 @@ async function showRadioOptions(item: MenuRadio, ev: Event) {
 }
 
 async function showChildren(item: MenuParent, ev: Event) {
+	ev.stopPropagation();
+
 	const children: MenuItem[] = await (async () => {
 		if (childrenCache.has(item)) {
 			return childrenCache.get(item)!;
@@ -418,48 +427,69 @@ onBeforeUnmount(() => {
 
 <style lang="scss" module>
 .root {
-	padding: 8px 0;
-	box-sizing: border-box;
-	max-width: 100vw;
-	min-width: 200px;
-	overflow: auto;
-	overscroll-behavior: contain;
-
-	&:focus-visible {
-		outline: none;
+	&.center {
+		> .menu {
+			> .item {
+				text-align: center;
+			}
+		}
 	}
 
-	&.center {
-		> .item {
-			text-align: center;
+	&.big:not(.asDrawer) {
+		> .menu {
+			min-width: 230px;
+
+			> .item {
+				padding: 6px 20px;
+				font-size: 0.95em;
+				line-height: 24px;
+			}
 		}
 	}
 
 	&.asDrawer {
-		padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
-		width: 100%;
-		border-radius: 24px;
-		border-bottom-right-radius: 0;
-		border-bottom-left-radius: 0;
-
-		> .item {
-			font-size: 1em;
-			padding: 12px 24px;
+		max-width: 600px;
+		margin: auto;
 
-			&::before {
-				width: calc(100% - 24px);
-				border-radius: 12px;
+		> .menu {
+			padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
+			width: 100%;
+			border-radius: 24px;
+			border-bottom-right-radius: 0;
+			border-bottom-left-radius: 0;
+
+			> .item {
+				font-size: 1em;
+				padding: 12px 24px;
+
+				&::before {
+					width: calc(100% - 24px);
+					border-radius: 12px;
+				}
+
+				> .icon {
+					margin-right: 14px;
+					width: 24px;
+				}
 			}
 
-			> .icon {
-				margin-right: 14px;
-				width: 24px;
+			> .divider {
+				margin: 12px 0;
 			}
 		}
+	}
+}
 
-		> .divider {
-			margin: 12px 0;
-		}
+.menu {
+	padding: 8px 0;
+	box-sizing: border-box;
+	max-width: 100vw;
+	min-width: 200px;
+	overflow: auto;
+	overscroll-behavior: contain;
+
+	&:focus-visible {
+		outline: none;
 	}
 }
 
@@ -477,7 +507,7 @@ onBeforeUnmount(() => {
 	overflow: hidden;
 	text-overflow: ellipsis;
 	text-decoration: none !important;
-	color: var(--menuFg, var(--fg));
+	color: var(--menuFg, var(--MI_THEME-fg));
 
 	&::before {
 		content: "";
@@ -497,7 +527,7 @@ onBeforeUnmount(() => {
 		outline: none;
 
 		&:not(:hover):not(:active)::before {
-			outline: var(--focus) solid 2px;
+			outline: var(--MI_THEME-focus) solid 2px;
 			outline-offset: -2px;
 		}
 	}
@@ -506,19 +536,19 @@ onBeforeUnmount(() => {
 		&:hover,
 		&:focus-visible:active,
 		&:focus-visible.active {
-			color: var(--menuHoverFg, var(--accent));
+			color: var(--menuHoverFg, var(--MI_THEME-accent));
 
 			&::before {
-				background-color: var(--menuHoverBg, var(--accentedBg));
+				background-color: var(--menuHoverBg, var(--MI_THEME-accentedBg));
 			}
 		}
 
 		&:not(:focus-visible):active,
 		&:not(:focus-visible).active {
-			color: var(--menuActiveFg, var(--fgOnAccent));
+			color: var(--menuActiveFg, var(--MI_THEME-fgOnAccent));
 
 			&::before {
-				background-color: var(--menuActiveBg, var(--accent));
+				background-color: var(--menuActiveBg, var(--MI_THEME-accent));
 			}
 		}
 	}
@@ -536,13 +566,13 @@ onBeforeUnmount(() => {
 	}
 
 	&.radio {
-		--menuActiveFg: var(--accent);
-		--menuActiveBg: var(--accentedBg);
+		--menuActiveFg: var(--MI_THEME-accent);
+		--menuActiveBg: var(--MI_THEME-accentedBg);
 	}
 
 	&.parent {
-		--menuActiveFg: var(--accent);
-		--menuActiveBg: var(--accentedBg);
+		--menuActiveFg: var(--MI_THEME-accent);
+		--menuActiveBg: var(--MI_THEME-accentedBg);
 	}
 
 	&.label {
@@ -607,14 +637,13 @@ onBeforeUnmount(() => {
 .indicator {
 	display: flex;
 	align-items: center;
-	color: var(--indicator);
+	color: var(--MI_THEME-indicator);
 	font-size: 12px;
-	animation: global-blink 1s infinite;
 }
 
 .divider {
 	margin: 8px 0;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .radioIcon {
@@ -624,11 +653,11 @@ onBeforeUnmount(() => {
 	height: 1em;
 	vertical-align: -0.125em;
 	border-radius: 50%;
-	border: solid 2px var(--divider);
-	background-color: var(--panel);
+	border: solid 2px var(--MI_THEME-divider);
+	background-color: var(--MI_THEME-panel);
 
 	&.radioChecked {
-		border-color: var(--accent);
+		border-color: var(--MI_THEME-accent);
 
 		&::after {
 			content: "";
@@ -640,7 +669,7 @@ onBeforeUnmount(() => {
 			width: 50%;
 			height: 50%;
 			border-radius: 50%;
-			background-color: var(--accent);
+			background-color: var(--MI_THEME-accent);
 		}
 	}
 }
diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue
index 1b6f6cef31e4..7ea585ecc281 100644
--- a/packages/frontend/src/components/MkMiniChart.vue
+++ b/packages/frontend/src/components/MkMiniChart.vue
@@ -48,7 +48,7 @@ const polygonPoints = ref('');
 const headX = ref<number | null>(null);
 const headY = ref<number | null>(null);
 const clock = ref<number | null>(null);
-const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
+const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent'));
 const color = accent.toRgbString();
 
 function draw(): void {
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index f8032f9b431b..c766a338239a 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -106,7 +106,7 @@ const zIndex = os.claimZIndex(props.zPriority);
 const useSendAnime = ref(false);
 const type = computed<ModalTypes>(() => {
 	if (props.preferType === 'auto') {
-		if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
+		if ((defaultStore.state.menuStyle === 'drawer') || (defaultStore.state.menuStyle === 'auto' && isTouchUsing && deviceKind === 'smartphone')) {
 			return 'drawer';
 		} else {
 			return props.src != null ? 'popup' : 'dialog';
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index f26959888b42..f06cfffee430 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -26,11 +26,11 @@ import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
 import MkModal from './MkModal.vue';
 
 const props = withDefaults(defineProps<{
-	withOkButton: boolean;
-	withCloseButton: boolean;
-	okButtonDisabled: boolean;
-	width: number;
-	height: number;
+	withOkButton?: boolean;
+	withCloseButton?: boolean;
+	okButtonDisabled?: boolean;
+	width?: number;
+	height?: number;
 }>(), {
 	withOkButton: false,
 	withCloseButton: true,
@@ -90,12 +90,12 @@ defineExpose({
 	display: flex;
 	flex-direction: column;
 	contain: content;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 
 	--root-margin: 24px;
 
-	--headerHeight: 46px;
-	--headerHeightNarrow: 42px;
+	--MI_THEME-headerHeight: 46px;
+	--MI_THEME-headerHeightNarrow: 42px;
 
 	@media (max-width: 500px) {
 		--root-margin: 16px;
@@ -105,24 +105,24 @@ defineExpose({
 .header {
 	display: flex;
 	flex-shrink: 0;
-	background: var(--windowHeader);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	background: var(--MI_THEME-windowHeader);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 
 .headerButton {
-	height: var(--headerHeight);
-	width: var(--headerHeight);
+	height: var(--MI_THEME-headerHeight);
+	width: var(--MI_THEME-headerHeight);
 
 	@media (max-width: 500px) {
-		height: var(--headerHeightNarrow);
-		width: var(--headerHeightNarrow);
+		height: var(--MI_THEME-headerHeightNarrow);
+		width: var(--MI_THEME-headerHeightNarrow);
 	}
 }
 
 .title {
 	flex: 1;
-	line-height: var(--headerHeight);
+	line-height: var(--MI_THEME-headerHeight);
 	padding-left: 32px;
 	font-weight: bold;
 	white-space: nowrap;
@@ -131,7 +131,7 @@ defineExpose({
 	pointer-events: none;
 
 	@media (max-width: 500px) {
-		line-height: var(--headerHeightNarrow);
+		line-height: var(--MI_THEME-headerHeightNarrow);
 		padding-left: 16px;
 	}
 }
@@ -143,7 +143,7 @@ defineExpose({
 .body {
 	flex: 1;
 	overflow: auto;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	container-type: size;
 }
 </style>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index eca94e99d87a..cf0d0787b18b 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	v-show="!isDeleted"
 	ref="rootEl"
 	v-hotkey="keymap"
-	:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
+	:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender }]"
 	:tabindex="isDeleted ? '-1' : '0'"
 >
 	<MkNoteSub v-if="appearNote.reply && !renoteCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
@@ -53,7 +53,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
 			<div style="container-type: inline-size;">
 				<p v-if="appearNote.cw != null" :class="$style.cw">
-					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
+					<Mfm
+						v-if="appearNote.cw != ''"
+						:text="appearNote.cw"
+						:author="appearNote.user"
+						:nyaize="'respect'"
+						:enableEmojiMenu="true"
+						:enableEmojiMenuReaction="true"
+					/>
 					<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;"/>
 				</p>
 				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
@@ -119,8 +126,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<i class="ti ti-ban"></i>
 				</button>
 				<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()">
-					<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
-					<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
+					<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
+					<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
 					<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 					<i v-else class="ti ti-plus"></i>
 					<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
@@ -164,6 +171,9 @@ import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } fro
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
+import { host } from '@@/js/config.js';
+import type { MenuItem } from '@/types/menu.js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
@@ -193,11 +203,8 @@ import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getNoteSummary } from '@/scripts/get-note-summary.js';
-import { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
-import { shouldCollapsed } from '@@/js/collapsed.js';
-import { host } from '@@/js/config.js';
 import { isEnabledUrlPreview } from '@/instance.js';
 import { type Keymap } from '@/scripts/hotkey.js';
 import { focusPrev, focusNext } from '@/scripts/focus.js';
@@ -220,6 +227,7 @@ const emit = defineEmits<{
 }>();
 
 const inTimeline = inject<boolean>('inTimeline', false);
+const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(true));
 const inChannel = inject('inChannel', null);
 const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
 
@@ -284,15 +292,18 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string
 function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
 */
 function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
-	if (mutedWords == null) return false;
-
-	if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
-	if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
-	if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+	if (mutedWords != null) {
+		if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
+		if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
+		if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+	}
 
 	if (checkOnly) return false;
 
-	if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
+	if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) {
+		return 'sensitiveMute';
+	}
+
 	return false;
 }
 
@@ -412,7 +423,7 @@ if (!props.mock) {
 }
 
 function renote(viaKeyboard = false) {
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 
 	const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock });
@@ -422,7 +433,7 @@ function renote(viaKeyboard = false) {
 }
 
 function reply(): void {
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	if (props.mock) {
 		return;
 	}
@@ -435,7 +446,7 @@ function reply(): void {
 }
 
 function react(): void {
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	if (appearNote.value.reactionAcceptance === 'likeOnly') {
 		sound.playMisskeySfx('reaction');
@@ -556,7 +567,7 @@ function showRenoteMenu(): void {
 	}
 
 	if (isMyRenote) {
-		pleaseLogin(undefined, pleaseLoginContext.value);
+		pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 		os.popupMenu([
 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
 			{ type: 'divider' },
@@ -612,14 +623,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 	overflow: clip;
 	contain: content;
 
-	// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
-	// 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう
-	// ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、
-	// 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる
-	// 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?)
-	//content-visibility: auto;
-	//contain-intrinsic-size: 0 128px;
-
 	&:focus-visible {
 		outline: none;
 
@@ -636,8 +639,8 @@ function emitUpdReaction(emoji: string, delta: number) {
 			margin: auto;
 			width: calc(100% - 8px);
 			height: calc(100% - 8px);
-			border: dashed 2px var(--focus);
-			border-radius: var(--radius);
+			border: dashed 2px var(--MI_THEME-focus);
+			border-radius: var(--MI-radius);
 			box-sizing: border-box;
 		}
 	}
@@ -659,9 +662,9 @@ function emitUpdReaction(emoji: string, delta: number) {
 			right: 12px;
 			padding: 0 4px;
 			margin-bottom: 0 !important;
-			background: var(--popup);
+			background: var(--MI_THEME-popup);
 			border-radius: 8px;
-			box-shadow: 0px 4px 32px var(--shadow);
+			box-shadow: 0px 4px 32px var(--MI_THEME-shadow);
 		}
 
 		.footerButton {
@@ -680,6 +683,11 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 }
 
+.skipRender {
+	content-visibility: auto;
+	contain-intrinsic-size: 0 150px;
+}
+
 .tip {
 	display: flex;
 	align-items: center;
@@ -706,7 +714,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	padding: 16px 32px 8px 32px;
 	line-height: 28px;
 	white-space: pre;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 
 	& + .article {
 		padding-top: 8px;
@@ -803,7 +811,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	width: 58px;
 	height: 58px;
 	position: sticky !important;
-	top: calc(22px + var(--stickyTop, 0px));
+	top: calc(22px + var(--MI-stickyTop, 0px));
 	left: 0;
 }
 
@@ -824,12 +832,12 @@ function emitUpdReaction(emoji: string, delta: number) {
 	width: 100%;
 	margin-top: 14px;
 	position: sticky;
-	bottom: calc(var(--stickyBottom, 0px) + 14px);
+	bottom: calc(var(--MI-stickyBottom, 0px) + 14px);
 }
 
 .showLessLabel {
 	display: inline-block;
-	background: var(--popup);
+	background: var(--MI_THEME-popup);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
@@ -850,16 +858,16 @@ function emitUpdReaction(emoji: string, delta: number) {
 	z-index: 2;
 	width: 100%;
 	height: 64px;
-	background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+	background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 	&:hover > .collapsedLabel {
-		background: var(--panelHighlight);
+		background: var(--MI_THEME-panelHighlight);
 	}
 }
 
 .collapsedLabel {
 	display: inline-block;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
@@ -871,13 +879,13 @@ function emitUpdReaction(emoji: string, delta: number) {
 }
 
 .replyIcon {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	margin-right: 0.5em;
 }
 
 .translation {
-	border: solid 0.5px var(--divider);
-	border-radius: var(--radius);
+	border: solid 0.5px var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	padding: 12px;
 	margin-top: 8px;
 }
@@ -896,7 +904,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 
 .quoteNote {
 	padding: 16px;
-	border: dashed 1px var(--renote);
+	border: dashed 1px var(--MI_THEME-renote);
 	border-radius: 8px;
 	overflow: clip;
 }
@@ -920,7 +928,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 
 	&:hover {
-		color: var(--fgHighlighted);
+		color: var(--MI_THEME-fgHighlighted);
 	}
 }
 
@@ -991,7 +999,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 		margin: 0 10px 0 0;
 		width: 46px;
 		height: 46px;
-		top: calc(14px + var(--stickyTop, 0px));
+		top: calc(14px + var(--MI-stickyTop, 0px));
 	}
 }
 
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 1867f82c0f73..e0473dce5e13 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -68,7 +68,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</header>
 		<div :class="$style.noteContent">
 			<p v-if="appearNote.cw != null" :class="$style.cw">
-				<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
+				<Mfm
+					v-if="appearNote.cw != ''"
+					:text="appearNote.cw"
+					:author="appearNote.user"
+					:nyaize="'respect'"
+					:enableEmojiMenu="true"
+					:enableEmojiMenuReaction="true"
+				/>
 				<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll"/>
 			</p>
 			<div v-show="appearNote.cw == null || showContent">
@@ -128,8 +135,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<i class="ti ti-ban"></i>
 			</button>
 			<button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()">
-				<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
-				<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
+				<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
+				<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
 				<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 				<i v-else class="ti ti-plus"></i>
 				<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
@@ -200,6 +207,7 @@ import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
+import { host } from '@@/js/config.js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@@ -223,7 +231,6 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { host } from '@@/js/config.js';
 import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js';
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
 import { deepClone } from '@/scripts/clone.js';
@@ -397,7 +404,7 @@ if (appearNote.value.reactionAcceptance === 'likeOnly') {
 }
 
 function renote() {
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 
 	const { menu } = getRenoteMenu({ note: note.value, renoteButton });
@@ -405,7 +412,7 @@ function renote() {
 }
 
 function reply(): void {
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	os.post({
 		reply: appearNote.value,
@@ -416,7 +423,7 @@ function reply(): void {
 }
 
 function react(): void {
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	showMovedDialog();
 	if (appearNote.value.reactionAcceptance === 'likeOnly') {
 		sound.playMisskeySfx('reaction');
@@ -492,7 +499,7 @@ async function clip(): Promise<void> {
 
 function showRenoteMenu(): void {
 	if (!isMyRenote) return;
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 	os.popupMenu([{
 		text: i18n.ts.unrenote,
 		icon: 'ti ti-trash',
@@ -562,8 +569,8 @@ function loadConversation() {
 			margin: auto;
 			width: calc(100% - 8px);
 			height: calc(100% - 8px);
-			border: dashed 2px var(--focus);
-			border-radius: var(--radius);
+			border: dashed 2px var(--MI_THEME-focus);
+			border-radius: var(--MI-radius);
 			box-sizing: border-box;
 		}
 	}
@@ -584,7 +591,7 @@ function loadConversation() {
 	padding: 16px 32px 8px 32px;
 	line-height: 28px;
 	white-space: pre;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 }
 
 .renoteAvatar {
@@ -664,7 +671,7 @@ function loadConversation() {
 	padding: 4px 6px;
 	font-size: 80%;
 	line-height: 1;
-	border: solid 0.5px var(--divider);
+	border: solid 0.5px var(--MI_THEME-divider);
 	border-radius: 4px;
 }
 
@@ -692,19 +699,19 @@ function loadConversation() {
 }
 
 .noteReplyTarget {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	margin-right: 0.5em;
 }
 
 .rn {
 	margin-left: 4px;
 	font-style: oblique;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 }
 
 .translation {
-	border: solid 0.5px var(--divider);
-	border-radius: var(--radius);
+	border: solid 0.5px var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	padding: 12px;
 	margin-top: 8px;
 }
@@ -719,7 +726,7 @@ function loadConversation() {
 
 .quoteNote {
 	padding: 16px;
-	border: dashed 1px var(--renote);
+	border: dashed 1px var(--MI_THEME-renote);
 	border-radius: 8px;
 	overflow: clip;
 }
@@ -745,7 +752,7 @@ function loadConversation() {
 	}
 
 	&:hover {
-		color: var(--fgHighlighted);
+		color: var(--MI_THEME-fgHighlighted);
 	}
 }
 
@@ -755,17 +762,17 @@ function loadConversation() {
 	opacity: 0.7;
 
 	&.reacted {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
 .reply:not(:first-child) {
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .tabs {
-	border-top: solid 0.5px var(--divider);
-	border-bottom: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	border-bottom: solid 0.5px var(--MI_THEME-divider);
 	display: flex;
 }
 
@@ -777,7 +784,7 @@ function loadConversation() {
 }
 
 .tabActive {
-	border-bottom: solid 2px var(--accent);
+	border-bottom: solid 2px var(--MI_THEME-accent);
 }
 
 .tab_renotes {
@@ -797,12 +804,12 @@ function loadConversation() {
 
 .reactionTab {
 	padding: 4px 6px;
-	border: solid 1px var(--divider);
+	border: solid 1px var(--MI_THEME-divider);
 	border-radius: 6px;
 }
 
 .reactionTabActive {
-	border-color: var(--accent);
+	border-color: var(--MI_THEME-accent);
 }
 
 @container (max-width: 500px) {
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index be5829d92f0d..750e32a9ff37 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -40,6 +40,7 @@ import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import { notePage } from '@/filters/note.js';
 import { userPage } from '@/filters/user.js';
+import { defaultStore } from '@/store.js';
 
 defineProps<{
 	note: Misskey.entities.Note;
@@ -77,7 +78,7 @@ const mock = inject<boolean>('mock', false);
 	margin: 0 .5em 0 0;
 	padding: 1px 6px;
 	font-size: 80%;
-	border: solid 0.5px var(--divider);
+	border: solid 0.5px var(--MI_THEME-divider);
 	border-radius: 3px;
 }
 
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index c3f3c42b42d6..e684cf2a30d0 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -51,7 +51,7 @@ const showContent = ref(false);
 	height: 34px;
 	border-radius: 8px;
 	position: sticky !important;
-	top: calc(16px + var(--stickyTop, 0px));
+	top: calc(16px + var(--MI-stickyTop, 0px));
 	left: 0;
 }
 
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 829b37e7a7c5..e4bade309b1c 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -135,7 +135,7 @@ if (props.detail) {
 }
 
 .reply, .more {
-	border-left: solid 0.5px var(--divider);
+	border-left: solid 0.5px var(--MI_THEME-divider);
 	margin-top: 10px;
 }
 
@@ -156,7 +156,7 @@ if (props.detail) {
 .muted {
 	text-align: center;
 	padding: 8px !important;
-	border: 1px solid var(--divider);
+	border: 1px solid var(--MI_THEME-divider);
 	margin: 8px 8px 0 8px;
 	border-radius: 8px;
 }
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index 0856c146ba97..1c17c6b6912c 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -56,17 +56,17 @@ defineExpose({
 .root {
 	&.noGap {
 		> .notes {
-			background: var(--panel);
+			background: var(--MI_THEME-panel);
 		}
 	}
 
 	&:not(.noGap) {
 		> .notes {
-			background: var(--bg);
+			background: var(--MI_THEME-bg);
 
 			.note {
-				background: var(--panel);
-				border-radius: var(--radius);
+				background: var(--MI_THEME-panel);
+				border-radius: var(--MI-radius);
 			}
 		}
 	}
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index ee65743574c2..093bdb8b4cd0 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div :class="$style.root">
 	<div :class="$style.head">
 		<MkAvatar v-if="['pollEnded', 'note'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/>
-		<MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
+		<MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
 		<div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ti ti-heart" style="line-height: 1;"></i></div>
 		<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ti ti-plus" style="line-height: 1;"></i></div>
 		<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div>
 		<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
 		<MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/>
-		<img v-else-if="'icon' in notification" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
+		<img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
 		<div
 			:class="[$style.subIcon, {
 				[$style.t_follow]: notification.type === 'follow',
@@ -25,6 +25,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				[$style.t_quote]: notification.type === 'quote',
 				[$style.t_pollEnded]: notification.type === 'pollEnded',
 				[$style.t_achievementEarned]: notification.type === 'achievementEarned',
+				[$style.t_exportCompleted]: notification.type === 'exportCompleted',
+				[$style.t_login]: notification.type === 'login',
 				[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
 			}]"
 		>
@@ -37,6 +39,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
 			<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
 			<i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i>
+			<i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i>
+			<i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i>
 			<template v-else-if="notification.type === 'roleAssigned'">
 				<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
 				<i v-else class="ti ti-badges"></i>
@@ -46,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				:withTooltip="true"
 				:reaction="notification.reaction.replace(/^:(\w+):$/, ':$1@.:')"
 				:noStyle="true"
-				style="width: 100%; height: 100%;"
+				style="width: 100%; height: 100% !important; object-fit: contain;"
 			/>
 		</div>
 	</div>
@@ -56,7 +60,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span>
 			<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
 			<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
+			<span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span>
 			<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
+			<span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span>
 			<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
 			<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
 			<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
@@ -98,10 +104,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
 				{{ i18n.ts._achievements._types['_' + notification.achievement].title }}
 			</MkA>
+			<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
+				{{ i18n.ts.showFile }}
+			</MkA>
 			<template v-else-if="notification.type === 'follow'">
 				<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
 			</template>
-			<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
+			<template v-else-if="notification.type === 'followRequestAccepted'">
+				<div :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</div>
+				<div v-if="notification.message" :class="$style.text" style="opacity: 0.6; font-style: oblique;">
+					<i class="ti ti-quote" :class="$style.quote"></i>
+					<span>{{ notification.message }}</span>
+					<i class="ti ti-quote" :class="$style.quote"></i>
+				</div>
+			</template>
 			<template v-else-if="notification.type === 'receiveFollowRequest'">
 				<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span>
 				<div v-if="full && !followRequestDone" :class="$style.followRequestCommands">
@@ -122,7 +138,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							:withTooltip="true"
 							:reaction="reaction.reaction.replace(/^:(\w+):$/, ':$1@.:')"
 							:noStyle="true"
-							style="width: 100%; height: 100%;"
+							style="width: 100%; height: 100% !important; object-fit: contain;"
 						/>
 					</div>
 				</div>
@@ -161,6 +177,20 @@ const props = withDefaults(defineProps<{
 	full: false,
 });
 
+type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
+
+const exportEntityName = {
+	antenna: i18n.ts.antennas,
+	blocking: i18n.ts.blockedUsers,
+	clip: i18n.ts.clips,
+	customEmoji: i18n.ts.customEmojis,
+	favorite: i18n.ts.favorites,
+	following: i18n.ts.following,
+	muting: i18n.ts.mutedUsers,
+	note: i18n.ts.notes,
+	userList: i18n.ts.lists,
+} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
+
 const followRequestDone = ref(false);
 
 const acceptFollowRequest = () => {
@@ -190,6 +220,17 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 	overflow-wrap: break-word;
 	display: flex;
 	contain: content;
+	content-visibility: auto;
+	contain-intrinsic-size: 0 100px;
+
+	--eventFollow: #36aed2;
+	--eventRenote: #36d298;
+	--eventReply: #007aff;
+	--eventReactionHeart: var(--MI_THEME-love);
+	--eventReaction: #e99a0b;
+	--eventAchievement: #cb9a11;
+	--eventLogin: #007aff;
+	--eventOther: #88a6b7;
 }
 
 .head {
@@ -245,8 +286,8 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 	height: 20px;
 	box-sizing: border-box;
 	border-radius: 100%;
-	background: var(--panel);
-	box-shadow: 0 0 0 3px var(--panel);
+	background: var(--MI_THEME-panel);
+	box-shadow: 0 0 0 3px var(--MI_THEME-panel);
 	font-size: 11px;
 	text-align: center;
 	color: #fff;
@@ -298,12 +339,24 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 	pointer-events: none;
 }
 
+.t_exportCompleted {
+	padding: 3px;
+	background: var(--eventOther);
+	pointer-events: none;
+}
+
 .t_roleAssigned {
 	padding: 3px;
 	background: var(--eventOther);
 	pointer-events: none;
 }
 
+.t_login {
+	padding: 3px;
+	background: var(--eventLogin);
+	pointer-events: none;
+}
+
 .tail {
 	flex: 1;
 	min-width: 0;
@@ -386,8 +439,8 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 	height: 20px;
 	box-sizing: border-box;
 	border-radius: 100%;
-	background: var(--panel);
-	box-shadow: 0 0 0 3px var(--panel);
+	background: var(--MI_THEME-panel);
+	box-shadow: 0 0 0 3px var(--MI_THEME-panel);
 	font-size: 11px;
 	text-align: center;
 	color: #fff;
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index 47a9c79e4551..d07827d11a24 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{
 
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
-const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any);
+const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);
 
 function ok() {
 	emit('done', {
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index d67616e6b203..5a6ada474a1a 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -106,6 +106,6 @@ defineExpose({
 
 <style lang="scss" module>
 .list {
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 }
 </style>
diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue
index 1825cc5405df..80c634fdce8c 100644
--- a/packages/frontend/src/components/MkNumberDiff.vue
+++ b/packages/frontend/src/components/MkNumberDiff.vue
@@ -24,11 +24,11 @@ const isZero = computed(() => props.value === 0);
 
 <style lang="scss" module>
 .isPlus {
-	color: var(--success);
+	color: var(--MI_THEME-success);
 }
 
 .isMinus {
-	color: var(--error);
+	color: var(--MI_THEME-error);
 }
 
 .isZero {
diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue
index 870599aa94b1..7fa8c23c6ccd 100644
--- a/packages/frontend/src/components/MkObjectView.value.vue
+++ b/packages/frontend/src/components/MkObjectView.value.vue
@@ -39,7 +39,7 @@ import number from '@/filters/number.js';
 import XValue from '@/components/MkObjectView.value.vue';
 
 const props = defineProps<{
-	value: any;
+	value: unknown;
 }>();
 
 const collapsed = reactive({});
@@ -50,19 +50,19 @@ if (isObject(props.value)) {
 	}
 }
 
-function isObject(v): boolean {
+function isObject(v: unknown): v is Record<PropertyKey, unknown> {
 	return typeof v === 'object' && !Array.isArray(v) && v !== null;
 }
 
-function isArray(v): boolean {
+function isArray(v: unknown): v is unknown[] {
 	return Array.isArray(v);
 }
 
-function isEmpty(v): boolean {
+function isEmpty(v: unknown): v is Record<PropertyKey, never> | never[] {
 	return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
 }
 
-function collapsable(v): boolean {
+function collapsable(v: unknown): boolean {
 	return (isObject(v) || isArray(v)) && !isEmpty(v);
 }
 </script>
@@ -78,7 +78,7 @@ function collapsable(v): boolean {
 
 	> .boolean {
 		display: inline;
-		color: var(--codeBoolean);
+		color: var(--MI_THEME-codeBoolean);
 
 		&.true {
 			font-weight: bold;
@@ -91,12 +91,12 @@ function collapsable(v): boolean {
 
 	> .string {
 		display: inline;
-		color: var(--codeString);
+		color: var(--MI_THEME-codeString);
 	}
 
 	> .number {
 		display: inline;
-		color: var(--codeNumber);
+		color: var(--MI_THEME-codeNumber);
 	}
 
 	> .array.empty {
@@ -127,7 +127,7 @@ function collapsable(v): boolean {
 
 			> .toggle {
 				width: 16px;
-				color: var(--accent);
+				color: var(--MI_THEME-accent);
 				visibility: hidden;
 
 				&.visible {
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index ee1f15c18960..a05176e2f461 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -47,7 +47,7 @@ onUnmounted(() => {
 
 <style lang="scss" module>
 .content {
-	--stickyTop: 0px;
+	--MI-stickyTop: 0px;
 
 	&.omitted {
 		position: relative;
@@ -62,11 +62,11 @@ onUnmounted(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+			background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
-				background: var(--panel);
+				background: var(--MI_THEME-panel);
 				padding: 6px 10px;
 				font-size: 0.8em;
 				border-radius: 999px;
@@ -75,7 +75,7 @@ onUnmounted(() => {
 
 			&:hover {
 				> .fadeLabel {
-					background: var(--panelHighlight);
+					background: var(--MI_THEME-panelHighlight);
 				}
 			}
 		}
diff --git a/packages/frontend/src/components/MkPagePreview.vue b/packages/frontend/src/components/MkPagePreview.vue
index 8559d4b96e72..35a37a1f7d5d 100644
--- a/packages/frontend/src/components/MkPagePreview.vue
+++ b/packages/frontend/src/components/MkPagePreview.vue
@@ -42,7 +42,7 @@ const props = defineProps<{
 .eyeCatchingImageRoot {
 	width: 100%;
 	height: 200px;
-	border-radius: var(--radius) var(--radius) 0 0;
+	border-radius: var(--MI-radius) var(--MI-radius) 0 0;
 	overflow: hidden;
 }
 </style>
@@ -54,7 +54,7 @@ const props = defineProps<{
 
 	&:hover {
 		text-decoration: none;
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 
 	&:focus-within {
@@ -67,22 +67,22 @@ const props = defineProps<{
 			left: 0;
 			width: 100%;
 			height: 100%;
-			border-radius: var(--radius);
+			border-radius: var(--MI-radius);
 			pointer-events: none;
-			box-shadow: inset 0 0 0 2px var(--focus);
+			box-shadow: inset 0 0 0 2px var(--MI_THEME-focus);
 		}
 	}
 
 	> .thumbnail {
 		& + article {
-			border-radius: 0 0 var(--radius) var(--radius);
+			border-radius: 0 0 var(--MI-radius) var(--MI-radius);
 		}
 	}
 
 	> article {
-		background-color: var(--panel);
+		background-color: var(--MI_THEME-panel);
 		padding: 16px;
-		border-radius: var(--radius);
+		border-radius: var(--MI-radius);
 
 		> header {
 			margin-bottom: 8px;
@@ -115,7 +115,6 @@ const props = defineProps<{
 			> p {
 				display: inline-block;
 				margin: 0;
-				color: var(--urlPreviewInfo);
 				font-size: 0.8em;
 				line-height: 16px;
 				vertical-align: top;
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 2b993ab12f9c..9547423227a6 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:buttonsLeft="buttonsLeft"
 	:buttonsRight="buttonsRight"
 	:contextmenu="contextmenu"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 >
 	<template #header>
 		<template v-if="pageMetadata">
@@ -30,17 +30,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
+import { url } from '@@/js/config.js';
+import { getScrollContainer } from '@@/js/scroll.js';
 import RouterView from '@/components/global/RouterView.vue';
 import MkWindow from '@/components/MkWindow.vue';
 import { popout as _popout } from '@/scripts/popout.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@@/js/config.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { i18n } from '@/i18n.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { openingWindowsCount } from '@/os.js';
 import { claimAchievement } from '@/scripts/achievements.js';
-import { getScrollContainer } from '@@/js/scroll.js';
 import { useRouterFactory } from '@/router/supplier.js';
 import { mainRouter } from '@/router/main.js';
 
@@ -48,7 +48,7 @@ const props = defineProps<{
 	initialPath: string;
 }>();
 
-defineEmits<{
+const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
@@ -58,7 +58,7 @@ const windowRouter = routerFactory(props.initialPath);
 const contents = shallowRef<HTMLElement | null>(null);
 const pageMetadata = ref<null | PageMetadata>(null);
 const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
-const history = ref<{ path: string; key: any; }[]>([{
+const history = ref<{ path: string; key: string; }[]>([{
 	path: windowRouter.getCurrentPath(),
 	key: windowRouter.getCurrentKey(),
 }]);
@@ -179,8 +179,8 @@ defineExpose({
 	overscroll-behavior: contain;
 
 	min-height: 100%;
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 
-	--margin: var(--marginHalf);
+	--MI-margin: var(--MI-marginHalf);
 }
 </style>
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index d30f915c5523..ea299c319e79 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -125,8 +125,6 @@ const items = ref<MisskeyEntityMap>(new Map());
  */
 const queue = ref<MisskeyEntityMap>(new Map());
 
-const offset = ref(0);
-
 /**
  * 初期化中かどうか(trueならMkLoadingで全て隠す)
  */
@@ -179,7 +177,9 @@ watch([backed, contentEl], () => {
 	if (!backed.value) {
 		if (!contentEl.value) return;
 
-		scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE);
+		scrollRemove.value = props.pagination.reversed
+			? onScrollBottom(contentEl.value, executeQueue, TOLERANCE)
+			: onScrollTop(contentEl.value, (topVisible) => { if (topVisible) executeQueue(); }, TOLERANCE);
 	} else {
 		if (scrollRemove.value) scrollRemove.value();
 		scrollRemove.value = null;
@@ -223,7 +223,6 @@ async function init(): Promise<void> {
 			more.value = true;
 		}
 
-		offset.value = res.length;
 		error.value = false;
 		fetching.value = false;
 	}, err => {
@@ -244,7 +243,7 @@ const fetchMore = async (): Promise<void> => {
 		...params,
 		limit: SECOND_FETCH_LIMIT,
 		...(props.pagination.offsetMode ? {
-			offset: offset.value,
+			offset: items.value.size,
 		} : {
 			untilId: Array.from(items.value.keys()).at(-1),
 		}),
@@ -294,7 +293,6 @@ const fetchMore = async (): Promise<void> => {
 				moreFetching.value = false;
 			}
 		}
-		offset.value += res.length;
 	}, err => {
 		moreFetching.value = false;
 	});
@@ -308,7 +306,7 @@ const fetchMoreAhead = async (): Promise<void> => {
 		...params,
 		limit: SECOND_FETCH_LIMIT,
 		...(props.pagination.offsetMode ? {
-			offset: offset.value,
+			offset: items.value.size,
 		} : {
 			sinceId: Array.from(items.value.keys()).at(-1),
 		}),
@@ -320,7 +318,6 @@ const fetchMoreAhead = async (): Promise<void> => {
 			items.value = concatMapWithArray(items.value, res);
 			more.value = true;
 		}
-		offset.value += res.length;
 		moreFetching.value = false;
 	}, err => {
 		moreFetching.value = false;
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index e1d5db273043..e70ac7ff1a00 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<li v-for="(choice, i) in poll.choices" :key="i" :class="$style.choice" @click="vote(i)">
 			<div :class="$style.bg" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
 			<span :class="$style.fg">
-				<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template>
+				<template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--MI_THEME-accent);"></i></template>
 				<Mfm :text="choice.text" :plain="true"/>
 				<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span>
 			</span>
@@ -29,14 +29,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { host } from '@@/js/config.js';
+import { useInterval } from '@@/js/use-interval.js';
 import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
 import { sum } from '@/scripts/array.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { host } from '@@/js/config.js';
-import { useInterval } from '@@/js/use-interval.js';
 
 const props = defineProps<{
 	noteId: string;
@@ -85,7 +85,7 @@ if (props.poll.expiresAt) {
 const vote = async (id) => {
 	if (props.readOnly || closed.value || isVoted.value) return;
 
-	pleaseLogin(undefined, pleaseLoginContext.value);
+	pleaseLogin({ openOnRemote: pleaseLoginContext.value });
 
 	const { canceled } = await os.confirm({
 		type: 'question',
@@ -114,8 +114,8 @@ const vote = async (id) => {
 	position: relative;
 	margin: 4px 0;
 	padding: 4px;
-	//border: solid 0.5px var(--divider);
-	background: var(--accentedBg);
+	//border: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-accentedBg);
 	border-radius: 4px;
 	overflow: clip;
 	cursor: pointer;
@@ -126,8 +126,8 @@ const vote = async (id) => {
 	top: 0;
 	left: 0;
 	height: 100%;
-	background: var(--accent);
-	background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
+	background: var(--MI_THEME-accent);
+	background: linear-gradient(90deg,var(--MI_THEME-buttonGradateA),var(--MI_THEME-buttonGradateB));
 	transition: width 1s ease;
 }
 
@@ -135,12 +135,12 @@ const vote = async (id) => {
 	position: relative;
 	display: inline-block;
 	padding: 3px 5px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 3px;
 }
 
 .info {
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 
 .done {
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index 8a0c7b1e546c..df664e49f792 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -13,13 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, shallowRef } from 'vue';
 import MkModal from './MkModal.vue';
 import MkMenu from './MkMenu.vue';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 
 defineProps<{
 	items: MenuItem[];
 	align?: 'center' | string;
 	width?: number;
-	src?: any;
+	src?: HTMLElement | null;
 	returnFocusTo?: HTMLElement | null;
 }>();
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 039393887de1..0b5794d1e309 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -65,10 +65,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</div>
 	<MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo>
-	<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
+	<input v-show="useCw" ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd">
 	<div :class="[$style.textOuter, { [$style.withCw]: useCw }]">
 		<div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div>
-		<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
+		<textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
 		<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
 	</div>
 	<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
@@ -105,11 +105,11 @@ import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import insertTextAtCursor from 'insert-text-at-cursor';
 import { toASCII } from 'punycode/';
+import { host, url } from '@@/js/config.js';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkNotePreview from '@/components/MkNotePreview.vue';
 import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
 import MkPollEditor, { type PollEditorModelValue } from '@/components/MkPollEditor.vue';
-import { host, url } from '@@/js/config.js';
 import { erase, unique } from '@/scripts/array.js';
 import { extractMentions } from '@/scripts/extract-mentions.js';
 import { formatTimeString } from '@/scripts/format-time-string.js';
@@ -129,25 +129,13 @@ import { miLocalStorage } from '@/local-storage.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { emojiPicker } from '@/scripts/emoji-picker.js';
 import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
+import type { PostFormProps } from '@/types/post-form.js';
 
 const $i = signinRequired();
 
 const modal = inject('modal');
 
-const props = withDefaults(defineProps<{
-	reply?: Misskey.entities.Note;
-	renote?: Misskey.entities.Note;
-	channel?: Misskey.entities.Channel; // TODO
-	mention?: Misskey.entities.User;
-	specified?: Misskey.entities.UserDetailed;
-	initialText?: string;
-	initialCw?: string;
-	initialVisibility?: (typeof Misskey.noteVisibilities)[number];
-	initialFiles?: Misskey.entities.DriveFile[];
-	initialLocalOnly?: boolean;
-	initialVisibleUsers?: Misskey.entities.UserDetailed[];
-	initialNote?: Misskey.entities.Note;
-	instant?: boolean;
+const props = withDefaults(defineProps<PostFormProps & {
 	fixed?: boolean;
 	autofocus?: boolean;
 	freezeAfterPosted?: boolean;
@@ -201,6 +189,7 @@ const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'
 const imeText = ref('');
 const showingOptions = ref(false);
 const textAreaReadOnly = ref(false);
+const justEndedComposition = ref(false);
 
 const draftKey = computed((): string => {
 	let key = props.channel ? `channel:${props.channel.id}` : '';
@@ -573,7 +562,13 @@ function clear() {
 function onKeydown(ev: KeyboardEvent) {
 	if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey) && canPost.value) post();
 
-	if (ev.key === 'Escape') emit('esc');
+	// justEndedComposition.value is for Safari, which keyDown occurs after compositionend.
+	// ev.isComposing is for another browsers.
+	if (ev.key === 'Escape' && !justEndedComposition.value && !ev.isComposing) emit('esc');
+}
+
+function onKeyup(ev: KeyboardEvent) {
+	justEndedComposition.value = false;
 }
 
 function onCompositionUpdate(ev: CompositionEvent) {
@@ -582,6 +577,7 @@ function onCompositionUpdate(ev: CompositionEvent) {
 
 function onCompositionEnd(ev: CompositionEvent) {
 	imeText.value = '';
+	justEndedComposition.value = true;
 }
 
 async function onPaste(ev: ClipboardEvent) {
@@ -947,8 +943,8 @@ function showActions(ev: MouseEvent) {
 			action.handler({
 				text: text.value,
 				cw: cw.value,
-			}, (key, value: any) => {
-				if (typeof key !== 'string') return;
+			}, (key, value) => {
+				if (typeof key !== 'string' || typeof value !== 'string') return;
 				if (key === 'text') { text.value = value; }
 				if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
 			});
@@ -1112,8 +1108,8 @@ defineExpose({
 	&:focus-visible {
 		outline: none;
 
-		.submitInner {
-			outline: 2px solid var(--fgOnAccent);
+		> .submitInner {
+			outline: 2px solid var(--MI_THEME-fgOnAccent);
 			outline-offset: -4px;
 		}
 	}
@@ -1127,14 +1123,14 @@ defineExpose({
 	}
 
 	&:not(:disabled):hover {
-		> .inner {
-			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		> .submitInner {
+			background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 		}
 	}
 
 	&:not(:disabled):active {
-		> .inner {
-			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		> .submitInner {
+			background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 		}
 	}
 }
@@ -1156,8 +1152,8 @@ defineExpose({
 	border-radius: 6px;
 	min-width: 90px;
 	box-sizing: border-box;
-	color: var(--fgOnAccent);
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+	color: var(--MI_THEME-fgOnAccent);
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 }
 
 .headerRightItem {
@@ -1166,7 +1162,7 @@ defineExpose({
 	border-radius: 6px;
 
 	&:hover {
-		background: var(--X5);
+		background: var(--MI_THEME-X5);
 	}
 
 	&:disabled {
@@ -1201,6 +1197,15 @@ defineExpose({
 	min-height: 75px;
 	max-height: 150px;
 	overflow: auto;
+	background-size: auto auto;
+}
+
+html[data-color-scheme=dark] .preview {
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, #0004 5px, #0004 10px);
+}
+
+html[data-color-scheme=light] .preview {
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, #00000005 5px, #00000005 10px);
 }
 
 .targetNote {
@@ -1209,7 +1214,7 @@ defineExpose({
 
 .withQuote {
 	margin: 0 0 8px 0;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 .toSpecified {
@@ -1229,7 +1234,7 @@ defineExpose({
 	margin-right: 14px;
 	padding: 8px 0 8px 8px;
 	border-radius: 8px;
-	background: var(--X4);
+	background: var(--MI_THEME-X4);
 }
 
 .hasNotSpecifiedMentions {
@@ -1248,7 +1253,7 @@ defineExpose({
 	border: none;
 	border-radius: 0;
 	background: transparent;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	font-family: inherit;
 
 	&:focus {
@@ -1263,14 +1268,14 @@ defineExpose({
 .cw {
 	z-index: 1;
 	padding-bottom: 8px;
-	border-bottom: solid 0.5px var(--divider);
+	border-bottom: solid 0.5px var(--MI_THEME-divider);
 }
 
 .hashtags {
 	z-index: 1;
 	padding-top: 8px;
 	padding-bottom: 8px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .textOuter {
@@ -1296,7 +1301,7 @@ defineExpose({
 	right: 2px;
 	padding: 4px 6px;
 	font-size: .9em;
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 	border-radius: 6px;
 	min-width: 1.6em;
 	text-align: center;
@@ -1340,16 +1345,16 @@ defineExpose({
 	border-radius: 6px;
 
 	&:hover {
-		background: var(--X5);
+		background: var(--MI_THEME-X5);
 	}
 
 	&.footerButtonActive {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
 .previewButtonActive {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 @container (max-width: 500px) {
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index 8854babb6bf0..56e026aa3cde 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -6,8 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div v-show="props.modelValue.length != 0" :class="$style.root">
 	<Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)">
-		<template #item="{element}">
-			<div :class="$style.file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
+		<template #item="{ element }">
+			<div
+				:class="$style.file"
+				role="button"
+				tabindex="0"
+				@click="showFileMenu(element, $event)"
+				@keydown.space.enter="showFileMenu(element, $event)"
+				@contextmenu.prevent="showFileMenu(element, $event)"
+			>
 				<MkDriveFileThumbnail :data-id="element.id" :class="$style.thumbnail" :file="element" fit="cover"/>
 				<div v-if="element.isSensitive" :class="$style.sensitive">
 					<i class="ti ti-eye-exclamation" style="margin: auto;"></i>
@@ -26,18 +33,19 @@ import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
+import type { MenuItem } from '@/types/menu.js';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
 const props = defineProps<{
-	modelValue: any[];
+	modelValue: Misskey.entities.DriveFile[];
 	detachMediaFn?: (id: string) => void;
 }>();
 
 const mock = inject<boolean>('mock', false);
 
 const emit = defineEmits<{
-	(ev: 'update:modelValue', value: any[]): void;
+	(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
 	(ev: 'detach', id: string): void;
 	(ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void;
 	(ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void;
@@ -63,7 +71,7 @@ async function detachAndDeleteMedia(file: Misskey.entities.DriveFile) {
 
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
+		text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }),
 	});
 
 	if (canceled) return;
@@ -105,7 +113,7 @@ async function rename(file) {
 	});
 }
 
-async function describe(file) {
+async function describe(file: Misskey.entities.DriveFile) {
 	if (mock) return;
 
 	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
@@ -132,11 +140,14 @@ async function crop(file: Misskey.entities.DriveFile): Promise<void> {
 	emit('replaceFile', file, newFile);
 }
 
-function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
+function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | KeyboardEvent): void {
 	if (menuShowing) return;
 
 	const isImage = file.type.startsWith('image/');
-	os.popupMenu([{
+
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
 		text: i18n.ts.renameFile,
 		icon: 'ti ti-forms',
 		action: () => { rename(file); },
@@ -148,11 +159,17 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 		text: i18n.ts.describeFile,
 		icon: 'ti ti-text-caption',
 		action: () => { describe(file); },
-	}, ...isImage ? [{
-		text: i18n.ts.cropImage,
-		icon: 'ti ti-crop',
-		action: () : void => { crop(file); },
-	}] : [], {
+	});
+
+	if (isImage) {
+		menuItems.push({
+			text: i18n.ts.cropImage,
+			icon: 'ti ti-crop',
+			action: () : void => { crop(file); },
+		});
+	}
+
+	menuItems.push({
 		type: 'divider',
 	}, {
 		text: i18n.ts.attachCancel,
@@ -163,7 +180,9 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 		icon: 'ti ti-trash',
 		danger: true,
 		action: () => { detachAndDeleteMedia(file); },
-	}], ev.currentTarget ?? ev.target).then(() => menuShowing = false);
+	});
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => menuShowing = false);
 	menuShowing = true;
 }
 </script>
@@ -187,13 +206,17 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 	border-radius: 4px;
 	overflow: hidden;
 	cursor: move;
+
+	&:focus-visible {
+		outline-offset: 4px;
+	}
 }
 
 .thumbnail {
 	width: 100%;
 	height: 100%;
 	z-index: 1;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 
 .sensitive {
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index d6bca290504d..32d8df1504db 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -11,23 +11,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { shallowRef } from 'vue';
-import * as Misskey from 'misskey-js';
 import MkModal from '@/components/MkModal.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
+import type { PostFormProps } from '@/types/post-form.js';
 
-const props = withDefaults(defineProps<{
-	reply?: Misskey.entities.Note;
-	renote?: Misskey.entities.Note;
-	channel?: any; // TODO
-	mention?: Misskey.entities.User;
-	specified?: Misskey.entities.UserDetailed;
-	initialText?: string;
-	initialCw?: string;
-	initialVisibility?: (typeof Misskey.noteVisibilities)[number];
-	initialFiles?: Misskey.entities.DriveFile[];
-	initialLocalOnly?: boolean;
-	initialVisibleUsers?: Misskey.entities.UserDetailed[];
-	initialNote?: Misskey.entities.Note;
+const props = withDefaults(defineProps<PostFormProps & {
 	instant?: boolean;
 	fixed?: boolean;
 	autofocus?: boolean;
diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue
index 22fc86723e46..f16c8f6c2a35 100644
--- a/packages/frontend/src/components/MkRadio.vue
+++ b/packages/frontend/src/components/MkRadio.vue
@@ -24,17 +24,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 </div>
 </template>
 
-<script lang="ts" setup>
+<script lang="ts" setup generic="T extends unknown">
 import { computed } from 'vue';
 
 const props = defineProps<{
-	modelValue: any;
-	value: any;
+	modelValue: T;
+	value: T;
 	disabled?: boolean;
 }>();
 
 const emit = defineEmits<{
-	(ev: 'update:modelValue', value: any): void;
+	(ev: 'update:modelValue', value: T): void;
 }>();
 
 const checked = computed(() => props.modelValue === props.value);
@@ -53,9 +53,9 @@ function toggle(): void {
 	cursor: pointer;
 	padding: 7px 10px;
 	min-width: 60px;
-	background-color: var(--panel);
+	background-color: var(--MI_THEME-panel);
 	background-clip: padding-box !important;
-	border: solid 1px var(--panel);
+	border: solid 1px var(--MI_THEME-panel);
 	border-radius: 6px;
 	font-size: 90%;
 	transition: all 0.2s;
@@ -67,25 +67,25 @@ function toggle(): void {
 	}
 
 	&:hover {
-		border-color: var(--inputBorderHover) !important;
+		border-color: var(--MI_THEME-inputBorderHover) !important;
 	}
 
 	&:focus-within {
 		outline: none;
-		box-shadow: 0 0 0 2px var(--focus);
+		box-shadow: 0 0 0 2px var(--MI_THEME-focus);
 	}
 
 	&.checked {
-		background-color: var(--accentedBg) !important;
-		border-color: var(--accentedBg) !important;
-		color: var(--accent);
+		background-color: var(--MI_THEME-accentedBg) !important;
+		border-color: var(--MI_THEME-accentedBg) !important;
+		color: var(--MI_THEME-accent);
 		cursor: default !important;
 
 		> .button {
-			border-color: var(--accent);
+			border-color: var(--MI_THEME-accent);
 
 			&::after {
-				background-color: var(--accent);
+				background-color: var(--MI_THEME-accent);
 				transform: scale(1);
 				opacity: 1;
 			}
@@ -106,7 +106,7 @@ function toggle(): void {
 	width: 14px;
 	height: 14px;
 	background: none;
-	border: solid 2px var(--inputBorder);
+	border: solid 2px var(--MI_THEME-inputBorder);
 	border-radius: 100%;
 	transition: inherit;
 
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index 705c93f770c3..af81eb814df7 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -77,7 +77,7 @@ export default defineComponent({
 	> .caption {
 		font-size: 0.85em;
 		padding: 8px 0 0 0;
-		color: var(--fgTransparentWeak);
+		color: var(--MI_THEME-fgTransparentWeak);
 
 		&:empty {
 			display: none;
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue
index 1eae6429378e..264b55922245 100644
--- a/packages/frontend/src/components/MkRange.vue
+++ b/packages/frontend/src/components/MkRange.vue
@@ -5,7 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div class="timctyfi" :class="{ disabled, easing }">
-	<div class="label"><slot name="label"></slot></div>
+	<div class="label">
+		<slot name="label"></slot>
+	</div>
 	<div v-adaptive-border class="body">
 		<div ref="containerEl" class="container">
 			<div class="track">
@@ -14,15 +16,25 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-if="steps && showTicks" class="ticks">
 				<div v-for="i in (steps + 1)" class="tick" :style="{ left: (((i - 1) / steps) * 100) + '%' }"></div>
 			</div>
-			<div ref="thumbEl" v-tooltip="textConverter(finalValue)" class="thumb" :style="{ left: thumbPosition + 'px' }" @mousedown="onMousedown" @touchstart="onMousedown"></div>
+			<div
+				ref="thumbEl"
+				class="thumb"
+				:style="{ left: thumbPosition + 'px' }"
+				@mouseenter.passive="onMouseenter"
+				@mousedown="onMousedown"
+				@touchstart="onMousedown"
+			></div>
 		</div>
 	</div>
-	<div class="caption"><slot name="caption"></slot></div>
+	<div class="caption">
+		<slot name="caption"></slot>
+	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { isTouchUsing } from '@/scripts/touch.js';
 import * as os from '@/os.js';
 
 const props = withDefaults(defineProps<{
@@ -101,12 +113,36 @@ const steps = computed(() => {
 	}
 });
 
+const tooltipForDragShowing = ref(false);
+const tooltipForHoverShowing = ref(false);
+
+function onMouseenter() {
+	if (isTouchUsing) return;
+
+	tooltipForHoverShowing.value = true;
+
+	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), {
+		showing: computed(() => tooltipForHoverShowing.value && !tooltipForDragShowing.value),
+		text: computed(() => {
+			return props.textConverter(finalValue.value);
+		}),
+		targetElement: thumbEl,
+	}, {
+		closed: () => dispose(),
+	});
+
+	thumbEl.value!.addEventListener('mouseleave', () => {
+		tooltipForHoverShowing.value = false;
+	}, { once: true, passive: true });
+}
+
 function onMousedown(ev: MouseEvent | TouchEvent) {
 	ev.preventDefault();
 
-	const tooltipShowing = ref(true);
+	tooltipForDragShowing.value = true;
+
 	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTooltip.vue')), {
-		showing: tooltipShowing,
+		showing: tooltipForDragShowing,
 		text: computed(() => {
 			return props.textConverter(finalValue.value);
 		}),
@@ -137,7 +173,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 
 	const onMouseup = () => {
 		document.head.removeChild(style);
-		tooltipShowing.value = false;
+		tooltipForDragShowing.value = false;
 		window.removeEventListener('mousemove', onDrag);
 		window.removeEventListener('touchmove', onDrag);
 		window.removeEventListener('mouseup', onMouseup);
@@ -176,7 +212,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 	> .caption {
 		font-size: 0.85em;
 		padding: 8px 0 0 0;
-		color: var(--fgTransparentWeak);
+		color: var(--MI_THEME-fgTransparentWeak);
 
 		&:empty {
 			display: none;
@@ -188,8 +224,8 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 
 	> .body {
 		padding: 7px 12px;
-		background: var(--panel);
-		border: solid 1px var(--panel);
+		background: var(--MI_THEME-panel);
+		border: solid 1px var(--MI_THEME-panel);
 		border-radius: 6px;
 
 		> .container {
@@ -214,7 +250,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 					top: 0;
 					left: 0;
 					height: 100%;
-					background: var(--accent);
+					background: var(--MI_THEME-accent);
 					opacity: 0.5;
 				}
 			}
@@ -236,7 +272,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 					width: $tickWidth;
 					height: 3px;
 					margin-left: - math.div($tickWidth, 2);
-					background: var(--divider);
+					background: var(--MI_THEME-divider);
 					border-radius: 999px;
 				}
 			}
@@ -246,11 +282,11 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 				width: $thumbWidth;
 				height: $thumbHeight;
 				cursor: grab;
-				background: var(--accent);
+				background: var(--MI_THEME-accent);
 				border-radius: 999px;
 
 				&:hover {
-					background: var(--accentLighten);
+					background: var(--MI_THEME-accentLighten);
 				}
 			}
 		}
@@ -261,12 +297,12 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
 			> .container {
 				> .track {
 					> .highlight {
-						transition: width 0.2s cubic-bezier(0,0,0,1);
+						transition: width 0.2s cubic-bezier(0, 0, 0, 1);
 					}
 				}
 
 				> .thumb {
-					transition: left 0.2s cubic-bezier(0,0,0,1);
+					transition: left 0.2s cubic-bezier(0, 0, 0, 1);
 				}
 			}
 		}
diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue
index 361e246e9fbb..5a59a5e05563 100644
--- a/packages/frontend/src/components/MkReactionEffect.vue
+++ b/packages/frontend/src/components/MkReactionEffect.vue
@@ -60,7 +60,7 @@ onMounted(() => {
 	right: 0;
 	bottom: 0;
 	margin: auto;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	font-size: 18px;
 	font-weight: bold;
 	transform: translateY(-30px);
diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue
index 15409a216a30..77ca841ad0c0 100644
--- a/packages/frontend/src/components/MkReactionTooltip.vue
+++ b/packages/frontend/src/components/MkReactionTooltip.vue
@@ -36,6 +36,7 @@ const emit = defineEmits<{
 .icon {
 	display: block;
 	width: 60px;
+	max-height: 60px;
 	font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
 	margin: 0 auto;
 	object-fit: contain;
diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue
index 3dd02b261c7f..d24e0b15bf96 100644
--- a/packages/frontend/src/components/MkReactionsViewer.details.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.details.vue
@@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { } from 'vue';
+import * as Misskey from 'misskey-js';
 import { getEmojiName } from '@@/js/emojilist.js';
 import MkTooltip from './MkTooltip.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
@@ -30,7 +31,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
 defineProps<{
 	showing: boolean;
 	reaction: string;
-	users: any[]; // TODO
+	users: Misskey.entities.UserLite[];
 	count: number;
 	targetElement: HTMLElement;
 }>();
@@ -57,12 +58,13 @@ function getReactionName(reaction: string): string {
 	max-width: 100px;
 	padding-right: 10px;
 	text-align: center;
-	border-right: solid 0.5px var(--divider);
+	border-right: solid 0.5px var(--MI_THEME-divider);
 }
 
 .reactionIcon {
 	display: block;
 	width: 60px;
+	max-height: 60px;
 	font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
 	object-fit: contain;
 	margin: 0 auto;
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index f42a0b3227f3..b65038aadcb8 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -180,7 +180,7 @@ if (!mock) {
 	justify-content: center;
 
 	&.canToggle {
-		background: var(--buttonBg);
+		background: var(--MI_THEME-buttonBg);
 
 		&:hover {
 			background: rgba(0, 0, 0, 0.1);
@@ -214,12 +214,12 @@ if (!mock) {
 	}
 
 	&.reacted, &.reacted:hover {
-		background: var(--accentedBg);
-		color: var(--accent);
-		box-shadow: 0 0 0 1px var(--accent) inset;
+		background: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
+		box-shadow: 0 0 0 1px var(--MI_THEME-accent) inset;
 
 		> .count {
-			color: var(--accent);
+			color: var(--MI_THEME-accent);
 		}
 
 		> .icon {
diff --git a/packages/frontend/src/components/MkRemoteCaution.vue b/packages/frontend/src/components/MkRemoteCaution.vue
index f1050d26e6e4..a56a4b167199 100644
--- a/packages/frontend/src/components/MkRemoteCaution.vue
+++ b/packages/frontend/src/components/MkRemoteCaution.vue
@@ -19,14 +19,14 @@ defineProps<{
 .root {
 	font-size: 0.8em;
 	padding: 16px;
-	background: var(--infoWarnBg);
-	color: var(--infoWarnFg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-infoWarnBg);
+	color: var(--MI_THEME-infoWarnFg);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 
 .link {
 	margin-left: 4px;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 </style>
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index c3daa9c9a42c..d41793b0fa59 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -44,7 +44,7 @@ onMounted(async () => {
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
-	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
+	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent'));
 	const color = accent.toHex();
 
 	if (chartEl.value == null) return;
diff --git a/packages/frontend/src/components/MkRippleEffect.vue b/packages/frontend/src/components/MkRippleEffect.vue
index ee5bb73ebfda..2949cf156df8 100644
--- a/packages/frontend/src/components/MkRippleEffect.vue
+++ b/packages/frontend/src/components/MkRippleEffect.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
 	<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
-		<circle fill="none" cx="64" cy="64" style="stroke: var(--accent);">
+		<circle fill="none" cx="64" cy="64" style="stroke: var(--MI_THEME-accent);">
 			<animate
 				attributeName="r"
 				begin="0s" dur="0.5s"
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			/>
 		</circle>
 		<g fill="none" fill-rule="evenodd">
-			<circle v-for="(particle, i) in particles" :key="i" :fill="particle.color" style="stroke: var(--accent);">
+			<circle v-for="(particle, i) in particles" :key="i" :fill="particle.color" style="stroke: var(--MI_THEME-accent);">
 				<animate
 					attributeName="r"
 					begin="0s" dur="0.8s"
diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue
index ce17ae08e0f0..3f14c5b5e0d6 100644
--- a/packages/frontend/src/components/MkRolePreview.vue
+++ b/packages/frontend/src/components/MkRolePreview.vue
@@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <MkA :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }">
 	<template v-if="forModeration">
-		<i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--success)"></i>
-		<i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--warn)"></i>
+		<i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--MI_THEME-success)"></i>
+		<i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--MI_THEME-warn)"></i>
 	</template>
 
 	<div v-adaptive-bg class="_panel" :class="$style.body">
@@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<img :class="$style.bodyBadge" :src="role.iconUrl"/>
 				</template>
 				<template v-else>
-					<i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i>
-					<i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i>
+					<i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--MI_THEME-accent);"></i>
+					<i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--MI_THEME-accent);"></i>
 					<i v-else class="ti ti-user" style="opacity: 0.7;"></i>
 				</template>
 			</span>
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 360d697d7cd5..eeadd4993698 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -16,9 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		@keydown.space.enter="show"
 	>
 		<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
-		<select
+		<div
 			ref="inputEl"
-			v-model="v"
 			v-adaptive-border
 			tabindex="-1"
 			:class="$style.inputCore"
@@ -26,55 +25,48 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:required="required"
 			:readonly="readonly"
 			:placeholder="placeholder"
-			@input="onInput"
 			@mousedown.prevent="() => {}"
 			@keydown.prevent="() => {}"
 		>
-			<slot></slot>
-		</select>
+			<div style="pointer-events: none;">{{ currentValueText ?? '' }}</div>
+			<div style="display: none;">
+				<slot></slot>
+			</div>
+		</div>
 		<div ref="suffixEl" :class="$style.suffix"><i class="ti ti-chevron-down" :class="[$style.chevron, { [$style.chevronOpening]: opening }]"></i></div>
 	</div>
 	<div :class="$style.caption"><slot name="caption"></slot></div>
-
-	<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
 <script lang="ts" setup>
 import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
-import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os.js';
 import { useInterval } from '@@/js/use-interval.js';
-import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
+import * as os from '@/os.js';
 
 const props = defineProps<{
-	modelValue: string | null;
+	modelValue: string | number | null;
 	required?: boolean;
 	readonly?: boolean;
 	disabled?: boolean;
 	placeholder?: string;
 	autofocus?: boolean;
 	inline?: boolean;
-	manualSave?: boolean;
 	small?: boolean;
 	large?: boolean;
 }>();
 
 const emit = defineEmits<{
-	(ev: 'changeByUser', value: string | null): void;
-	(ev: 'update:modelValue', value: string | null): void;
+	(ev: 'update:modelValue', value: string | number | null): void;
 }>();
 
 const slots = useSlots();
 
 const { modelValue, autofocus } = toRefs(props);
-const v = ref(modelValue.value);
 const focused = ref(false);
 const opening = ref(false);
-const changed = ref(false);
-const invalid = ref(false);
-const filled = computed(() => v.value !== '' && v.value != null);
+const currentValueText = ref<string | null>(null);
 const inputEl = ref<HTMLObjectElement | null>(null);
 const prefixEl = ref<HTMLElement | null>(null);
 const suffixEl = ref<HTMLElement | null>(null);
@@ -85,26 +77,6 @@ const height =
 	36;
 
 const focus = () => container.value?.focus();
-const onInput = (ev) => {
-	changed.value = true;
-};
-
-const updated = () => {
-	changed.value = false;
-	emit('update:modelValue', v.value);
-};
-
-watch(modelValue, newValue => {
-	v.value = newValue;
-});
-
-watch(v, () => {
-	if (!props.manualSave) {
-		updated();
-	}
-
-	invalid.value = inputEl.value?.validity.badInput ?? true;
-});
 
 // このコンポーネントが作成された時、非表示状態である場合がある
 // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
@@ -134,6 +106,31 @@ onMounted(() => {
 	});
 });
 
+watch(modelValue, () => {
+	const scanOptions = (options: VNodeChild[]) => {
+		for (const vnode of options) {
+			if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
+			if (vnode.type === 'optgroup') {
+				const optgroup = vnode;
+				if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
+			} else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
+				const fragment = vnode;
+				if (Array.isArray(fragment.children)) scanOptions(fragment.children);
+			} else if (vnode.props == null) { // v-if で条件が false のときにこうなる
+				// nop?
+			} else {
+				const option = vnode;
+				if (option.props?.value === modelValue.value) {
+					currentValueText.value = option.children as string;
+					break;
+				}
+			}
+		}
+	};
+
+	scanOptions(slots.default!());
+}, { immediate: true });
+
 function show() {
 	if (opening.value) return;
 	focus();
@@ -146,11 +143,9 @@ function show() {
 	const pushOption = (option: VNode) => {
 		menu.push({
 			text: option.children as string,
-			active: computed(() => v.value === option.props?.value),
+			active: computed(() => modelValue.value === option.props?.value),
 			action: () => {
-				v.value = option.props?.value;
-				changed.value = true;
-				emit('changeByUser', v.value);
+				emit('update:modelValue', option.props?.value);
 			},
 		});
 	};
@@ -202,7 +197,7 @@ function show() {
 .caption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
@@ -220,8 +215,8 @@ function show() {
 
 	&.focused {
 		> .inputCore {
-			border-color: var(--accent) !important;
-			//box-shadow: 0 0 0 4px var(--focus);
+			border-color: var(--MI_THEME-accent) !important;
+			//box-shadow: 0 0 0 4px var(--MI_THEME-focus);
 		}
 	}
 
@@ -240,7 +235,7 @@ function show() {
 
 	&:hover {
 		> .inputCore {
-			border-color: var(--inputBorderHover) !important;
+			border-color: var(--MI_THEME-inputBorderHover) !important;
 		}
 	}
 }
@@ -248,7 +243,8 @@ function show() {
 .inputCore {
 	appearance: none;
 	-webkit-appearance: none;
-	display: block;
+	display: flex;
+	align-items: center;
 	height: v-bind("height + 'px'");
 	width: 100%;
 	margin: 0;
@@ -256,9 +252,9 @@ function show() {
 	font: inherit;
 	font-weight: normal;
 	font-size: 1em;
-	color: var(--fg);
-	background: var(--panel);
-	border: solid 1px var(--panel);
+	color: var(--MI_THEME-fg);
+	background: var(--MI_THEME-panel);
+	border: solid 1px var(--MI_THEME-panel);
 	border-radius: 6px;
 	outline: none;
 	box-shadow: none;
diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue
new file mode 100644
index 000000000000..34c22abc3181
--- /dev/null
+++ b/packages/frontend/src/components/MkSignin.input.vue
@@ -0,0 +1,206 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.wrapper" data-cy-signin-page-input>
+	<div :class="$style.root">
+		<div :class="$style.avatar">
+			<i class="ti ti-user"></i>
+		</div>
+
+		<!-- ログイン画面メッセージ -->
+		<MkInfo v-if="message">
+			{{ message }}
+		</MkInfo>
+
+		<!-- 外部サーバーへの転送 -->
+		<div v-if="openOnRemote" class="_gaps_m">
+			<div class="_gaps_s">
+				<MkButton type="button" rounded primary style="margin: 0 auto;" @click="openRemote(openOnRemote)">
+					{{ i18n.ts.continueOnRemote }} <i class="ti ti-external-link"></i>
+				</MkButton>
+				<button type="button" class="_button" :class="$style.instanceManualSelectButton" @click="specifyHostAndOpenRemote(openOnRemote)">
+					{{ i18n.ts.specifyServerHost }}
+				</button>
+			</div>
+			<div :class="$style.orHr">
+				<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
+			</div>
+		</div>
+
+		<!-- username入力 -->
+		<form class="_gaps_s" @submit.prevent="emit('usernameSubmitted', username)">
+			<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username>
+				<template #prefix>@</template>
+				<template #suffix>@{{ host }}</template>
+			</MkInput>
+			<MkButton type="submit" large primary rounded style="margin: 0 auto;" data-cy-signin-page-input-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+		</form>
+
+		<!-- パスワードレスログイン -->
+		<div :class="$style.orHr">
+			<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
+		</div>
+		<div>
+			<MkButton type="submit" style="margin: auto auto;" large rounded primary gradate @click="emit('passkeyClick', $event)">
+				<i class="ti ti-device-usb" style="font-size: medium;"></i>{{ i18n.ts.signinWithPasskey }}
+			</MkButton>
+		</div>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+import { toUnicode } from 'punycode/';
+
+import { query, extractDomain } from '@@/js/url.js';
+import { host as configHost } from '@@/js/config.js';
+import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkInfo from '@/components/MkInfo.vue';
+
+const props = withDefaults(defineProps<{
+	message?: string,
+	openOnRemote?: OpenOnRemoteOptions,
+}>(), {
+	message: '',
+	openOnRemote: undefined,
+});
+
+const emit = defineEmits<{
+	(ev: 'usernameSubmitted', v: string): void;
+	(ev: 'passkeyClick', v: MouseEvent): void;
+}>();
+
+const host = toUnicode(configHost);
+
+const username = ref('');
+
+//#region Open on remote
+function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void {
+	switch (options.type) {
+		case 'web':
+		case 'lookup': {
+			let _path: string;
+
+			if (options.type === 'lookup') {
+				// TODO: v2024.7.0以降が浸透してきたら正式なURLに変更する▼
+				// _path = `/lookup?uri=${encodeURIComponent(_path)}`;
+				_path = `/authorize-follow?acct=${encodeURIComponent(options.url)}`;
+			} else {
+				_path = options.path;
+			}
+
+			if (targetHost) {
+				window.open(`https://${targetHost}${_path}`, '_blank', 'noopener');
+			} else {
+				window.open(`https://misskey-hub.net/mi-web/?path=${encodeURIComponent(_path)}`, '_blank', 'noopener');
+			}
+			break;
+		}
+		case 'share': {
+			const params = query(options.params);
+			if (targetHost) {
+				window.open(`https://${targetHost}/share?${params}`, '_blank', 'noopener');
+			} else {
+				window.open(`https://misskey-hub.net/share/?${params}`, '_blank', 'noopener');
+			}
+			break;
+		}
+	}
+}
+
+async function specifyHostAndOpenRemote(options: OpenOnRemoteOptions): Promise<void> {
+	const { canceled, result: hostTemp } = await os.inputText({
+		title: i18n.ts.inputHostName,
+		placeholder: 'misskey.example.com',
+	});
+
+	if (canceled) return;
+
+	let targetHost: string | null = hostTemp;
+
+	// ドメイン部分だけを取り出す
+	targetHost = extractDomain(targetHost ?? '');
+	if (targetHost == null) {
+		os.alert({
+			type: 'error',
+			title: i18n.ts.invalidValue,
+			text: i18n.ts.tryAgain,
+		});
+		return;
+	}
+	openRemote(options, targetHost);
+}
+//#endregion
+</script>
+
+<style lang="scss" module>
+.root {
+	display: flex;
+	flex-direction: column;
+	gap: 20px;
+}
+
+.wrapper {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	min-height: 336px;
+
+	> .root {
+		width: 100%;
+	}
+}
+
+.avatar {
+	margin: 0 auto;
+	background-color: color-mix(in srgb, var(--MI_THEME-fg), transparent 85%);
+	color: color-mix(in srgb, var(--MI_THEME-fg), transparent 25%);
+	text-align: center;
+	height: 64px;
+	width: 64px;
+	font-size: 24px;
+	line-height: 64px;
+	border-radius: 50%;
+}
+
+.instanceManualSelectButton {
+	display: block;
+	text-align: center;
+	opacity: .7;
+	font-size: .8em;
+
+	&:hover {
+		text-decoration: underline;
+	}
+}
+
+.orHr {
+	position: relative;
+	margin: .4em auto;
+	width: 100%;
+	height: 1px;
+	background: var(--MI_THEME-divider);
+}
+
+.orMsg {
+	position: absolute;
+	top: -.6em;
+	display: inline-block;
+	padding: 0 1em;
+	background: var(--MI_THEME-panel);
+	font-size: 0.8em;
+	color: var(--MI_THEME-fgOnPanel);
+	margin: 0;
+	left: 50%;
+	transform: translateX(-50%);
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignin.passkey.vue b/packages/frontend/src/components/MkSignin.passkey.vue
new file mode 100644
index 000000000000..e5a56ab66d0d
--- /dev/null
+++ b/packages/frontend/src/components/MkSignin.passkey.vue
@@ -0,0 +1,92 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.wrapper">
+	<div class="_gaps" :class="$style.root">
+		<div class="_gaps_s">
+			<div :class="$style.passkeyIcon">
+				<i class="ti ti-fingerprint"></i>
+			</div>
+			<div :class="$style.passkeyDescription">{{ i18n.ts.useSecurityKey }}</div>
+		</div>
+
+		<MkButton large primary rounded :disabled="queryingKey" style="margin: 0 auto;" @click="queryKey">{{ i18n.ts.retry }}</MkButton>
+
+		<MkButton v-if="isPerformingPasswordlessLogin !== true" transparent rounded :disabled="queryingKey" style="margin: 0 auto;" @click="emit('useTotp')">{{ i18n.ts.useTotp }}</MkButton>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue';
+import { get as webAuthnRequest } from '@github/webauthn-json/browser-ponyfill';
+
+import { i18n } from '@/i18n.js';
+
+import MkButton from '@/components/MkButton.vue';
+
+import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
+
+const props = defineProps<{
+	credentialRequest: CredentialRequestOptions;
+	isPerformingPasswordlessLogin?: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'done', credential: AuthenticationPublicKeyCredential): void;
+	(ev: 'useTotp'): void;
+}>();
+
+const queryingKey = ref(true);
+
+async function queryKey() {
+	queryingKey.value = true;
+	await webAuthnRequest(props.credentialRequest)
+		.catch(() => {
+			return Promise.reject(null);
+		})
+		.then((credential) => {
+			emit('done', credential);
+		})
+		.finally(() => {
+			queryingKey.value = false;
+		});
+}
+
+onMounted(() => {
+	queryKey();
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	min-height: 336px;
+
+	> .root {
+		width: 100%;
+	}
+}
+
+.passkeyIcon {
+	margin: 0 auto;
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
+	text-align: center;
+	height: 64px;
+	width: 64px;
+	font-size: 24px;
+	line-height: 64px;
+	border-radius: 50%;
+}
+
+.passkeyDescription {
+	text-align: center;
+	font-size: 1.1em;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignin.password.vue b/packages/frontend/src/components/MkSignin.password.vue
new file mode 100644
index 000000000000..cd003a39df32
--- /dev/null
+++ b/packages/frontend/src/components/MkSignin.password.vue
@@ -0,0 +1,188 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.wrapper" data-cy-signin-page-password>
+	<div class="_gaps" :class="$style.root">
+		<div :class="$style.avatar" :style="{ backgroundImage: user ? `url('${user.avatarUrl}')` : undefined }"></div>
+		<div :class="$style.welcomeBackMessage">
+			<I18n :src="i18n.ts.welcomeBackWithName" tag="span">
+				<template #name><Mfm :text="user.name ?? user.username" :plain="true"/></template>
+			</I18n>
+		</div>
+
+		<!-- password入力 -->
+		<form class="_gaps_s" @submit.prevent="onSubmit">
+			<!-- ブラウザ オートコンプリート用 -->
+			<input type="hidden" name="username" autocomplete="username" :value="user.username">
+
+			<MkInput v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required autofocus data-cy-signin-password>
+				<template #prefix><i class="ti ti-lock"></i></template>
+				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
+			</MkInput>
+
+			<div v-if="needCaptcha">
+				<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
+				<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
+				<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
+				<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
+				<MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" provider="testcaptcha"/>
+			</div>
+
+			<MkButton type="submit" :disabled="needCaptcha && captchaFailed" large primary rounded style="margin: 0 auto;" data-cy-signin-page-password-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+		</form>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+export type PwResponse = {
+	password: string;
+	captcha: {
+		hCaptchaResponse: string | null;
+		mCaptchaResponse: string | null;
+		reCaptchaResponse: string | null;
+		turnstileResponse: string | null;
+		testcaptchaResponse: string | null;
+	};
+};
+</script>
+
+<script setup lang="ts">
+import { ref, computed, useTemplateRef, defineAsyncComponent } from 'vue';
+import * as Misskey from 'misskey-js';
+
+import { instance } from '@/instance.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkCaptcha from '@/components/MkCaptcha.vue';
+
+const props = defineProps<{
+	user: Misskey.entities.UserDetailed;
+	needCaptcha: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'passwordSubmitted', v: PwResponse): void;
+}>();
+
+const password = ref('');
+
+const hCaptcha = useTemplateRef('hcaptcha');
+const mCaptcha = useTemplateRef('mcaptcha');
+const reCaptcha = useTemplateRef('recaptcha');
+const turnstile = useTemplateRef('turnstile');
+const testcaptcha = useTemplateRef('testcaptcha');
+
+const hCaptchaResponse = ref<string | null>(null);
+const mCaptchaResponse = ref<string | null>(null);
+const reCaptchaResponse = ref<string | null>(null);
+const turnstileResponse = ref<string | null>(null);
+const testcaptchaResponse = ref<string | null>(null);
+
+const captchaFailed = computed((): boolean => {
+	return (
+		(instance.enableHcaptcha && !hCaptchaResponse.value) ||
+		(instance.enableMcaptcha && !mCaptchaResponse.value) ||
+		(instance.enableRecaptcha && !reCaptchaResponse.value) ||
+		(instance.enableTurnstile && !turnstileResponse.value) ||
+		(instance.enableTestcaptcha && !testcaptchaResponse.value)
+	);
+});
+
+function resetPassword(): void {
+	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, {
+		closed: () => dispose(),
+	});
+}
+
+function onSubmit() {
+	emit('passwordSubmitted', {
+		password: password.value,
+		captcha: {
+			hCaptchaResponse: hCaptchaResponse.value,
+			mCaptchaResponse: mCaptchaResponse.value,
+			reCaptchaResponse: reCaptchaResponse.value,
+			turnstileResponse: turnstileResponse.value,
+			testcaptchaResponse: testcaptchaResponse.value,
+		},
+	});
+}
+
+function resetCaptcha() {
+	hCaptcha.value?.reset();
+	mCaptcha.value?.reset();
+	reCaptcha.value?.reset();
+	turnstile.value?.reset();
+	testcaptcha.value?.reset();
+}
+
+defineExpose({
+	resetCaptcha,
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	min-height: 336px;
+
+	> .root {
+		width: 100%;
+	}
+}
+
+.avatar {
+	margin: 0 auto 0 auto;
+	width: 64px;
+	height: 64px;
+	background: #ddd;
+	background-position: center;
+	background-size: cover;
+	border-radius: 100%;
+}
+
+.welcomeBackMessage {
+	text-align: center;
+	font-size: 1.1em;
+}
+
+.instanceManualSelectButton {
+	display: block;
+	text-align: center;
+	opacity: .7;
+	font-size: .8em;
+
+	&:hover {
+		text-decoration: underline;
+	}
+}
+
+.orHr {
+	position: relative;
+	margin: .4em auto;
+	width: 100%;
+	height: 1px;
+	background: var(--MI_THEME-divider);
+}
+
+.orMsg {
+	position: absolute;
+	top: -.6em;
+	display: inline-block;
+	padding: 0 1em;
+	background: var(--MI_THEME-panel);
+	font-size: 0.8em;
+	color: var(--MI_THEME-fgOnPanel);
+	margin: 0;
+	left: 50%;
+	transform: translateX(-50%);
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignin.totp.vue b/packages/frontend/src/components/MkSignin.totp.vue
new file mode 100644
index 000000000000..670b8057c2af
--- /dev/null
+++ b/packages/frontend/src/components/MkSignin.totp.vue
@@ -0,0 +1,74 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.wrapper">
+	<div class="_gaps" :class="$style.root">
+		<div class="_gaps_s">
+			<div :class="$style.totpIcon">
+				<i class="ti ti-key"></i>
+			</div>
+			<div :class="$style.totpDescription">{{ i18n.ts['2fa'] }}</div>
+		</div>
+
+		<!-- totp入力 -->
+		<form class="_gaps_s" @submit.prevent="emit('totpSubmitted', token)">
+			<MkInput v-model="token" type="text" :pattern="isBackupCode ? '^[A-Z0-9]{32}$' :'^[0-9]{6}$'" autocomplete="one-time-code" required autofocus :spellcheck="false" :inputmode="isBackupCode ? undefined : 'numeric'">
+				<template #label>{{ i18n.ts.token }} ({{ i18n.ts['2fa'] }})</template>
+				<template #prefix><i v-if="isBackupCode" class="ti ti-key"></i><i v-else class="ti ti-123"></i></template>
+				<template #caption><button class="_textButton" type="button" @click="isBackupCode = !isBackupCode">{{ isBackupCode ? i18n.ts.useTotp : i18n.ts.useBackupCode }}</button></template>
+			</MkInput>
+
+			<MkButton type="submit" large primary rounded style="margin: 0 auto;">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+		</form>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue';
+
+import { i18n } from '@/i18n.js';
+
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+
+const emit = defineEmits<{
+	(ev: 'totpSubmitted', token: string): void;
+}>();
+
+const token = ref('');
+const isBackupCode = ref(false);
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	min-height: 336px;
+
+	> .root {
+		width: 100%;
+	}
+}
+
+.totpIcon {
+	margin: 0 auto;
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
+	text-align: center;
+	height: 64px;
+	width: 64px;
+	font-size: 24px;
+	line-height: 64px;
+	border-radius: 50%;
+}
+
+.totpDescription {
+	text-align: center;
+	font-size: 1.1em;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index 231a6dfcf5c5..776ee20e36f6 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -4,186 +4,288 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
-	<div class="_gaps_m">
-		<div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${user.avatarUrl}')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
-		<MkInfo v-if="message">
-			{{ message }}
-		</MkInfo>
-		<div v-if="openOnRemote" class="_gaps_m">
-			<div class="_gaps_s">
-				<MkButton type="button" rounded primary style="margin: 0 auto;" @click="openRemote(openOnRemote)">
-					{{ i18n.ts.continueOnRemote }} <i class="ti ti-external-link"></i>
-				</MkButton>
-				<button type="button" class="_button" :class="$style.instanceManualSelectButton" @click="specifyHostAndOpenRemote(openOnRemote)">
-					{{ i18n.ts.specifyServerHost }}
-				</button>
-			</div>
-			<div :class="$style.orHr">
-				<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
-			</div>
-		</div>
-		<div v-if="!totpLogin" class="normal-signin _gaps_m">
-			<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" 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" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password>
-				<template #prefix><i class="ti ti-lock"></i></template>
-				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
-			</MkInput>
-			<MkButton type="submit" large primary rounded :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>{{ i18n.ts.useSecurityKey }}</p>
-				<MkButton v-if="!queryingKey" @click="queryKey">
-					{{ i18n.ts.retry }}
-				</MkButton>
-			</div>
-			<div v-if="user && user.securityKeys" :class="$style.orHr">
-				<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
-			</div>
-			<div class="twofa-group totp-group _gaps">
-				<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :withPasswordToggle="true" required>
-					<template #label>{{ i18n.ts.password }}</template>
-					<template #prefix><i class="ti ti-lock"></i></template>
-				</MkInput>
-				<MkInput v-model="token" type="text" :pattern="isBackupCode ? '^[A-Z0-9]{32}$' :'^[0-9]{6}$'" autocomplete="one-time-code" required :spellcheck="false" :inputmode="isBackupCode ? undefined : 'numeric'">
-					<template #label>{{ i18n.ts.token }} ({{ i18n.ts['2fa'] }})</template>
-					<template #prefix><i v-if="isBackupCode" class="ti ti-key"></i><i v-else class="ti ti-123"></i></template>
-					<template #caption><button class="_textButton" type="button" @click="isBackupCode = !isBackupCode">{{ isBackupCode ? i18n.ts.useTotp : i18n.ts.useBackupCode }}</button></template>
-				</MkInput>
-				<MkButton type="submit" :disabled="signing" large primary rounded style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
-			</div>
-		</div>
+<div :class="$style.signinRoot">
+	<Transition
+		mode="out-in"
+		:enterActiveClass="$style.transition_enterActive"
+		:leaveActiveClass="$style.transition_leaveActive"
+		:enterFromClass="$style.transition_enterFrom"
+		:leaveToClass="$style.transition_leaveTo"
+
+		:inert="waiting"
+	>
+		<!-- 1. 外部サーバーへの転送・username入力・パスキー -->
+		<XInput
+			v-if="page === 'input'"
+			key="input"
+			:message="message"
+			:openOnRemote="openOnRemote"
+
+			@usernameSubmitted="onUsernameSubmitted"
+			@passkeyClick="onPasskeyLogin"
+		/>
+
+		<!-- 2. パスワード入力 -->
+		<XPassword
+			v-else-if="page === 'password'"
+			key="password"
+			ref="passwordPageEl"
+
+			:user="userInfo!"
+			:needCaptcha="needCaptcha"
+
+			@passwordSubmitted="onPasswordSubmitted"
+		/>
+
+		<!-- 3. ワンタイムパスワード -->
+		<XTotp
+			v-else-if="page === 'totp'"
+			key="totp"
+
+			@totpSubmitted="onTotpSubmitted"
+		/>
+
+		<!-- 4. パスキー -->
+		<XPasskey
+			v-else-if="page === 'passkey'"
+			key="passkey"
+
+			:credentialRequest="credentialRequest!"
+			:isPerformingPasswordlessLogin="doingPasskeyFromInputPage"
+
+			@done="onPasskeyDone"
+			@useTotp="onUseTotp"
+		/>
+	</Transition>
+	<div v-if="waiting" :class="$style.waitingRoot">
+		<MkLoading/>
 	</div>
-</form>
+</div>
 </template>
 
-<script lang="ts" setup>
-import { defineAsyncComponent, ref } from 'vue';
-import { toUnicode } from 'punycode/';
+<script setup lang="ts">
+import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
-import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
-import { query, extractDomain } from '@@/js/url.js';
+import { supported as webAuthnSupported, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
+
+import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill';
 import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
-import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
-import MkButton from '@/components/MkButton.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkInfo from '@/components/MkInfo.vue';
-import { host as configHost } from '@@/js/config.js';
-import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
+import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
 
-const signing = ref(false);
-const user = ref<Misskey.entities.UserDetailed | null>(null);
-const username = ref('');
-const password = ref('');
-const token = ref('');
-const host = ref(toUnicode(configHost));
-const totpLogin = ref(false);
-const isBackupCode = ref(false);
-const queryingKey = ref(false);
-let credentialRequest: CredentialRequestOptions | null = null;
+import XInput from '@/components/MkSignin.input.vue';
+import XPassword, { type PwResponse } from '@/components/MkSignin.password.vue';
+import XTotp from '@/components/MkSignin.totp.vue';
+import XPasskey from '@/components/MkSignin.passkey.vue';
 
 const emit = defineEmits<{
-	(ev: 'login', v: any): void;
+	(ev: 'login', v: Misskey.entities.SigninFlowResponse & { finished: true }): void;
 }>();
 
 const props = withDefaults(defineProps<{
-	withAvatar?: boolean;
 	autoSet?: boolean;
 	message?: string,
 	openOnRemote?: OpenOnRemoteOptions,
 }>(), {
-	withAvatar: true,
 	autoSet: false,
 	message: '',
 	openOnRemote: undefined,
 });
 
-function onUsernameChange(): void {
-	misskeyApi('users/show', {
-		username: username.value,
-	}).then(userResponse => {
-		user.value = userResponse;
-	}, () => {
-		user.value = null;
-	});
+const page = ref<'input' | 'password' | 'totp' | 'passkey'>('input');
+const waiting = ref(false);
+
+const passwordPageEl = useTemplateRef('passwordPageEl');
+const needCaptcha = ref(false);
+
+const userInfo = ref<null | Misskey.entities.UserDetailed>(null);
+const password = ref('');
+
+//#region Passkey Passwordless
+const credentialRequest = shallowRef<CredentialRequestOptions | null>(null);
+const passkeyContext = ref('');
+const doingPasskeyFromInputPage = ref(false);
+
+function onPasskeyLogin(): void {
+	if (webAuthnSupported()) {
+		doingPasskeyFromInputPage.value = true;
+		waiting.value = true;
+		misskeyApi('signin-with-passkey', {})
+			.then((res) => {
+				passkeyContext.value = res.context ?? '';
+				credentialRequest.value = parseRequestOptionsFromJSON({
+					publicKey: res.option,
+				});
+
+				page.value = 'passkey';
+				waiting.value = false;
+			})
+			.catch(onSigninApiError);
+	}
 }
 
-function onLogin(res: any): Promise<void> | void {
-	if (props.autoSet) {
-		return login(res.i);
+function onPasskeyDone(credential: AuthenticationPublicKeyCredential): void {
+	waiting.value = true;
+
+	if (doingPasskeyFromInputPage.value) {
+		misskeyApi('signin-with-passkey', {
+			credential: credential.toJSON(),
+			context: passkeyContext.value,
+		}).then((res) => {
+			if (res.signinResponse == null) {
+				onSigninApiError();
+				return;
+			}
+			emit('login', res.signinResponse);
+		}).catch(onSigninApiError);
+	} else if (userInfo.value != null) {
+		tryLogin({
+			username: userInfo.value.username,
+			password: password.value,
+			credential: credential.toJSON(),
+		});
 	}
 }
 
-async function queryKey(): Promise<void> {
-	if (credentialRequest == null) return;
-	queryingKey.value = true;
-	await webAuthnRequest(credentialRequest)
-		.catch(() => {
-			queryingKey.value = false;
-			return Promise.reject(null);
-		}).then(credential => {
-			credentialRequest = null;
-			queryingKey.value = false;
-			signing.value = true;
-			return misskeyApi('signin', {
-				username: username.value,
-				password: password.value,
-				credential: credential.toJSON(),
-			});
-		}).then(res => {
-			emit('login', res);
-			return onLogin(res);
-		}).catch(err => {
-			if (err === null) return;
-			os.alert({
-				type: 'error',
-				text: i18n.ts.signinFailed,
-			});
-			signing.value = false;
+function onUseTotp(): void {
+	page.value = 'totp';
+}
+//#endregion
+
+async function onUsernameSubmitted(username: string) {
+	waiting.value = true;
+
+	userInfo.value = await misskeyApi('users/show', {
+		username,
+	}).catch(() => null);
+
+	await tryLogin({
+		username,
+	});
+}
+
+async function onPasswordSubmitted(pw: PwResponse) {
+	waiting.value = true;
+	password.value = pw.password;
+
+	if (userInfo.value == null) {
+		await os.alert({
+			type: 'error',
+			title: i18n.ts.noSuchUser,
+			text: i18n.ts.signinFailed,
+		});
+		waiting.value = false;
+		return;
+	} else {
+		await tryLogin({
+			username: userInfo.value.username,
+			password: pw.password,
+			'hcaptcha-response': pw.captcha.hCaptchaResponse,
+			'm-captcha-response': pw.captcha.mCaptchaResponse,
+			'g-recaptcha-response': pw.captcha.reCaptchaResponse,
+			'turnstile-response': pw.captcha.turnstileResponse,
+			'testcaptcha-response': pw.captcha.testcaptchaResponse,
 		});
+	}
 }
 
-function onSubmit(): void {
-	signing.value = true;
-	if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
-		if (webAuthnSupported() && user.value.securityKeys) {
-			misskeyApi('signin', {
-				username: username.value,
-				password: password.value,
-			}).then(res => {
-				totpLogin.value = true;
-				signing.value = false;
-				credentialRequest = parseRequestOptionsFromJSON({
-					publicKey: res,
-				});
-			})
-				.then(() => queryKey())
-				.catch(loginFailed);
-		} else {
-			totpLogin.value = true;
-			signing.value = false;
-		}
+async function onTotpSubmitted(token: string) {
+	waiting.value = true;
+
+	if (userInfo.value == null) {
+		await os.alert({
+			type: 'error',
+			title: i18n.ts.noSuchUser,
+			text: i18n.ts.signinFailed,
+		});
+		waiting.value = false;
+		return;
 	} else {
-		misskeyApi('signin', {
-			username: username.value,
+		await tryLogin({
+			username: userInfo.value.username,
 			password: password.value,
-			token: user.value?.twoFactorEnabled ? token.value : undefined,
-		}).then(res => {
+			token,
+		});
+	}
+}
+
+async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promise<Misskey.entities.SigninFlowResponse> {
+	const _req = {
+		username: req.username ?? userInfo.value?.username,
+		...req,
+	};
+
+	function assertIsSigninFlowRequest(x: Partial<Misskey.entities.SigninFlowRequest>): x is Misskey.entities.SigninFlowRequest {
+		return x.username != null;
+	}
+
+	if (!assertIsSigninFlowRequest(_req)) {
+		throw new Error('Invalid request');
+	}
+
+	return await misskeyApi('signin-flow', _req).then(async (res) => {
+		if (res.finished) {
 			emit('login', res);
-			onLogin(res);
-		}).catch(loginFailed);
+			await onLoginSucceeded(res);
+		} else {
+			switch (res.next) {
+				case 'captcha': {
+					needCaptcha.value = true;
+					page.value = 'password';
+					break;
+				}
+				case 'password': {
+					needCaptcha.value = false;
+					page.value = 'password';
+					break;
+				}
+				case 'totp': {
+					page.value = 'totp';
+					break;
+				}
+				case 'passkey': {
+					if (webAuthnSupported()) {
+						credentialRequest.value = parseRequestOptionsFromJSON({
+							publicKey: res.authRequest,
+						});
+						page.value = 'passkey';
+					} else {
+						page.value = 'totp';
+					}
+					break;
+				}
+			}
+
+			if (doingPasskeyFromInputPage.value === true) {
+				doingPasskeyFromInputPage.value = false;
+				page.value = 'input';
+				password.value = '';
+			}
+			passwordPageEl.value?.resetCaptcha();
+			nextTick(() => {
+				waiting.value = false;
+			});
+		}
+		return res;
+	}).catch((err) => {
+		onSigninApiError(err);
+		return Promise.reject(err);
+	});
+}
+
+async function onLoginSucceeded(res: Misskey.entities.SigninFlowResponse & { finished: true }) {
+	if (props.autoSet) {
+		await login(res.i);
 	}
 }
 
-function loginFailed(err: any): void {
-	switch (err.id) {
+function onSigninApiError(err?: any): void {
+	const id = err?.id ?? null;
+
+	switch (id) {
 		case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
 			os.alert({
 				type: 'error',
@@ -212,123 +314,105 @@ function loginFailed(err: any): void {
 			});
 			break;
 		}
-		default: {
-			console.error(err);
+		case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': {
 			os.alert({
 				type: 'error',
 				title: i18n.ts.loginFailed,
-				text: JSON.stringify(err),
+				text: i18n.ts.incorrectTotp,
 			});
+			break;
 		}
-	}
-
-	totpLogin.value = false;
-	signing.value = false;
-}
-
-function resetPassword(): void {
-	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, {
-		closed: () => dispose(),
-	});
-}
-
-function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void {
-	switch (options.type) {
-		case 'web':
-		case 'lookup': {
-			let _path: string;
-
-			if (options.type === 'lookup') {
-				// TODO: v2024.7.0以降が浸透してきたら正式なURLに変更する▼
-				// _path = `/lookup?uri=${encodeURIComponent(_path)}`;
-				_path = `/authorize-follow?acct=${encodeURIComponent(options.url)}`;
-			} else {
-				_path = options.path;
-			}
-
-			if (targetHost) {
-				window.open(`https://${targetHost}${_path}`, '_blank', 'noopener');
-			} else {
-				window.open(`https://misskey-hub.net/mi-web/?path=${encodeURIComponent(_path)}`, '_blank', 'noopener');
-			}
+		case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.unknownWebAuthnKey,
+			});
 			break;
 		}
-		case 'share': {
-			const params = query(options.params);
-			if (targetHost) {
-				window.open(`https://${targetHost}/share?${params}`, '_blank', 'noopener');
-			} else {
-				window.open(`https://misskey-hub.net/share/?${params}`, '_blank', 'noopener');
-			}
+		case '93b86c4b-72f9-40eb-9815-798928603d1e': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.passkeyVerificationFailed,
+			});
 			break;
 		}
+		case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.passkeyVerificationFailed,
+			});
+			break;
+		}
+		case '2d84773e-f7b7-4d0b-8f72-bb69b584c912': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.passkeyVerificationSucceededButPasswordlessLoginDisabled,
+			});
+			break;
+		}
+		default: {
+			console.error(err);
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: JSON.stringify(err),
+			});
+		}
 	}
-}
 
-async function specifyHostAndOpenRemote(options: OpenOnRemoteOptions): Promise<void> {
-	const { canceled, result: hostTemp } = await os.inputText({
-		title: i18n.ts.inputHostName,
-		placeholder: 'misskey.example.com',
-	});
-
-	if (canceled) return;
-
-	let targetHost: string | null = hostTemp;
-
-	// ドメイン部分だけを取り出す
-	targetHost = extractDomain(targetHost);
-	if (targetHost == null) {
-		os.alert({
-			type: 'error',
-			title: i18n.ts.invalidValue,
-			text: i18n.ts.tryAgain,
-		});
-		return;
+	if (doingPasskeyFromInputPage.value === true) {
+		doingPasskeyFromInputPage.value = false;
+		page.value = 'input';
+		password.value = '';
 	}
-	openRemote(options, targetHost);
+	passwordPageEl.value?.resetCaptcha();
+	nextTick(() => {
+		waiting.value = false;
+	});
 }
+
+onBeforeUnmount(() => {
+	password.value = '';
+	needCaptcha.value = false;
+	userInfo.value = null;
+});
 </script>
 
 <style lang="scss" module>
-.avatar {
-	margin: 0 auto 0 auto;
-	width: 64px;
-	height: 64px;
-	background: #ddd;
-	background-position: center;
-	background-size: cover;
-	border-radius: 100%;
+.transition_enterActive,
+.transition_leaveActive {
+	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
 }
-
-.instanceManualSelectButton {
-	display: block;
-	text-align: center;
-	opacity: .7;
-	font-size: .8em;
-
-	&:hover {
-		text-decoration: underline;
-	}
+.transition_enterFrom {
+	opacity: 0;
+	transform: translateX(50px);
 }
+.transition_leaveTo {
+	opacity: 0;
+	transform: translateX(-50px);
+}
+
+.signinRoot {
+	overflow-x: hidden;
+	overflow-x: clip;
 
-.orHr {
 	position: relative;
-	margin: .4em auto;
-	width: 100%;
-	height: 1px;
-	background: var(--divider);
 }
 
-.orMsg {
+.waitingRoot {
 	position: absolute;
-	top: -.6em;
-	display: inline-block;
-	padding: 0 1em;
-	background: var(--panel);
-	font-size: 0.8em;
-	color: var(--fgOnPanel);
-	margin: 0;
-	left: 50%;
-	transform: translateX(-50%);
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background-color: color-mix(in srgb, var(--MI_THEME-panel), transparent 50%);
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	z-index: 1;
 }
 </style>
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index 524c62b4d3aa..676a336ec7de 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -4,26 +4,30 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModalWindow
-	ref="dialog"
-	:width="400"
-	:height="430"
-	@close="onClose"
+<MkModal
+	ref="modal"
+	:preferType="'dialog'"
+	@click="onClose"
 	@closed="emit('closed')"
 >
-	<template #header>{{ i18n.ts.login }}</template>
-
-	<MkSpacer :marginMin="20" :marginMax="28">
-		<MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" @login="onLogin"/>
-	</MkSpacer>
-</MkModalWindow>
+	<div :class="$style.root">
+		<div :class="$style.header">
+			<div :class="$style.headerText"><i class="ti ti-login-2"></i> {{ i18n.ts.login }}</div>
+			<button :class="$style.closeButton" class="_button" @click="onClose"><i class="ti ti-x"></i></button>
+		</div>
+		<div :class="$style.content">
+			<MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" @login="onLogin"/>
+		</div>
+	</div>
+</MkModal>
 </template>
 
 <script lang="ts" setup>
+import * as Misskey from 'misskey-js';
 import { shallowRef } from 'vue';
 import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
 import MkSignin from '@/components/MkSignin.vue';
-import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkModal from '@/components/MkModal.vue';
 import { i18n } from '@/i18n.js';
 
 withDefaults(defineProps<{
@@ -37,20 +41,67 @@ withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'done', v: any): void;
+	(ev: 'done', v: Misskey.entities.SigninFlowResponse & { finished: true }): void;
 	(ev: 'closed'): void;
 	(ev: 'cancelled'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 function onClose() {
 	emit('cancelled');
-	if (dialog.value) dialog.value.close();
+	if (modal.value) modal.value.close();
 }
 
-function onLogin(res) {
+function onLogin(res: Misskey.entities.SigninFlowResponse & { finished: true }) {
 	emit('done', res);
-	if (dialog.value) dialog.value.close();
+	if (modal.value) modal.value.close();
 }
 </script>
+
+<style lang="scss" module>
+.root {
+	overflow: auto;
+	margin: auto;
+	position: relative;
+	width: 100%;
+	max-width: 400px;
+	height: 100%;
+	max-height: 450px;
+	box-sizing: border-box;
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
+}
+
+.header {
+	position: sticky;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 50px;
+	box-sizing: border-box;
+	display: flex;
+	align-items: center;
+	font-weight: bold;
+	backdrop-filter: var(--MI-blur, blur(15px));
+	background: var(--MI_THEME-acrylicBg);
+	z-index: 1;
+}
+
+.headerText {
+	padding: 0 20px;
+	box-sizing: border-box;
+}
+
+.closeButton {
+	margin-left: auto;
+	padding: 16px;
+	font-size: 16px;
+	line-height: 16px;
+}
+
+.content {
+	padding: 32px;
+	box-sizing: border-box;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 4ab4380ad503..e1f4e26d6205 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -21,12 +21,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #caption>
 					<div><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.cannotBeChangedLater }}</div>
 					<span v-if="usernameState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span>
-					<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
-					<span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
-					<span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
-					<span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
-					<span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span>
-					<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span>
+					<span v-else-if="usernameState === 'ok'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
+					<span v-else-if="usernameState === 'unavailable'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
+					<span v-else-if="usernameState === 'error'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
+					<span v-else-if="usernameState === 'invalid-format'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
+					<span v-else-if="usernameState === 'min-range'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span>
+					<span v-else-if="usernameState === 'max-range'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span>
 				</template>
 			</MkInput>
 			<MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
@@ -34,38 +34,39 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #prefix><i class="ti ti-mail"></i></template>
 				<template #caption>
 					<span v-if="emailState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span>
-					<span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
-					<span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span>
-					<span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span>
-					<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
-					<span v-else-if="emailState === 'unavailable:banned'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.banned }}</span>
-					<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
-					<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
-					<span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
-					<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
+					<span v-else-if="emailState === 'ok'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
+					<span v-else-if="emailState === 'unavailable:used'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span>
+					<span v-else-if="emailState === 'unavailable:format'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span>
+					<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
+					<span v-else-if="emailState === 'unavailable:banned'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.banned }}</span>
+					<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
+					<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
+					<span v-else-if="emailState === 'unavailable'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
+					<span v-else-if="emailState === 'error'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
 				</template>
 			</MkInput>
 			<MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
 				<template #label>{{ i18n.ts.password }}</template>
 				<template #prefix><i class="ti ti-lock"></i></template>
 				<template #caption>
-					<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span>
-					<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span>
-					<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span>
+					<span v-if="passwordStrength == 'low'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span>
+					<span v-if="passwordStrength == 'medium'" style="color: var(--MI_THEME-warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span>
+					<span v-if="passwordStrength == 'high'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span>
 				</template>
 			</MkInput>
 			<MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
 				<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
 				<template #prefix><i class="ti ti-lock"></i></template>
 				<template #caption>
-					<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span>
-					<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span>
+					<span v-if="passwordRetypeState == 'match'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span>
+					<span v-if="passwordRetypeState == 'not-match'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span>
 				</template>
 			</MkInput>
 			<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
 			<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
 			<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
 			<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
+			<MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha"/>
 			<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
 				<template v-if="submitting">
 					<MkLoading :em="true" :colored="false"/>
@@ -81,10 +82,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, computed } from 'vue';
 import { toUnicode } from 'punycode/';
 import * as Misskey from 'misskey-js';
+import * as config from '@@/js/config.js';
 import MkButton from './MkButton.vue';
 import MkInput from './MkInput.vue';
 import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
-import * as config from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
@@ -98,15 +99,17 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'signup', user: Misskey.entities.SigninResponse): void;
+	(ev: 'signup', user: Misskey.entities.SignupResponse): void;
 	(ev: 'signupEmailPending'): void;
 }>();
 
 const host = toUnicode(config.host);
 
 const hcaptcha = ref<Captcha | undefined>();
+const mcaptcha = ref<Captcha | undefined>();
 const recaptcha = ref<Captcha | undefined>();
 const turnstile = ref<Captcha | undefined>();
+const testcaptcha = ref<Captcha | undefined>();
 
 const username = ref<string>('');
 const password = ref<string>('');
@@ -122,6 +125,7 @@ const hCaptchaResponse = ref<string | null>(null);
 const mCaptchaResponse = ref<string | null>(null);
 const reCaptchaResponse = ref<string | null>(null);
 const turnstileResponse = ref<string | null>(null);
+const testcaptchaResponse = ref<string | null>(null);
 const usernameAbortController = ref<null | AbortController>(null);
 const emailAbortController = ref<null | AbortController>(null);
 
@@ -131,6 +135,7 @@ const shouldDisableSubmitting = computed((): boolean => {
 		instance.enableMcaptcha && !mCaptchaResponse.value ||
 		instance.enableRecaptcha && !reCaptchaResponse.value ||
 		instance.enableTurnstile && !turnstileResponse.value ||
+		instance.enableTestcaptcha && !testcaptchaResponse.value ||
 		instance.emailRequiredForSignup && emailState.value !== 'ok' ||
 		usernameState.value !== 'ok' ||
 		passwordRetypeState.value !== 'match';
@@ -249,18 +254,31 @@ async function onSubmit(): Promise<void> {
 	if (submitting.value) return;
 	submitting.value = true;
 
-	try {
-		await misskeyApi('signup', {
-			username: username.value,
-			password: password.value,
-			emailAddress: email.value,
-			invitationCode: invitationCode.value,
-			'hcaptcha-response': hCaptchaResponse.value,
-			'm-captcha-response': mCaptchaResponse.value,
-			'g-recaptcha-response': reCaptchaResponse.value,
-			'turnstile-response': turnstileResponse.value,
-		});
-		if (instance.emailRequiredForSignup) {
+	const signupPayload: Misskey.entities.SignupRequest = {
+		username: username.value,
+		password: password.value,
+		emailAddress: email.value,
+		invitationCode: invitationCode.value,
+		'hcaptcha-response': hCaptchaResponse.value,
+		'm-captcha-response': mCaptchaResponse.value,
+		'g-recaptcha-response': reCaptchaResponse.value,
+		'turnstile-response': turnstileResponse.value,
+		'testcaptcha-response': testcaptchaResponse.value,
+	};
+
+	const res = await fetch(`${config.apiUrl}/signup`, {
+		method: 'POST',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+		body: JSON.stringify(signupPayload),
+	}).catch(() => {
+		onSignupApiError();
+		return null;
+	});
+
+	if (res && res.ok) {
+		if (res.status === 204 || instance.emailRequiredForSignup) {
 			os.alert({
 				type: 'success',
 				title: i18n.ts._signup.almostThere,
@@ -268,27 +286,34 @@ async function onSubmit(): Promise<void> {
 			});
 			emit('signupEmailPending');
 		} else {
-			const res = await misskeyApi('signin', {
-				username: username.value,
-				password: password.value,
-			});
-			emit('signup', res);
+			const resJson = (await res.json()) as Misskey.entities.SignupResponse;
+			if (_DEV_) console.log(resJson);
+
+			emit('signup', resJson);
 
 			if (props.autoSet) {
-				return login(res.i);
+				await login(resJson.token);
 			}
 		}
-	} catch {
-		submitting.value = false;
-		hcaptcha.value?.reset?.();
-		recaptcha.value?.reset?.();
-		turnstile.value?.reset?.();
-
-		os.alert({
-			type: 'error',
-			text: i18n.ts.somethingHappened,
-		});
+	} else {
+		onSignupApiError();
 	}
+
+	submitting.value = false;
+}
+
+function onSignupApiError() {
+	submitting.value = false;
+	hcaptcha.value?.reset?.();
+	mcaptcha.value?.reset?.();
+	recaptcha.value?.reset?.();
+	turnstile.value?.reset?.();
+	testcaptcha.value?.reset?.();
+
+	os.alert({
+		type: 'error',
+		text: i18n.ts.somethingHappened,
+	});
 }
 </script>
 
@@ -297,8 +322,8 @@ async function onSubmit(): Promise<void> {
 	padding: 16px;
 	text-align: center;
 	font-size: 26px;
-	background-color: var(--accentedBg);
-	color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 }
 
 .captcha {
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
index 59a3651cd474..e2a06dd91f41 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.vue
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 			<MkFolder v-if="availableServerRules" :defaultOpen="true">
 				<template #label>{{ i18n.ts.serverRules }}</template>
-				<template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--success)"></i></template>
+				<template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
 
 				<ol class="_gaps_s" :class="$style.rules">
 					<li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 			<MkFolder v-if="availableTos || availablePrivacyPolicy" :defaultOpen="true">
 				<template #label>{{ tosPrivacyPolicyLabel }}</template>
-				<template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--success)"></i></template>
+				<template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
 				<div class="_gaps_s">
 					<div v-if="availableTos"><a :href="instance.tosUrl ?? undefined" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a></div>
 					<div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl ?? undefined" class="_link" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ti ti-external-link"></i></a></div>
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 			<MkFolder :defaultOpen="true">
 				<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
-				<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template>
+				<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
 
 				<a href="https://misskey-hub.net/docs/for-users/onboarding/warning/" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a>
 
@@ -150,8 +150,8 @@ async function updateAgreeNote(v: boolean) {
 	padding: 16px;
 	text-align: center;
 	font-size: 26px;
-	background-color: var(--accentedBg);
-	color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 }
 
 .rules {
@@ -170,14 +170,14 @@ async function updateAgreeNote(v: boolean) {
 		flex-shrink: 0;
 		display: flex;
 		position: sticky;
-		top: calc(var(--stickyTop, 0px) + 8px);
+		top: calc(var(--MI-stickyTop, 0px) + 8px);
 		counter-increment: item;
 		content: counter(item);
 		width: 32px;
 		height: 32px;
 		line-height: 32px;
-		background-color: var(--accentedBg);
-		color: var(--accent);
+		background-color: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
 		font-size: 13px;
 		font-weight: bold;
 		align-items: center;
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 97310d32a64e..6fb9d7783704 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 	ref="dialog"
 	:width="500"
 	:height="600"
-	@close="dialog?.close()"
-	@closed="$emit('closed')"
+	@close="onClose"
+	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.signup }}</template>
 
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:leaveToClass="$style.transition_x_leaveTo"
 		>
 			<template v-if="!isAcceptedServerRule">
-				<XServerRules @done="isAcceptedServerRule = true" @cancel="dialog?.close()"/>
+				<XServerRules @done="isAcceptedServerRule = true" @cancel="onClose"/>
 			</template>
 			<template v-else>
 				<XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
@@ -47,7 +47,8 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'done', res: Misskey.entities.SigninResponse): void;
+	(ev: 'done', res: Misskey.entities.SignupResponse): void;
+	(ev: 'cancelled'): void;
 	(ev: 'closed'): void;
 }>();
 
@@ -55,7 +56,12 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
 const isAcceptedServerRule = ref(false);
 
-function onSignup(res: Misskey.entities.SigninResponse) {
+function onClose() {
+	emit('cancelled');
+	dialog.value?.close();
+}
+
+function onSignup(res: Misskey.entities.SignupResponse) {
 	emit('done', res);
 	dialog.value?.close();
 }
diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
index 1845b01b6944..4c197ed43e7d 100644
--- a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
+++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
@@ -63,12 +63,12 @@ function close() {
 .root {
 	position: fixed;
 	z-index: v-bind(zIndex);
-	bottom: var(--margin);
+	bottom: var(--MI-margin);
 	left: 0;
 	right: 0;
 	margin: auto;
 	box-sizing: border-box;
-	width: calc(100% - (var(--margin) * 2));
+	width: calc(100% - (var(--MI-margin) * 2));
 	max-width: 500px;
 	display: flex;
 }
@@ -77,7 +77,7 @@ function close() {
 	text-align: center;
 	padding-top: 25px;
 	width: 100px;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 @media (max-width: 500px) {
 	.icon {
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 3bbb163f0f8b..9e02884b8ca1 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -62,11 +62,11 @@ const collapsed = ref(isLong);
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+			background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
-				background: var(--panel);
+				background: var(--MI_THEME-panel);
 				padding: 6px 10px;
 				font-size: 0.8em;
 				border-radius: 999px;
@@ -75,7 +75,7 @@ const collapsed = ref(isLong);
 
 			&:hover {
 				> .fadeLabel {
-					background: var(--panelHighlight);
+					background: var(--MI_THEME-panelHighlight);
 				}
 			}
 		}
@@ -84,25 +84,25 @@ const collapsed = ref(isLong);
 
 .reply {
 	margin-right: 6px;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 .rp {
 	margin-left: 4px;
 	font-style: oblique;
-	color: var(--renote);
+	color: var(--MI_THEME-renote);
 }
 
 .showLess {
 	width: 100%;
 	margin-top: 14px;
 	position: sticky;
-	bottom: calc(var(--stickyBottom, 0px) + 14px);
+	bottom: calc(var(--MI-stickyBottom, 0px) + 14px);
 }
 
 .showLessLabel {
 	display: inline-block;
-	background: var(--popup);
+	background: var(--MI_THEME-popup);
 	padding: 6px 10px;
 	font-size: 0.8em;
 	border-radius: 999px;
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 3746ffd8f3e9..0caaed6f39d0 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -28,11 +28,38 @@ SPDX-License-Identifier: AGPL-3.0-only
 </div>
 </template>
 
-<script lang="ts" setup>
-import { } from 'vue';
+<script lang="ts">
+export type SuperMenuDef = {
+	title?: string;
+	items: ({
+		type: 'a';
+		href: string;
+		target?: string;
+		icon?: string;
+		text: string;
+		danger?: boolean;
+		active?: boolean;
+	} | {
+		type: 'button';
+		icon?: string;
+		text: string;
+		danger?: boolean;
+		active?: boolean;
+		action: (ev: MouseEvent) => void;
+	} | {
+		type: 'link';
+		to: string;
+		icon?: string;
+		text: string;
+		danger?: boolean;
+		active?: boolean;
+	})[];
+};
+</script>
 
+<script lang="ts" setup>
 defineProps<{
-	def: any[];
+	def: SuperMenuDef[];
 	grid?: boolean;
 }>();
 </script>
@@ -43,7 +70,7 @@ defineProps<{
 		& + .group {
 			margin-top: 16px;
 			padding-top: 16px;
-			border-top: solid 0.5px var(--divider);
+			border-top: solid 0.5px var(--MI_THEME-divider);
 		}
 
 		> .title {
@@ -64,7 +91,7 @@ defineProps<{
 
 				&:hover {
 					text-decoration: none;
-					background: var(--panelHighlight);
+					background: var(--MI_THEME-panelHighlight);
 				}
 
 				&:focus-visible {
@@ -72,12 +99,12 @@ defineProps<{
 				}
 
 				&.active {
-					color: var(--accent);
-					background: var(--accentedBg);
+					color: var(--MI_THEME-accent);
+					background: var(--MI_THEME-accentedBg);
 				}
 
 				&.danger {
-					color: var(--error);
+					color: var(--MI_THEME-error);
 				}
 
 				> .icon {
@@ -128,10 +155,10 @@ defineProps<{
 					&:hover {
 						text-decoration: none;
 						background: none;
-						color: var(--accent);
+						color: var(--MI_THEME-accent);
 
 						> .icon {
-							background: var(--accentedBg);
+							background: var(--MI_THEME-accentedBg);
 						}
 					}
 
@@ -144,7 +171,7 @@ defineProps<{
 						width: 60px;
 						height: 60px;
 						aspect-ratio: 1;
-						background: var(--panel);
+						background: var(--MI_THEME-panel);
 						border-radius: 100%;
 					}
 
diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue
index 226908e221e4..fd8ed0992e66 100644
--- a/packages/frontend/src/components/MkSwitch.button.vue
+++ b/packages/frontend/src/components/MkSwitch.button.vue
@@ -51,9 +51,9 @@ const toggle = () => {
 	width: calc(var(--height) * 1.6);
 	height: calc(var(--height) + 2px); // 枠線
 	outline: none;
-	background: var(--switchOffBg);
+	background: var(--MI_THEME-switchOffBg);
 	background-clip: content-box;
-	border: solid 1px var(--switchOffBg);
+	border: solid 1px var(--MI_THEME-switchOffBg);
 	border-radius: 999px;
 	cursor: pointer;
 	transition: inherit;
@@ -61,8 +61,8 @@ const toggle = () => {
 }
 
 .buttonChecked {
-	background-color: var(--switchOnBg) !important;
-	border-color: var(--switchOnBg) !important;
+	background-color: var(--MI_THEME-switchOnBg) !important;
+	border-color: var(--MI_THEME-switchOnBg) !important;
 }
 
 .buttonDisabled {
@@ -80,12 +80,12 @@ const toggle = () => {
 
 	&:not(.knobChecked) {
 		left: 3px;
-		background: var(--switchOffFg);
+		background: var(--MI_THEME-switchOffFg);
 	}
 }
 
 .knobChecked {
 	left: calc(calc(100% - var(--height)) + 3px);
-	background: var(--switchOnFg);
+	background: var(--MI_THEME-switchOnFg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue
index a0994d9cc91d..5e6029ee40ca 100644
--- a/packages/frontend/src/components/MkSwitch.vue
+++ b/packages/frontend/src/components/MkSwitch.vue
@@ -59,7 +59,7 @@ const toggle = () => {
 
 	&:hover {
 		> .button {
-			border-color: var(--inputBorderHover) !important;
+			border-color: var(--MI_THEME-inputBorderHover) !important;
 		}
 	}
 
@@ -77,7 +77,7 @@ const toggle = () => {
 	margin: 0;
 
 	&:focus-visible ~ .toggle {
-		outline: 2px solid var(--focus);
+		outline: 2px solid var(--MI_THEME-focus);
 		outline-offset: 2px;
 	}
 }
@@ -87,7 +87,7 @@ const toggle = () => {
 	margin-top: 2px;
 	display: block;
 	transition: inherit;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 
 .label {
@@ -99,7 +99,7 @@ const toggle = () => {
 
 .caption {
 	margin: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 	font-size: 0.85em;
 
 	&:empty {
diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts
index 69b8edd85aec..19e4eea733cd 100644
--- a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts
+++ b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts
@@ -4,9 +4,10 @@
  */
 
 import { defineAsyncComponent } from 'vue';
+import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
 
-export type SystemWebhookEventType = 'abuseReport' | 'abuseReportResolved';
+export type SystemWebhookEventType = Misskey.entities.SystemWebhook['on'][number];
 
 export type MkSystemWebhookEditorProps = {
 	mode: 'create' | 'edit';
diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue
index f5c7a3160bb4..485d003f93a3 100644
--- a/packages/frontend/src/components/MkSystemWebhookEditor.vue
+++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue
@@ -35,16 +35,43 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkFolder :defaultOpen="true">
 					<template #label>{{ i18n.ts._webhookSettings.trigger }}</template>
 
-					<div class="_gaps_s">
-						<MkSwitch v-model="events.abuseReport" :disabled="disabledEvents.abuseReport">
-							<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReport }}</template>
-						</MkSwitch>
-						<MkSwitch v-model="events.abuseReportResolved" :disabled="disabledEvents.abuseReportResolved">
-							<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReportResolved }}</template>
-						</MkSwitch>
-						<MkSwitch v-model="events.userCreated" :disabled="disabledEvents.userCreated">
-							<template #label>{{ i18n.ts._webhookSettings._systemEvents.userCreated }}</template>
-						</MkSwitch>
+					<div class="_gaps">
+						<div class="_gaps_s">
+							<div :class="$style.switchBox">
+								<MkSwitch v-model="events.abuseReport" :disabled="disabledEvents.abuseReport">
+									<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReport }}</template>
+								</MkSwitch>
+								<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.abuseReport)" @click="test('abuseReport')"><i class="ti ti-send"></i></MkButton>
+							</div>
+							<div :class="$style.switchBox">
+								<MkSwitch v-model="events.abuseReportResolved" :disabled="disabledEvents.abuseReportResolved">
+									<template #label>{{ i18n.ts._webhookSettings._systemEvents.abuseReportResolved }}</template>
+								</MkSwitch>
+								<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.abuseReportResolved)" @click="test('abuseReportResolved')"><i class="ti ti-send"></i></MkButton>
+							</div>
+							<div :class="$style.switchBox">
+								<MkSwitch v-model="events.userCreated" :disabled="disabledEvents.userCreated">
+									<template #label>{{ i18n.ts._webhookSettings._systemEvents.userCreated }}</template>
+								</MkSwitch>
+								<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.userCreated)" @click="test('userCreated')"><i class="ti ti-send"></i></MkButton>
+							</div>
+							<div :class="$style.switchBox">
+								<MkSwitch v-model="events.inactiveModeratorsWarning" :disabled="disabledEvents.inactiveModeratorsWarning">
+									<template #label>{{ i18n.ts._webhookSettings._systemEvents.inactiveModeratorsWarning }}</template>
+								</MkSwitch>
+								<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.inactiveModeratorsWarning)" @click="test('inactiveModeratorsWarning')"><i class="ti ti-send"></i></MkButton>
+							</div>
+							<div :class="$style.switchBox">
+								<MkSwitch v-model="events.inactiveModeratorsInvitationOnlyChanged" :disabled="disabledEvents.inactiveModeratorsInvitationOnlyChanged">
+									<template #label>{{ i18n.ts._webhookSettings._systemEvents.inactiveModeratorsInvitationOnlyChanged }}</template>
+								</MkSwitch>
+								<MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.inactiveModeratorsInvitationOnlyChanged)" @click="test('inactiveModeratorsInvitationOnlyChanged')"><i class="ti ti-send"></i></MkButton>
+							</div>
+						</div>
+
+						<div v-show="mode === 'edit'" :class="$style.description">
+							{{ i18n.ts._webhookSettings.testRemarks }}
+						</div>
 					</div>
 				</MkFolder>
 
@@ -66,6 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script setup lang="ts">
 import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import {
@@ -84,6 +112,8 @@ type EventType = {
 	abuseReport: boolean;
 	abuseReportResolved: boolean;
 	userCreated: boolean;
+	inactiveModeratorsWarning: boolean;
+	inactiveModeratorsInvitationOnlyChanged: boolean;
 }
 
 const emit = defineEmits<{
@@ -107,6 +137,8 @@ const events = ref<EventType>({
 	abuseReport: true,
 	abuseReportResolved: true,
 	userCreated: true,
+	inactiveModeratorsWarning: true,
+	inactiveModeratorsInvitationOnlyChanged: true,
 });
 const isActive = ref<boolean>(true);
 
@@ -114,6 +146,8 @@ const disabledEvents = ref<EventType>({
 	abuseReport: false,
 	abuseReportResolved: false,
 	userCreated: false,
+	inactiveModeratorsWarning: false,
+	inactiveModeratorsInvitationOnlyChanged: false,
 });
 
 const disableSubmitButton = computed(() => {
@@ -180,6 +214,21 @@ async function loadingScope<T>(fn: () => Promise<T>): Promise<T> {
 	}
 }
 
+async function test(type: Misskey.entities.SystemWebhook['on'][number]): Promise<void> {
+	if (!id.value) {
+		return Promise.resolve();
+	}
+
+	await os.apiWithDialog('admin/system-webhook/test', {
+		webhookId: id.value,
+		type,
+		override: {
+			secret: secret.value,
+			url: url.value,
+		},
+	});
+}
+
 onMounted(async () => {
 	await loadingScope(async () => {
 		switch (mode.value) {
@@ -230,9 +279,34 @@ onMounted(async () => {
 	bottom: 0;
 	left: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
-	background: var(--acrylicBg);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-acrylicBg);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+}
+
+.switchBox {
+	display: flex;
+	align-items: center;
+	justify-content: start;
+
+	.testButton {
+		$buttonSize: 28px;
+		padding: 0;
+		width: $buttonSize;
+		min-width: $buttonSize;
+		max-width: $buttonSize;
+		height: $buttonSize;
+		margin-left: auto;
+		line-height: normal;
+		font-size: 90%;
+		border-radius: 9999px;
+	}
+}
+
+.description {
+	font-size: 0.85em;
+	padding: 8px 0 0 0;
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 </style>
diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue
index f2d0c9501363..f557ffa5dcfa 100644
--- a/packages/frontend/src/components/MkTab.vue
+++ b/packages/frontend/src/components/MkTab.vue
@@ -47,13 +47,13 @@ export default defineComponent({
 		}
 
 		&.active {
-			color: var(--accent);
-			background: var(--accentedBg);
+			color: var(--MI_THEME-accent);
+			background: var(--MI_THEME-accentedBg);
 		}
 
 		&:not(.active):hover {
-			color: var(--fgHighlighted);
-			background: var(--panelHighlight);
+			color: var(--MI_THEME-fgHighlighted);
+			background: var(--MI_THEME-panelHighlight);
 		}
 
 		&:not(:first-child) {
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index 6b9c18159768..87aa0469630e 100644
--- a/packages/frontend/src/components/MkTagCloud.vue
+++ b/packages/frontend/src/components/MkTagCloud.vue
@@ -33,7 +33,7 @@ watch(available, () => {
 	try {
 		window.TagCanvas.Start(idForCanvas, idForTags, {
 			textColour: '#ffffff',
-			outlineColour: tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(),
+			outlineColour: tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(),
 			outlineRadius: 10,
 			initial: [-0.030, -0.010],
 			frontSelect: true,
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index 59490c552acd..d1a6e1ebbf95 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -159,7 +159,7 @@ onUnmounted(() => {
 .caption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
@@ -179,9 +179,9 @@ onUnmounted(() => {
 	font: inherit;
 	font-weight: normal;
 	font-size: 1em;
-	color: var(--fg);
-	background: var(--panel);
-	border: solid 1px var(--panel);
+	color: var(--MI_THEME-fg);
+	background: var(--MI_THEME-panel);
+	border: solid 1px var(--MI_THEME-panel);
 	border-radius: 6px;
 	outline: none;
 	box-shadow: none;
@@ -189,13 +189,13 @@ onUnmounted(() => {
 	transition: border-color 0.1s ease-out;
 
 	&:hover {
-		border-color: var(--inputBorderHover) !important;
+		border-color: var(--MI_THEME-inputBorderHover) !important;
 	}
 }
 
 .focused {
 	> .textarea {
-		border-color: var(--accent) !important;
+		border-color: var(--MI_THEME-accent) !important;
 	}
 }
 
@@ -226,7 +226,7 @@ onUnmounted(() => {
 
 .mfmPreview {
   padding: 12px;
-  border-radius: var(--radius);
+  border-radius: var(--MI-radius);
   box-sizing: border-box;
   min-height: 130px;
 	pointer-events: none;
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index ca87316bf7c7..fb8eb4ae37d8 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -38,10 +38,12 @@ const props = withDefaults(defineProps<{
 	sound?: boolean;
 	withRenotes?: boolean;
 	withReplies?: boolean;
+	withSensitive?: boolean;
 	onlyFiles?: boolean;
 }>(), {
 	withRenotes: true,
 	withReplies: false,
+	withSensitive: true,
 	onlyFiles: false,
 });
 
@@ -51,6 +53,7 @@ const emit = defineEmits<{
 }>();
 
 provide('inTimeline', true);
+provide('tl_withSensitive', computed(() => props.withSensitive));
 provide('inChannel', computed(() => props.src === 'channel'));
 
 type TimelineQueryType = {
@@ -248,6 +251,9 @@ function refreshEndpointAndChannel() {
 // IDが切り替わったら切り替え先のTLを表示させたい
 watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel);
 
+// withSensitiveはクライアントで完結する処理のため、単にリロードするだけでOK
+watch(() => props.withSensitive, reloadTimeline);
+
 // 初回表示用
 refreshEndpointAndChannel();
 
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index b32066c95047..73aef6896483 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:okButtonDisabled="false"
 	:canClose="false"
 	@close="dialog?.close()"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 	@ok="ok()"
 >
 	<template #header>{{ title || i18n.ts.generateAccessToken }}</template>
@@ -136,15 +136,15 @@ function enableAll(): void {
 .adminPermissions {
 	margin: 8px -6px 0;
 	padding: 24px 6px 6px;
-	border: 2px solid var(--error);
-	border-radius: calc(var(--radius) / 2);
+	border: 2px solid var(--MI_THEME-error);
+	border-radius: calc(var(--MI-radius) / 2);
 }
 
 .adminPermissionsHeader {
 	margin: -34px 0 6px 12px;
 	padding: 0 4px;
 	width: fit-content;
-	color: var(--error);
-	background: var(--panel);
+	color: var(--MI_THEME-error);
+	background: var(--MI_THEME-panel);
 }
 </style>
diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue
index a3620aab6871..10365d29b16e 100644
--- a/packages/frontend/src/components/MkTooltip.vue
+++ b/packages/frontend/src/components/MkTooltip.vue
@@ -110,7 +110,7 @@ onUnmounted(() => {
 	box-sizing: border-box;
 	text-align: center;
 	border-radius: 4px;
-	border: solid 0.5px var(--divider);
+	border: solid 0.5px var(--MI_THEME-divider);
 	pointer-events: none;
 	transform-origin: center center;
 }
diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue
index 2a26d22dc27c..b26a01737ea5 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Note.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._reaction.description }}</div>
 	<div>{{ i18n.ts._initialTutorial._reaction.letsTryReacting }}</div>
 	<MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction"/>
-	<div v-if="onceReacted"><b style="color: var(--accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br>{{ i18n.ts._initialTutorial._reaction.reactDone }}</div>
+	<div v-if="onceReacted"><b style="color: var(--MI_THEME-accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br>{{ i18n.ts._initialTutorial._reaction.reactDone }}</div>
 </div>
 </template>
 
@@ -105,13 +105,13 @@ function removeReaction(emoji) {
 
 <style lang="scss" module>
 .exampleNoteRoot {
-	border-radius: var(--radius);
-	border: var(--panelBorder);
-	background: var(--panel);
+	border-radius: var(--MI-radius);
+	border: var(--MI_THEME-panelBorder);
+	background: var(--MI_THEME-panel);
 }
 
 .divider {
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue
index 27483cc7c23e..6559307a94bc 100644
--- a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue
@@ -81,14 +81,14 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
 <style lang="scss" module>
 .exampleRoot {
 	max-width: none!important;
-	border-radius: var(--radius);
-	border: var(--panelBorder);
-	background: var(--panel);
+	border-radius: var(--MI-radius);
+	border: var(--MI_THEME-panelBorder);
+	background: var(--MI_THEME-panel);
 }
 
 .divider {
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 
 .image {
@@ -101,7 +101,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
 	display: block;
 	width: 100%;
 	height: 40px;
-	color: var(--fgOnAccent);
+	color: var(--MI_THEME-fgOnAccent);
 	font-weight: bold;
 	text-align: left;
 
@@ -117,7 +117,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
 		right: 0;
 		bottom: 0;
 		border-radius: 999px;
-		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 	}
 
 }
diff --git a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
index d8d4b5aab72d..f7b60fbc45aa 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:initialNote="exampleNote"
 		@fileChangeSensitive="doSucceeded"
 	></MkPostForm>
-	<div v-if="onceSucceeded"><b style="color: var(--accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._howToMakeAttachmentsSensitive.sensitiveSucceeded }}</div>
+	<div v-if="onceSucceeded"><b style="color: var(--MI_THEME-accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._howToMakeAttachmentsSensitive.sensitiveSucceeded }}</div>
 	<MkFolder>
 		<template #label>{{ i18n.ts.previewNoteText }}</template>
 		<MkNote :mock="true" :note="exampleNote" :class="$style.exampleRoot"></MkNote>
@@ -91,14 +91,14 @@ const exampleNote = reactive<Misskey.entities.Note>({
 
 <style lang="scss" module>
 .exampleRoot {
-	border-radius: var(--radius);
-	border: var(--panelBorder);
-	background: var(--panel);
+	border-radius: var(--MI-radius);
+	border: var(--MI_THEME-panelBorder);
+	background: var(--MI_THEME-panel);
 }
 
 .divider {
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 
 .image {
@@ -111,7 +111,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
 	display: block;
 	width: 100%;
 	height: 40px;
-	color: var(--fgOnAccent);
+	color: var(--MI_THEME-fgOnAccent);
 	font-weight: bold;
 	text-align: left;
 
@@ -127,7 +127,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
 		right: 0;
 		bottom: 0;
 		border-radius: 999px;
-		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 	}
 
 }
diff --git a/packages/frontend/src/components/MkTutorialDialog.Timeline.vue b/packages/frontend/src/components/MkTutorialDialog.Timeline.vue
index 2d4da3fbd413..b203110ef09d 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Timeline.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Timeline.vue
@@ -31,14 +31,14 @@ import { basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js';
 
 <style lang="scss" module>
 .exampleNoteRoot {
-	border-radius: var(--radius);
-	border: var(--panelBorder);
-	background: var(--panel);
+	border-radius: var(--MI-radius);
+	border: var(--MI_THEME-panelBorder);
+	background: var(--MI_THEME-panel);
 }
 
 .divider {
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 
 .image {
@@ -51,7 +51,7 @@ import { basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js';
 	display: block;
 	width: 100%;
 	height: 40px;
-	color: var(--fgOnAccent);
+	color: var(--MI_THEME-fgOnAccent);
 	font-weight: bold;
 	text-align: left;
 
@@ -67,7 +67,7 @@ import { basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js';
 		right: 0;
 		bottom: 0;
 		border-radius: 999px;
-		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 	}
 
 }
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index 1f5a2b938167..11d7c8dc4d39 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
 					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
-							<i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialTutorial._landing.title }}</div>
 							<div>{{ i18n.ts._initialTutorial._landing.description }}</div>
 							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" @click="page++">{{ i18n.ts._initialTutorial.launchTutorial }} <i class="ti ti-arrow-right"></i></MkButton>
@@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
 					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
-							<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div>
 							<I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;">
 								<template #link>
@@ -223,7 +223,7 @@ async function close(skip: boolean) {
 
 .progressBarValue {
 	height: 100%;
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 	transition: all 0.5s cubic-bezier(0,.5,.5,1);
 }
 
@@ -253,7 +253,7 @@ async function close(skip: boolean) {
 	left: 0;
 	flex-shrink: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 	-webkit-backdrop-filter: blur(15px);
 	backdrop-filter: blur(15px);
 }
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index f8af276836e2..c937b4ce5929 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -46,8 +46,8 @@ onMounted(() => {
 	max-width: 480px;
 	box-sizing: border-box;
 	text-align: center;
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 }
 
 .title {
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index f5f9b431970d..f0da8fd3f2bb 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -84,13 +84,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue';
-import type { summaly } from '@misskey-dev/summaly';
 import { url as local } from '@@/js/config.js';
+import { versatileLang } from '@@/js/intl-const.js';
+import type { summaly } from '@misskey-dev/summaly';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import MkButton from '@/components/MkButton.vue';
-import { versatileLang } from '@@/js/intl-const.js';
 import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
 import { defaultStore } from '@/store.js';
 
@@ -180,7 +180,7 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa
 		sensitive.value = info.sensitive ?? false;
 	});
 
-function adjustTweetHeight(message: any) {
+function adjustTweetHeight(message: MessageEvent) {
 	if (message.origin !== 'https://platform.twitter.com') return;
 	const embed = message.data?.['twttr.embed'];
 	if (embed?.method !== 'twttr.private.resize') return;
@@ -193,14 +193,16 @@ function openPlayer(): void {
 	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkYouTubePlayer.vue')), {
 		url: requestUrl.href,
 	}, {
-		// TODO
+		closed: () => {
+			dispose();
+		},
 	});
 }
 
-(window as any).addEventListener('message', adjustTweetHeight);
+window.addEventListener('message', adjustTweetHeight);
 
 onUnmounted(() => {
-	(window as any).removeEventListener('message', adjustTweetHeight);
+	window.removeEventListener('message', adjustTweetHeight);
 });
 </script>
 
@@ -219,7 +221,7 @@ onUnmounted(() => {
 	height: 1.5em;
 	padding: 0;
 	margin: 0;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	background: rgba(128, 128, 128, 0.2);
 	opacity: 0.7;
 
@@ -240,7 +242,7 @@ onUnmounted(() => {
 	position: relative;
 	display: block;
 	font-size: 14px;
-	box-shadow: 0 0 0 1px var(--divider);
+	box-shadow: 0 0 0 1px var(--MI_THEME-divider);
 	border-radius: 8px;
 	overflow: clip;
 
@@ -270,7 +272,7 @@ onUnmounted(() => {
 	height: 100%;
 	background-position: center;
 	background-size: cover;
-	background-color: var(--bg);
+	background-color: var(--MI_THEME-bg);
 	display: flex;
 	justify-content: center;
 	align-items: center;
@@ -317,7 +319,6 @@ onUnmounted(() => {
 .siteName {
 	display: inline-block;
 	margin: 0;
-	color: var(--urlPreviewInfo);
 	font-size: 0.8em;
 	line-height: 16px;
 	vertical-align: top;
diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
index 3c5f563aa000..fe499fabbf1e 100644
--- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
+++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	ref="dialog"
 	:width="400"
 	@close="dialog?.close()"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 >
 	<template v-if="announcement" #header>:{{ announcement.title }}:</template>
 	<template v-else #header>New announcement</template>
@@ -25,9 +25,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkRadios v-model="icon">
 					<template #label>{{ i18n.ts.icon }}</template>
 					<option value="info"><i class="ti ti-info-circle"></i></option>
-					<option value="warning"><i class="ti ti-alert-triangle" style="color: var(--warn);"></i></option>
-					<option value="error"><i class="ti ti-circle-x" style="color: var(--error);"></i></option>
-					<option value="success"><i class="ti ti-check" style="color: var(--success);"></i></option>
+					<option value="warning"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i></option>
+					<option value="error"><i class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i></option>
+					<option value="success"><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i></option>
 				</MkRadios>
 				<MkRadios v-model="display">
 					<template #label>{{ i18n.ts.display }}</template>
@@ -62,9 +62,16 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkRadios from '@/components/MkRadios.vue';
 
+type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; }
+
 const props = defineProps<{
 	user: Misskey.entities.User,
-	announcement?: Misskey.entities.Announcement,
+	announcement?: Required<AdminAnnouncementType>,
+}>();
+
+const emit = defineEmits<{
+	(ev: 'done', v: { deleted?: boolean; updated?: AdminAnnouncementType; created?: AdminAnnouncementType; }): void,
+	(ev: 'closed'): void
 }>();
 
 const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
@@ -74,11 +81,6 @@ const icon = ref(props.announcement ? props.announcement.icon : 'info');
 const display = ref(props.announcement ? props.announcement.display : 'dialog');
 const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
 
-const emit = defineEmits<{
-	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
-	(ev: 'closed'): void
-}>();
-
 async function done() {
 	const params = {
 		title: title.value,
@@ -88,7 +90,7 @@ async function done() {
 		display: display.value,
 		needConfirmationToRead: needConfirmationToRead.value,
 		userId: props.user.id,
-	};
+	} satisfies Misskey.entities.AdminAnnouncementsCreateRequest;
 
 	if (props.announcement) {
 		await os.apiWithDialog('admin/announcements/update', {
@@ -141,8 +143,8 @@ async function del() {
 	bottom: 0;
 	left: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue
index d3e77b281841..7a2e878931af 100644
--- a/packages/frontend/src/components/MkUserCardMini.vue
+++ b/packages/frontend/src/components/MkUserCardMini.vue
@@ -23,7 +23,7 @@ import { acct } from '@/filters/user.js';
 
 const props = withDefaults(defineProps<{
 	user: Misskey.entities.User;
-	withChart: boolean;
+	withChart?: boolean;
 }>(), {
 	withChart: true,
 });
@@ -49,7 +49,7 @@ $bodyInfoHieght: 16px;
 	display: flex;
 	align-items: center;
 	padding: 16px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 8px;
 }
 
@@ -64,7 +64,7 @@ $bodyInfoHieght: 16px;
 	flex: 1;
 	overflow: hidden;
 	font-size: 0.9em;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	padding-right: 8px;
 }
 
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index f0b9606590f1..0164515a8ac6 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -69,7 +69,7 @@ defineProps<{
 	z-index: 2;
 	width: 58px;
 	height: 58px;
-	border: solid 4px var(--panel);
+	border: solid 4px var(--MI_THEME-panel);
 }
 
 .title {
@@ -90,7 +90,7 @@ defineProps<{
 	margin: 0;
 	line-height: 16px;
 	font-size: 0.8em;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	opacity: 0.7;
 }
 
@@ -108,7 +108,7 @@ defineProps<{
 .description {
 	padding: 16px;
 	font-size: 0.8em;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .mfm {
@@ -120,7 +120,7 @@ defineProps<{
 
 .status {
 	padding: 10px 16px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .statusItem {
@@ -131,12 +131,12 @@ defineProps<{
 .statusItemLabel {
 	margin: 0;
 	font-size: 0.7em;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 
 .statusItemValue {
 	font-size: 1em;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 .follow {
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index 17a9254d019d..8b4afd7994fc 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -39,6 +39,6 @@ const props = withDefaults(defineProps<{
 .root {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
-	grid-gap: var(--margin);
+	grid-gap: var(--MI-margin);
 }
 </style>
diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue
index c39a900bcf76..5cebeea2f493 100644
--- a/packages/frontend/src/components/MkUserOnlineIndicator.vue
+++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue
@@ -36,7 +36,7 @@ const text = computed(() => {
 
 <style lang="scss" module>
 .root {
-	box-shadow: 0 0 0 3px var(--panel);
+	box-shadow: 0 0 0 3px var(--MI_THEME-panel);
 	border-radius: 120%; // Blinkのバグか知らんけど、100%ぴったりにすると何故か若干楕円でレンダリングされる
 
 	&.status_online {
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index ea1241002e6e..740202f28bf2 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<svg viewBox="0 0 128 128" :class="$style.avatarBack">
 				<g transform="matrix(1.6,0,0,1.6,-38.4,-51.2)">
-					<path d="M64,32C81.661,32 96,46.339 96,64C95.891,72.184 104,72 104,72C104,72 74.096,80 64,80C52.755,80 24,72 24,72C24,72 31.854,72.018 32,64C32,46.339 46.339,32 64,32Z" style="fill: var(--popup);"/>
+					<path d="M64,32C81.661,32 96,46.339 96,64C95.891,72.184 104,72 104,72C104,72 74.096,80 64,80C52.755,80 24,72 24,72C24,72 31.854,72.018 32,64C32,46.339 46.339,32 64,32Z" style="fill: var(--MI_THEME-popup);"/>
 				</g>
 			</svg>
 			<MkAvatar :class="$style.avatar" :user="user" indicator/>
@@ -197,8 +197,8 @@ onMounted(() => {
 	padding: 16px 26px;
 	font-size: 0.8em;
 	text-align: center;
-	border-top: solid 1px var(--divider);
-	border-bottom: solid 1px var(--divider);
+	border-top: solid 1px var(--MI_THEME-divider);
+	border-bottom: solid 1px var(--MI_THEME-divider);
 }
 
 .mfm {
@@ -220,7 +220,7 @@ onMounted(() => {
 
 .statusItemLabel {
 	font-size: 0.7em;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 
 .menu {
@@ -228,7 +228,7 @@ onMounted(() => {
 	top: 8px;
 	right: 44px;
 	padding: 6px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 999px;
 }
 
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 1374817c7265..764bf74f2191 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@click="cancel()"
 	@close="cancel()"
 	@ok="ok()"
-	@closed="$emit('closed')"
+	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.selectUser }}</template>
 	<div>
@@ -195,11 +195,11 @@ onMounted(() => {
 	font-size: 14px;
 
 	&:hover {
-		background: var(--X7);
+		background: var(--MI_THEME-X7);
 	}
 
 	&.selected {
-		background: var(--accent);
+		background: var(--MI_THEME-accent);
 		color: #fff;
 	}
 }
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index 1524ea0ec989..5153c06139f5 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -62,7 +62,7 @@ const popularUsers: Paging = {
 .users {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
-	grid-gap: var(--margin);
+	grid-gap: var(--MI-margin);
 	justify-content: center;
 }
 </style>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
index 3194641cdb7e..7cb48f6afb81 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -51,6 +51,11 @@ watch(name, () => {
 		// 空文字列をnullにしたいので??は使うな
 		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
 		name: name.value || null,
+	}, undefined, {
+		'0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191': {
+			title: i18n.ts.yourNameContainsProhibitedWords,
+			text: i18n.ts.yourNameContainsProhibitedWordsDescription,
+		},
 	});
 });
 
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue
index bb9af676e24a..004edab6300a 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue
@@ -61,7 +61,7 @@ async function follow() {
 	z-index: 2;
 	width: 58px;
 	height: 58px;
-	border: solid 4px var(--panel);
+	border: solid 4px var(--MI_THEME-panel);
 }
 
 .title {
@@ -82,7 +82,7 @@ async function follow() {
 	margin: 0;
 	line-height: 16px;
 	font-size: 0.8em;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	opacity: 0.7;
 }
 
@@ -99,7 +99,7 @@ async function follow() {
 }
 
 .footer {
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 	padding: 16px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index 1fb1eda03989..b7261129ef34 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
 					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
-							<i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.accountCreated }}</div>
 							<div>{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}</div>
 							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts._initialAccountSetting.profileSetting }} <i class="ti ti-arrow-right"></i></MkButton>
@@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div :class="$style.centerPage">
 					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
-							<i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div>
 							<div style="padding: 0 16px;">{{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}</div>
 							<MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/>
@@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
 					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
-							<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
 							<div>{{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}</div>
 							<div class="_buttonsCenter" style="margin-top: 16px;">
@@ -223,7 +223,7 @@ async function later(later: boolean) {
 
 .progressBarValue {
 	height: 100%;
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 	transition: all 0.5s cubic-bezier(0,.5,.5,1);
 }
 
@@ -252,7 +252,7 @@ async function later(later: boolean) {
 	left: 0;
 	flex-shrink: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 	-webkit-backdrop-filter: blur(15px);
 	backdrop-filter: blur(15px);
 }
diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue
index 054a5032575d..0cb7f22e933e 100644
--- a/packages/frontend/src/components/MkUsersTooltip.vue
+++ b/packages/frontend/src/components/MkUsersTooltip.vue
@@ -16,12 +16,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkTooltip from './MkTooltip.vue';
 
 defineProps<{
 	showing: boolean;
-	users: any[]; // TODO
+	users: Misskey.entities.UserLite[];
 	count: number;
 	targetElement: HTMLElement;
 }>();
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 75066bbc329c..650e639c4f89 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -124,7 +124,7 @@ function choose(visibility: typeof Misskey.noteVisibilities[number]): void {
 	}
 
 	&.active {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
index cab42cd59d2e..d098dad9a140 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -62,7 +62,7 @@ async function renderChart() {
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const computedStyle = getComputedStyle(document.documentElement);
-	const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+	const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
 
 	const colorRead = accent;
 	const colorWrite = '#2ecc71';
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index a6c8baeaaad0..97c765d81c73 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -106,8 +106,8 @@ function showMenu(ev: MouseEvent) {
 
 .panel {
 	position: relative;
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 	box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
 }
 
@@ -178,14 +178,14 @@ function showMenu(ev: MouseEvent) {
 }
 
 .statsItemLabel {
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 	font-size: 0.9em;
 }
 
 .statsItemCount {
 	font-weight: bold;
 	font-size: 1.2em;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 }
 
 .tl {
@@ -194,7 +194,7 @@ function showMenu(ev: MouseEvent) {
 
 .tlHeader {
 	padding: 12px 16px;
-	border-bottom: solid 1px var(--divider);
+	border-bottom: solid 1px var(--MI_THEME-divider);
 }
 
 .tlBody {
diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue
index 60b75b6d3029..34fa6b07234c 100644
--- a/packages/frontend/src/components/MkWaitingDialog.vue
+++ b/packages/frontend/src/components/MkWaitingDialog.vue
@@ -47,8 +47,8 @@ watch(() => props.showing, () => {
 	padding: 32px;
 	box-sizing: border-box;
 	text-align: center;
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 	width: 250px;
 
 	&.iconOnly {
@@ -65,7 +65,7 @@ watch(() => props.showing, () => {
 	font-size: 32px;
 
 	&.success {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 
 	&.waiting {
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index 0c51cfa9ce5e..ba619f606314 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div :class="$style.root">
 	<template v-if="edit">
 		<header :class="$style.editHeader">
-			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
+			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--MI-margin)" data-cy-widget-select>
 				<template #label>{{ i18n.ts.selectWidget }}</template>
 				<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
 			</MkSelect>
 			<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
-			<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
+			<MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton>
 		</header>
 		<Sortable
 			:modelValue="props.widgets"
@@ -123,7 +123,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
 
 .widget {
 	contain: content;
-	margin: var(--margin) 0;
+	margin: var(--MI-margin) 0;
 
 	&:first-of-type {
 		margin-top: 0;
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index 26ba598498d7..2953f656d47c 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
 	:leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
 	appear
-	@afterLeave="$emit('closed')"
+	@afterLeave="emit('closed')"
 >
 	<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
 		<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
@@ -54,12 +54,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
+import type { MenuItem } from '@/types/menu.js';
 import contains from '@/scripts/contains.js';
 import * as os from '@/os.js';
-import { MenuItem } from '@/types/menu.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 
+type WindowButton = {
+	title: string;
+	icon: string;
+	onClick: () => void;
+	highlighted?: boolean;
+};
+
 const minHeight = 50;
 const minWidth = 250;
 
@@ -87,8 +94,8 @@ const props = withDefaults(defineProps<{
 	mini?: boolean;
 	front?: boolean;
 	contextmenu?: MenuItem[] | null;
-	buttonsLeft?: any[];
-	buttonsRight?: any[];
+	buttonsLeft?: WindowButton[];
+	buttonsRight?: WindowButton[];
 }>(), {
 	initialWidth: 400,
 	initialHeight: null,
@@ -484,6 +491,10 @@ defineExpose({
 }
 
 .root {
+	// universal.vueとかで直接--MI-stickyBottomが定義されていたりするのでリセット
+	--MI-stickyTop: 0;
+	--MI-stickyBottom: 0;
+
 	position: fixed;
 	top: 0;
 	left: 0;
@@ -502,7 +513,7 @@ defineExpose({
 	contain: content;
 	width: 100%;
 	height: 100%;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 }
 
 .header {
@@ -514,10 +525,10 @@ defineExpose({
 	flex-shrink: 0;
 	user-select: none;
 	height: var(--height);
-	background: var(--windowHeader);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	//border-bottom: solid 1px var(--divider);
+	background: var(--MI_THEME-windowHeader);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	//border-bottom: solid 1px var(--MI_THEME-divider);
 	font-size: 90%;
 	font-weight: bold;
 
@@ -531,11 +542,11 @@ defineExpose({
 	width: var(--height);
 
 	&:hover {
-		color: var(--fgHighlighted);
+		color: var(--MI_THEME-fgHighlighted);
 	}
 
 	&.highlighted {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
@@ -560,7 +571,7 @@ defineExpose({
 .content {
 	flex: 1;
 	overflow: auto;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	container-type: size;
 }
 
diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue
index e76ed9a84923..8fa9e4affbd9 100644
--- a/packages/frontend/src/components/form/link.vue
+++ b/packages/frontend/src/components/form/link.vue
@@ -51,18 +51,18 @@ const props = defineProps<{
 	width: 100%;
 	box-sizing: border-box;
 	padding: 10px 14px;
-	background: var(--buttonBg);
+	background: var(--MI_THEME-folderHeaderBg);
 	border-radius: 6px;
 	font-size: 0.9em;
 
 	&:hover {
 		text-decoration: none;
-		background: var(--buttonHoverBg);
+		background: var(--MI_THEME-folderHeaderHoverBg);
 	}
 
 	&.active {
-		color: var(--accent);
-		background: var(--buttonHoverBg);
+		color: var(--MI_THEME-accent);
+		background: var(--MI_THEME-folderHeaderHoverBg);
 	}
 }
 
@@ -70,7 +70,7 @@ const props = defineProps<{
 	margin-right: 0.75em;
 	flex-shrink: 0;
 	text-align: center;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
diff --git a/packages/frontend/src/components/form/section.vue b/packages/frontend/src/components/form/section.vue
index ad37daa2657a..5fca3acc31c7 100644
--- a/packages/frontend/src/components/form/section.vue
+++ b/packages/frontend/src/components/form/section.vue
@@ -21,8 +21,8 @@ defineProps<{
 
 <style lang="scss" module>
 .root {
-	border-top: solid 0.5px var(--divider);
-	//border-bottom: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	//border-bottom: solid 0.5px var(--MI_THEME-divider);
 }
 
 .rootFirst {
@@ -49,7 +49,7 @@ defineProps<{
 
 .description {
 	font-size: 0.85em;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 	margin: 0 0 8px 0;
 }
 </style>
diff --git a/packages/frontend/src/components/form/slot.vue b/packages/frontend/src/components/form/slot.vue
index f54db0ca823d..da94b7abbb0c 100644
--- a/packages/frontend/src/components/form/slot.vue
+++ b/packages/frontend/src/components/form/slot.vue
@@ -35,7 +35,7 @@ function focus() {
 .caption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 
 	&:empty {
 		display: none;
diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue
index 5226c61d6836..821f07510b3a 100644
--- a/packages/frontend/src/components/form/suspense.vue
+++ b/packages/frontend/src/components/form/suspense.vue
@@ -18,19 +18,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </div>
 </template>
 
-<script lang="ts" setup>
+<script lang="ts" setup generic="T extends unknown">
 import { ref, watch } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
-	p: () => Promise<any>;
+	p: () => Promise<T>;
 }>();
 
 const pending = ref(true);
 const resolved = ref(false);
 const rejected = ref(false);
-const result = ref<any>(null);
+const result = ref<T | null>(null);
 
 const process = () => {
 	if (props.p == null) {
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 8a03f7846ec5..9a1ac3aca2d7 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -4,11 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :minScale="2 / 3">
-	<span>@{{ user.username }}</span>
-	<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
-</MkCondensedLine>
-<span v-else>
+<span>
 	<span>@{{ user.username }}</span>
 	<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
 </span>
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index f0e943960de3..08a78c8d8157 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div v-if="chosen && !shouldHide" :class="$style.root">
+<div v-if="chosen && !shouldHide">
 	<div
 		v-if="!showMenu"
 		:class="[$style.main, {
@@ -30,12 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</component>
 	</div>
 	<div v-else :class="$style.menu">
-		<div :class="$style.menuContainer">
-			<div>Ads by {{ host }}</div>
-			<!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>-->
-			<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton>
-			<button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button>
-		</div>
+		<div>Ads by {{ host }}</div>
+		<!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>-->
+		<MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton>
+		<button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button>
 	</div>
 </div>
 <div v-else></div>
@@ -43,9 +41,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
+import { url as local, host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { url as local, host } from '@@/js/config.js';
 import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
@@ -122,11 +120,6 @@ function reduceFrequency(): void {
 </script>
 
 <style lang="scss" module>
-.root {
-	background-size: auto auto;
-	background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--ad) 8px, var(--ad) 14px );
-}
-
 .main {
 	text-align: center;
 
@@ -139,8 +132,6 @@ function reduceFrequency(): void {
 	}
 
 	&.form_horizontal {
-		padding: 8px;
-
 		> .link,
 		> .link > .img {
 			max-width: min(600px, 100%);
@@ -149,8 +140,6 @@ function reduceFrequency(): void {
 	}
 
 	&.form_horizontalBig {
-		padding: 8px;
-
 		> .link,
 		> .link > .img {
 			max-width: min(600px, 100%);
@@ -191,7 +180,7 @@ function reduceFrequency(): void {
 	right: 1px;
 	display: grid;
 	place-content: center;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 100%;
 	padding: 2px;
 }
@@ -202,15 +191,12 @@ function reduceFrequency(): void {
 }
 
 .menu {
-	padding: 8px;
 	text-align: center;
-}
-
-.menuContainer {
 	padding: 8px;
 	margin: 0 auto;
 	max-width: 400px;
-	border: solid 1px var(--divider);
+	background: var(--MI_THEME-panel);
+	border: solid 1px var(--MI_THEME-divider);
 }
 
 .menuButton {
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
index 7c4957d77fac..473d444c1606 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.vue
+++ b/packages/frontend/src/components/global/MkCondensedLine.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <span :class="$style.container">
-	<span ref="content" :class="$style.content">
+	<span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }">
 		<slot/>
 	</span>
 </span>
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index dff56cd7f0d7..66f82a7898de 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -35,6 +35,7 @@ import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import * as sound from '@/scripts/sound.js';
 import { i18n } from '@/i18n.js';
 import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
+import type { MenuItem } from '@/types/menu.js';
 
 const props = defineProps<{
 	name: string;
@@ -85,7 +86,9 @@ const errored = ref(url.value == null);
 
 function onClick(ev: MouseEvent) {
 	if (props.menu) {
-		os.popupMenu([{
+		const menuItems: MenuItem[] = [];
+
+		menuItems.push({
 			type: 'label',
 			text: `:${props.name}:`,
 		}, {
@@ -95,14 +98,20 @@ function onClick(ev: MouseEvent) {
 				copyToClipboard(`:${props.name}:`);
 				os.success();
 			},
-		}, ...(props.menuReaction && react ? [{
-			text: i18n.ts.doReaction,
-			icon: 'ti ti-plus',
-			action: () => {
-				react(`:${props.name}:`);
-				sound.playMisskeySfx('reaction');
-			},
-		}] : []), {
+		});
+
+		if (props.menuReaction && react) {
+			menuItems.push({
+				text: i18n.ts.doReaction,
+				icon: 'ti ti-plus',
+				action: () => {
+					react(`:${props.name}:`);
+					sound.playMisskeySfx('reaction');
+				},
+			});
+		}
+
+		menuItems.push({
 			text: i18n.ts.info,
 			icon: 'ti ti-info-circle',
 			action: async () => {
@@ -114,7 +123,9 @@ function onClick(ev: MouseEvent) {
 					closed: () => dispose(),
 				});
 			},
-		}], ev.currentTarget ?? ev.target);
+		});
+
+		os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 	}
 }
 </script>
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue
index fc3745c0090d..f0acd3bc27c5 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -17,6 +17,7 @@ import * as os from '@/os.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import * as sound from '@/scripts/sound.js';
 import { i18n } from '@/i18n.js';
+import type { MenuItem } from '@/types/menu.js';
 
 const props = defineProps<{
 	emoji: string;
@@ -39,7 +40,9 @@ function computeTitle(event: PointerEvent): void {
 
 function onClick(ev: MouseEvent) {
 	if (props.menu) {
-		os.popupMenu([{
+		const menuItems: MenuItem[] = [];
+
+		menuItems.push({
 			type: 'label',
 			text: props.emoji,
 		}, {
@@ -49,14 +52,20 @@ function onClick(ev: MouseEvent) {
 				copyToClipboard(props.emoji);
 				os.success();
 			},
-		}, ...(props.menuReaction && react ? [{
-			text: i18n.ts.doReaction,
-			icon: 'ti ti-plus',
-			action: () => {
-				react(props.emoji);
-				sound.playMisskeySfx('reaction');
-			},
-		}] : [])], ev.currentTarget ?? ev.target);
+		});
+
+		if (props.menuReaction && react) {
+			menuItems.push({
+				text: i18n.ts.doReaction,
+				icon: 'ti ti-plus',
+				action: () => {
+					react(props.emoji);
+					sound.playMisskeySfx('reaction');
+				},
+			});
+		}
+
+		os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 	}
 }
 </script>
diff --git a/packages/frontend/src/components/global/MkLoading.vue b/packages/frontend/src/components/global/MkLoading.vue
index 49d8ace37bee..47d797606bd8 100644
--- a/packages/frontend/src/components/global/MkLoading.vue
+++ b/packages/frontend/src/components/global/MkLoading.vue
@@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{
 	--size: 38px;
 
 	&.colored {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 
 	&.inline {
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMfm.stories.impl.ts
similarity index 78%
rename from packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
rename to packages/frontend/src/components/global/MkMfm.stories.impl.ts
index 730351f79534..1daf7a29cb60 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkMfm.stories.impl.ts
@@ -2,16 +2,15 @@
  * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-
-/* eslint-disable @typescript-eslint/explicit-function-return-type */
+ 
 import { StoryObj } from '@storybook/vue3';
 import { expect, within } from '@storybook/test';
-import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js';
+import MkMfm from './MkMfm.js';
 export const Default = {
 	render(args) {
 		return {
 			components: {
-				MkMisskeyFlavoredMarkdown,
+				MkMfm,
 			},
 			setup() {
 				return {
@@ -25,7 +24,7 @@ export const Default = {
 					};
 				},
 			},
-			template: '<MkMisskeyFlavoredMarkdown v-bind="props" />',
+			template: '<MkMfm v-bind="props" />',
 		};
 	},
 	async play({ canvasElement, args }) {
@@ -54,25 +53,25 @@ export const Default = {
 	parameters: {
 		layout: 'centered',
 	},
-} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
+} satisfies StoryObj<typeof MkMfm>;
 export const Plain = {
 	...Default,
 	args: {
 		...Default.args,
 		plain: true,
 	},
-} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
+} satisfies StoryObj<typeof MkMfm>;
 export const Nowrap = {
 	...Default,
 	args: {
 		...Default.args,
 		nowrap: true,
 	},
-} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
+} satisfies StoryObj<typeof MkMfm>;
 export const IsNotNote = {
 	...Default,
 	args: {
 		...Default.args,
 		isNote: false,
 	},
-} satisfies StoryObj<typeof MkMisskeyFlavoredMarkdown>;
+} satisfies StoryObj<typeof MkMfm>;
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMfm.ts
similarity index 95%
rename from packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
rename to packages/frontend/src/components/global/MkMfm.ts
index d914492231d8..0d138d1f1c3c 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMfm.ts
@@ -6,6 +6,7 @@
 import { VNode, h, SetupContext, provide } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
+import { host } from '@@/js/config.js';
 import MkUrl from '@/components/global/MkUrl.vue';
 import MkTime from '@/components/global/MkTime.vue';
 import MkLink from '@/components/MkLink.vue';
@@ -17,7 +18,6 @@ import MkCodeInline from '@/components/MkCodeInline.vue';
 import MkGoogle from '@/components/MkGoogle.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
 import MkA, { MkABehavior } from '@/components/global/MkA.vue';
-import { host } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
 
 function safeParseFloat(str: unknown): number | null {
@@ -31,8 +31,8 @@ const QUOTE_STYLE = `
 display: block;
 margin: 8px;
 padding: 6px 0 6px 12px;
-color: var(--fg);
-border-left: solid 3px var(--fg);
+color: var(--MI_THEME-fg);
+border-left: solid 3px var(--MI_THEME-fg);
 opacity: 0.7;
 `.split('\n').join(' ');
 
@@ -57,7 +57,8 @@ type MfmEvents = {
 
 // eslint-disable-next-line import/no-default-export
 export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) {
-	provide('linkNavigationBehavior', props.linkNavigationBehavior);
+	// こうしたいところだけど functional component 内では provide は使えない
+	//provide('linkNavigationBehavior', props.linkNavigationBehavior);
 
 	const isNote = props.isNote ?? true;
 	const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat : false : false;
@@ -269,7 +270,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 					}
 					case 'border': {
 						let color = validColor(token.props.args.color);
-						color = color ? `#${color}` : 'var(--accent)';
+						color = color ? `#${color}` : 'var(--MI_THEME-accent)';
 						let b_style = token.props.args.style;
 						if (
 							typeof b_style !== 'string' ||
@@ -302,7 +303,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 						const child = token.children[0];
 						const unixtime = parseInt(child.type === 'text' ? child.props.text : '');
 						return h('span', {
-							style: 'display: inline-block; font-size: 90%; border: solid 1px var(--divider); border-radius: 999px; padding: 4px 10px 4px 6px;',
+							style: 'display: inline-block; font-size: 90%; border: solid 1px var(--MI_THEME-divider); border-radius: 999px; padding: 4px 10px 4px 6px;',
 						}, [
 							h('i', {
 								class: 'ti ti-clock',
@@ -350,6 +351,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 					key: Math.random(),
 					url: token.props.url,
 					rel: 'nofollow noopener',
+					navigationBehavior: props.linkNavigationBehavior,
 				})];
 			}
 
@@ -358,6 +360,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 					key: Math.random(),
 					url: token.props.url,
 					rel: 'nofollow noopener',
+					navigationBehavior: props.linkNavigationBehavior,
 				}, genEl(token.children, scale, true))];
 			}
 
@@ -366,6 +369,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 					key: Math.random(),
 					host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host,
 					username: token.props.username,
+					navigationBehavior: props.linkNavigationBehavior,
 				})];
 			}
 
@@ -373,7 +377,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 				return [h(MkA, {
 					key: Math.random(),
 					to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
-					style: 'color:var(--hashtag);',
+					style: 'color:var(--MI_THEME-hashtag);',
+					behavior: props.linkNavigationBehavior,
 				}, `#${token.props.hashtag}`)];
 			}
 
@@ -462,8 +467,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 			}
 
 			default: {
-				// eslint-disable-next-line @typescript-eslint/no-explicit-any
-				console.error('unrecognized ast type:', (token as any).type);
+				// @ts-expect-error 存在しないASTタイプ
+				console.error('unrecognized ast type:', token.type);
 
 				return [];
 			}
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index fcc46cc34569..aaef8b8fca28 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -53,7 +53,7 @@ export type Tab = {
 </script>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, watch, nextTick, shallowRef } from 'vue';
+import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
 import { defaultStore } from '@/store.js';
 
 const props = withDefaults(defineProps<{
@@ -120,14 +120,14 @@ function onTabWheel(ev: WheelEvent) {
 
 let entering = false;
 
-async function enter(element: Element) {
+async function enter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	entering = true;
-	const el = element as HTMLElement;
 	const elementWidth = el.getBoundingClientRect().width;
 	el.style.width = '0';
 	el.style.paddingLeft = '0';
-	el.offsetWidth; // force reflow
-	el.style.width = elementWidth + 'px';
+	el.offsetWidth; // reflow
+	el.style.width = `${elementWidth}px`;
 	el.style.paddingLeft = '';
 	nextTick(() => {
 		entering = false;
@@ -136,22 +136,23 @@ async function enter(element: Element) {
 	setTimeout(renderTab, 170);
 }
 
-function afterEnter(element: Element) {
-	//el.style.width = '';
+function afterEnter(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
+	// element.style.width = '';
 }
 
-async function leave(element: Element) {
-	const el = element as HTMLElement;
+async function leave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	const elementWidth = el.getBoundingClientRect().width;
-	el.style.width = elementWidth + 'px';
+	el.style.width = `${elementWidth}px`;
 	el.style.paddingLeft = '';
-	el.offsetWidth; // force reflow
+	el.offsetWidth; // reflow
 	el.style.width = '0';
 	el.style.paddingLeft = '0';
 }
 
-function afterLeave(element: Element) {
-	const el = element as HTMLElement;
+function afterLeave(el: Element) {
+	if (!(el instanceof HTMLElement)) return;
 	el.style.width = '';
 }
 
@@ -247,7 +248,7 @@ onUnmounted(() => {
 	position: absolute;
 	bottom: 0;
 	height: 3px;
-	background: var(--accent);
+	background: var(--MI_THEME-accent);
 	border-radius: 999px;
 	transition: none;
 	pointer-events: none;
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index f1a451808f44..aa4be69b2cbf 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -99,7 +99,7 @@ function onTabClick(): void {
 }
 
 const calcBg = () => {
-	const rawBg = 'var(--bg)';
+	const rawBg = 'var(--MI_THEME-bg)';
 	const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
 	tinyBg.setAlpha(0.85);
 	bg.value = tinyBg.toRgbString();
@@ -130,9 +130,9 @@ onUnmounted(() => {
 
 <style lang="scss" module>
 .root {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	border-bottom: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	border-bottom: solid 0.5px var(--MI_THEME-divider);
 	width: 100%;
 }
 
@@ -145,7 +145,7 @@ onUnmounted(() => {
 .upper {
 	--height: 50px;
 	display: flex;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 	height: var(--height);
 
 	.tabs:first-child {
@@ -230,7 +230,7 @@ onUnmounted(() => {
 	}
 
 	&.highlighted {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 3f3735490811..1aebf487bbc1 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -5,31 +5,30 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div ref="rootEl">
-	<div ref="headerEl">
+	<div ref="headerEl" :class="$style.header">
 		<slot name="header"></slot>
 	</div>
 	<div
-		ref="bodyEl"
+		:class="$style.body"
 		:data-sticky-container-header-height="headerHeight"
 		:data-sticky-container-footer-height="footerHeight"
 	>
 		<slot></slot>
 	</div>
-	<div ref="footerEl">
+	<div ref="footerEl" :class="$style.footer">
 		<slot name="footer"></slot>
 	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue';
+import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, useTemplateRef } from 'vue';
 
 import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
 
-const rootEl = shallowRef<HTMLElement>();
-const headerEl = shallowRef<HTMLElement>();
-const footerEl = shallowRef<HTMLElement>();
-const bodyEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const headerEl = useTemplateRef('headerEl');
+const footerEl = useTemplateRef('footerEl');
 
 const headerHeight = ref<string | undefined>();
 const childStickyTop = ref(0);
@@ -66,31 +65,11 @@ onMounted(() => {
 
 	watch([parentStickyTop, parentStickyBottom], calc);
 
-	watch(childStickyTop, () => {
-		if (bodyEl.value == null) return;
-		bodyEl.value.style.setProperty('--stickyTop', `${childStickyTop.value}px`);
-	}, {
-		immediate: true,
-	});
-
-	watch(childStickyBottom, () => {
-		if (bodyEl.value == null) return;
-		bodyEl.value.style.setProperty('--stickyBottom', `${childStickyBottom.value}px`);
-	}, {
-		immediate: true,
-	});
-
 	if (headerEl.value != null) {
-		headerEl.value.style.position = 'sticky';
-		headerEl.value.style.top = 'var(--stickyTop, 0)';
-		headerEl.value.style.zIndex = '1000';
 		observer.observe(headerEl.value);
 	}
 
 	if (footerEl.value != null) {
-		footerEl.value.style.position = 'sticky';
-		footerEl.value.style.bottom = 'var(--stickyBottom, 0)';
-		footerEl.value.style.zIndex = '1000';
 		observer.observe(footerEl.value);
 	}
 });
@@ -100,6 +79,27 @@ onUnmounted(() => {
 });
 
 defineExpose({
-	rootEl: rootEl,
+	rootEl,
 });
 </script>
+
+<style lang='scss' module>
+.body {
+	position: relative;
+	z-index: 0;
+	--MI-stickyTop: v-bind("childStickyTop + 'px'");
+	--MI-stickyBottom: v-bind("childStickyBottom + 'px'");
+}
+
+.header {
+	position: sticky;
+	top: var(--MI-stickyTop, 0);
+	z-index: 1;
+}
+
+.footer {
+	position: sticky;
+	bottom: var(--MI-stickyBottom, 0);
+	z-index: 1;
+}
+</style>
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 50bec990a1ee..f600f7eed2bf 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -99,10 +99,10 @@ if (!invalid && props.origin === null && (props.mode === 'relative' || props.mod
 
 <style lang="scss" module>
 .old1 {
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 }
 
 .old1.old2 {
-	color: var(--error);
+	color: var(--MI_THEME-error);
 }
 </style>
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index 19bd794a5d5d..38bdfc52d482 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -27,6 +27,7 @@ import MkLoadingPage from '@/pages/_loading_.vue';
 
 const props = defineProps<{
 	router?: IRouter;
+	nested?: boolean;
 }>();
 
 const router = props.router ?? inject('router');
@@ -39,6 +40,8 @@ const currentDepth = inject('routerCurrentDepth', 0);
 provide('routerCurrentDepth', currentDepth + 1);
 
 function resolveNested(current: Resolved, d = 0): Resolved | null {
+	if (!props.nested) return current;
+
 	if (d === currentDepth) {
 		return current;
 	} else {
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 44d8d59941b3..b36625ed1b47 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -5,7 +5,7 @@
 
 import { App } from 'vue';
 
-import Mfm from './global/MkMisskeyFlavoredMarkdown.js';
+import Mfm from './global/MkMfm.js';
 import MkA from './global/MkA.vue';
 import MkAcct from './global/MkAcct.vue';
 import MkAvatar from './global/MkAvatar.vue';
diff --git a/packages/frontend/src/components/page/page.dynamic.vue b/packages/frontend/src/components/page/page.dynamic.vue
index 8c511a690da4..c2449931c101 100644
--- a/packages/frontend/src/components/page/page.dynamic.vue
+++ b/packages/frontend/src/components/page/page.dynamic.vue
@@ -27,9 +27,9 @@ const props = defineProps<{
 
 <style lang="scss" module>
 .root {
-	border: 1px solid var(--divider);
-	border-radius: var(--radius);
-	padding: var(--margin);
+	border: 1px solid var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
+	padding: var(--MI-margin);
 	text-align: center;
 }
 
diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue
index fc1ce9fc7b39..69443ce7dd1c 100644
--- a/packages/frontend/src/components/page/page.image.vue
+++ b/packages/frontend/src/components/page/page.image.vue
@@ -28,8 +28,8 @@ onMounted(() => {
 
 <style lang="scss" module>
 .root {
-	border: 1px solid var(--divider);
-	border-radius: var(--radius);
+	border: 1px solid var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	overflow: hidden;
 }
 .mediaList {
diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue
index b5ba4078065c..84436e7adbb4 100644
--- a/packages/frontend/src/components/page/page.note.vue
+++ b/packages/frontend/src/components/page/page.note.vue
@@ -35,7 +35,7 @@ onMounted(() => {
 
 <style lang="scss" module>
 .root {
-	border: 1px solid var(--divider);
-	border-radius: var(--radius);
+	border: 1px solid var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 }
 </style>
diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts
index 23fd1bddf4a2..f88996019f9d 100644
--- a/packages/frontend/src/directives/adaptive-bg.ts
+++ b/packages/frontend/src/directives/adaptive-bg.ts
@@ -4,24 +4,16 @@
  */
 
 import { Directive } from 'vue';
+import { getBgColor } from '@/scripts/get-bg-color.js';
 
 export default {
 	mounted(src, binding, vn) {
-		const getBgColor = (el: HTMLElement) => {
-			const style = window.getComputedStyle(el);
-			if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
-				return style.backgroundColor;
-			} else {
-				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
-			}
-		};
-
-		const parentBg = getBgColor(src.parentElement);
+		const parentBg = getBgColor(src.parentElement) ?? 'transparent';
 
 		const myBg = window.getComputedStyle(src).backgroundColor;
 
 		if (parentBg === myBg) {
-			src.style.backgroundColor = 'var(--bg)';
+			src.style.backgroundColor = 'var(--MI_THEME-bg)';
 		} else {
 			src.style.backgroundColor = myBg;
 		}
diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts
index b436075fcd4a..1305f312bdeb 100644
--- a/packages/frontend/src/directives/adaptive-border.ts
+++ b/packages/frontend/src/directives/adaptive-border.ts
@@ -4,24 +4,16 @@
  */
 
 import { Directive } from 'vue';
+import { getBgColor } from '@/scripts/get-bg-color.js';
 
 export default {
 	mounted(src, binding, vn) {
-		const getBgColor = (el: HTMLElement) => {
-			const style = window.getComputedStyle(el);
-			if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
-				return style.backgroundColor;
-			} else {
-				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
-			}
-		};
-
-		const parentBg = getBgColor(src.parentElement);
+		const parentBg = getBgColor(src.parentElement) ?? 'transparent';
 
 		const myBg = window.getComputedStyle(src).backgroundColor;
 
 		if (parentBg === myBg) {
-			src.style.borderColor = 'var(--divider)';
+			src.style.borderColor = 'var(--MI_THEME-divider)';
 		} else {
 			src.style.borderColor = myBg;
 		}
diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts
index bbcc220e0941..aa26b94d0bc0 100644
--- a/packages/frontend/src/directives/panel.ts
+++ b/packages/frontend/src/directives/panel.ts
@@ -4,26 +4,18 @@
  */
 
 import { Directive } from 'vue';
+import { getBgColor } from '@/scripts/get-bg-color.js';
 
 export default {
 	mounted(src, binding, vn) {
-		const getBgColor = (el: HTMLElement) => {
-			const style = window.getComputedStyle(el);
-			if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
-				return style.backgroundColor;
-			} else {
-				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
-			}
-		};
+		const parentBg = getBgColor(src.parentElement) ?? 'transparent';
 
-		const parentBg = getBgColor(src.parentElement);
-
-		const myBg = getComputedStyle(document.documentElement).getPropertyValue('--panel');
+		const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel');
 
 		if (parentBg === myBg) {
-			src.style.backgroundColor = 'var(--bg)';
+			src.style.backgroundColor = 'var(--MI_THEME-bg)';
 		} else {
-			src.style.backgroundColor = 'var(--panel)';
+			src.style.backgroundColor = 'var(--MI_THEME-panel)';
 		}
 	},
 } as Directive;
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index a96a4f0539ce..ac730f8021f3 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -125,7 +125,7 @@ export const navbarItemDef = reactive({
 	ui: {
 		title: i18n.ts.switchUi,
 		icon: 'ti ti-devices',
-		action: (ev) => {
+		action: (ev: MouseEvent) => {
 			os.popupMenu([{
 				text: i18n.ts.default,
 				active: ui === 'default' || ui === null,
diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts
index 25f853453a0d..965bd6f0bc67 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/nirax.ts
@@ -36,6 +36,8 @@ interface RouteDefWithRedirect extends RouteDefBase {
 
 export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
 
+export type RouterFlag = 'forcePage';
+
 type ParsedPath = (string | {
 	name: string;
 	startsWith?: string;
@@ -107,7 +109,7 @@ export interface IRouter extends EventEmitter<RouterEvent> {
 	current: Resolved;
 	currentRef: ShallowRef<Resolved>;
 	currentRoute: ShallowRef<RouteDef>;
-	navHook: ((path: string, flag?: any) => boolean) | null;
+	navHook: ((path: string, flag?: RouterFlag) => boolean) | null;
 
 	/**
 	 * ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
@@ -116,11 +118,11 @@ export interface IRouter extends EventEmitter<RouterEvent> {
 
 	resolve(path: string): Resolved | null;
 
-	getCurrentPath(): any;
+	getCurrentPath(): string;
 
 	getCurrentKey(): string;
 
-	push(path: string, flag?: any): void;
+	push(path: string, flag?: RouterFlag): void;
 
 	replace(path: string, key?: string | null): void;
 
@@ -197,7 +199,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
 	private currentKey = Date.now().toString();
 	private redirectCount = 0;
 
-	public navHook: ((path: string, flag?: any) => boolean) | null = null;
+	public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null;
 
 	constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) {
 		super();
@@ -404,7 +406,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter {
 		return this.currentKey;
 	}
 
-	public push(path: string, flag?: any) {
+	public push(path: string, flag?: RouterFlag) {
 		const beforePath = this.currentPath;
 		if (path === beforePath) {
 			this.emit('same');
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index f42e2ed3c525..ea1b673de9a6 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -10,6 +10,7 @@ import { EventEmitter } from 'eventemitter3';
 import * as Misskey from 'misskey-js';
 import type { ComponentProps as CP } from 'vue-component-type-helpers';
 import type { Form, GetFormResultType } from '@/scripts/form.js';
+import type { MenuItem } from '@/types/menu.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
@@ -22,19 +23,20 @@ import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
 import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
 import MkPopupMenu from '@/components/MkPopupMenu.vue';
 import MkContextMenu from '@/components/MkContextMenu.vue';
-import { MenuItem } from '@/types/menu.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
 import { focusParent } from '@/scripts/focus.js';
+import type { PostFormProps } from '@/types/post-form.js';
 
 export const openingWindowsCount = ref(0);
 
-export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
+export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
 	endpoint: E,
-	data: P = {} as any,
+	data: P,
 	token?: string | null | undefined,
+	customErrors?: Record<string, { title?: string; text: string; }>,
 ) => {
 	const promise = misskeyApi(endpoint, data, token);
 	promiseDialog(promise, null, async (err) => {
@@ -77,6 +79,9 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey
 		} else if (err.message.startsWith('Unexpected token')) {
 			title = i18n.ts.gotInvalidResponseError;
 			text = i18n.ts.gotInvalidResponseErrorDescription;
+		} else if (customErrors && customErrors[err.id] != null) {
+			title = customErrors[err.id].title;
+			text = customErrors[err.id].text;
 		}
 		alert({
 			type: 'error',
@@ -86,11 +91,11 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey
 	});
 
 	return promise;
-}) as typeof misskeyApi;
+});
 
 export function promiseDialog<T extends Promise<any>>(
 	promise: T,
-	onSuccess?: ((res: any) => void) | null,
+	onSuccess?: ((res: Awaited<T>) => void) | null,
 	onFailure?: ((err: Misskey.api.APIError) => void) | null,
 	text?: string,
 ): T {
@@ -132,12 +137,12 @@ export function promiseDialog<T extends Promise<any>>(
 }
 
 let popupIdCount = 0;
-export const popups = ref([]) as Ref<{
+export const popups = ref<{
 	id: number;
 	component: Component;
 	props: Record<string, any>;
 	events: Record<string, any>;
-}[]>;
+}[]>([]);
 
 const zIndexes = {
 	veryLow: 500000,
@@ -454,7 +459,7 @@ type SelectItem<C> = {
 };
 
 // default が指定されていたら result は null になり得ないことを保証する overload function
-export function select<C = any>(props: {
+export function select<C = unknown>(props: {
 	title?: string;
 	text?: string;
 	default: string;
@@ -467,7 +472,7 @@ export function select<C = any>(props: {
 } | {
 	canceled: false; result: C;
 }>;
-export function select<C = any>(props: {
+export function select<C = unknown>(props: {
 	title?: string;
 	text?: string;
 	default?: string | null;
@@ -480,7 +485,7 @@ export function select<C = any>(props: {
 } | {
 	canceled: false; result: C | null;
 }>;
-export function select<C = any>(props: {
+export function select<C = unknown>(props: {
 	title?: string;
 	text?: string;
 	default?: string | null;
@@ -683,15 +688,17 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
 	}));
 }
 
-export function post(props: Record<string, any> = {}): Promise<void> {
-	pleaseLogin(undefined, (props.initialText || props.initialNote ? {
-		type: 'share',
-		params: {
-			text: props.initialText ?? props.initialNote.text,
-			visibility: props.initialVisibility ?? props.initialNote?.visibility,
-			localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0',
-		},
-	} : undefined));
+export function post(props: PostFormProps = {}): Promise<void> {
+	pleaseLogin({
+		openOnRemote: (props.initialText || props.initialNote ? {
+			type: 'share',
+			params: {
+				text: props.initialText ?? props.initialNote?.text ?? '',
+				visibility: props.initialVisibility ?? props.initialNote?.visibility ?? 'public',
+				localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0',
+			},
+		} : undefined),
+	});
 
 	showMovedDialog();
 	return new Promise(resolve => {
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 960df5948596..f2becfd8f55a 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -266,6 +266,15 @@ const patronsWithIcon = [{
 }, {
 	name: 'なっかあ',
 	icon: 'https://assets.misskey-hub.net/patrons/c2f5f3e394e74a64912284a2f4ca710e.jpg',
+}, {
+	name: '如月ユカ',
+	icon: 'https://assets.misskey-hub.net/patrons/f24a042076a041b6811a2f124eb620ca.jpg',
+}, {
+	name: 'Yatoigawa',
+	icon: 'https://assets.misskey-hub.net/patrons/505e3568885a4a488431a8f22b4553d0.jpg',
+}, {
+	name: '秋瀬カヲル',
+	icon: 'https://assets.misskey-hub.net/patrons/0f22aeb866484f4fa51db6721e3f9847.jpg',
 }];
 
 const patrons = [
@@ -371,6 +380,10 @@ const patrons = [
 	'塩キャベツ',
 	'はとぽぷさん',
 	'100の人 (エスパー・イーシア)',
+	'ケモナーのケシン',
+	'こまつぶり',
+	'まゆつな空高',
+	'asata',
 ];
 
 const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));
@@ -440,7 +453,7 @@ definePageMetadata(() => ({
 .znqjceqz {
 	> .about {
 		position: relative;
-		border-radius: var(--radius);
+		border-radius: var(--MI-radius);
 
 		> .treasure {
 			position: absolute;
@@ -528,17 +541,17 @@ definePageMetadata(() => ({
 	display: flex;
 	align-items: center;
 	padding: 12px;
-	background: var(--buttonBg);
+	background: var(--MI_THEME-buttonBg);
 	border-radius: 6px;
 
 	&:hover {
 		text-decoration: none;
-		background: var(--buttonHoverBg);
+		background: var(--MI_THEME-buttonHoverBg);
 	}
 
 	&.active {
-		color: var(--accent);
-		background: var(--buttonHoverBg);
+		color: var(--MI_THEME-accent);
+		background: var(--MI_THEME-buttonHoverBg);
 	}
 }
 
@@ -561,7 +574,7 @@ definePageMetadata(() => ({
 	display: flex;
 	align-items: center;
 	padding: 12px;
-	background: var(--buttonBg);
+	background: var(--MI_THEME-buttonBg);
 	border-radius: 6px;
 }
 
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index b3776c67e601..0a7cb8a50b99 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<template #prefix><i class="ti ti-search"></i></template>
 			<template #label>{{ i18n.ts.host }}</template>
 		</MkInput>
-		<FormSplit style="margin-top: var(--margin);">
+		<FormSplit style="margin-top: var(--MI-margin);">
 			<MkSelect v-model="state">
 				<template #label>{{ i18n.ts.state }}</template>
 				<option value="all">{{ i18n.ts.all }}</option>
diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue
index b645506effdf..e5e57c05c403 100644
--- a/packages/frontend/src/pages/about.overview.vue
+++ b/packages/frontend/src/pages/about.overview.vue
@@ -147,7 +147,7 @@ const initStats = () => misskeyApi('stats', {});
 	text-align: center;
 	border-radius: 10px;
 	overflow: clip;
-	background-color: var(--panel);
+	background-color: var(--MI_THEME-panel);
 	background-size: cover;
 	background-position: center center;
 }
@@ -183,14 +183,14 @@ const initStats = () => misskeyApi('stats', {});
 		flex-shrink: 0;
 		display: flex;
 		position: sticky;
-		top: calc(var(--stickyTop, 0px) + 8px);
+		top: calc(var(--MI-stickyTop, 0px) + 8px);
 		counter-increment: item;
 		content: counter(item);
 		width: 32px;
 		height: 32px;
 		line-height: 32px;
-		background-color: var(--accentedBg);
-		color: var(--accent);
+		background-color: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
 		font-size: 13px;
 		font-weight: bold;
 		align-items: center;
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index d8311186ab84..60f6be51d42d 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -44,6 +44,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
 			</div>
 		</div>
+		<div v-else-if="tab === 'notes' && info" class="_gaps_m">
+			<XNotes :fileId="fileId"/>
+		</div>
 		<div v-else-if="tab === 'ip' && info" class="_gaps_m">
 			<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
 			<MkKeyValue v-if="info.requestIp" class="_monospace" :copy="info.requestIp" oneline>
@@ -67,7 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue';
+import { computed, defineAsyncComponent, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -88,6 +91,7 @@ const tab = ref('overview');
 const file = ref<Misskey.entities.DriveFile | null>(null);
 const info = ref<Misskey.entities.AdminDriveShowFileResponse | null>(null);
 const isSensitive = ref<boolean>(false);
+const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
 
 const props = defineProps<{
 	fileId: string,
@@ -131,6 +135,10 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.overview,
 	icon: 'ti ti-info-circle',
 }, iAmModerator ? {
+	key: 'notes',
+	title: i18n.ts._fileViewer.attachedNotes,
+	icon: 'ti ti-pencil',
+} : null, iAmModerator ? {
 	key: 'ip',
 	title: 'IP',
 	icon: 'ti ti-password',
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index d40d1eee582b..948e7a3cce61 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -53,6 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 				<MkTextarea v-model="moderationNote" manualSave>
 					<template #label>{{ i18n.ts.moderationNote }}</template>
+					<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
 				</MkTextarea>
 
 				<!--
@@ -152,15 +153,21 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-else-if="tab === 'announcements'" class="_gaps">
 				<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
 
+				<MkSelect v-model="announcementsStatus">
+					<template #label>{{ i18n.ts.filter }}</template>
+					<option value="active">{{ i18n.ts.active }}</option>
+					<option value="archived">{{ i18n.ts.archived }}</option>
+				</MkSelect>
+
 				<MkPagination :pagination="announcementsPagination">
 					<template #default="{ items }">
 						<div class="_gaps_s">
 							<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
 								<span style="margin-right: 0.5em;">
 									<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
-									<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
-									<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
-									<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
+									<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
+									<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
+									<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
 								</span>
 								<span>{{ announcement.title }}</span>
 								<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span>
@@ -205,6 +212,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, defineAsyncComponent, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
 import MkChart from '@/components/MkChart.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -220,7 +228,6 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@@/js/config.js';
 import { acct } from '@/filters/user.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
@@ -253,11 +260,15 @@ const filesPagination = {
 		userId: props.userId,
 	})),
 };
+
+const announcementsStatus = ref<'active' | 'archived'>('active');
+
 const announcementsPagination = {
 	endpoint: 'admin/announcements/list' as const,
 	limit: 10,
 	params: computed(() => ({
 		userId: props.userId,
+		status: announcementsStatus.value,
 	})),
 };
 const expandedRoles = ref([]);
@@ -581,18 +592,18 @@ definePageMetadata(() => ({
 			}
 
 			> .suspended {
-				color: var(--error);
-				border-color: var(--error);
+				color: var(--MI_THEME-error);
+				border-color: var(--MI_THEME-error);
 			}
 
 			> .silenced {
-				color: var(--warn);
-				border-color: var(--warn);
+				color: var(--MI_THEME-warn);
+				border-color: var(--MI_THEME-warn);
 			}
 
 			> .moderator {
-				color: var(--success);
-				border-color: var(--success);
+				color: var(--MI_THEME-success);
+				border-color: var(--MI_THEME-success);
 			}
 		}
 	}
@@ -639,7 +650,7 @@ definePageMetadata(() => ({
 .roleItemSub {
 	padding: 6px 12px;
 	font-size: 85%;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 
 .roleUnassign {
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
index f001a4ac2031..4762ef3f97ff 100644
--- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -155,12 +155,12 @@ function removeSelf() {
 }
 
 .item {
-	border: solid 2px var(--divider);
-	border-radius: var(--radius);
+	border: solid 2px var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 	padding: 12px;
 
 	&:hover {
-		border-color: var(--accent);
+		border-color: var(--MI_THEME-accent);
 	}
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index d22e078c2a82..9b1bf51f5855 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -119,7 +119,7 @@ function onTabClick(tab: Tab, ev: MouseEvent): void {
 }
 
 const calcBg = () => {
-	const rawBg = pageMetadata.value?.bg ?? 'var(--bg)';
+	const rawBg = pageMetadata.value?.bg ?? 'var(--MI_THEME-bg)';
 	const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
 	tinyBg.setAlpha(0.85);
 	bg.value = tinyBg.toRgbString();
@@ -156,8 +156,8 @@ onUnmounted(() => {
 	--height: 60px;
 	display: flex;
 	width: 100%;
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 
 	> .buttons {
 		--margin: 8px;
@@ -189,7 +189,7 @@ onUnmounted(() => {
 			}
 
 			&.highlighted {
-				color: var(--accent);
+				color: var(--MI_THEME-accent);
 			}
 		}
 
@@ -286,7 +286,7 @@ onUnmounted(() => {
 			position: absolute;
 			bottom: 0;
 			height: 3px;
-			background: var(--accent);
+			background: var(--MI_THEME-accent);
 			border-radius: 999px;
 			transition: all 0.2s ease;
 			pointer-events: none;
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
index 827e22e8ae31..eef24afd3223 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
@@ -294,10 +294,10 @@ onMounted(async () => {
 	bottom: 0;
 	left: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
-	background: var(--acrylicBg);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-acrylicBg);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 
 .systemWebhook {
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
index 0b86808fafb1..36d586bd233d 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue
@@ -87,7 +87,7 @@ function onDeleteButtonClicked() {
 }
 
 .rightDivider {
-	border-right: 0.5px solid var(--divider);
+	border-right: 0.5px solid var(--MI_THEME-divider);
 }
 
 .recipientButtons {
@@ -108,7 +108,7 @@ function onDeleteButtonClicked() {
 	padding: 8px;
 
 	&:hover {
-		background-color: var(--buttonBg);
+		background-color: var(--MI_THEME-buttonBg);
 	}
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 0b9847fed3b4..22173bb8880d 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -12,6 +12,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton>
 			</div>
 
+			<MkInfo v-if="!defaultStore.reactiveState.abusesTutorial.value" closable @close="closeTutorial()">
+				{{ i18n.ts._abuseUserReport.resolveTutorial }}
+			</MkInfo>
+
 			<div :class="$style.inputs" class="_gaps">
 				<MkSelect v-model="state" style="margin: 0; flex: 1;">
 					<template #label>{{ i18n.ts.state }}</template>
@@ -44,8 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			-->
 
-			<MkPagination v-slot="{items}" ref="reports" :pagination="pagination" style="margin-top: var(--margin);">
-				<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
+			<MkPagination v-slot="{items}" ref="reports" :pagination="pagination">
+				<div class="_gaps">
+					<XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/>
+				</div>
 			</MkPagination>
 		</div>
 	</MkSpacer>
@@ -54,7 +60,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, shallowRef, ref } from 'vue';
-
 import XHeader from './_header_.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkPagination from '@/components/MkPagination.vue';
@@ -62,6 +67,8 @@ import XAbuseReport from '@/components/MkAbuseReport.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { defaultStore } from '@/store.js';
 
 const reports = shallowRef<InstanceType<typeof MkPagination>>();
 
@@ -85,6 +92,10 @@ function resolved(reportId) {
 	reports.value?.removeItem(reportId);
 }
 
+function closeTutorial() {
+	defaultStore.set('abusesTutorial', false);
+}
+
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index bd442ccc691a..0d67359e47fb 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -65,18 +65,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkTextarea v-model="ad.memo">
 					<template #label>{{ i18n.ts.memo }}</template>
 				</MkTextarea>
-				<div class="buttons">
-					<MkButton class="button" inline primary style="margin-right: 12px;" @click="save(ad)">
+				<div class="_buttons">
+					<MkButton inline primary style="margin-right: 12px;" @click="save(ad)">
 						<i
 							class="ti ti-device-floppy"
 						></i> {{ i18n.ts.save }}
 					</MkButton>
-					<MkButton class="button" inline danger @click="remove(ad)">
+					<MkButton inline danger @click="remove(ad)">
 						<i class="ti ti-trash"></i> {{ i18n.ts.remove }}
 					</MkButton>
 				</div>
 			</div>
-			<MkButton class="button" @click="more()">
+			<MkButton @click="more()">
 				<i class="ti ti-reload"></i>{{ i18n.ts.more }}
 			</MkButton>
 		</div>
@@ -266,7 +266,7 @@ definePageMetadata(() => ({
 	padding: 32px;
 
 	&:not(:last-child) {
-		margin-bottom: var(--margin);
+		margin-bottom: var(--MI-margin);
 	}
 }
 .input {
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index b9e09c8d03a6..e42058601721 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -24,13 +24,21 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #label>{{ announcement.title }}</template>
 					<template #icon>
 						<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
-						<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
-						<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
-						<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
+						<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
+						<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
+						<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
 					</template>
 					<template #caption>{{ announcement.text }}</template>
+					<template #footer>
+						<div class="_buttons">
+							<MkButton rounded primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+							<MkButton v-if="announcement.id != null && announcement.isActive" rounded @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
+							<MkButton v-if="announcement.id != null && !announcement.isActive" rounded @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton>
+							<MkButton v-if="announcement.id != null" rounded danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+						</div>
+					</template>
 
-					<div class="_gaps_m">
+					<div class="_gaps">
 						<MkInput v-model="announcement.title">
 							<template #label>{{ i18n.ts.title }}</template>
 						</MkInput>
@@ -43,9 +51,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<MkRadios v-model="announcement.icon">
 							<template #label>{{ i18n.ts.icon }}</template>
 							<option value="info"><i class="ti ti-info-circle"></i></option>
-							<option value="warning"><i class="ti ti-alert-triangle" style="color: var(--warn);"></i></option>
-							<option value="error"><i class="ti ti-circle-x" style="color: var(--error);"></i></option>
-							<option value="success"><i class="ti ti-check" style="color: var(--success);"></i></option>
+							<option value="warning"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i></option>
+							<option value="error"><i class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i></option>
+							<option value="success"><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i></option>
 						</MkRadios>
 						<MkRadios v-model="announcement.display">
 							<template #label>{{ i18n.ts.display }}</template>
@@ -64,16 +72,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 							{{ i18n.ts._announcement.needConfirmationToRead }}
 						</MkSwitch>
 						<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
-						<div class="buttons _buttons">
-							<MkButton class="button" inline primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-							<MkButton v-if="announcement.id != null && announcement.isActive" class="button" inline @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
-							<MkButton v-if="announcement.id != null && !announcement.isActive" class="button" inline @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton>
-							<MkButton v-if="announcement.id != null" class="button" inline danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
-						</div>
 					</div>
 				</MkFolder>
 				<MkLoading v-if="loadingMore"/>
-				<MkButton class="button" @click="more()">
+				<MkButton @click="more()">
 					<i class="ti ti-reload"></i>{{ i18n.ts.more }}
 				</MkButton>
 			</template>
@@ -170,7 +172,7 @@ function more() {
 	loadingMore.value = true;
 	misskeyApi('admin/announcements/list', {
 		status: announcementsStatus.value,
-		untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id
+		untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id,
 	}).then(announcementResponse => {
 		announcements.value = announcements.value.concat(announcementResponse);
 		loadingMore.value = false;
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 73c5e1919f41..d07add440842 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -4,145 +4,156 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div>
-	<FormSuspense :p="init">
-		<div class="_gaps_m">
-			<MkRadios v-model="provider">
-				<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
-				<option value="hcaptcha">hCaptcha</option>
-				<option value="mcaptcha">mCaptcha</option>
-				<option value="recaptcha">reCAPTCHA</option>
-				<option value="turnstile">Turnstile</option>
-			</MkRadios>
+<MkFolder>
+	<template #icon><i class="ti ti-shield"></i></template>
+	<template #label>{{ i18n.ts.botProtection }}</template>
+	<template v-if="botProtectionForm.savedState.provider === 'hcaptcha'" #suffix>hCaptcha</template>
+	<template v-else-if="botProtectionForm.savedState.provider === 'mcaptcha'" #suffix>mCaptcha</template>
+	<template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template>
+	<template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template>
+	<template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template>
+	<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
+	<template v-if="botProtectionForm.modified.value" #footer>
+		<MkFormFooter :form="botProtectionForm"/>
+	</template>
 
-			<template v-if="provider === 'hcaptcha'">
-				<MkInput v-model="hcaptchaSiteKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
-				</MkInput>
-				<MkInput v-model="hcaptchaSecretKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
-				</MkInput>
-				<FormSlot>
-					<template #label>{{ i18n.ts.preview }}</template>
-					<MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
-				</FormSlot>
-			</template>
-			<template v-else-if="provider === 'mcaptcha'">
-				<MkInput v-model="mcaptchaSiteKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
-				</MkInput>
-				<MkInput v-model="mcaptchaSecretKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
-				</MkInput>
-				<MkInput v-model="mcaptchaInstanceUrl">
-					<template #prefix><i class="ti ti-link"></i></template>
-					<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
-				</MkInput>
-				<FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl">
-					<template #label>{{ i18n.ts.preview }}</template>
-					<MkCaptcha provider="mcaptcha" :sitekey="mcaptchaSiteKey" :instanceUrl="mcaptchaInstanceUrl"/>
-				</FormSlot>
-			</template>
-			<template v-else-if="provider === 'recaptcha'">
-				<MkInput v-model="recaptchaSiteKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
-				</MkInput>
-				<MkInput v-model="recaptchaSecretKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
-				</MkInput>
-				<FormSlot v-if="recaptchaSiteKey">
-					<template #label>{{ i18n.ts.preview }}</template>
-					<MkCaptcha provider="recaptcha" :sitekey="recaptchaSiteKey"/>
-				</FormSlot>
-			</template>
-			<template v-else-if="provider === 'turnstile'">
-				<MkInput v-model="turnstileSiteKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
-				</MkInput>
-				<MkInput v-model="turnstileSecretKey">
-					<template #prefix><i class="ti ti-key"></i></template>
-					<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
-				</MkInput>
-				<FormSlot>
-					<template #label>{{ i18n.ts.preview }}</template>
-					<MkCaptcha provider="turnstile" :sitekey="turnstileSiteKey || '1x00000000000000000000AA'"/>
-				</FormSlot>
-			</template>
+	<div class="_gaps_m">
+		<MkRadios v-model="botProtectionForm.state.provider">
+			<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
+			<option value="hcaptcha">hCaptcha</option>
+			<option value="mcaptcha">mCaptcha</option>
+			<option value="recaptcha">reCAPTCHA</option>
+			<option value="turnstile">Turnstile</option>
+			<option value="testcaptcha">testCaptcha</option>
+		</MkRadios>
 
-			<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-		</div>
-	</FormSuspense>
-</div>
+		<template v-if="botProtectionForm.state.provider === 'hcaptcha'">
+			<MkInput v-model="botProtectionForm.state.hcaptchaSiteKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
+			</MkInput>
+			<MkInput v-model="botProtectionForm.state.hcaptchaSecretKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.hcaptchaSecretKey }}</template>
+			</MkInput>
+			<FormSlot>
+				<template #label>{{ i18n.ts.preview }}</template>
+				<MkCaptcha provider="hcaptcha" :sitekey="botProtectionForm.state.hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
+			</FormSlot>
+		</template>
+		<template v-else-if="botProtectionForm.state.provider === 'mcaptcha'">
+			<MkInput v-model="botProtectionForm.state.mcaptchaSiteKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
+			</MkInput>
+			<MkInput v-model="botProtectionForm.state.mcaptchaSecretKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
+			</MkInput>
+			<MkInput v-model="botProtectionForm.state.mcaptchaInstanceUrl">
+				<template #prefix><i class="ti ti-link"></i></template>
+				<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
+			</MkInput>
+			<FormSlot v-if="botProtectionForm.state.mcaptchaSiteKey && botProtectionForm.state.mcaptchaInstanceUrl">
+				<template #label>{{ i18n.ts.preview }}</template>
+				<MkCaptcha provider="mcaptcha" :sitekey="botProtectionForm.state.mcaptchaSiteKey" :instanceUrl="botProtectionForm.state.mcaptchaInstanceUrl"/>
+			</FormSlot>
+		</template>
+		<template v-else-if="botProtectionForm.state.provider === 'recaptcha'">
+			<MkInput v-model="botProtectionForm.state.recaptchaSiteKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.recaptchaSiteKey }}</template>
+			</MkInput>
+			<MkInput v-model="botProtectionForm.state.recaptchaSecretKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.recaptchaSecretKey }}</template>
+			</MkInput>
+			<FormSlot v-if="botProtectionForm.state.recaptchaSiteKey">
+				<template #label>{{ i18n.ts.preview }}</template>
+				<MkCaptcha provider="recaptcha" :sitekey="botProtectionForm.state.recaptchaSiteKey"/>
+			</FormSlot>
+		</template>
+		<template v-else-if="botProtectionForm.state.provider === 'turnstile'">
+			<MkInput v-model="botProtectionForm.state.turnstileSiteKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.turnstileSiteKey }}</template>
+			</MkInput>
+			<MkInput v-model="botProtectionForm.state.turnstileSecretKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.turnstileSecretKey }}</template>
+			</MkInput>
+			<FormSlot>
+				<template #label>{{ i18n.ts.preview }}</template>
+				<MkCaptcha provider="turnstile" :sitekey="botProtectionForm.state.turnstileSiteKey || '1x00000000000000000000AA'"/>
+			</FormSlot>
+		</template>
+		<template v-else-if="botProtectionForm.state.provider === 'testcaptcha'">
+			<MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo>
+			<FormSlot>
+				<template #label>{{ i18n.ts.preview }}</template>
+				<MkCaptcha provider="testcaptcha"/>
+			</FormSlot>
+		</template>
+	</div>
+</MkFolder>
 </template>
 
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
-import type { CaptchaProvider } from '@/components/MkCaptcha.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkInput from '@/components/MkInput.vue';
-import MkButton from '@/components/MkButton.vue';
-import FormSuspense from '@/components/form/suspense.vue';
 import FormSlot from '@/components/form/slot.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
+import { useForm } from '@/scripts/use-form.js';
+import MkFormFooter from '@/components/MkFormFooter.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkInfo from '@/components/MkInfo.vue';
 
 const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
 
-const provider = ref<CaptchaProvider | null>(null);
-const hcaptchaSiteKey = ref<string | null>(null);
-const hcaptchaSecretKey = ref<string | null>(null);
-const mcaptchaSiteKey = ref<string | null>(null);
-const mcaptchaSecretKey = ref<string | null>(null);
-const mcaptchaInstanceUrl = ref<string | null>(null);
-const recaptchaSiteKey = ref<string | null>(null);
-const recaptchaSecretKey = ref<string | null>(null);
-const turnstileSiteKey = ref<string | null>(null);
-const turnstileSecretKey = ref<string | null>(null);
+const meta = await misskeyApi('admin/meta');
 
-async function init() {
-	const meta = await misskeyApi('admin/meta');
-	hcaptchaSiteKey.value = meta.hcaptchaSiteKey;
-	hcaptchaSecretKey.value = meta.hcaptchaSecretKey;
-	mcaptchaSiteKey.value = meta.mcaptchaSiteKey;
-	mcaptchaSecretKey.value = meta.mcaptchaSecretKey;
-	mcaptchaInstanceUrl.value = meta.mcaptchaInstanceUrl;
-	recaptchaSiteKey.value = meta.recaptchaSiteKey;
-	recaptchaSecretKey.value = meta.recaptchaSecretKey;
-	turnstileSiteKey.value = meta.turnstileSiteKey;
-	turnstileSecretKey.value = meta.turnstileSecretKey;
-
-	provider.value = meta.enableHcaptcha ? 'hcaptcha' :
-		meta.enableRecaptcha ? 'recaptcha' :
-		meta.enableTurnstile ? 'turnstile' :
-		meta.enableMcaptcha ? 'mcaptcha' : null;
-}
-
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		enableHcaptcha: provider.value === 'hcaptcha',
-		hcaptchaSiteKey: hcaptchaSiteKey.value,
-		hcaptchaSecretKey: hcaptchaSecretKey.value,
-		enableMcaptcha: provider.value === 'mcaptcha',
-		mcaptchaSiteKey: mcaptchaSiteKey.value,
-		mcaptchaSecretKey: mcaptchaSecretKey.value,
-		mcaptchaInstanceUrl: mcaptchaInstanceUrl.value,
-		enableRecaptcha: provider.value === 'recaptcha',
-		recaptchaSiteKey: recaptchaSiteKey.value,
-		recaptchaSecretKey: recaptchaSecretKey.value,
-		enableTurnstile: provider.value === 'turnstile',
-		turnstileSiteKey: turnstileSiteKey.value,
-		turnstileSecretKey: turnstileSecretKey.value,
-	}).then(() => {
-		fetchInstance(true);
+const botProtectionForm = useForm({
+	provider: meta.enableHcaptcha
+		? 'hcaptcha'
+		: meta.enableRecaptcha
+			? 'recaptcha'
+			: meta.enableTurnstile
+				? 'turnstile'
+				: meta.enableMcaptcha
+					? 'mcaptcha'
+					: meta.enableTestcaptcha
+						? 'testcaptcha'
+						: null,
+	hcaptchaSiteKey: meta.hcaptchaSiteKey,
+	hcaptchaSecretKey: meta.hcaptchaSecretKey,
+	mcaptchaSiteKey: meta.mcaptchaSiteKey,
+	mcaptchaSecretKey: meta.mcaptchaSecretKey,
+	mcaptchaInstanceUrl: meta.mcaptchaInstanceUrl,
+	recaptchaSiteKey: meta.recaptchaSiteKey,
+	recaptchaSecretKey: meta.recaptchaSecretKey,
+	turnstileSiteKey: meta.turnstileSiteKey,
+	turnstileSecretKey: meta.turnstileSecretKey,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableHcaptcha: state.provider === 'hcaptcha',
+		hcaptchaSiteKey: state.hcaptchaSiteKey,
+		hcaptchaSecretKey: state.hcaptchaSecretKey,
+		enableMcaptcha: state.provider === 'mcaptcha',
+		mcaptchaSiteKey: state.mcaptchaSiteKey,
+		mcaptchaSecretKey: state.mcaptchaSecretKey,
+		mcaptchaInstanceUrl: state.mcaptchaInstanceUrl,
+		enableRecaptcha: state.provider === 'recaptcha',
+		recaptchaSiteKey: state.recaptchaSiteKey,
+		recaptchaSecretKey: state.recaptchaSecretKey,
+		enableTurnstile: state.provider === 'turnstile',
+		turnstileSiteKey: state.turnstileSiteKey,
+		turnstileSecretKey: state.turnstileSecretKey,
+		enableTestcaptcha: state.provider === 'testcaptcha',
 	});
-}
+	fetchInstance(true);
+});
 </script>
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 947dde767eef..95f82c1f24c3 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -183,7 +183,7 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index 4a858887f353..5b60e67dac70 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -138,7 +138,7 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index e0b82eb02ea0..91f41166e990 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
-			<FormSection>
+			<MkFolder>
 				<template #label>DeepL Translation</template>
 
 				<div class="_gaps_m">
@@ -19,17 +19,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkSwitch v-model="deeplIsPro">
 						<template #label>Pro account</template>
 					</MkSwitch>
+					<MkButton primary @click="save_deepl">Save</MkButton>
 				</div>
-			</FormSection>
+			</MkFolder>
 		</FormSuspense>
 	</MkSpacer>
-	<template #footer>
-		<div :class="$style.footer">
-			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
-				<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
-			</MkSpacer>
-		</div>
-	</template>
 </MkStickyContainer>
 </template>
 
@@ -40,12 +34,12 @@ import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import FormSuspense from '@/components/form/suspense.vue';
-import FormSection from '@/components/form/section.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkFolder from '@/components/MkFolder.vue';
 
 const deeplAuthKey = ref<string>('');
 const deeplIsPro = ref<boolean>(false);
@@ -56,7 +50,7 @@ async function init() {
 	deeplIsPro.value = meta.deeplIsPro;
 }
 
-function save() {
+function save_deepl() {
 	os.apiWithDialog('admin/update-meta', {
 		deeplAuthKey: deeplAuthKey.value,
 		deeplIsPro: deeplIsPro.value,
@@ -74,10 +68,3 @@ definePageMetadata(() => ({
 	icon: 'ti ti-link',
 }));
 </script>
-
-<style lang="scss" module>
-.footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-}
-</style>
diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
index debf684c9b3e..e7b9fd86212d 100644
--- a/packages/frontend/src/pages/admin/federation.vue
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #prefix><i class="ti ti-search"></i></template>
 						<template #label>{{ i18n.ts.host }}</template>
 					</MkInput>
-					<FormSplit style="margin-top: var(--margin);">
+					<FormSplit style="margin-top: var(--MI-margin);">
 						<MkSelect v-model="state">
 							<template #label>{{ i18n.ts.state }}</template>
 							<option value="all">{{ i18n.ts.all }}</option>
diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue
index 5132b85c64ed..4cc859227fb2 100644
--- a/packages/frontend/src/pages/admin/files.vue
+++ b/packages/frontend/src/pages/admin/files.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #header><XHeader :actions="headerActions"/></template>
 		<MkSpacer :contentMax="900">
 			<div class="_gaps">
-				<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+				<div class="inputs" style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;">
 					<MkSelect v-model="origin" style="margin: 0; flex: 1;">
 						<template #label>{{ i18n.ts.instance }}</template>
 						<option value="combined">{{ i18n.ts.all }}</option>
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #label>{{ i18n.ts.host }}</template>
 					</MkInput>
 				</div>
-				<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+				<div class="inputs" style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;">
 					<MkInput v-model="userId" :debounce="true" type="search" style="margin: 0; flex: 1;">
 						<template #label>User ID</template>
 					</MkInput>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 40dec55deb75..fd15ae1d663c 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div class="_gaps_s">
 					<MkInfo v-if="thereIsUnresolvedAbuseReport" warn>{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }} <MkA to="/admin/abuses" class="_link">{{ i18n.ts.check }}</MkA></MkInfo>
 					<MkInfo v-if="noMaintainerInformation" warn>{{ i18n.ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
-					<MkInfo v-if="noInquiryUrl" warn>{{ i18n.ts.noInquiryUrlWarning }} <MkA to="/admin/moderation" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
+					<MkInfo v-if="noInquiryUrl" warn>{{ i18n.ts.noInquiryUrlWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
 					<MkInfo v-if="noBotProtection" warn>{{ i18n.ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
 					<MkInfo v-if="noEmailServer" warn>{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
 				</div>
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</MkSpacer>
 	</div>
 	<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
-		<RouterView/>
+		<RouterView nested/>
 	</div>
 </div>
 </template>
@@ -199,16 +199,6 @@ const menuDef = computed(() => [{
 		text: i18n.ts.relays,
 		to: '/admin/relays',
 		active: currentPage.value?.route.name === 'relays',
-	}, {
-		icon: 'ti ti-ban',
-		text: i18n.ts.instanceBlocking,
-		to: '/admin/instance-block',
-		active: currentPage.value?.route.name === 'instance-block',
-	}, {
-		icon: 'ti ti-ghost',
-		text: i18n.ts.proxyAccount,
-		to: '/admin/proxy-account',
-		active: currentPage.value?.route.name === 'proxy-account',
 	}, {
 		icon: 'ti ti-link',
 		text: i18n.ts.externalServices,
@@ -220,10 +210,10 @@ const menuDef = computed(() => [{
 		to: '/admin/system-webhook',
 		active: currentPage.value?.route.name === 'system-webhook',
 	}, {
-		icon: 'ti ti-adjustments',
-		text: i18n.ts.other,
-		to: '/admin/other-settings',
-		active: currentPage.value?.route.name === 'other-settings',
+		icon: 'ti ti-bolt',
+		text: i18n.ts.performance,
+		to: '/admin/performance',
+		active: currentPage.value?.route.name === 'performance',
 	}],
 }, {
 	title: i18n.ts.info,
@@ -341,7 +331,7 @@ defineExpose({
 			width: 32%;
 			max-width: 280px;
 			box-sizing: border-box;
-			border-right: solid 0.5px var(--divider);
+			border-right: solid 0.5px var(--MI_THEME-divider);
 			overflow: auto;
 			height: 100%;
 		}
diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue
deleted file mode 100644
index e090616b26a2..000000000000
--- a/packages/frontend/src/pages/admin/instance-block.vue
+++ /dev/null
@@ -1,84 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<MkStickyContainer>
-	<template #header><XHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
-		<FormSuspense :p="init">
-			<template v-if="tab === 'block'">
-				<MkTextarea v-model="blockedHosts">
-					<span>{{ i18n.ts.blockedInstances }}</span>
-					<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
-				</MkTextarea>
-			</template>
-			<template v-else-if="tab === 'silence'">
-				<MkTextarea v-model="silencedHosts" class="_formBlock">
-					<span>{{ i18n.ts.silencedInstances }}</span>
-					<template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
-				</MkTextarea>
-				<MkTextarea v-model="mediaSilencedHosts" class="_formBlock">
-					<span>{{ i18n.ts.mediaSilencedInstances }}</span>
-					<template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template>
-				</MkTextarea>
-			</template>
-			<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-		</FormSuspense>
-	</MkSpacer>
-</MkStickyContainer>
-</template>
-
-<script lang="ts" setup>
-import { ref, computed } from 'vue';
-import XHeader from './_header_.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkTextarea from '@/components/MkTextarea.vue';
-import FormSuspense from '@/components/form/suspense.vue';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { fetchInstance } from '@/instance.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-
-const blockedHosts = ref<string>('');
-const silencedHosts = ref<string>('');
-const mediaSilencedHosts = ref<string>('');
-const tab = ref('block');
-
-async function init() {
-	const meta = await misskeyApi('admin/meta');
-	blockedHosts.value = meta.blockedHosts.join('\n');
-	silencedHosts.value = meta.silencedHosts.join('\n');
-	mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n');
-}
-
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		blockedHosts: blockedHosts.value.split('\n') || [],
-		silencedHosts: silencedHosts.value.split('\n') || [],
-		mediaSilencedHosts: mediaSilencedHosts.value.split('\n') || [],
-
-	}).then(() => {
-		fetchInstance(true);
-	});
-}
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => [{
-	key: 'block',
-	title: i18n.ts.block,
-	icon: 'ti ti-ban',
-}, {
-	key: 'silence',
-	title: i18n.ts.silence,
-	icon: 'ti ti-eye-off',
-}]);
-
-definePageMetadata(() => ({
-	title: i18n.ts.instanceBlocking,
-	icon: 'ti ti-ban',
-}));
-</script>
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index a75799696d8f..5d8a581b2e54 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -10,61 +10,115 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 			<FormSuspense :p="init">
 				<div class="_gaps_m">
-					<MkSwitch v-model="enableRegistration">
+					<MkSwitch v-model="enableRegistration" @change="onChange_enableRegistration">
 						<template #label>{{ i18n.ts.enableRegistration }}</template>
+						<template #caption>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</template>
 					</MkSwitch>
 
-					<MkSwitch v-model="emailRequiredForSignup">
+					<MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
 						<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
 					</MkSwitch>
 
 					<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
 
-					<MkInput v-model="tosUrl" type="url">
-						<template #prefix><i class="ti ti-link"></i></template>
-						<template #label>{{ i18n.ts.tosUrl }}</template>
-					</MkInput>
-
-					<MkInput v-model="privacyPolicyUrl" type="url">
-						<template #prefix><i class="ti ti-link"></i></template>
-						<template #label>{{ i18n.ts.privacyPolicyUrl }}</template>
-					</MkInput>
-
-					<MkInput v-model="inquiryUrl" type="url">
-						<template #prefix><i class="ti ti-link"></i></template>
-						<template #label>{{ i18n.ts._serverSettings.inquiryUrl }}</template>
-						<template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template>
-					</MkInput>
-
-					<MkTextarea v-model="preservedUsernames">
+					<MkFolder>
+						<template #icon><i class="ti ti-lock-star"></i></template>
 						<template #label>{{ i18n.ts.preservedUsernames }}</template>
-						<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
-					</MkTextarea>
 
-					<MkTextarea v-model="sensitiveWords">
+						<div class="_gaps">
+							<MkTextarea v-model="preservedUsernames">
+								<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_preservedUsernames">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-message-exclamation"></i></template>
 						<template #label>{{ i18n.ts.sensitiveWords }}</template>
-						<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
-					</MkTextarea>
 
-					<MkTextarea v-model="prohibitedWords">
+						<div class="_gaps">
+							<MkTextarea v-model="sensitiveWords">
+								<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_sensitiveWords">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-message-x"></i></template>
 						<template #label>{{ i18n.ts.prohibitedWords }}</template>
-						<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
-					</MkTextarea>
 
-					<MkTextarea v-model="hiddenTags">
+						<div class="_gaps">
+							<MkTextarea v-model="prohibitedWords">
+								<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_prohibitedWords">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-user-x"></i></template>
+						<template #label>{{ i18n.ts.prohibitedWordsForNameOfUser }}</template>
+
+						<div class="_gaps">
+							<MkTextarea v-model="prohibitedWordsForNameOfUser">
+								<template #caption>{{ i18n.ts.prohibitedWordsForNameOfUserDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_prohibitedWordsForNameOfUser">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-eye-off"></i></template>
 						<template #label>{{ i18n.ts.hiddenTags }}</template>
-						<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
-					</MkTextarea>
+
+						<div class="_gaps">
+							<MkTextarea v-model="hiddenTags">
+								<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_hiddenTags">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-eye-off"></i></template>
+						<template #label>{{ i18n.ts.silencedInstances }}</template>
+
+						<div class="_gaps">
+							<MkTextarea v-model="silencedHosts">
+								<template #caption>{{ i18n.ts.silencedInstancesDescription }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_silencedHosts">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-eye-off"></i></template>
+						<template #label>{{ i18n.ts.mediaSilencedInstances }}</template>
+
+						<div class="_gaps">
+							<MkTextarea v-model="mediaSilencedHosts">
+								<template #caption>{{ i18n.ts.mediaSilencedInstancesDescription }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_mediaSilencedHosts">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
+						<template #icon><i class="ti ti-ban"></i></template>
+						<template #label>{{ i18n.ts.blockedInstances }}</template>
+
+						<div class="_gaps">
+							<MkTextarea v-model="blockedHosts">
+								<template #caption>{{ i18n.ts.blockedInstancesDescription }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_blockedHosts">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
 				</div>
 			</FormSuspense>
 		</MkSpacer>
-		<template #footer>
-			<div :class="$style.footer">
-				<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
-					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
-				</MkSpacer>
-			</div>
-		</template>
 	</MkStickyContainer>
 </div>
 </template>
@@ -83,16 +137,18 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 import FormLink from '@/components/form/link.vue';
+import MkFolder from '@/components/MkFolder.vue';
 
 const enableRegistration = ref<boolean>(false);
 const emailRequiredForSignup = ref<boolean>(false);
 const sensitiveWords = ref<string>('');
 const prohibitedWords = ref<string>('');
+const prohibitedWordsForNameOfUser = ref<string>('');
 const hiddenTags = ref<string>('');
 const preservedUsernames = ref<string>('');
-const tosUrl = ref<string | null>(null);
-const privacyPolicyUrl = ref<string | null>(null);
-const inquiryUrl = ref<string | null>(null);
+const blockedHosts = ref<string>('');
+const silencedHosts = ref<string>('');
+const mediaSilencedHosts = ref<string>('');
 
 async function init() {
 	const meta = await misskeyApi('admin/meta');
@@ -100,24 +156,89 @@ async function init() {
 	emailRequiredForSignup.value = meta.emailRequiredForSignup;
 	sensitiveWords.value = meta.sensitiveWords.join('\n');
 	prohibitedWords.value = meta.prohibitedWords.join('\n');
+	prohibitedWordsForNameOfUser.value = meta.prohibitedWordsForNameOfUser.join('\n');
 	hiddenTags.value = meta.hiddenTags.join('\n');
 	preservedUsernames.value = meta.preservedUsernames.join('\n');
-	tosUrl.value = meta.tosUrl;
-	privacyPolicyUrl.value = meta.privacyPolicyUrl;
-	inquiryUrl.value = meta.inquiryUrl;
+	blockedHosts.value = meta.blockedHosts.join('\n');
+	silencedHosts.value = meta.silencedHosts?.join('\n') ?? '';
+	mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n');
+}
+
+function onChange_enableRegistration(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		disableRegistration: !value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function onChange_emailRequiredForSignup(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		emailRequiredForSignup: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_preservedUsernames() {
+	os.apiWithDialog('admin/update-meta', {
+		preservedUsernames: preservedUsernames.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
 }
 
-function save() {
+function save_sensitiveWords() {
 	os.apiWithDialog('admin/update-meta', {
-		disableRegistration: !enableRegistration.value,
-		emailRequiredForSignup: emailRequiredForSignup.value,
-		tosUrl: tosUrl.value,
-		privacyPolicyUrl: privacyPolicyUrl.value,
-		inquiryUrl: inquiryUrl.value,
 		sensitiveWords: sensitiveWords.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_prohibitedWords() {
+	os.apiWithDialog('admin/update-meta', {
 		prohibitedWords: prohibitedWords.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_prohibitedWordsForNameOfUser() {
+	os.apiWithDialog('admin/update-meta', {
+		prohibitedWordsForNameOfUser: prohibitedWordsForNameOfUser.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_hiddenTags() {
+	os.apiWithDialog('admin/update-meta', {
 		hiddenTags: hiddenTags.value.split('\n'),
-		preservedUsernames: preservedUsernames.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_blockedHosts() {
+	os.apiWithDialog('admin/update-meta', {
+		blockedHosts: blockedHosts.value.split('\n') || [],
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_silencedHosts() {
+	os.apiWithDialog('admin/update-meta', {
+		silencedHosts: silencedHosts.value.split('\n') || [],
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_mediaSilencedHosts() {
+	os.apiWithDialog('admin/update-meta', {
+		mediaSilencedHosts: mediaSilencedHosts.value.split('\n') || [],
 	}).then(() => {
 		fetchInstance(true);
 	});
@@ -130,10 +251,3 @@ definePageMetadata(() => ({
 	icon: 'ti ti-shield',
 }));
 </script>
-
-<style lang="scss" module>
-.footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-}
-</style>
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 64d7f2584572..1e144394fbaf 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</template>
 
 	<div>
-		<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+		<div style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;">
 			<div style="flex: 1;">{{ i18n.ts.moderator }}: <MkA :to="`/admin/user/${log.userId}`" class="_link">@{{ log.user?.username }}</MkA></div>
 			<div style="flex: 1;">{{ i18n.ts.dateAndTime }}: <MkTime :time="log.createdAt" mode="detail"/></div>
 		</div>
@@ -165,6 +165,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
 			</div>
 		</template>
+		<template v-else-if="log.type === 'updateAbuseReportNote'">
+			<div :class="$style.diff">
+				<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
+			</div>
+		</template>
 
 		<details>
 			<summary>raw</summary>
@@ -200,14 +205,14 @@ const props = defineProps<{
 }
 
 .logYellow {
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 }
 
 .logRed {
-	color: var(--error);
+	color: var(--MI_THEME-error);
 }
 
 .logGreen {
-	color: var(--success);
+	color: var(--MI_THEME-success);
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue
index 8590ee1651d5..c9eaf0753129 100644
--- a/packages/frontend/src/pages/admin/modlog.vue
+++ b/packages/frontend/src/pages/admin/modlog.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="900">
 		<div>
-			<div style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+			<div style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;">
 				<MkSelect v-model="type" style="margin: 0; flex: 1;">
 					<template #label>{{ i18n.ts.type }}</template>
 					<option :value="null">{{ i18n.ts.all }}</option>
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</MkInput>
 			</div>
 
-			<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--margin);">
-				<div class="_gaps_s">
-					<XModLog v-for="item in items" :key="item.id" :log="item"/>
-				</div>
+			<MkPagination v-slot="{items}" ref="logs" :pagination="pagination" style="margin-top: var(--MI-margin);">
+				<MkDateSeparatedList v-slot="{ item }" :items="items" :noGap="false" style="--MI-margin: 8px;">
+					<XModLog :key="item.id" :log="item"/>
+				</MkDateSeparatedList>
 			</MkPagination>
 		</div>
 	</MkSpacer>
@@ -39,6 +39,7 @@ import MkInput from '@/components/MkInput.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 
 const logs = shallowRef<InstanceType<typeof MkPagination>>();
 
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index 5fddb715cd5c..d5a664934cf2 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -157,7 +157,7 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue
deleted file mode 100644
index 345cf333b51f..000000000000
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<MkStickyContainer>
-	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
-		<FormSuspense :p="init">
-			<div class="_gaps">
-				<div class="_panel" style="padding: 16px;">
-					<MkSwitch v-model="enableServerMachineStats">
-						<template #label>{{ i18n.ts.enableServerMachineStats }}</template>
-						<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
-					</MkSwitch>
-				</div>
-
-				<div class="_panel" style="padding: 16px;">
-					<MkSwitch v-model="enableIdenticonGeneration">
-						<template #label>{{ i18n.ts.enableIdenticonGeneration }}</template>
-						<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
-					</MkSwitch>
-				</div>
-
-				<div class="_panel" style="padding: 16px;">
-					<MkSwitch v-model="enableChartsForRemoteUser">
-						<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
-						<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
-					</MkSwitch>
-				</div>
-
-				<div class="_panel" style="padding: 16px;">
-					<MkSwitch v-model="enableChartsForFederatedInstances">
-						<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
-						<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
-					</MkSwitch>
-				</div>
-			</div>
-		</FormSuspense>
-	</MkSpacer>
-</MkStickyContainer>
-</template>
-
-<script lang="ts" setup>
-import { ref, computed } from 'vue';
-import XHeader from './_header_.vue';
-import FormSuspense from '@/components/form/suspense.vue';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { fetchInstance } from '@/instance.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-import MkSwitch from '@/components/MkSwitch.vue';
-
-const enableServerMachineStats = ref<boolean>(false);
-const enableIdenticonGeneration = ref<boolean>(false);
-const enableChartsForRemoteUser = ref<boolean>(false);
-const enableChartsForFederatedInstances = ref<boolean>(false);
-
-async function init() {
-	const meta = await misskeyApi('admin/meta');
-	enableServerMachineStats.value = meta.enableServerMachineStats;
-	enableIdenticonGeneration.value = meta.enableIdenticonGeneration;
-	enableChartsForRemoteUser.value = meta.enableChartsForRemoteUser;
-	enableChartsForFederatedInstances.value = meta.enableChartsForFederatedInstances;
-}
-
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		enableServerMachineStats: enableServerMachineStats.value,
-		enableIdenticonGeneration: enableIdenticonGeneration.value,
-		enableChartsForRemoteUser: enableChartsForRemoteUser.value,
-		enableChartsForFederatedInstances: enableChartsForFederatedInstances.value,
-	}).then(() => {
-		fetchInstance(true);
-	});
-}
-
-const headerActions = computed(() => [{
-	asFullButton: true,
-	icon: 'ti ti-check',
-	text: i18n.ts.save,
-	handler: save,
-}]);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
-	title: i18n.ts.other,
-	icon: 'ti ti-adjustments',
-}));
-</script>
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
new file mode 100644
index 000000000000..584cd3e4d954
--- /dev/null
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.stories.impl.ts
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { StoryObj } from '@storybook/vue3';
+import { http, HttpResponse } from 'msw';
+import { action } from '@storybook/addon-actions';
+import { commonHandlers } from '../../../.storybook/mocks.js';
+import overview_ap_requests from './overview.ap-requests.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				overview_ap_requests,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			template: '<overview_ap_requests />',
+		};
+	},
+	parameters: {
+		layout: 'fullscreen',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				http.post('/api/charts/ap-request', async ({ request }) => {
+					action('POST /api/charts/ap-request')(await request.json());
+					return HttpResponse.json({
+						deliverFailed: [0, 0, 0, 2, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 0, 0, 0, 3, 1, 1, 2, 0, 0],
+						deliverSucceeded: [0, 1, 51, 34, 136, 189, 51, 17, 17, 34, 1, 17, 18, 51, 34, 68, 287, 0, 17, 33, 32, 96, 96, 0, 49, 64, 0, 32, 0, 32, 81, 48, 65, 1, 16, 50, 90, 148, 33, 43, 72, 127, 17, 138, 78, 91, 78, 91, 13, 52],
+						inboxReceived: [507, 1173, 1096, 871, 958, 937, 908, 1026, 956, 909, 807, 1002, 832, 995, 1039, 1047, 1109, 930, 711, 835, 764, 679, 835, 958, 634, 654, 691, 895, 811, 676, 1044, 1389, 1318, 863, 887, 952, 1011, 1061, 592, 900, 611, 595, 604, 562, 607, 621, 854, 666, 1197, 644],
+					});
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof overview_ap_requests>;
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index d4c83f21b685..570fcddc07dc 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
+import isChromatic from 'chromatic';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
@@ -41,7 +42,7 @@ const { handler: externalTooltipHandler } = useChartTooltip();
 const { handler: externalTooltipHandler2 } = useChartTooltip();
 
 onMounted(async () => {
-	const now = new Date();
+	const now = isChromatic() ? new Date('2024-08-31T10:00:00Z') : new Date();
 
 	const getDate = (ago: number) => {
 		const y = now.getFullYear();
@@ -51,14 +52,14 @@ onMounted(async () => {
 		return new Date(y, m, d - ago);
 	};
 
-	const format = (arr) => {
+	const format = (arr: number[]) => {
 		return arr.map((v, i) => ({
 			x: getDate(i).getTime(),
 			y: v,
 		}));
 	};
 
-	const formatMinus = (arr) => {
+	const formatMinus = (arr: number[]) => {
 		return arr.map((v, i) => ({
 			x: getDate(i).getTime(),
 			y: -v,
@@ -78,7 +79,6 @@ onMounted(async () => {
 		type: 'line',
 		data: {
 			datasets: [{
-				stack: 'a',
 				parsing: false,
 				label: 'Out: Succ',
 				data: format(raw.deliverSucceeded).slice().reverse(),
@@ -92,7 +92,6 @@ onMounted(async () => {
 				fill: true,
 				clip: 8,
 			}, {
-				stack: 'a',
 				parsing: false,
 				label: 'Out: Fail',
 				data: formatMinus(raw.deliverFailed).slice().reverse(),
@@ -137,7 +136,6 @@ onMounted(async () => {
 					min: getDate(chartLimit).getTime(),
 				},
 				y: {
-					stacked: true,
 					position: 'left',
 					suggestedMax: 10,
 					grid: {
@@ -171,6 +169,9 @@ onMounted(async () => {
 						duration: 0,
 					},
 					external: externalTooltipHandler,
+					callbacks: {
+						label: context => `${context.dataset.label}: ${Math.abs(context.parsed.y)}`,
+					},
 				},
 				gradient,
 			},
@@ -277,7 +278,7 @@ onMounted(async () => {
 				padding: 16px;
 
 				&:first-child {
-					border-bottom: solid 0.5px var(--divider);
+					border-bottom: solid 0.5px var(--MI_THEME-divider);
 				}
 			}
 		}
diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue
index 022b392d2d21..0896859f3cf0 100644
--- a/packages/frontend/src/pages/admin/overview.federation.vue
+++ b/packages/frontend/src/pages/admin/overview.federation.vue
@@ -151,8 +151,8 @@ onMounted(async () => {
 					height: 100%;
 					aspect-ratio: 1;
 					margin-right: 12px;
-					background: var(--accentedBg);
-					color: var(--accent);
+					background: var(--MI_THEME-accentedBg);
+					color: var(--MI_THEME-accent);
 					border-radius: 10px;
 				}
 
diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue
index c7a9f2a7027f..a21ec6c464a5 100644
--- a/packages/frontend/src/pages/admin/overview.pie.vue
+++ b/packages/frontend/src/pages/admin/overview.pie.vue
@@ -41,7 +41,7 @@ onMounted(() => {
 			labels: props.data.map(x => x.name),
 			datasets: [{
 				backgroundColor: props.data.map(x => x.color),
-				borderColor: getComputedStyle(document.documentElement).getPropertyValue('--panel'),
+				borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'),
 				borderWidth: 2,
 				hoverOffset: 0,
 				data: props.data.map(x => x.value),
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index fb190f53258b..de6b25441265 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -119,8 +119,8 @@ onUnmounted(() => {
 			> .chart {
 				min-width: 0;
 				padding: 16px;
-				background: var(--panel);
-				border-radius: var(--radius);
+				background: var(--MI_THEME-panel);
+				border-radius: var(--MI-radius);
 
 				> .title {
 					font-size: 0.85em;
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index 0f4707f08d38..222e9f4673aa 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -114,8 +114,8 @@ onMounted(async () => {
 				height: 100%;
 				aspect-ratio: 1;
 				margin-right: 12px;
-				background: var(--accentedBg);
-				color: var(--accent);
+				background: var(--MI_THEME-accentedBg);
+				color: var(--MI_THEME-accent);
 				border-radius: 10px;
 			}
 
diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue
new file mode 100644
index 000000000000..12338f0bf996
--- /dev/null
+++ b/packages/frontend/src/pages/admin/performance.vue
@@ -0,0 +1,209 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
+		<div class="_gaps">
+			<div class="_panel" style="padding: 16px;">
+				<MkSwitch v-model="enableServerMachineStats" @change="onChange_enableServerMachineStats">
+					<template #label>{{ i18n.ts.enableServerMachineStats }}</template>
+					<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+				</MkSwitch>
+			</div>
+
+			<div class="_panel" style="padding: 16px;">
+				<MkSwitch v-model="enableIdenticonGeneration" @change="onChange_enableIdenticonGeneration">
+					<template #label>{{ i18n.ts.enableIdenticonGeneration }}</template>
+					<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+				</MkSwitch>
+			</div>
+
+			<div class="_panel" style="padding: 16px;">
+				<MkSwitch v-model="enableChartsForRemoteUser" @change="onChange_enableChartsForRemoteUser">
+					<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
+					<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+				</MkSwitch>
+			</div>
+
+			<div class="_panel" style="padding: 16px;">
+				<MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances">
+					<template #label>{{ i18n.ts.enableStatsForFederatedInstances }}</template>
+					<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+				</MkSwitch>
+			</div>
+
+			<div class="_panel" style="padding: 16px;">
+				<MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances">
+					<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
+					<template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template>
+				</MkSwitch>
+			</div>
+
+			<MkFolder :defaultOpen="true">
+				<template #icon><i class="ti ti-bolt"></i></template>
+				<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
+				<template v-if="fttForm.savedState.enableFanoutTimeline" #suffix>Enabled</template>
+				<template v-else #suffix>Disabled</template>
+				<template v-if="fttForm.modified.value" #footer>
+					<MkFormFooter :form="fttForm"/>
+				</template>
+
+				<div class="_gaps">
+					<MkSwitch v-model="fttForm.state.enableFanoutTimeline">
+						<template #label>{{ i18n.ts.enable }}<span v-if="fttForm.modifiedStates.enableFanoutTimeline" class="_modified">{{ i18n.ts.modified }}</span></template>
+						<template #caption>
+							<div>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</div>
+							<div><MkLink target="_blank" url="https://misskey-hub.net/docs/for-admin/features/ftt/">{{ i18n.ts.details }}</MkLink></div>
+						</template>
+					</MkSwitch>
+
+					<template v-if="fttForm.state.enableFanoutTimeline">
+						<MkSwitch v-model="fttForm.state.enableFanoutTimelineDbFallback">
+							<template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}<span v-if="fttForm.modifiedStates.enableFanoutTimelineDbFallback" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
+						</MkSwitch>
+
+						<MkInput v-model="fttForm.state.perLocalUserUserTimelineCacheMax" type="number">
+							<template #label>perLocalUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perLocalUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+						</MkInput>
+
+						<MkInput v-model="fttForm.state.perRemoteUserUserTimelineCacheMax" type="number">
+							<template #label>perRemoteUserUserTimelineCacheMax<span v-if="fttForm.modifiedStates.perRemoteUserUserTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+						</MkInput>
+
+						<MkInput v-model="fttForm.state.perUserHomeTimelineCacheMax" type="number">
+							<template #label>perUserHomeTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserHomeTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+						</MkInput>
+
+						<MkInput v-model="fttForm.state.perUserListTimelineCacheMax" type="number">
+							<template #label>perUserListTimelineCacheMax<span v-if="fttForm.modifiedStates.perUserListTimelineCacheMax" class="_modified">{{ i18n.ts.modified }}</span></template>
+						</MkInput>
+					</template>
+				</div>
+			</MkFolder>
+
+			<MkFolder :defaultOpen="true">
+				<template #icon><i class="ti ti-bolt"></i></template>
+				<template #label>Misskey® Reactions Boost Technology™ (RBT)<span class="_beta">{{ i18n.ts.beta }}</span></template>
+				<template v-if="rbtForm.savedState.enableReactionsBuffering" #suffix>Enabled</template>
+				<template v-else #suffix>Disabled</template>
+				<template v-if="rbtForm.modified.value" #footer>
+					<MkFormFooter :form="rbtForm"/>
+				</template>
+
+				<div class="_gaps_m">
+					<MkSwitch v-model="rbtForm.state.enableReactionsBuffering">
+						<template #label>{{ i18n.ts.enable }}<span v-if="rbtForm.modifiedStates.enableReactionsBuffering" class="_modified">{{ i18n.ts.modified }}</span></template>
+						<template #caption>{{ i18n.ts._serverSettings.reactionsBufferingDescription }}</template>
+					</MkSwitch>
+				</div>
+			</MkFolder>
+		</div>
+	</MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
+import XHeader from './_header_.vue';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { fetchInstance } from '@/instance.js';
+import { i18n } from '@/i18n.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkLink from '@/components/MkLink.vue';
+import { useForm } from '@/scripts/use-form.js';
+import MkFormFooter from '@/components/MkFormFooter.vue';
+
+const meta = await misskeyApi('admin/meta');
+
+const enableServerMachineStats = ref(meta.enableServerMachineStats);
+const enableIdenticonGeneration = ref(meta.enableIdenticonGeneration);
+const enableChartsForRemoteUser = ref(meta.enableChartsForRemoteUser);
+const enableStatsForFederatedInstances = ref(meta.enableStatsForFederatedInstances);
+const enableChartsForFederatedInstances = ref(meta.enableChartsForFederatedInstances);
+
+function onChange_enableServerMachineStats(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		enableServerMachineStats: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function onChange_enableIdenticonGeneration(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		enableIdenticonGeneration: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function onChange_enableChartsForRemoteUser(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		enableChartsForRemoteUser: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function onChange_enableStatsForFederatedInstances(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		enableStatsForFederatedInstances: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function onChange_enableChartsForFederatedInstances(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		enableChartsForFederatedInstances: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+const fttForm = useForm({
+	enableFanoutTimeline: meta.enableFanoutTimeline,
+	enableFanoutTimelineDbFallback: meta.enableFanoutTimelineDbFallback,
+	perLocalUserUserTimelineCacheMax: meta.perLocalUserUserTimelineCacheMax,
+	perRemoteUserUserTimelineCacheMax: meta.perRemoteUserUserTimelineCacheMax,
+	perUserHomeTimelineCacheMax: meta.perUserHomeTimelineCacheMax,
+	perUserListTimelineCacheMax: meta.perUserListTimelineCacheMax,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableFanoutTimeline: state.enableFanoutTimeline,
+		enableFanoutTimelineDbFallback: state.enableFanoutTimelineDbFallback,
+		perLocalUserUserTimelineCacheMax: state.perLocalUserUserTimelineCacheMax,
+		perRemoteUserUserTimelineCacheMax: state.perRemoteUserUserTimelineCacheMax,
+		perUserHomeTimelineCacheMax: state.perUserHomeTimelineCacheMax,
+		perUserListTimelineCacheMax: state.perUserListTimelineCacheMax,
+	});
+	fetchInstance(true);
+});
+
+const rbtForm = useForm({
+	enableReactionsBuffering: meta.enableReactionsBuffering,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableReactionsBuffering: state.enableReactionsBuffering,
+	});
+	fetchInstance(true);
+});
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => []);
+
+definePageMetadata(() => ({
+	title: i18n.ts.other,
+	icon: 'ti ti-adjustments',
+}));
+</script>
diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue
deleted file mode 100644
index 81db9f1da9c2..000000000000
--- a/packages/frontend/src/pages/admin/proxy-account.vue
+++ /dev/null
@@ -1,71 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and misskey-project
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<MkStickyContainer>
-	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
-		<FormSuspense :p="init">
-			<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
-			<MkKeyValue>
-				<template #key>{{ i18n.ts.proxyAccount }}</template>
-				<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
-			</MkKeyValue>
-
-			<MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton>
-		</FormSuspense>
-	</MkSpacer>
-</MkStickyContainer>
-</template>
-
-<script lang="ts" setup>
-import { ref, computed } from 'vue';
-import * as Misskey from 'misskey-js';
-import MkKeyValue from '@/components/MkKeyValue.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkInfo from '@/components/MkInfo.vue';
-import FormSuspense from '@/components/form/suspense.vue';
-import * as os from '@/os.js';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { fetchInstance } from '@/instance.js';
-import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
-
-const proxyAccount = ref<Misskey.entities.UserDetailed | null>(null);
-const proxyAccountId = ref<string | null>(null);
-
-async function init() {
-	const meta = await misskeyApi('admin/meta');
-	proxyAccountId.value = meta.proxyAccountId;
-	if (proxyAccountId.value) {
-		proxyAccount.value = await misskeyApi('users/show', { userId: proxyAccountId.value });
-	}
-}
-
-function chooseProxyAccount() {
-	os.selectUser({ localOnly: true }).then(user => {
-		proxyAccount.value = user;
-		proxyAccountId.value = user.id;
-		save();
-	});
-}
-
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		proxyAccountId: proxyAccountId.value,
-	}).then(() => {
-		fetchInstance(true);
-	});
-}
-
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
-definePageMetadata(() => ({
-	title: i18n.ts.proxyAccount,
-	icon: 'ti ti-ghost',
-}));
-</script>
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 960a263a8628..7c171ba0e1dc 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -135,8 +135,8 @@ onUnmounted(() => {
 .chart {
 	min-width: 0;
 	padding: 16px;
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 }
 
 .chartTitle {
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index 04982eea1fab..17e99e65934a 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -11,8 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
 				<div>{{ relay.inbox }}</div>
 				<div style="margin: 8px 0;">
-					<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--success);"></i>
-					<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--error);"></i>
+					<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--MI_THEME-success);"></i>
+					<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--MI_THEME-error);"></i>
 					<i v-else class="ti ti-clock" :class="$style.icon"></i>
 					<span>{{ i18n.ts._relayStatus[relay.status] }}</span>
 				</div>
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 60f06d50badb..2b4006c3f78c 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -95,7 +95,7 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index b0137abb3f86..ae01432d0c74 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -590,6 +590,106 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</MkRange>
 				</div>
 			</MkFolder>
+
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportAntennas, 'canImportAntennas'])">
+				<template #label>{{ i18n.ts._role._options.canImportAntennas }}</template>
+				<template #suffix>
+					<span v-if="role.policies.canImportAntennas.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.canImportAntennas.value ? i18n.ts.yes : i18n.ts.no }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportAntennas)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.canImportAntennas.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkSwitch v-model="role.policies.canImportAntennas.value" :disabled="role.policies.canImportAntennas.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts.enable }}</template>
+					</MkSwitch>
+					<MkRange v-model="role.policies.canImportAntennas.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
+
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportBlocking, 'canImportBlocking'])">
+				<template #label>{{ i18n.ts._role._options.canImportBlocking }}</template>
+				<template #suffix>
+					<span v-if="role.policies.canImportBlocking.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.canImportBlocking.value ? i18n.ts.yes : i18n.ts.no }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportBlocking)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.canImportBlocking.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkSwitch v-model="role.policies.canImportBlocking.value" :disabled="role.policies.canImportBlocking.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts.enable }}</template>
+					</MkSwitch>
+					<MkRange v-model="role.policies.canImportBlocking.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
+
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportFollowing, 'canImportFollowing'])">
+				<template #label>{{ i18n.ts._role._options.canImportFollowing }}</template>
+				<template #suffix>
+					<span v-if="role.policies.canImportFollowing.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.canImportFollowing.value ? i18n.ts.yes : i18n.ts.no }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportFollowing)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.canImportFollowing.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkSwitch v-model="role.policies.canImportFollowing.value" :disabled="role.policies.canImportFollowing.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts.enable }}</template>
+					</MkSwitch>
+					<MkRange v-model="role.policies.canImportFollowing.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
+
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportMuting, 'canImportMuting'])">
+				<template #label>{{ i18n.ts._role._options.canImportMuting }}</template>
+				<template #suffix>
+					<span v-if="role.policies.canImportMuting.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.canImportMuting.value ? i18n.ts.yes : i18n.ts.no }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportMuting)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.canImportMuting.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkSwitch v-model="role.policies.canImportMuting.value" :disabled="role.policies.canImportMuting.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts.enable }}</template>
+					</MkSwitch>
+					<MkRange v-model="role.policies.canImportMuting.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
+
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportUserLists, 'canImportUserLists'])">
+				<template #label>{{ i18n.ts._role._options.canImportUserLists }}</template>
+				<template #suffix>
+					<span v-if="role.policies.canImportUserLists.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.canImportUserLists.value ? i18n.ts.yes : i18n.ts.no }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canImportUserLists)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.canImportUserLists.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkSwitch v-model="role.policies.canImportUserLists.value" :disabled="role.policies.canImportUserLists.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts.enable }}</template>
+					</MkSwitch>
+					<MkRange v-model="role.policies.canImportUserLists.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
 		</div>
 	</FormSlot>
 </div>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 8b3c906d8a6e..1c237a69b43e 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -184,7 +184,7 @@ definePageMetadata(() => ({
 .userItemSub {
 	padding: 6px 12px;
 	font-size: 85%;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 
 .userItemMainBody {
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 7e29f6e0d8bb..b1cbdad137f5 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -11,6 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div class="_gaps">
 				<MkFolder>
 					<template #label>{{ i18n.ts._role.baseRole }}</template>
+					<template #footer>
+						<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
+					</template>
 					<div class="_gaps_s">
 						<MkInput v-model="baseRoleQ" type="search">
 							<template #prefix><i class="ti ti-search"></i></template>
@@ -214,7 +217,45 @@ SPDX-License-Identifier: AGPL-3.0-only
 							</MkInput>
 						</MkFolder>
 
-						<MkButton primary rounded @click="updateBaseRole">{{ i18n.ts.save }}</MkButton>
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportAntennas, 'canImportAntennas'])">
+							<template #label>{{ i18n.ts._role._options.canImportAntennas }}</template>
+							<template #suffix>{{ policies.canImportAntennas ? i18n.ts.yes : i18n.ts.no }}</template>
+							<MkSwitch v-model="policies.canImportAntennas">
+								<template #label>{{ i18n.ts.enable }}</template>
+							</MkSwitch>
+						</MkFolder>
+
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportBlocking, 'canImportBlocking'])">
+							<template #label>{{ i18n.ts._role._options.canImportBlocking }}</template>
+							<template #suffix>{{ policies.canImportBlocking ? i18n.ts.yes : i18n.ts.no }}</template>
+							<MkSwitch v-model="policies.canImportBlocking">
+								<template #label>{{ i18n.ts.enable }}</template>
+							</MkSwitch>
+						</MkFolder>
+
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportFollowing, 'canImportFollowing'])">
+							<template #label>{{ i18n.ts._role._options.canImportFollowing }}</template>
+							<template #suffix>{{ policies.canImportFollowing ? i18n.ts.yes : i18n.ts.no }}</template>
+							<MkSwitch v-model="policies.canImportFollowing">
+								<template #label>{{ i18n.ts.enable }}</template>
+							</MkSwitch>
+						</MkFolder>
+
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportMuting, 'canImportMuting'])">
+							<template #label>{{ i18n.ts._role._options.canImportMuting }}</template>
+							<template #suffix>{{ policies.canImportMuting ? i18n.ts.yes : i18n.ts.no }}</template>
+							<MkSwitch v-model="policies.canImportMuting">
+								<template #label>{{ i18n.ts.enable }}</template>
+							</MkSwitch>
+						</MkFolder>
+
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.canImportUserLists, 'canImportUserList'])">
+							<template #label>{{ i18n.ts._role._options.canImportUserLists }}</template>
+							<template #suffix>{{ policies.canImportUserLists ? i18n.ts.yes : i18n.ts.no }}</template>
+							<MkSwitch v-model="policies.canImportUserLists">
+								<template #label>{{ i18n.ts.enable }}</template>
+							</MkSwitch>
+						</MkFolder>
 					</div>
 				</MkFolder>
 				<MkButton primary rounded @click="create"><i class="ti ti-plus"></i> {{ i18n.ts._role.new }}</MkButton>
@@ -240,6 +281,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, reactive, ref } from 'vue';
+import { ROLE_POLICIES } from '@@/js/const.js';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -253,7 +295,6 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { instance, fetchInstance } from '@/instance.js';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { ROLE_POLICIES } from '@@/js/const.js';
 import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 9bccee89a50f..975a4a126502 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -7,119 +7,115 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
-		<FormSuspense :p="init">
-			<div class="_gaps_m">
-				<MkFolder>
-					<template #icon><i class="ti ti-shield"></i></template>
-					<template #label>{{ i18n.ts.botProtection }}</template>
-					<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
-					<template v-else-if="enableMcaptcha" #suffix>mCaptcha</template>
-					<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
-					<template v-else-if="enableTurnstile" #suffix>Turnstile</template>
-					<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
-
-					<XBotProtection/>
-				</MkFolder>
-
-				<MkFolder>
-					<template #icon><i class="ti ti-eye-off"></i></template>
-					<template #label>{{ i18n.ts.sensitiveMediaDetection }}</template>
-					<template v-if="sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
-					<template v-else-if="sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template>
-					<template v-else-if="sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template>
-					<template v-else #suffix>{{ i18n.ts.none }}</template>
-
-					<div class="_gaps_m">
-						<span>{{ i18n.ts._sensitiveMediaDetection.description }}</span>
-
-						<MkRadios v-model="sensitiveMediaDetection">
-							<option value="none">{{ i18n.ts.none }}</option>
-							<option value="all">{{ i18n.ts.all }}</option>
-							<option value="local">{{ i18n.ts.localOnly }}</option>
-							<option value="remote">{{ i18n.ts.remoteOnly }}</option>
-						</MkRadios>
-
-						<MkRange v-model="sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
-							<template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template>
-							<template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template>
-						</MkRange>
-
-						<MkSwitch v-model="enableSensitiveMediaDetectionForVideos">
-							<template #label>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
-							<template #caption>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</template>
-						</MkSwitch>
-
-						<MkSwitch v-model="setSensitiveFlagAutomatically">
-							<template #label>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }} ({{ i18n.ts.notRecommended }})</template>
-							<template #caption>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</template>
-						</MkSwitch>
-
-						<!-- 現状 false positive が多すぎて実用に耐えない
-						<MkSwitch v-model="disallowUploadWhenPredictedAsPorn">
-							<template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template>
-						</MkSwitch>
-						-->
-
-						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-					</div>
-				</MkFolder>
-
-				<MkFolder>
-					<template #label>Active Email Validation</template>
-					<template v-if="enableActiveEmailValidation" #suffix>Enabled</template>
-					<template v-else #suffix>Disabled</template>
-
-					<div class="_gaps_m">
-						<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
-						<MkSwitch v-model="enableActiveEmailValidation">
-							<template #label>Enable</template>
-						</MkSwitch>
-						<MkSwitch v-model="enableVerifymailApi">
-							<template #label>Use Verifymail.io API</template>
-						</MkSwitch>
-						<MkInput v-model="verifymailAuthKey">
-							<template #prefix><i class="ti ti-key"></i></template>
-							<template #label>Verifymail.io API Auth Key</template>
-						</MkInput>
-						<MkSwitch v-model="enableTruemailApi">
-							<template #label>Use TrueMail API</template>
-						</MkSwitch>
-						<MkInput v-model="truemailInstance">
-							<template #prefix><i class="ti ti-key"></i></template>
-							<template #label>TrueMail API Instance</template>
-						</MkInput>
-						<MkInput v-model="truemailAuthKey">
-							<template #prefix><i class="ti ti-key"></i></template>
-							<template #label>TrueMail API Auth Key</template>
-						</MkInput>
-						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-					</div>
-				</MkFolder>
-
-				<MkFolder>
-					<template #label>Banned Email Domains</template>
-
-					<div class="_gaps_m">
-						<MkTextarea v-model="bannedEmailDomains">
-							<template #label>Banned Email Domains List</template>
-						</MkTextarea>
-						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-					</div>
-				</MkFolder>
-
-				<MkFolder>
-					<template #label>Log IP address</template>
-					<template v-if="enableIpLogging" #suffix>Enabled</template>
-					<template v-else #suffix>Disabled</template>
-
-					<div class="_gaps_m">
-						<MkSwitch v-model="enableIpLogging" @update:modelValue="save">
-							<template #label>Enable</template>
-						</MkSwitch>
-					</div>
-				</MkFolder>
-			</div>
-		</FormSuspense>
+		<div class="_gaps_m">
+			<XBotProtection/>
+
+			<MkFolder>
+				<template #icon><i class="ti ti-eye-off"></i></template>
+				<template #label>{{ i18n.ts.sensitiveMediaDetection }}</template>
+				<template v-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
+				<template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'local'" #suffix>{{ i18n.ts.localOnly }}</template>
+				<template v-else-if="sensitiveMediaDetectionForm.savedState.sensitiveMediaDetection === 'remote'" #suffix>{{ i18n.ts.remoteOnly }}</template>
+				<template v-else #suffix>{{ i18n.ts.none }}</template>
+				<template v-if="sensitiveMediaDetectionForm.modified.value" #footer>
+					<MkFormFooter :form="sensitiveMediaDetectionForm"/>
+				</template>
+
+				<div class="_gaps_m">
+					<span>{{ i18n.ts._sensitiveMediaDetection.description }}</span>
+
+					<MkRadios v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetection">
+						<option value="none">{{ i18n.ts.none }}</option>
+						<option value="all">{{ i18n.ts.all }}</option>
+						<option value="local">{{ i18n.ts.localOnly }}</option>
+						<option value="remote">{{ i18n.ts.remoteOnly }}</option>
+					</MkRadios>
+
+					<MkRange v-model="sensitiveMediaDetectionForm.state.sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
+						<template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template>
+						<template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template>
+					</MkRange>
+
+					<MkSwitch v-model="sensitiveMediaDetectionForm.state.enableSensitiveMediaDetectionForVideos">
+						<template #label>{{ i18n.ts._sensitiveMediaDetection.analyzeVideos }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
+						<template #caption>{{ i18n.ts._sensitiveMediaDetection.analyzeVideosDescription }}</template>
+					</MkSwitch>
+
+					<MkSwitch v-model="sensitiveMediaDetectionForm.state.setSensitiveFlagAutomatically">
+						<template #label>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomatically }} ({{ i18n.ts.notRecommended }})</template>
+						<template #caption>{{ i18n.ts._sensitiveMediaDetection.setSensitiveFlagAutomaticallyDescription }}</template>
+					</MkSwitch>
+
+					<!-- 現状 false positive が多すぎて実用に耐えない
+					<MkSwitch v-model="disallowUploadWhenPredictedAsPorn">
+						<template #label>{{ i18n.ts._sensitiveMediaDetection.disallowUploadWhenPredictedAsPorn }}</template>
+					</MkSwitch>
+					-->
+				</div>
+			</MkFolder>
+
+			<MkFolder>
+				<template #label>Active Email Validation</template>
+				<template v-if="emailValidationForm.savedState.enableActiveEmailValidation" #suffix>Enabled</template>
+				<template v-else #suffix>Disabled</template>
+				<template v-if="emailValidationForm.modified.value" #footer>
+					<MkFormFooter :form="emailValidationForm"/>
+				</template>
+
+				<div class="_gaps_m">
+					<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
+					<MkSwitch v-model="emailValidationForm.state.enableActiveEmailValidation">
+						<template #label>Enable</template>
+					</MkSwitch>
+					<MkSwitch v-model="emailValidationForm.state.enableVerifymailApi">
+						<template #label>Use Verifymail.io API</template>
+					</MkSwitch>
+					<MkInput v-model="emailValidationForm.state.verifymailAuthKey">
+						<template #prefix><i class="ti ti-key"></i></template>
+						<template #label>Verifymail.io API Auth Key</template>
+					</MkInput>
+					<MkSwitch v-model="emailValidationForm.state.enableTruemailApi">
+						<template #label>Use TrueMail API</template>
+					</MkSwitch>
+					<MkInput v-model="emailValidationForm.state.truemailInstance">
+						<template #prefix><i class="ti ti-key"></i></template>
+						<template #label>TrueMail API Instance</template>
+					</MkInput>
+					<MkInput v-model="emailValidationForm.state.truemailAuthKey">
+						<template #prefix><i class="ti ti-key"></i></template>
+						<template #label>TrueMail API Auth Key</template>
+					</MkInput>
+				</div>
+			</MkFolder>
+
+			<MkFolder>
+				<template #label>Banned Email Domains</template>
+				<template v-if="bannedEmailDomainsForm.modified.value" #footer>
+					<MkFormFooter :form="bannedEmailDomainsForm"/>
+				</template>
+
+				<div class="_gaps_m">
+					<MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
+						<template #label>Banned Email Domains List</template>
+					</MkTextarea>
+				</div>
+			</MkFolder>
+
+			<MkFolder>
+				<template #label>Log IP address</template>
+				<template v-if="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
+				<template v-else #suffix>Disabled</template>
+				<template v-if="ipLoggingForm.modified.value" #footer>
+					<MkFormFooter :form="ipLoggingForm"/>
+				</template>
+
+				<div class="_gaps_m">
+					<MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
+						<template #label>Enable</template>
+					</MkSwitch>
+				</div>
+			</MkFolder>
+		</div>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -131,83 +127,80 @@ import XHeader from './_header_.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
-import FormSuspense from '@/components/form/suspense.vue';
 import MkRange from '@/components/MkRange.vue';
 import MkInput from '@/components/MkInput.vue';
-import MkButton from '@/components/MkButton.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-
-const enableHcaptcha = ref<boolean>(false);
-const enableMcaptcha = ref<boolean>(false);
-const enableRecaptcha = ref<boolean>(false);
-const enableTurnstile = ref<boolean>(false);
-const sensitiveMediaDetection = ref<string>('none');
-const sensitiveMediaDetectionSensitivity = ref<number>(0);
-const setSensitiveFlagAutomatically = ref<boolean>(false);
-const enableSensitiveMediaDetectionForVideos = ref<boolean>(false);
-const enableIpLogging = ref<boolean>(false);
-const enableActiveEmailValidation = ref<boolean>(false);
-const enableVerifymailApi = ref<boolean>(false);
-const verifymailAuthKey = ref<string | null>(null);
-const enableTruemailApi = ref<boolean>(false);
-const truemailInstance = ref<string | null>(null);
-const truemailAuthKey = ref<string | null>(null);
-const bannedEmailDomains = ref<string>('');
-
-async function init() {
-	const meta = await misskeyApi('admin/meta');
-	enableHcaptcha.value = meta.enableHcaptcha;
-	enableMcaptcha.value = meta.enableMcaptcha;
-	enableRecaptcha.value = meta.enableRecaptcha;
-	enableTurnstile.value = meta.enableTurnstile;
-	sensitiveMediaDetection.value = meta.sensitiveMediaDetection;
-	sensitiveMediaDetectionSensitivity.value =
-		meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 :
-		meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 :
-		meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 :
-		meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 :
-		meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0;
-	setSensitiveFlagAutomatically.value = meta.setSensitiveFlagAutomatically;
-	enableSensitiveMediaDetectionForVideos.value = meta.enableSensitiveMediaDetectionForVideos;
-	enableIpLogging.value = meta.enableIpLogging;
-	enableActiveEmailValidation.value = meta.enableActiveEmailValidation;
-	enableVerifymailApi.value = meta.enableVerifymailApi;
-	verifymailAuthKey.value = meta.verifymailAuthKey;
-	enableTruemailApi.value = meta.enableTruemailApi;
-	truemailInstance.value = meta.truemailInstance;
-	truemailAuthKey.value = meta.truemailAuthKey;
-	bannedEmailDomains.value = meta.bannedEmailDomains?.join('\n') || '';
-}
-
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		sensitiveMediaDetection: sensitiveMediaDetection.value,
+import { useForm } from '@/scripts/use-form.js';
+import MkFormFooter from '@/components/MkFormFooter.vue';
+
+const meta = await misskeyApi('admin/meta');
+
+const sensitiveMediaDetectionForm = useForm({
+	sensitiveMediaDetection: meta.sensitiveMediaDetection,
+	sensitiveMediaDetectionSensitivity: meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 :
+	meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 :
+	meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 :
+	meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 :
+	meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0,
+	setSensitiveFlagAutomatically: meta.setSensitiveFlagAutomatically,
+	enableSensitiveMediaDetectionForVideos: meta.enableSensitiveMediaDetectionForVideos,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		sensitiveMediaDetection: state.sensitiveMediaDetection,
 		sensitiveMediaDetectionSensitivity:
-			sensitiveMediaDetectionSensitivity.value === 0 ? 'veryLow' :
-			sensitiveMediaDetectionSensitivity.value === 1 ? 'low' :
-			sensitiveMediaDetectionSensitivity.value === 2 ? 'medium' :
-			sensitiveMediaDetectionSensitivity.value === 3 ? 'high' :
-			sensitiveMediaDetectionSensitivity.value === 4 ? 'veryHigh' :
+			state.sensitiveMediaDetectionSensitivity === 0 ? 'veryLow' :
+			state.sensitiveMediaDetectionSensitivity === 1 ? 'low' :
+			state.sensitiveMediaDetectionSensitivity === 2 ? 'medium' :
+			state.sensitiveMediaDetectionSensitivity === 3 ? 'high' :
+			state.sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' :
 			0,
-		setSensitiveFlagAutomatically: setSensitiveFlagAutomatically.value,
-		enableSensitiveMediaDetectionForVideos: enableSensitiveMediaDetectionForVideos.value,
-		enableIpLogging: enableIpLogging.value,
-		enableActiveEmailValidation: enableActiveEmailValidation.value,
-		enableVerifymailApi: enableVerifymailApi.value,
-		verifymailAuthKey: verifymailAuthKey.value,
-		enableTruemailApi: enableTruemailApi.value,
-		truemailInstance: truemailInstance.value,
-		truemailAuthKey: truemailAuthKey.value,
-		bannedEmailDomains: bannedEmailDomains.value.split('\n'),
-	}).then(() => {
-		fetchInstance(true);
+		setSensitiveFlagAutomatically: state.setSensitiveFlagAutomatically,
+		enableSensitiveMediaDetectionForVideos: state.enableSensitiveMediaDetectionForVideos,
+	});
+	fetchInstance(true);
+});
+
+const ipLoggingForm = useForm({
+	enableIpLogging: meta.enableIpLogging,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableIpLogging: state.enableIpLogging,
+	});
+	fetchInstance(true);
+});
+
+const emailValidationForm = useForm({
+	enableActiveEmailValidation: meta.enableActiveEmailValidation,
+	enableVerifymailApi: meta.enableVerifymailApi,
+	verifymailAuthKey: meta.verifymailAuthKey,
+	enableTruemailApi: meta.enableTruemailApi,
+	truemailInstance: meta.truemailInstance,
+	truemailAuthKey: meta.truemailAuthKey,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableActiveEmailValidation: state.enableActiveEmailValidation,
+		enableVerifymailApi: state.enableVerifymailApi,
+		verifymailAuthKey: state.verifymailAuthKey,
+		enableTruemailApi: state.enableTruemailApi,
+		truemailInstance: state.truemailInstance,
+		truemailAuthKey: state.truemailAuthKey,
+	});
+	fetchInstance(true);
+});
+
+const bannedEmailDomainsForm = useForm({
+	bannedEmailDomains: meta.bannedEmailDomains?.join('\n') || '',
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		bannedEmailDomains: state.bannedEmailDomains.split('\n'),
 	});
-}
+	fetchInstance(true);
+});
 
 const headerActions = computed(() => []);
 
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index ff9b8d62993a..552958af6fdf 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -76,7 +76,7 @@ definePageMetadata(() => ({
 <style lang="scss" module>
 .item {
 	display: block;
-	color: var(--navFg);
+	color: var(--MI_THEME-navFg);
 }
 
 .itemHeader {
@@ -96,8 +96,8 @@ definePageMetadata(() => ({
 
 .itemNumber {
 	display: flex;
-	background-color: var(--accentedBg);
-	color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 	font-size: 14px;
 	font-weight: bold;
 	width: 28px;
@@ -117,12 +117,12 @@ definePageMetadata(() => ({
 .itemRemove {
 	width: 40px;
 	height: 40px;
-	color: var(--error);
+	color: var(--MI_THEME-error);
 	margin-left: auto;
 	border-radius: 6px;
 
 	&:hover {
-		background: var(--X5);
+		background: var(--MI_THEME-X5);
 	}
 }
 
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 6f45c212ece1..ea7603a45adb 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -8,173 +8,191 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkStickyContainer>
 		<template #header><XHeader :tabs="headerTabs"/></template>
 		<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
-			<FormSuspense :p="init">
-				<div class="_gaps_m">
-					<MkInput v-model="name">
-						<template #label>{{ i18n.ts.instanceName }}</template>
-					</MkInput>
-
-					<MkInput v-model="shortName">
-						<template #label>{{ i18n.ts._serverSettings.shortName }} ({{ i18n.ts.optional }})</template>
-						<template #caption>{{ i18n.ts._serverSettings.shortNameDescription }}</template>
-					</MkInput>
-
-					<MkTextarea v-model="description">
-						<template #label>{{ i18n.ts.instanceDescription }}</template>
-					</MkTextarea>
-
-					<FormSplit :minWidth="300">
-						<MkInput v-model="maintainerName">
-							<template #label>{{ i18n.ts.maintainerName }}</template>
+			<div class="_gaps_m">
+				<MkFolder :defaultOpen="true">
+					<template #icon><i class="ti ti-info-circle"></i></template>
+					<template #label>{{ i18n.ts.info }}</template>
+					<template v-if="infoForm.modified.value" #footer>
+						<MkFormFooter :form="infoForm"/>
+					</template>
+
+					<div class="_gaps">
+						<MkInput v-model="infoForm.state.name">
+							<template #label>{{ i18n.ts.instanceName }}<span v-if="infoForm.modifiedStates.name" class="_modified">{{ i18n.ts.modified }}</span></template>
 						</MkInput>
 
-						<MkInput v-model="maintainerEmail" type="email">
-							<template #prefix><i class="ti ti-mail"></i></template>
-							<template #label>{{ i18n.ts.maintainerEmail }}</template>
+						<MkInput v-model="infoForm.state.shortName">
+							<template #label>{{ i18n.ts._serverSettings.shortName }} ({{ i18n.ts.optional }})<span v-if="infoForm.modifiedStates.shortName" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts._serverSettings.shortNameDescription }}</template>
 						</MkInput>
-					</FormSplit>
-
-					<MkInput v-model="repositoryUrl" type="url">
-						<template #label>{{ i18n.ts.repositoryUrl }}</template>
-						<template #prefix><i class="ti ti-link"></i></template>
-						<template #caption>{{ i18n.ts.repositoryUrlDescription }}</template>
-					</MkInput>
-
-					<MkInfo v-if="!instance.providesTarball && !repositoryUrl" warn>
-						{{ i18n.ts.repositoryUrlOrTarballRequired }}
-					</MkInfo>
-
-					<MkInput v-model="impressumUrl" type="url">
-						<template #label>{{ i18n.ts.impressumUrl }}</template>
-						<template #prefix><i class="ti ti-link"></i></template>
-						<template #caption>{{ i18n.ts.impressumDescription }}</template>
-					</MkInput>
-
-					<MkTextarea v-model="pinnedUsers">
-						<template #label>{{ i18n.ts.pinnedUsers }}</template>
-						<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
-					</MkTextarea>
 
-					<FormSection>
-						<template #label>{{ i18n.ts.files }}</template>
+						<MkTextarea v-model="infoForm.state.description">
+							<template #label>{{ i18n.ts.instanceDescription }}<span v-if="infoForm.modifiedStates.description" class="_modified">{{ i18n.ts.modified }}</span></template>
+						</MkTextarea>
 
-						<div class="_gaps_m">
-							<MkSwitch v-model="cacheRemoteFiles">
-								<template #label>{{ i18n.ts.cacheRemoteFiles }}</template>
-								<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
-							</MkSwitch>
-
-							<template v-if="cacheRemoteFiles">
-								<MkSwitch v-model="cacheRemoteSensitiveFiles">
-									<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}</template>
-									<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
-								</MkSwitch>
-							</template>
-						</div>
-					</FormSection>
+						<FormSplit :minWidth="300">
+							<MkInput v-model="infoForm.state.maintainerName">
+								<template #label>{{ i18n.ts.maintainerName }}<span v-if="infoForm.modifiedStates.maintainerName" class="_modified">{{ i18n.ts.modified }}</span></template>
+							</MkInput>
 
-					<FormSection>
-						<template #label>ServiceWorker</template>
+							<MkInput v-model="infoForm.state.maintainerEmail" type="email">
+								<template #label>{{ i18n.ts.maintainerEmail }}<span v-if="infoForm.modifiedStates.maintainerEmail" class="_modified">{{ i18n.ts.modified }}</span></template>
+								<template #prefix><i class="ti ti-mail"></i></template>
+							</MkInput>
+						</FormSplit>
 
-						<div class="_gaps_m">
-							<MkSwitch v-model="enableServiceWorker">
-								<template #label>{{ i18n.ts.enableServiceworker }}</template>
-								<template #caption>{{ i18n.ts.serviceworkerInfo }}</template>
-							</MkSwitch>
+						<MkInput v-model="infoForm.state.tosUrl" type="url">
+							<template #label>{{ i18n.ts.tosUrl }}<span v-if="infoForm.modifiedStates.tosUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #prefix><i class="ti ti-link"></i></template>
+						</MkInput>
 
-							<template v-if="enableServiceWorker">
-								<MkInput v-model="swPublicKey">
-									<template #prefix><i class="ti ti-key"></i></template>
-									<template #label>Public key</template>
-								</MkInput>
+						<MkInput v-model="infoForm.state.privacyPolicyUrl" type="url">
+							<template #label>{{ i18n.ts.privacyPolicyUrl }}<span v-if="infoForm.modifiedStates.privacyPolicyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #prefix><i class="ti ti-link"></i></template>
+						</MkInput>
 
-								<MkInput v-model="swPrivateKey">
-									<template #prefix><i class="ti ti-key"></i></template>
-									<template #label>Private key</template>
-								</MkInput>
-							</template>
-						</div>
-					</FormSection>
+						<MkInput v-model="infoForm.state.inquiryUrl" type="url">
+							<template #label>{{ i18n.ts._serverSettings.inquiryUrl }}<span v-if="infoForm.modifiedStates.inquiryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template>
+							<template #prefix><i class="ti ti-link"></i></template>
+						</MkInput>
 
-					<FormSection>
-						<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
+						<MkInput v-model="infoForm.state.repositoryUrl" type="url">
+							<template #label>{{ i18n.ts.repositoryUrl }}<span v-if="infoForm.modifiedStates.repositoryUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.repositoryUrlDescription }}</template>
+							<template #prefix><i class="ti ti-link"></i></template>
+						</MkInput>
 
-						<div class="_gaps_m">
-							<MkSwitch v-model="enableFanoutTimeline">
-								<template #label>{{ i18n.ts.enable }}</template>
-								<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</template>
-							</MkSwitch>
+						<MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn>
+							{{ i18n.ts.repositoryUrlOrTarballRequired }}
+						</MkInfo>
 
-							<MkSwitch v-model="enableFanoutTimelineDbFallback">
-								<template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</template>
-								<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
+						<MkInput v-model="infoForm.state.impressumUrl" type="url">
+							<template #label>{{ i18n.ts.impressumUrl }}<span v-if="infoForm.modifiedStates.impressumUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.impressumDescription }}</template>
+							<template #prefix><i class="ti ti-link"></i></template>
+						</MkInput>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-user-star"></i></template>
+					<template #label>{{ i18n.ts.pinnedUsers }}</template>
+					<template v-if="pinnedUsersForm.modified.value" #footer>
+						<MkFormFooter :form="pinnedUsersForm"/>
+					</template>
+
+					<MkTextarea v-model="pinnedUsersForm.state.pinnedUsers">
+						<template #label>{{ i18n.ts.pinnedUsers }}<span v-if="pinnedUsersForm.modifiedStates.pinnedUsers" class="_modified">{{ i18n.ts.modified }}</span></template>
+						<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
+					</MkTextarea>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-cloud"></i></template>
+					<template #label>{{ i18n.ts.files }}</template>
+					<template v-if="filesForm.modified.value" #footer>
+						<MkFormFooter :form="filesForm"/>
+					</template>
+
+					<div class="_gaps">
+						<MkSwitch v-model="filesForm.state.cacheRemoteFiles">
+							<template #label>{{ i18n.ts.cacheRemoteFiles }}<span v-if="filesForm.modifiedStates.cacheRemoteFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.cacheRemoteFilesDescription }}{{ i18n.ts.youCanCleanRemoteFilesCache }}</template>
+						</MkSwitch>
+
+						<template v-if="filesForm.state.cacheRemoteFiles">
+							<MkSwitch v-model="filesForm.state.cacheRemoteSensitiveFiles">
+								<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}<span v-if="filesForm.modifiedStates.cacheRemoteSensitiveFiles" class="_modified">{{ i18n.ts.modified }}</span></template>
+								<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
 							</MkSwitch>
-
-							<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
-								<template #label>perLocalUserUserTimelineCacheMax</template>
+						</template>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-world-cog"></i></template>
+					<template #label>ServiceWorker</template>
+					<template v-if="serviceWorkerForm.modified.value" #footer>
+						<MkFormFooter :form="serviceWorkerForm"/>
+					</template>
+
+					<div class="_gaps">
+						<MkSwitch v-model="serviceWorkerForm.state.enableServiceWorker">
+							<template #label>{{ i18n.ts.enableServiceworker }}<span v-if="serviceWorkerForm.modifiedStates.enableServiceWorker" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.serviceworkerInfo }}</template>
+						</MkSwitch>
+
+						<template v-if="serviceWorkerForm.state.enableServiceWorker">
+							<MkInput v-model="serviceWorkerForm.state.swPublicKey">
+								<template #label>Public key<span v-if="serviceWorkerForm.modifiedStates.swPublicKey" class="_modified">{{ i18n.ts.modified }}</span></template>
+								<template #prefix><i class="ti ti-key"></i></template>
 							</MkInput>
 
-							<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
-								<template #label>perRemoteUserUserTimelineCacheMax</template>
+							<MkInput v-model="serviceWorkerForm.state.swPrivateKey">
+								<template #label>Private key<span v-if="serviceWorkerForm.modifiedStates.swPrivateKey" class="_modified">{{ i18n.ts.modified }}</span></template>
+								<template #prefix><i class="ti ti-key"></i></template>
 							</MkInput>
-
-							<MkInput v-model="perUserHomeTimelineCacheMax" type="number">
-								<template #label>perUserHomeTimelineCacheMax</template>
-							</MkInput>
-
-							<MkInput v-model="perUserListTimelineCacheMax" type="number">
-								<template #label>perUserListTimelineCacheMax</template>
+						</template>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-ad"></i></template>
+					<template #label>{{ i18n.ts._ad.adsSettings }}</template>
+					<template v-if="adForm.modified.value" #footer>
+						<MkFormFooter :form="adForm"/>
+					</template>
+
+					<div class="_gaps">
+						<div class="_gaps_s">
+							<MkInput v-model="adForm.state.notesPerOneAd" :min="0" type="number">
+								<template #label>{{ i18n.ts._ad.notesPerOneAd }}<span v-if="adForm.modifiedStates.notesPerOneAd" class="_modified">{{ i18n.ts.modified }}</span></template>
+								<template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
 							</MkInput>
+							<MkInfo v-if="adForm.state.notesPerOneAd > 0 && adForm.state.notesPerOneAd < 20" :warn="true">
+								{{ i18n.ts._ad.adsTooClose }}
+							</MkInfo>
 						</div>
-					</FormSection>
-
-					<FormSection>
-						<template #label>{{ i18n.ts._ad.adsSettings }}</template>
-
-						<div class="_gaps_m">
-							<div class="_gaps_s">
-								<MkInput v-model="notesPerOneAd" :min="0" type="number">
-									<template #label>{{ i18n.ts._ad.notesPerOneAd }}</template>
-									<template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
-								</MkInput>
-								<MkInfo v-if="notesPerOneAd > 0 && notesPerOneAd < 20" :warn="true">
-									{{ i18n.ts._ad.adsTooClose }}
-								</MkInfo>
-							</div>
-						</div>
-					</FormSection>
-
-					<FormSection>
-						<template #label>{{ i18n.ts._urlPreviewSetting.title }}</template>
-
-						<div class="_gaps_m">
-							<MkSwitch v-model="urlPreviewEnabled">
-								<template #label>{{ i18n.ts._urlPreviewSetting.enable }}</template>
-							</MkSwitch>
-
-							<MkSwitch v-model="urlPreviewRequireContentLength">
-								<template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}</template>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-world-search"></i></template>
+					<template #label>{{ i18n.ts._urlPreviewSetting.title }}</template>
+					<template v-if="urlPreviewForm.modified.value" #footer>
+						<MkFormFooter :form="urlPreviewForm"/>
+					</template>
+
+					<div class="_gaps">
+						<MkSwitch v-model="urlPreviewForm.state.urlPreviewEnabled">
+							<template #label>{{ i18n.ts._urlPreviewSetting.enable }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewEnabled" class="_modified">{{ i18n.ts.modified }}</span></template>
+						</MkSwitch>
+
+						<template v-if="urlPreviewForm.state.urlPreviewEnabled">
+							<MkSwitch v-model="urlPreviewForm.state.urlPreviewRequireContentLength">
+								<template #label>{{ i18n.ts._urlPreviewSetting.requireContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewRequireContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
 								<template #caption>{{ i18n.ts._urlPreviewSetting.requireContentLengthDescription }}</template>
 							</MkSwitch>
 
-							<MkInput v-model="urlPreviewMaximumContentLength" type="number">
-								<template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}</template>
+							<MkInput v-model="urlPreviewForm.state.urlPreviewMaximumContentLength" type="number">
+								<template #label>{{ i18n.ts._urlPreviewSetting.maximumContentLength }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewMaximumContentLength" class="_modified">{{ i18n.ts.modified }}</span></template>
 								<template #caption>{{ i18n.ts._urlPreviewSetting.maximumContentLengthDescription }}</template>
 							</MkInput>
 
-							<MkInput v-model="urlPreviewTimeout" type="number">
-								<template #label>{{ i18n.ts._urlPreviewSetting.timeout }}</template>
+							<MkInput v-model="urlPreviewForm.state.urlPreviewTimeout" type="number">
+								<template #label>{{ i18n.ts._urlPreviewSetting.timeout }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewTimeout" class="_modified">{{ i18n.ts.modified }}</span></template>
 								<template #caption>{{ i18n.ts._urlPreviewSetting.timeoutDescription }}</template>
 							</MkInput>
 
-							<MkInput v-model="urlPreviewUserAgent" type="text">
-								<template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}</template>
+							<MkInput v-model="urlPreviewForm.state.urlPreviewUserAgent" type="text">
+								<template #label>{{ i18n.ts._urlPreviewSetting.userAgent }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewUserAgent" class="_modified">{{ i18n.ts.modified }}</span></template>
 								<template #caption>{{ i18n.ts._urlPreviewSetting.userAgentDescription }}</template>
 							</MkInput>
 
 							<div>
-								<MkInput v-model="urlPreviewSummaryProxyUrl" type="text">
-									<template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}</template>
+								<MkInput v-model="urlPreviewForm.state.urlPreviewSummaryProxyUrl" type="text">
+									<template #label>{{ i18n.ts._urlPreviewSetting.summaryProxy }}<span v-if="urlPreviewForm.modifiedStates.urlPreviewSummaryProxyUrl" class="_modified">{{ i18n.ts.modified }}</span></template>
 									<template #caption>[{{ i18n.ts.notUsePleaseLeaveBlank }}] {{ i18n.ts._urlPreviewSetting.summaryProxyDescription }}</template>
 								</MkInput>
 
@@ -188,18 +206,51 @@ SPDX-License-Identifier: AGPL-3.0-only
 									</ul>
 								</div>
 							</div>
-						</div>
-					</FormSection>
-				</div>
-			</FormSuspense>
-		</MkSpacer>
-		<template #footer>
-			<div :class="$style.footer">
-				<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
-					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
-				</MkSpacer>
+						</template>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-planet"></i></template>
+					<template #label>{{ i18n.ts.federation }}</template>
+					<template v-if="federationForm.savedState.federation === 'all'" #suffix>{{ i18n.ts.all }}</template>
+					<template v-else-if="federationForm.savedState.federation === 'specified'" #suffix>{{ i18n.ts.specifyHost }}</template>
+					<template v-else-if="federationForm.savedState.federation === 'none'" #suffix>{{ i18n.ts.none }}</template>
+					<template v-if="federationForm.modified.value" #footer>
+						<MkFormFooter :form="federationForm"/>
+					</template>
+
+					<div class="_gaps">
+						<MkRadios v-model="federationForm.state.federation">
+							<template #label>{{ i18n.ts.behavior }}<span v-if="federationForm.modifiedStates.federation" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<option value="all">{{ i18n.ts.all }}</option>
+							<option value="specified">{{ i18n.ts.specifyHost }}</option>
+							<option value="none">{{ i18n.ts.none }}</option>
+						</MkRadios>
+
+						<MkTextarea v-if="federationForm.state.federation === 'specified'" v-model="federationForm.state.federationHosts">
+							<template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
+						</MkTextarea>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ti ti-ghost"></i></template>
+					<template #label>{{ i18n.ts.proxyAccount }}</template>
+
+					<div class="_gaps">
+						<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
+						<MkKeyValue>
+							<template #key>{{ i18n.ts.proxyAccount }}</template>
+							<template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
+						</MkKeyValue>
+
+						<MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton>
+					</div>
+				</MkFolder>
 			</div>
-		</template>
+		</MkSpacer>
 	</MkStickyContainer>
 </div>
 </template>
@@ -211,9 +262,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import FormSection from '@/components/form/section.vue';
 import FormSplit from '@/components/form/split.vue';
-import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance, instance } from '@/instance.js';
@@ -221,96 +270,123 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 import MkFolder from '@/components/MkFolder.vue';
-import MkSelect from '@/components/MkSelect.vue';
-
-const name = ref<string | null>(null);
-const shortName = ref<string | null>(null);
-const description = ref<string | null>(null);
-const maintainerName = ref<string | null>(null);
-const maintainerEmail = ref<string | null>(null);
-const repositoryUrl = ref<string | null>(null);
-const impressumUrl = ref<string | null>(null);
-const pinnedUsers = ref<string>('');
-const cacheRemoteFiles = ref<boolean>(false);
-const cacheRemoteSensitiveFiles = ref<boolean>(false);
-const enableServiceWorker = ref<boolean>(false);
-const swPublicKey = ref<string | null>(null);
-const swPrivateKey = ref<string | null>(null);
-const enableFanoutTimeline = ref<boolean>(false);
-const enableFanoutTimelineDbFallback = ref<boolean>(false);
-const perLocalUserUserTimelineCacheMax = ref<number>(0);
-const perRemoteUserUserTimelineCacheMax = ref<number>(0);
-const perUserHomeTimelineCacheMax = ref<number>(0);
-const perUserListTimelineCacheMax = ref<number>(0);
-const notesPerOneAd = ref<number>(0);
-const urlPreviewEnabled = ref<boolean>(true);
-const urlPreviewTimeout = ref<number>(10000);
-const urlPreviewMaximumContentLength = ref<number>(1024 * 1024 * 10);
-const urlPreviewRequireContentLength = ref<boolean>(true);
-const urlPreviewUserAgent = ref<string | null>(null);
-const urlPreviewSummaryProxyUrl = ref<string | null>(null);
-
-async function init(): Promise<void> {
-	const meta = await misskeyApi('admin/meta');
-	name.value = meta.name;
-	shortName.value = meta.shortName;
-	description.value = meta.description;
-	maintainerName.value = meta.maintainerName;
-	maintainerEmail.value = meta.maintainerEmail;
-	repositoryUrl.value = meta.repositoryUrl;
-	impressumUrl.value = meta.impressumUrl;
-	pinnedUsers.value = meta.pinnedUsers.join('\n');
-	cacheRemoteFiles.value = meta.cacheRemoteFiles;
-	cacheRemoteSensitiveFiles.value = meta.cacheRemoteSensitiveFiles;
-	enableServiceWorker.value = meta.enableServiceWorker;
-	swPublicKey.value = meta.swPublickey;
-	swPrivateKey.value = meta.swPrivateKey;
-	enableFanoutTimeline.value = meta.enableFanoutTimeline;
-	enableFanoutTimelineDbFallback.value = meta.enableFanoutTimelineDbFallback;
-	perLocalUserUserTimelineCacheMax.value = meta.perLocalUserUserTimelineCacheMax;
-	perRemoteUserUserTimelineCacheMax.value = meta.perRemoteUserUserTimelineCacheMax;
-	perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax;
-	perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax;
-	notesPerOneAd.value = meta.notesPerOneAd;
-	urlPreviewEnabled.value = meta.urlPreviewEnabled;
-	urlPreviewTimeout.value = meta.urlPreviewTimeout;
-	urlPreviewMaximumContentLength.value = meta.urlPreviewMaximumContentLength;
-	urlPreviewRequireContentLength.value = meta.urlPreviewRequireContentLength;
-	urlPreviewUserAgent.value = meta.urlPreviewUserAgent;
-	urlPreviewSummaryProxyUrl.value = meta.urlPreviewSummaryProxyUrl;
-}
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import { useForm } from '@/scripts/use-form.js';
+import MkFormFooter from '@/components/MkFormFooter.vue';
+import MkRadios from '@/components/MkRadios.vue';
+
+const meta = await misskeyApi('admin/meta');
+
+const proxyAccount = ref(meta.proxyAccountId ? await misskeyApi('users/show', { userId: meta.proxyAccountId }) : null);
+
+const infoForm = useForm({
+	name: meta.name ?? '',
+	shortName: meta.shortName ?? '',
+	description: meta.description ?? '',
+	maintainerName: meta.maintainerName ?? '',
+	maintainerEmail: meta.maintainerEmail ?? '',
+	tosUrl: meta.tosUrl ?? '',
+	privacyPolicyUrl: meta.privacyPolicyUrl ?? '',
+	inquiryUrl: meta.inquiryUrl ?? '',
+	repositoryUrl: meta.repositoryUrl ?? '',
+	impressumUrl: meta.impressumUrl ?? '',
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		name: state.name,
+		shortName: state.shortName === '' ? null : state.shortName,
+		description: state.description,
+		maintainerName: state.maintainerName,
+		maintainerEmail: state.maintainerEmail,
+		tosUrl: state.tosUrl,
+		privacyPolicyUrl: state.privacyPolicyUrl,
+		inquiryUrl: state.inquiryUrl,
+		repositoryUrl: state.repositoryUrl,
+		impressumUrl: state.impressumUrl,
+	});
+	fetchInstance(true);
+});
 
-async function save() {
+const pinnedUsersForm = useForm({
+	pinnedUsers: meta.pinnedUsers.join('\n'),
+}, async (state) => {
 	await os.apiWithDialog('admin/update-meta', {
-		name: name.value,
-		shortName: shortName.value === '' ? null : shortName.value,
-		description: description.value,
-		maintainerName: maintainerName.value,
-		maintainerEmail: maintainerEmail.value,
-		repositoryUrl: repositoryUrl.value,
-		impressumUrl: impressumUrl.value,
-		pinnedUsers: pinnedUsers.value.split('\n'),
-		cacheRemoteFiles: cacheRemoteFiles.value,
-		cacheRemoteSensitiveFiles: cacheRemoteSensitiveFiles.value,
-		enableServiceWorker: enableServiceWorker.value,
-		swPublicKey: swPublicKey.value,
-		swPrivateKey: swPrivateKey.value,
-		enableFanoutTimeline: enableFanoutTimeline.value,
-		enableFanoutTimelineDbFallback: enableFanoutTimelineDbFallback.value,
-		perLocalUserUserTimelineCacheMax: perLocalUserUserTimelineCacheMax.value,
-		perRemoteUserUserTimelineCacheMax: perRemoteUserUserTimelineCacheMax.value,
-		perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value,
-		perUserListTimelineCacheMax: perUserListTimelineCacheMax.value,
-		notesPerOneAd: notesPerOneAd.value,
-		urlPreviewEnabled: urlPreviewEnabled.value,
-		urlPreviewTimeout: urlPreviewTimeout.value,
-		urlPreviewMaximumContentLength: urlPreviewMaximumContentLength.value,
-		urlPreviewRequireContentLength: urlPreviewRequireContentLength.value,
-		urlPreviewUserAgent: urlPreviewUserAgent.value,
-		urlPreviewSummaryProxyUrl: urlPreviewSummaryProxyUrl.value,
+		pinnedUsers: state.pinnedUsers.split('\n'),
 	});
+	fetchInstance(true);
+});
 
+const filesForm = useForm({
+	cacheRemoteFiles: meta.cacheRemoteFiles,
+	cacheRemoteSensitiveFiles: meta.cacheRemoteSensitiveFiles,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		cacheRemoteFiles: state.cacheRemoteFiles,
+		cacheRemoteSensitiveFiles: state.cacheRemoteSensitiveFiles,
+	});
 	fetchInstance(true);
+});
+
+const serviceWorkerForm = useForm({
+	enableServiceWorker: meta.enableServiceWorker,
+	swPublicKey: meta.swPublickey ?? '',
+	swPrivateKey: meta.swPrivateKey ?? '',
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableServiceWorker: state.enableServiceWorker,
+		swPublicKey: state.swPublicKey,
+		swPrivateKey: state.swPrivateKey,
+	});
+	fetchInstance(true);
+});
+
+const adForm = useForm({
+	notesPerOneAd: meta.notesPerOneAd,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		notesPerOneAd: state.notesPerOneAd,
+	});
+	fetchInstance(true);
+});
+
+const urlPreviewForm = useForm({
+	urlPreviewEnabled: meta.urlPreviewEnabled,
+	urlPreviewTimeout: meta.urlPreviewTimeout,
+	urlPreviewMaximumContentLength: meta.urlPreviewMaximumContentLength,
+	urlPreviewRequireContentLength: meta.urlPreviewRequireContentLength,
+	urlPreviewUserAgent: meta.urlPreviewUserAgent ?? '',
+	urlPreviewSummaryProxyUrl: meta.urlPreviewSummaryProxyUrl ?? '',
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		urlPreviewEnabled: state.urlPreviewEnabled,
+		urlPreviewTimeout: state.urlPreviewTimeout,
+		urlPreviewMaximumContentLength: state.urlPreviewMaximumContentLength,
+		urlPreviewRequireContentLength: state.urlPreviewRequireContentLength,
+		urlPreviewUserAgent: state.urlPreviewUserAgent,
+		urlPreviewSummaryProxyUrl: state.urlPreviewSummaryProxyUrl,
+	});
+	fetchInstance(true);
+});
+
+const federationForm = useForm({
+	federation: meta.federation,
+	federationHosts: meta.federationHosts.join('\n'),
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		federation: state.federation,
+		federationHosts: state.federationHosts.split('\n'),
+	});
+	fetchInstance(true);
+});
+
+function chooseProxyAccount() {
+	os.selectUser({ localOnly: true }).then(user => {
+		proxyAccount.value = user;
+		os.apiWithDialog('admin/update-meta', {
+			proxyAccountId: user.id,
+		}).then(() => {
+			fetchInstance(true);
+		});
+	});
 }
 
 const headerTabs = computed(() => []);
@@ -322,13 +398,8 @@ definePageMetadata(() => ({
 </script>
 
 <style lang="scss" module>
-.footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-}
-
 .subCaption {
 	font-size: 0.85em;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue
index 0c07122af33a..45f0fff10713 100644
--- a/packages/frontend/src/pages/admin/system-webhook.item.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.item.vue
@@ -4,33 +4,50 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="$style.main">
-	<span :class="$style.icon">
+<MkFolder>
+	<template #label>{{ entity.name || entity.url }}</template>
+	<template v-if="entity.name != null && entity.name != ''" #caption>{{ entity.url }}</template>
+	<template #icon>
 		<i v-if="!entity.isActive" class="ti ti-player-pause"/>
 		<i v-else-if="entity.latestStatus === null" class="ti ti-circle"/>
 		<i
 			v-else-if="[200, 201, 204].includes(entity.latestStatus)"
 			class="ti ti-check"
-			:style="{ color: 'var(--success)' }"
+			:style="{ color: 'var(--MI_THEME-success)' }"
 		/>
-		<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"/>
-	</span>
-	<span :class="$style.text">{{ entity.name || entity.url }}</span>
-	<span :class="$style.suffix">
+		<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"/>
+	</template>
+	<template #suffix>
 		<MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/>
-		<button :class="$style.suffixButton" @click="onEditClick">
-			<i class="ti ti-settings"></i>
-		</button>
-		<button :class="$style.suffixButton" @click="onDeleteClick">
-			<i class="ti ti-trash"></i>
-		</button>
-	</span>
-</div>
+		<span v-else>-</span>
+	</template>
+	<template #footer>
+		<div class="_buttons">
+			<MkButton @click="onEditClick">
+				<i class="ti ti-settings"></i> {{ i18n.ts.edit }}
+			</MkButton>
+			<MkButton danger @click="onDeleteClick">
+				<i class="ti ti-trash"></i> {{ i18n.ts.delete }}
+			</MkButton>
+		</div>
+	</template>
+
+	<div class="_gaps">
+		<MkKeyValue>
+			<template #key>latestStatus</template>
+			<template #value>{{ entity.latestStatus ?? '-' }}</template>
+		</MkKeyValue>
+	</div>
+</MkFolder>
 </template>
 
 <script lang="ts" setup>
 import { entities } from 'misskey-js';
 import { toRefs } from 'vue';
+import MkFolder from '@/components/MkFolder.vue';
+import { i18n } from '@/i18n.js';
+import MkButton from '@/components/MkButton.vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
 
 const emit = defineEmits<{
 	(ev: 'edit', value: entities.SystemWebhook): void;
@@ -54,64 +71,10 @@ function onDeleteClick() {
 </script>
 
 <style module lang="scss">
-.main {
-	display: flex;
-	align-items: center;
-	width: 100%;
-	box-sizing: border-box;
-	padding: 10px 14px;
-	background: var(--buttonBg);
-	border: none;
-	border-radius: 6px;
-	font-size: 0.9em;
-
-	&:hover {
-		text-decoration: none;
-		background: var(--buttonHoverBg);
-	}
-
-	&.active {
-		color: var(--accent);
-		background: var(--buttonHoverBg);
-	}
-}
-
 .icon {
 	margin-right: 0.75em;
 	flex-shrink: 0;
 	text-align: center;
-	color: var(--fgTransparentWeak);
-}
-
-.text {
-	flex-shrink: 1;
-	white-space: normal;
-	padding-right: 12px;
-	text-align: center;
-}
-
-.suffix {
-	display: flex;
-	flex-direction: row;
-	align-items: center;
-	justify-content: center;
-	gaps: 4px;
-	margin-left: auto;
-	margin-right: -8px;
-	opacity: 0.7;
-	white-space: nowrap;
-}
-
-.suffixButton {
-	background: transparent;
-	border: none;
-	border-radius: 9999px;
-	margin-top: -8px;
-	margin-bottom: -8px;
-	padding: 8px;
-
-	&:hover {
-		background: var(--buttonBg);
-	}
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/system-webhook.vue b/packages/frontend/src/pages/admin/system-webhook.vue
index 7a40eec94494..c59abda24a21 100644
--- a/packages/frontend/src/pages/admin/system-webhook.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.vue
@@ -11,8 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 	<MkSpacer :contentMax="900">
 		<div class="_gaps_m">
-			<MkButton :class="$style.linkButton" full @click="onCreateWebhookClicked">
-				{{ i18n.ts._webhookSettings.createWebhook }}
+			<MkButton primary @click="onCreateWebhookClicked">
+				<i class="ti ti-plus"></i> {{ i18n.ts._webhookSettings.createWebhook }}
 			</MkButton>
 
 			<FormSection>
@@ -89,8 +89,5 @@ definePageMetadata(() => ({
 </script>
 
 <style module lang="scss">
-.linkButton {
-	text-align: left;
-	padding: 10px 18px;
-}
+
 </style>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index d1bbb5b73418..870c3ce88b22 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -99,19 +99,19 @@ async function addUser() {
 	const { canceled: canceled1, result: username } = await os.inputText({
 		title: i18n.ts.username,
 	});
-	if (canceled1) return;
+	if (canceled1 || username == null) return;
 
 	const { canceled: canceled2, result: password } = await os.inputText({
 		title: i18n.ts.password,
 		type: 'password',
 	});
-	if (canceled2) return;
+	if (canceled2 || password == null) return;
 
 	os.apiWithDialog('admin/accounts/create', {
 		username: username,
 		password: password,
 	}).then(res => {
-		paginationComponent.value.reload();
+		paginationComponent.value?.reload();
 	});
 }
 
diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue
index 802a6bf399cd..56c10fb2928c 100644
--- a/packages/frontend/src/pages/announcement.vue
+++ b/packages/frontend/src/pages/announcement.vue
@@ -20,9 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
 					<span style="margin-right: 0.5em;">
 						<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
-						<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
-						<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
-						<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
+						<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
+						<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
+						<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
 					</span>
 					<Mfm :text="announcement.title"/>
 				</div>
@@ -55,7 +55,7 @@ import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i, updateAccount } from '@/account.js';
+import { $i, updateAccountPartial } from '@/account.js';
 import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
@@ -90,7 +90,7 @@ async function read(target: Misskey.entities.Announcement): Promise<void> {
 	target.isRead = true;
 	await misskeyApi('i/read-announcement', { announcementId: target.id });
 	if ($i) {
-		updateAccount({
+		updateAccountPartial({
 			unreadAnnouncements: $i.unreadAnnouncements.filter((a: { id: string; }) => a.id !== target.id),
 		});
 	}
@@ -103,7 +103,7 @@ const headerActions = computed(() => []);
 const headerTabs = computed(() => []);
 
 definePageMetadata(() => ({
-	title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements,
+	title: announcement.value ? announcement.value.title : i18n.ts.announcements,
 	icon: 'ti ti-speakerphone',
 }));
 </script>
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index e50b208775c7..75c0fd98dcb3 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -17,9 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
 							<span style="margin-right: 0.5em;">
 								<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
-								<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
-								<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
-								<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
+								<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
+								<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
+								<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
 							</span>
 							<MkA :to="`/announcements/${announcement.id}`"><span>{{ announcement.title }}</span></MkA>
 						</div>
@@ -56,7 +56,7 @@ import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i, updateAccount } from '@/account.js';
+import { $i, updateAccountPartial } from '@/account.js';
 
 const paginationCurrent = {
 	endpoint: 'announcements' as const,
@@ -94,7 +94,7 @@ async function read(target) {
 		return a;
 	});
 	misskeyApi('i/read-announcement', { announcementId: target.id });
-	updateAccount({
+	updateAccountPartial({
 		unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id),
 	});
 }
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 22c5231dd94f..a01bafd996c6 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -97,26 +97,26 @@ definePageMetadata(() => ({
 <style lang="scss" module>
 .new {
 	position: sticky;
-	top: calc(var(--stickyTop, 0px) + 16px);
+	top: calc(var(--MI-stickyTop, 0px) + 16px);
 	z-index: 1000;
 	width: 100%;
 	margin: calc(-0.675em - 8px) 0;
 
 	&:first-child {
-		margin-top: calc(-0.675em - 8px - var(--margin));
+		margin-top: calc(-0.675em - 8px - var(--MI-margin));
 	}
 }
 
 .newButton {
 	display: block;
-	margin: var(--margin) auto 0 auto;
+	margin: var(--MI-margin) auto 0 auto;
 	padding: 8px 16px;
 	border-radius: 32px;
 }
 
 .tl {
-	background: var(--bg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-bg);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index d8f8d0b42858..4170b4f73e6e 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -62,7 +62,7 @@ function accepted() {
 	state.value = 'accepted';
 	if (session.value && session.value.app.callbackUrl) {
 		const url = new URL(session.value.app.callbackUrl);
-		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
+		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(url.protocol)) throw new Error('invalid url');
 		location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
 	}
 }
diff --git a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
new file mode 100644
index 000000000000..a834f1c5fd4f
--- /dev/null
+++ b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue
@@ -0,0 +1,220 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkWindow
+	ref="windowEl"
+	:initialWidth="400"
+	:initialHeight="500"
+	:canResize="true"
+	@close="windowEl?.close()"
+	@closed="emit('closed')"
+>
+	<template v-if="avatarDecoration" #header>{{ avatarDecoration.name }}</template>
+	<template v-else #header>New decoration</template>
+
+	<div style="display: flex; flex-direction: column; min-height: 100%;">
+		<MkSpacer :marginMin="20" :marginMax="28" style="flex-grow: 1;">
+			<div class="_gaps_m">
+				<div :class="$style.preview">
+					<div :class="[$style.previewItem, $style.light]">
+						<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="url != '' ? [{ url }] : []" forceShowDecoration/>
+					</div>
+					<div :class="[$style.previewItem, $style.dark]">
+						<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="url != '' ? [{ url }] : []" forceShowDecoration/>
+					</div>
+				</div>
+				<MkInput v-model="name">
+					<template #label>{{ i18n.ts.name }}</template>
+				</MkInput>
+				<MkInput v-model="url">
+					<template #label>{{ i18n.ts.imageUrl }}</template>
+				</MkInput>
+				<MkTextarea v-model="description">
+					<template #label>{{ i18n.ts.description }}</template>
+				</MkTextarea>
+				<MkFolder>
+					<template #label>{{ i18n.ts.availableRoles }}</template>
+					<template #suffix>{{ rolesThatCanBeUsedThisDecoration.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisDecoration.length }}</template>
+
+					<div class="_gaps">
+						<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+
+						<div v-for="role in rolesThatCanBeUsedThisDecoration" :key="role.id" :class="$style.roleItem">
+							<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
+							<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button>
+							<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
+						</div>
+					</div>
+				</MkFolder>
+				<MkButton v-if="avatarDecoration" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+			</div>
+		</MkSpacer>
+		<div :class="$style.footer">
+			<MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.avatarDecoration ? i18n.ts.update : i18n.ts.create }}</MkButton>
+		</div>
+	</div>
+</MkWindow>
+</template>
+
+<script lang="ts" setup>
+import { computed, watch, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import MkWindow from '@/components/MkWindow.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { i18n } from '@/i18n.js';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkRolePreview from '@/components/MkRolePreview.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
+
+const props = defineProps<{
+	avatarDecoration?: any,
+}>();
+
+const emit = defineEmits<{
+	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
+	(ev: 'closed'): void
+}>();
+
+const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
+const url = ref<string>(props.avatarDecoration ? props.avatarDecoration.url : '');
+const name = ref<string>(props.avatarDecoration ? props.avatarDecoration.name : '');
+const description = ref<string>(props.avatarDecoration ? props.avatarDecoration.description : '');
+const roleIdsThatCanBeUsedThisDecoration = ref(props.avatarDecoration ? props.avatarDecoration.roleIdsThatCanBeUsedThisDecoration : []);
+const rolesThatCanBeUsedThisDecoration = ref<Misskey.entities.Role[]>([]);
+
+watch(roleIdsThatCanBeUsedThisDecoration, async () => {
+	rolesThatCanBeUsedThisDecoration.value = (await Promise.all(roleIdsThatCanBeUsedThisDecoration.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
+}, { immediate: true });
+
+async function addRole() {
+	const roles = await misskeyApi('admin/roles/list');
+	const currentRoleIds = rolesThatCanBeUsedThisDecoration.value.map(x => x.id);
+
+	const { canceled, result: role } = await os.select({
+		items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
+	});
+	if (canceled || role == null) return;
+
+	rolesThatCanBeUsedThisDecoration.value.push(role);
+}
+
+async function removeRole(role, ev) {
+	rolesThatCanBeUsedThisDecoration.value = rolesThatCanBeUsedThisDecoration.value.filter(x => x.id !== role.id);
+}
+
+async function done() {
+	const params = {
+		url: url.value,
+		name: name.value,
+		description: description.value,
+		roleIdsThatCanBeUsedThisDecoration: rolesThatCanBeUsedThisDecoration.value.map(x => x.id),
+	};
+
+	if (props.avatarDecoration) {
+		await os.apiWithDialog('admin/avatar-decorations/update', {
+			id: props.avatarDecoration.id,
+			...params,
+		});
+
+		emit('done', {
+			updated: {
+				id: props.avatarDecoration.id,
+				...params,
+			},
+		});
+
+		windowEl.value?.close();
+	} else {
+		const created = await os.apiWithDialog('admin/avatar-decorations/create', params);
+
+		emit('done', {
+			created: created,
+		});
+
+		windowEl.value?.close();
+	}
+}
+
+async function del() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.tsx.removeAreYouSure({ x: name.value }),
+	});
+	if (canceled) return;
+
+	misskeyApi('admin/avatar-decorations/delete', {
+		id: props.avatarDecoration.id,
+	}).then(() => {
+		emit('done', {
+			deleted: true,
+		});
+		windowEl.value?.close();
+	});
+}
+</script>
+
+<style lang="scss" module>
+.preview {
+	display: grid;
+	place-items: center;
+	grid-template-columns: 1fr 1fr;
+	grid-template-rows: 1fr;
+	gap: var(--MI-margin);
+}
+
+.previewItem {
+	width: 100%;
+	height: 100%;
+	min-height: 160px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	border-radius: var(--MI-radius);
+
+	&.light {
+		background: #eee;
+	}
+
+	&.dark {
+		background: #222;
+	}
+}
+
+.roleItem {
+	display: flex;
+}
+
+.role {
+	flex: 1;
+}
+
+.roleUnassign {
+	width: 32px;
+	height: 32px;
+	margin-left: 8px;
+	align-self: center;
+}
+
+.footer {
+	position: sticky;
+	z-index: 10000;
+	bottom: 0;
+	left: 0;
+	padding: 12px;
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-acrylicBg);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+}
+</style>
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index b377314856a5..a5cafb16788b 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -5,92 +5,38 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
+	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="900">
 		<div class="_gaps">
-			<MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null">
-				<template #label>{{ avatarDecoration.name }}</template>
-				<template #caption>{{ avatarDecoration.description }}</template>
-
-				<div :class="$style.editorRoot">
-					<div :class="$style.editorWrapper">
-						<div :class="$style.preview">
-							<div :class="[$style.previewItem, $style.light]">
-								<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/>
-							</div>
-							<div :class="[$style.previewItem, $style.dark]">
-								<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/>
-							</div>
-						</div>
-						<div class="_gaps_m">
-							<MkInput v-model="avatarDecoration.name">
-								<template #label>{{ i18n.ts.name }}</template>
-							</MkInput>
-							<MkTextarea v-model="avatarDecoration.description">
-								<template #label>{{ i18n.ts.description }}</template>
-							</MkTextarea>
-							<MkInput v-model="avatarDecoration.url">
-								<template #label>{{ i18n.ts.imageUrl }}</template>
-							</MkInput>
-							<div class="_buttons">
-								<MkButton inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-								<MkButton v-if="avatarDecoration.id != null" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
-							</div>
-						</div>
-					</div>
+			<div :class="$style.decorations">
+				<div
+					v-for="avatarDecoration in avatarDecorations"
+					:key="avatarDecoration.id"
+					v-panel
+					:class="$style.decoration"
+					@click="edit(avatarDecoration)"
+				>
+					<div :class="$style.decorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div>
+					<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: avatarDecoration.url }]" forceShowDecoration/>
 				</div>
-			</MkFolder>
+			</div>
 		</div>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, defineAsyncComponent } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkButton from '@/components/MkButton.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkTextarea from '@/components/MkTextarea.vue';
 import { signinRequired } from '@/account.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import MkFolder from '@/components/MkFolder.vue';
-
-const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
 
 const $i = signinRequired();
 
-function add() {
-	avatarDecorations.value.unshift({
-		_id: Math.random().toString(36),
-		id: null,
-		name: '',
-		description: '',
-		url: '',
-	});
-}
-
-function del(avatarDecoration) {
-	os.confirm({
-		type: 'warning',
-		text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }),
-	}).then(({ canceled }) => {
-		if (canceled) return;
-		avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
-		misskeyApi('admin/avatar-decorations/delete', avatarDecoration);
-	});
-}
-
-async function save(avatarDecoration) {
-	if (avatarDecoration.id == null) {
-		await os.apiWithDialog('admin/avatar-decorations/create', avatarDecoration);
-		load();
-	} else {
-		os.apiWithDialog('admin/avatar-decorations/update', avatarDecoration);
-	}
-}
+const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]);
 
 function load() {
 	misskeyApi('admin/avatar-decorations/list').then(_avatarDecorations => {
@@ -100,6 +46,37 @@ function load() {
 
 load();
 
+async function add(ev: MouseEvent) {
+	const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), {
+	}, {
+		done: result => {
+			if (result.created) {
+				avatarDecorations.value.unshift(result.created);
+			}
+		},
+		closed: () => dispose(),
+	});
+}
+
+function edit(avatarDecoration) {
+	const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), {
+		avatarDecoration: avatarDecoration,
+	}, {
+		done: result => {
+			if (result.updated) {
+				const index = avatarDecorations.value.findIndex(x => x.id === avatarDecoration.id);
+				avatarDecorations.value[index] = {
+					...avatarDecorations.value[index],
+					...result.updated,
+				};
+			} else if (result.deleted) {
+				avatarDecorations.value = avatarDecorations.value.filter(x => x.id !== avatarDecoration.id);
+			}
+		},
+		closed: () => dispose(),
+	});
+}
+
 const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-plus',
@@ -116,53 +93,26 @@ definePageMetadata(() => ({
 </script>
 
 <style lang="scss" module>
-.editorRoot {
-	container: editor / inline-size;
-}
-
-.editorWrapper {
+.decorations {
 	display: grid;
-	grid-template-columns: 1fr;
-	grid-template-rows: auto auto;
-	gap: var(--margin);
+	grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+	grid-gap: 12px;
 }
 
-.preview {
-	display: grid;
-	place-items: center;
-	grid-template-columns: 1fr 1fr;
-	grid-template-rows: 1fr;
-	gap: var(--margin);
-}
-
-.previewItem {
-	width: 100%;
-	height: 100%;
-	min-height: 160px;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	border-radius: var(--radius);
-
-	&.light {
-		background: #eee;
-	}
-
-	&.dark {
-		background: #222;
-	}
+.decoration {
+	cursor: pointer;
+	padding: 16px 16px 28px 16px;
+	border-radius: 8px;
+	text-align: center;
+	font-size: 90%;
+	overflow: clip;
+	contain: content;
 }
 
-@container editor (min-width: 600px) {
-	.editorWrapper {
-		grid-template-columns: 200px 1fr;
-		grid-template-rows: 1fr;
-		gap: calc(var(--margin) * 2);
-	}
-
-	.preview {
-		grid-template-columns: 1fr;
-		grid-template-rows: 1fr 1fr;
-	}
+.decorationName {
+	position: relative;
+	z-index: 10;
+	font-weight: bold;
+	margin-bottom: 20px;
 }
 </style>
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index d3f4a65b8932..6d8274a55c17 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -216,7 +216,7 @@ definePageMetadata(() => ({
 	text-overflow: ellipsis;
 	overflow: hidden;
 	white-space: nowrap;
-	color: var(--navFg);
+	color: var(--MI_THEME-navFg);
 }
 
 .pinnedNoteRemove {
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 8b014c7a4ec5..b61054118d3e 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -269,14 +269,14 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .main {
-	min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+	min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
 }
 
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	background: var(--acrylicBg);
-	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	background: var(--MI_THEME-acrylicBg);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .bannerContainer {
@@ -310,7 +310,7 @@ definePageMetadata(() => ({
 	left: 0;
 	width: 100%;
 	height: 64px;
-	background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+	background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 }
 
 .bannerStatus {
@@ -335,7 +335,7 @@ definePageMetadata(() => ({
 	bottom: 16px;
 	left: 16px;
 	background: rgba(0, 0, 0, 0.7);
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 	border-radius: 6px;
 	font-weight: bold;
 	font-size: 1em;
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 7bfa343b1d0d..7b1737fecee7 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -45,6 +45,7 @@ import { clipsCache } from '@/cache.js';
 import { isSupportShare } from '@/scripts/navigator.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { genEmbedCode } from '@/scripts/get-embed-code.js';
+import type { MenuItem } from '@/types/menu.js';
 
 const props = defineProps<{
 	clipId: string,
@@ -131,7 +132,9 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 	icon: 'ti ti-share',
 	text: i18n.ts.share,
 	handler: (ev: MouseEvent): void => {
-		os.popupMenu([{
+		const menuItems: MenuItem[] = [];
+
+		menuItems.push({
 			icon: 'ti ti-link',
 			text: i18n.ts.copyUrl,
 			action: () => {
@@ -144,17 +147,23 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 			action: () => {
 				genEmbedCode('clips', clip.value!.id);
 			},
-		}, ...(isSupportShare() ? [{
-			icon: 'ti ti-share',
-			text: i18n.ts.share,
-			action: async () => {
-				navigator.share({
-					title: clip.value!.name,
-					text: clip.value!.description ?? '',
-					url: `${url}/clips/${clip.value!.id}`,
-				});
-			},
-		}] : [])], ev.currentTarget ?? ev.target);
+		});
+
+		if (isSupportShare()) {
+			menuItems.push({
+				icon: 'ti ti-share',
+				text: i18n.ts.share,
+				action: async () => {
+					navigator.share({
+						title: clip.value!.name,
+						text: clip.value!.description ?? '',
+						url: `${url}/clips/${clip.value!.id}`,
+					});
+				},
+			});
+		}
+
+		os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 	},
 }] : []), {
 	icon: 'ti ti-trash',
@@ -189,7 +198,7 @@ definePageMetadata(() => ({
 .user {
 	--height: 32px;
 	padding: 16px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 	line-height: var(--height);
 }
 
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index eea3f681301b..cae3f3ede97e 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -116,7 +116,7 @@ const selectAll = () => {
 	if (selectedEmojis.value.length > 0) {
 		selectedEmojis.value = [];
 	} else {
-		selectedEmojis.value = Array.from(emojisPaginationComponent.value.items.values(), item => item.id);
+		selectedEmojis.value = Array.from(emojisPaginationComponent.value?.items.values(), item => item.id);
 	}
 };
 
@@ -133,7 +133,7 @@ const add = async (ev: MouseEvent) => {
 	}, {
 		done: result => {
 			if (result.created) {
-				emojisPaginationComponent.value.prepend(result.created);
+				emojisPaginationComponent.value?.prepend(result.created);
 			}
 		},
 		closed: () => dispose(),
@@ -146,12 +146,12 @@ const edit = (emoji) => {
 	}, {
 		done: result => {
 			if (result.updated) {
-				emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({
+				emojisPaginationComponent.value?.updateItem(result.updated.id, (oldEmoji) => ({
 					...oldEmoji,
 					...result.updated,
 				}));
 			} else if (result.deleted) {
-				emojisPaginationComponent.value.removeItem(emoji.id);
+				emojisPaginationComponent.value?.removeItem(emoji.id);
 			}
 		},
 		closed: () => dispose(),
@@ -226,7 +226,7 @@ const setCategoryBulk = async () => {
 		ids: selectedEmojis.value,
 		category: result,
 	});
-	emojisPaginationComponent.value.reload();
+	emojisPaginationComponent.value?.reload();
 };
 
 const setLicenseBulk = async () => {
@@ -238,43 +238,43 @@ const setLicenseBulk = async () => {
 		ids: selectedEmojis.value,
 		license: result,
 	});
-	emojisPaginationComponent.value.reload();
+	emojisPaginationComponent.value?.reload();
 };
 
 const addTagBulk = async () => {
 	const { canceled, result } = await os.inputText({
 		title: 'Tag',
 	});
-	if (canceled) return;
+	if (canceled || result == null) return;
 	await os.apiWithDialog('admin/emoji/add-aliases-bulk', {
 		ids: selectedEmojis.value,
 		aliases: result.split(' '),
 	});
-	emojisPaginationComponent.value.reload();
+	emojisPaginationComponent.value?.reload();
 };
 
 const removeTagBulk = async () => {
 	const { canceled, result } = await os.inputText({
 		title: 'Tag',
 	});
-	if (canceled) return;
+	if (canceled || result == null) return;
 	await os.apiWithDialog('admin/emoji/remove-aliases-bulk', {
 		ids: selectedEmojis.value,
 		aliases: result.split(' '),
 	});
-	emojisPaginationComponent.value.reload();
+	emojisPaginationComponent.value?.reload();
 };
 
 const setTagBulk = async () => {
 	const { canceled, result } = await os.inputText({
 		title: 'Tag',
 	});
-	if (canceled) return;
+	if (canceled || result == null) return;
 	await os.apiWithDialog('admin/emoji/set-aliases-bulk', {
 		ids: selectedEmojis.value,
 		aliases: result.split(' '),
 	});
-	emojisPaginationComponent.value.reload();
+	emojisPaginationComponent.value?.reload();
 };
 
 const delBulk = async () => {
@@ -286,7 +286,7 @@ const delBulk = async () => {
 	await os.apiWithDialog('admin/emoji/delete-bulk', {
 		ids: selectedEmojis.value,
 	});
-	emojisPaginationComponent.value.reload();
+	emojisPaginationComponent.value?.reload();
 };
 
 const headerActions = computed(() => [{
@@ -317,33 +317,34 @@ definePageMetadata(() => ({
 .ogwlenmc {
 	> .local {
 		.empty {
-			margin: var(--margin);
+			margin: var(--MI-margin);
 		}
 
 		.ldhfsamy {
 			display: grid;
 			grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
 			grid-gap: 12px;
-			margin: var(--margin) 0;
+			margin: var(--MI-margin) 0;
 
 			> .emoji {
 				display: flex;
 				align-items: center;
 				padding: 11px;
 				text-align: left;
-				border: solid 1px var(--panel);
+				border: solid 1px var(--MI_THEME-panel);
 
 				&:hover {
-					border-color: var(--inputBorderHover);
+					border-color: var(--MI_THEME-inputBorderHover);
 				}
 
 				&.selected {
-					border-color: var(--accent);
+					border-color: var(--MI_THEME-accent);
 				}
 
 				> .img {
 					width: 42px;
 					height: 42px;
+					object-fit: contain;
 				}
 
 				> .body {
@@ -368,14 +369,14 @@ definePageMetadata(() => ({
 
 	> .remote {
 		.empty {
-			margin: var(--margin);
+			margin: var(--MI-margin);
 		}
 
 		.ldhfsamy {
 			display: grid;
 			grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
 			grid-gap: 12px;
-			margin: var(--margin) 0;
+			margin: var(--MI-margin) 0;
 
 			> .emoji {
 				display: flex;
@@ -384,12 +385,13 @@ definePageMetadata(() => ({
 				text-align: left;
 
 				&:hover {
-					color: var(--accent);
+					color: var(--MI_THEME-accent);
 				}
 
 				> .img {
 					width: 32px;
 					height: 32px;
+					object-fit: contain;
 				}
 
 				> .body {
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
index ffedaf27bfec..dfcc82c77bbf 100644
--- a/packages/frontend/src/pages/drive.file.info.vue
+++ b/packages/frontend/src/pages/drive.file.info.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</MkKeyValue>
 			</button>
 			<button class="_button" :class="$style.kvEditBtn" @click="describe()">
-				<MkKeyValue>
+				<MkKeyValue :class="$style.multiline">
 					<template #key>{{ i18n.ts.description }}</template>
 					<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.kvEditIcon"></i></template>
 				</MkKeyValue>
@@ -231,8 +231,8 @@ onMounted(async () => {
 <style lang="scss" module>
 
 .filePreviewRoot {
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 	// MkMediaList 内の上部マージン 4px
 	padding: calc(1rem - 4px) 1rem 1rem;
 }
@@ -262,8 +262,8 @@ onMounted(async () => {
 
 		&:hover,
 		&:focus-visible {
-			background-color: var(--accentedBg);
-			color: var(--accent);
+			background-color: var(--MI_THEME-accentedBg);
+			color: var(--MI_THEME-accent);
 			text-decoration: none;
 			outline: none;
 		}
@@ -285,7 +285,7 @@ onMounted(async () => {
 	align-items: center;
 	min-width: 0;
 	font-weight: 700;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 	font-size: .8rem;
 
 	>.fileNameEditIcon {
@@ -299,12 +299,12 @@ onMounted(async () => {
 	}
 
 	&:hover {
-		background-color: var(--accentedBg);
+		background-color: var(--MI_THEME-accentedBg);
 
 		>.fileName,
 		>.fileNameEditIcon {
 			visibility: visible;
-			color: var(--accent);
+			color: var(--MI_THEME-accent);
 		}
 	}
 }
@@ -313,12 +313,16 @@ onMounted(async () => {
 	padding: .5rem 1rem;
 }
 
+.multiline {
+	white-space: pre-wrap;
+}
+
 .kvEditBtn {
 	text-align: start;
 	display: block;
 	width: 100%;
 	padding: .5rem 1rem;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 
 	.kvEditIcon {
 		display: inline-block;
@@ -328,11 +332,11 @@ onMounted(async () => {
 	}
 
 	&:hover {
-		color: var(--accent);
-		background-color: var(--accentedBg);
+		color: var(--MI_THEME-accent);
+		background-color: var(--MI_THEME-accentedBg);
 
 		.kvEditIcon {
-			color: var(--accent);
+			color: var(--MI_THEME-accent);
 			visibility: visible;
 		}
 	}
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 4db952eac239..fb4d599c2856 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -111,7 +111,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-if="replaying" class="_woodenFrame">
 				<div class="_woodenFrameInner">
 					<div style="background: #0004;">
-						<div style="height: 10px; background: var(--accent); will-change: width;" :style="{ width: `${(currentFrame / endedAtFrame) * 100}%` }"></div>
+						<div style="height: 10px; background: var(--MI_THEME-accent); will-change: width;" :style="{ width: `${(currentFrame / endedAtFrame) * 100}%` }"></div>
 					</div>
 				</div>
 				<div class="_woodenFrameInner">
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index 853c1d6b0b5d..3765319b2591 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 	ref="windowEl"
 	:initialWidth="400"
 	:initialHeight="500"
-	:canResize="false"
-	@close="windowEl.close()"
-	@closed="$emit('closed')"
+	:canResize="true"
+	@close="windowEl?.close()"
+	@closed="emit('closed')"
 >
 	<template v-if="emoji" #header>:{{ emoji.name }}:</template>
 	<template v-else #header>New emoji</template>
@@ -95,14 +95,19 @@ import { selectFile } from '@/scripts/select-file.js';
 import MkRolePreview from '@/components/MkRolePreview.vue';
 
 const props = defineProps<{
-	emoji?: any,
+	emoji?: Misskey.entities.EmojiDetailed,
+}>();
+
+const emit = defineEmits<{
+	(ev: 'done', v: { deleted?: boolean; updated?: Misskey.entities.AdminEmojiUpdateRequest; created?: Misskey.entities.AdminEmojiUpdateRequest }): void,
+	(ev: 'closed'): void
 }>();
 
 const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
 const name = ref<string>(props.emoji ? props.emoji.name : '');
-const category = ref<string>(props.emoji ? props.emoji.category : '');
+const category = ref<string>(props.emoji?.category ? props.emoji.category : '');
 const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
-const license = ref<string>(props.emoji ? (props.emoji.license ?? '') : '');
+const license = ref<string>(props.emoji?.license ? props.emoji.license : '');
 const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
 const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
 const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
@@ -115,12 +120,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
 
 const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
 
-const emit = defineEmits<{
-	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
-	(ev: 'closed'): void
-}>();
-
-async function changeImage(ev) {
+async function changeImage(ev: Event) {
 	file.value = await selectFile(ev.currentTarget ?? ev.target, null);
 	const candidate = file.value.name.replace(/\.(.+)$/, '');
 	if (candidate.match(/^[a-z0-9_]+$/)) {
@@ -140,7 +140,7 @@ async function addRole() {
 	rolesThatCanBeUsedThisEmojiAsReaction.value.push(role);
 }
 
-async function removeRole(role, ev) {
+async function removeRole(role: Misskey.entities.RoleLite, ev: Event) {
 	rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id);
 }
 
@@ -172,7 +172,7 @@ async function done() {
 			},
 		});
 
-		windowEl.value.close();
+		windowEl.value?.close();
 	} else {
 		const created = await os.apiWithDialog('admin/emoji/add', params);
 
@@ -180,11 +180,12 @@ async function done() {
 			created: created,
 		});
 
-		windowEl.value.close();
+		windowEl.value?.close();
 	}
 }
 
 async function del() {
+	if (!props.emoji) return;
 	const { canceled } = await os.confirm({
 		type: 'warning',
 		text: i18n.tsx.removeAreYouSure({ x: name.value }),
@@ -197,7 +198,7 @@ async function del() {
 		emit('done', {
 			deleted: true,
 		});
-		windowEl.value.close();
+		windowEl.value?.close();
 	});
 }
 </script>
@@ -243,9 +244,9 @@ async function del() {
 	bottom: 0;
 	left: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
-	background: var(--acrylicBg);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-acrylicBg);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index 97429c29a4b0..fcd22155b700 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -58,11 +58,11 @@ function menu(ev) {
 	align-items: center;
 	padding: 12px;
 	text-align: left;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 8px;
 
 	&:hover {
-		border-color: var(--accent);
+		border-color: var(--MI_THEME-accent);
 	}
 }
 
diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue
index cfdb235d3a59..8b16a88ff3b7 100644
--- a/packages/frontend/src/pages/explore.featured.vue
+++ b/packages/frontend/src/pages/explore.featured.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkSpacer :contentMax="800">
-	<MkTab v-model="tab" style="margin-bottom: var(--margin);">
+	<MkTab v-model="tab" style="margin-bottom: var(--MI-margin);">
 		<option value="notes">{{ i18n.ts.notes }}</option>
 		<option value="polls">{{ i18n.ts.poll }}</option>
 	</MkTab>
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index e9608ae94ea4..c9acfec04f13 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkSpacer :contentMax="1200">
-	<MkTab v-model="origin" style="margin-bottom: var(--margin);">
+	<MkTab v-model="origin" style="margin-bottom: var(--MI-margin);">
 		<option value="local">{{ i18n.ts.local }}</option>
 		<option value="remote">{{ i18n.ts.remote }}</option>
 	</MkTab>
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index c3d4cae4aa74..671656610178 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -46,7 +46,7 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .note {
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 }
 </style>
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index d282ed48107a..d84ec4873b9e 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -11,6 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkInput v-model="title">
 				<template #label>{{ i18n.ts._play.title }}</template>
 			</MkInput>
+			<MkSelect v-model="visibility">
+				<template #label>{{ i18n.ts.visibility }}</template>
+				<template #caption>{{ i18n.ts._play.visibilityDescription }}</template>
+				<option :key="'public'" :value="'public'">{{ i18n.ts.public }}</option>
+				<option :key="'private'" :value="'private'">{{ i18n.ts.private }}</option>
+			</MkSelect>
 			<MkTextarea v-model="summary" :mfmAutocomplete="true" :mfmPreview="true">
 				<template #label>{{ i18n.ts._play.summary }}</template>
 			</MkTextarea>
@@ -18,19 +24,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkCodeEditor v-model="script" lang="is">
 				<template #label>{{ i18n.ts._play.script }}</template>
 			</MkCodeEditor>
-			<MkSelect v-model="visibility">
-				<template #label>{{ i18n.ts.visibility }}</template>
-				<template #caption>{{ i18n.ts._play.visibilityDescription }}</template>
-				<option :key="'public'" :value="'public'">{{ i18n.ts.public }}</option>
-				<option :key="'private'" :value="'private'">{{ i18n.ts.private }}</option>
-			</MkSelect>
-			<div class="_buttons">
-				<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
-				<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
-				<MkButton v-if="flash" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
-			</div>
 		</div>
 	</MkSpacer>
+	<template #footer>
+		<div :class="$style.footer">
+			<MkSpacer>
+				<div class="_buttons">
+					<MkButton primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+					<MkButton @click="show"><i class="ti ti-eye"></i> {{ i18n.ts.show }}</MkButton>
+					<MkButton v-if="flash" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+				</div>
+			</MkSpacer>
+		</div>
+	</template>
 </MkStickyContainer>
 </template>
 
@@ -459,3 +465,10 @@ definePageMetadata(() => ({
 	title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new,
 }));
 </script>
+<style lang="scss" module>
+.footer {
+	backdrop-filter: var(--MI-blur, blur(15px));
+	background: var(--MI_THEME-acrylicBg);
+	border-top: solid .5px var(--MI_THEME-divider);
+}
+</style>
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index f63a799365de..2b854897069f 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -55,7 +55,8 @@ const tab = ref('featured');
 
 const featuredFlashsPagination = {
 	endpoint: 'flash/featured' as const,
-	noPaging: true,
+	limit: 5,
+	offsetMode: true,
 };
 const myFlashsPagination = {
 	endpoint: 'flash/my' as const,
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 3b4deaf5379a..3b982a405e67 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<div><i class="ti ti-clock"></i> {{ i18n.ts.createdAt }}: <MkTime :time="flash.createdAt" mode="detail"/></div>
 					</div>
 				</div>
-				<MkA v-if="$i && $i.id === flash.userId" :to="`/play/${flash.id}/edit`" style="color: var(--accent);">{{ i18n.ts._play.editThisPage }}</MkA>
+				<MkA v-if="$i && $i.id === flash.userId" :to="`/play/${flash.id}/edit`" style="color: var(--MI_THEME-accent);">{{ i18n.ts._play.editThisPage }}</MkA>
 				<MkAd :prefer="['horizontal', 'horizontal-big']"/>
 			</div>
 			<MkError v-else-if="error" @retry="fetchFlash()"/>
@@ -80,7 +80,7 @@ import { defaultStore } from '@/store.js';
 import { $i } from '@/account.js';
 import { isSupportShare } from '@/scripts/navigator.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { MenuItem } from '@/types/menu';
+import type { MenuItem } from '@/types/menu.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
 
 const props = defineProps<{
@@ -104,18 +104,23 @@ function fetchFlash() {
 function share(ev: MouseEvent) {
 	if (!flash.value) return;
 
-	os.popupMenu([
-		{
-			text: i18n.ts.shareWithNote,
-			icon: 'ti ti-pencil',
-			action: shareWithNote,
-		},
-		...(isSupportShare() ? [{
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
+		text: i18n.ts.shareWithNote,
+		icon: 'ti ti-pencil',
+		action: shareWithNote,
+	});
+
+	if (isSupportShare()) {
+		menuItems.push({
 			text: i18n.ts.share,
 			icon: 'ti ti-share',
 			action: shareWithNavigator,
-		}] : []),
-	], ev.currentTarget ?? ev.target);
+		});
+	}
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 }
 
 function copyLink() {
@@ -362,7 +367,7 @@ definePageMetadata(() => ({
 				justify-content: center;
 				gap: 12px;
 				padding: 16px;
-				border-bottom: 1px solid var(--divider);
+				border-bottom: 1px solid var(--MI_THEME-divider);
 
 				&:last-child {
 					border-bottom: none;
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index 8991af808627..a840d0d0b3ae 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -55,13 +55,13 @@ const pagination = {
 
 function accept(user) {
 	misskeyApi('following/requests/accept', { userId: user.id }).then(() => {
-		paginationComponent.value.reload();
+		paginationComponent.value?.reload();
 	});
 }
 
 function reject(user) {
 	misskeyApi('following/requests/reject', { userId: user.id }).then(() => {
-		paginationComponent.value.reload();
+		paginationComponent.value?.reload();
 	});
 }
 
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index a68a7e5c4142..70f8b2c31d76 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -141,7 +141,7 @@ definePageMetadata(() => ({
 		top: 8px;
 		left: 9px;
 		padding: 8px;
-		background: var(--panel);
+		background: var(--MI_THEME-panel);
 	}
 
 	> .remove {
@@ -149,7 +149,7 @@ definePageMetadata(() => ({
 		top: 8px;
 		right: 9px;
 		padding: 8px;
-		background: var(--panel);
+		background: var(--MI_THEME-panel);
 	}
 }
 </style>
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index e0e187f2ce08..f396fd2c0c6d 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -130,6 +130,6 @@ definePageMetadata(() => ({
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
 	grid-gap: 12px;
-	margin: 0 var(--margin);
+	margin: 0 var(--MI-margin);
 }
 </style>
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index dfee66d90614..feb4c6061106 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -80,7 +80,7 @@ import { $i } from '@/account.js';
 import { isSupportShare } from '@/scripts/navigator.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { useRouter } from '@/router/supplier.js';
-import { MenuItem } from '@/types/menu';
+import type { MenuItem } from '@/types/menu.js';
 
 const router = useRouter();
 
@@ -171,35 +171,35 @@ function reportAbuse() {
 function showMenu(ev: MouseEvent) {
 	if (!post.value) return;
 
-	const menu: MenuItem[] = [
-		...($i && $i.id !== post.value.userId ? [
-			{
-				icon: 'ti ti-exclamation-circle',
-				text: i18n.ts.reportAbuse,
-				action: reportAbuse,
-			},
-			...($i.isModerator || $i.isAdmin ? [
-				{
-					type: 'divider' as const,
-				},
-				{
-					icon: 'ti ti-trash',
-					text: i18n.ts.delete,
-					danger: true,
-					action: () => os.confirm({
-						type: 'warning',
-						text: i18n.ts.deleteConfirm,
-					}).then(({ canceled }) => {
-						if (canceled || !post.value) return;
-
-						os.apiWithDialog('gallery/posts/delete', { postId: post.value.id });
-					}),
-				},
-			] : []),
-		] : []),
-	];
-
-	os.popupMenu(menu, ev.currentTarget ?? ev.target);
+	const menuItems: MenuItem[] = [];
+
+	if ($i && $i.id !== post.value.userId) {
+		menuItems.push({
+			icon: 'ti ti-exclamation-circle',
+			text: i18n.ts.reportAbuse,
+			action: reportAbuse,
+		});
+
+		if ($i.isModerator || $i.isAdmin) {
+			menuItems.push({
+				type: 'divider',
+			}, {
+				icon: 'ti ti-trash',
+				text: i18n.ts.delete,
+				danger: true,
+				action: () => os.confirm({
+					type: 'warning',
+					text: i18n.ts.deleteConfirm,
+				}).then(({ canceled }) => {
+					if (canceled || !post.value) return;
+
+					os.apiWithDialog('gallery/posts/delete', { postId: post.value.id });
+				}),
+			});
+		}
+	}
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 }
 
 watch(() => props.postId, fetchPost, { immediate: true });
@@ -262,14 +262,14 @@ definePageMetadata(() => ({
 			align-items: center;
 			margin-top: 16px;
 			padding: 16px 0 0 0;
-			border-top: solid 0.5px var(--divider);
+			border-top: solid 0.5px var(--MI_THEME-divider);
 
 			> .like {
 				> .button {
-					--accent: rgb(241 97 132);
-					--X8: rgb(241 92 128);
-					--buttonBg: rgb(216 71 106 / 5%);
-					--buttonHoverBg: rgb(216 71 106 / 10%);
+					--MI_THEME-accent: rgb(241 97 132);
+					--MI_THEME-X8: rgb(241 92 128);
+					--MI_THEME-buttonBg: rgb(216 71 106 / 5%);
+					--MI_THEME-buttonHoverBg: rgb(216 71 106 / 10%);
 					color: #ff002f;
 
 					::v-deep(.count) {
@@ -286,7 +286,7 @@ definePageMetadata(() => ({
 					margin: 0 8px;
 
 					&:hover {
-						color: var(--fgHighlighted);
+						color: var(--MI_THEME-fgHighlighted);
 					}
 				}
 			}
@@ -295,7 +295,7 @@ definePageMetadata(() => ({
 		> .user {
 			margin-top: 16px;
 			padding: 16px 0 0 0;
-			border-top: solid 0.5px var(--divider);
+			border-top: solid 0.5px var(--MI_THEME-divider);
 			display: flex;
 			align-items: center;
 			flex-wrap: wrap;
@@ -321,7 +321,7 @@ definePageMetadata(() => ({
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
 	grid-gap: 12px;
-	margin: var(--margin);
+	margin: var(--MI-margin);
 
 	> .post {
 
diff --git a/packages/frontend/src/pages/games.vue b/packages/frontend/src/pages/games.vue
index b52f4decaaad..998b8be0f3a9 100644
--- a/packages/frontend/src/pages/games.vue
+++ b/packages/frontend/src/pages/games.vue
@@ -35,7 +35,7 @@ definePageMetadata(() => ({
 
 <style module>
 .link:focus-within {
-	outline: 2px solid var(--focus);
+	outline: 2px solid var(--MI_THEME-focus);
 	outline-offset: -2px;
 }
 </style>
diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue
index 4bee437f6582..6d68ed83b4a7 100644
--- a/packages/frontend/src/pages/install-extensions.vue
+++ b/packages/frontend/src/pages/install-extensions.vue
@@ -8,76 +8,26 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="500">
 		<MkLoading v-if="uiPhase === 'fetching'"/>
-		<div v-else-if="uiPhase === 'confirm' && data" class="_gaps_m" :class="$style.extInstallerRoot">
-			<div :class="$style.extInstallerIconWrapper">
-				<i v-if="data.type === 'plugin'" class="ti ti-plug"></i>
-				<i v-else-if="data.type === 'theme'" class="ti ti-palette"></i>
-				<i v-else class="ti ti-download"></i>
-			</div>
-			<h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${data.type}`].title }}</h2>
-			<div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div>
-			<MkInfo v-if="data.type === 'plugin'" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo>
-			<FormSection>
-				<template #label>{{ i18n.ts._externalResourceInstaller[`_${data.type}`].metaTitle }}</template>
-				<div class="_gaps_s">
-					<FormSplit>
+		<MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()">
+			<template #additionalInfo>
+				<FormSection>
+					<template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
+					<div class="_gaps_s">
 						<MkKeyValue>
-							<template #key>{{ i18n.ts.name }}</template>
-							<template #value>{{ data.meta?.name }}</template>
+							<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
+							<template #value><MkUrl :url="url" :showUrlPreview="false"></MkUrl></template>
 						</MkKeyValue>
 						<MkKeyValue>
-							<template #key>{{ i18n.ts.author }}</template>
-							<template #value>{{ data.meta?.author }}</template>
+							<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
+							<template #value>
+								<!-- この画面が出ている時点でハッシュの検証には成功している -->
+								<i class="ti ti-check" style="color: var(--MI_THEME-accent)"></i>
+							</template>
 						</MkKeyValue>
-					</FormSplit>
-					<MkKeyValue v-if="data.type === 'plugin'">
-						<template #key>{{ i18n.ts.description }}</template>
-						<template #value>{{ data.meta?.description }}</template>
-					</MkKeyValue>
-					<MkKeyValue v-if="data.type === 'plugin'">
-						<template #key>{{ i18n.ts.version }}</template>
-						<template #value>{{ data.meta?.version }}</template>
-					</MkKeyValue>
-					<MkKeyValue v-if="data.type === 'plugin'">
-						<template #key>{{ i18n.ts.permission }}</template>
-						<template #value>
-							<ul :class="$style.extInstallerKVList">
-								<li v-for="permission in data.meta?.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
-							</ul>
-						</template>
-					</MkKeyValue>
-					<MkKeyValue v-if="data.type === 'theme' && data.meta?.base">
-						<template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template>
-						<template #value>{{ i18n.ts[data.meta.base] }}</template>
-					</MkKeyValue>
-					<MkFolder>
-						<template #icon><i class="ti ti-code"></i></template>
-						<template #label>{{ i18n.ts._plugin.viewSource }}</template>
-
-						<MkCode :code="data.raw ?? ''"/>
-					</MkFolder>
-				</div>
-			</FormSection>
-			<FormSection>
-				<template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template>
-				<div class="_gaps_s">
-					<MkKeyValue>
-						<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template>
-						<template #value><MkUrl :url="url ?? ''" :showUrlPreview="false"></MkUrl></template>
-					</MkKeyValue>
-					<MkKeyValue>
-						<template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template>
-						<template #value>
-							<!--この画面が出ている時点でハッシュの検証には成功している-->
-							<i class="ti ti-check" style="color: var(--accent)"></i>
-						</template>
-					</MkKeyValue>
-				</div>
-			</FormSection>
-			<div class="_buttonsCenter">
-				<MkButton primary @click="install()"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
-			</div>
-		</div>
+					</div>
+				</FormSection>
+			</template>
+		</MkExtensionInstaller>
 		<div v-else-if="uiPhase === 'error'" class="_gaps_m" :class="[$style.extInstallerRoot, $style.error]">
 			<div :class="$style.extInstallerIconWrapper">
 				<i class="ti ti-circle-x"></i>
@@ -96,14 +46,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue';
 import MkLoading from '@/components/global/MkLoading.vue';
+import MkExtensionInstaller, { type Extension } from '@/components/MkExtensionInstaller.vue';
 import MkButton from '@/components/MkButton.vue';
-import FormSection from '@/components/form/section.vue';
-import FormSplit from '@/components/form/split.vue';
-import MkCode from '@/components/MkCode.vue';
-import MkUrl from '@/components/global/MkUrl.vue';
-import MkInfo from '@/components/MkInfo.vue';
-import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
+import MkUrl from '@/components/global/MkUrl.vue';
+import FormSection from '@/components/form/section.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
@@ -124,24 +71,7 @@ const errorKV = ref<{
 const url = ref<string | null>(null);
 const hash = ref<string | null>(null);
 
-const data = ref<{
-	type: 'plugin' | 'theme';
-	raw: string;
-	meta?: {
-		// Plugin & Theme Common
-		name: string;
-		author: string;
-
-		// Plugin
-		description?: string;
-		version?: string;
-		permissions?: string[];
-		config?: Record<string, any>;
-
-		// Theme
-		base?: 'light' | 'dark';
-	};
-} | null>(null);
+const data = ref<Extension | null>(null);
 
 function goBack(): void {
 	history.back();
@@ -227,7 +157,7 @@ async function fetch() {
 				data.value = {
 					type: 'theme',
 					meta: {
-						description,
+						// description, // 使用されていない
 						...meta,
 					},
 					raw: res.data,
@@ -320,8 +250,8 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .extInstallerRoot {
-	border-radius: var(--radius);
-	background: var(--panel);
+	border-radius: var(--MI-radius);
+	background: var(--MI_THEME-panel);
 	padding: 1.5rem;
 }
 
@@ -335,8 +265,8 @@ definePageMetadata(() => ({
 	margin-left: auto;
 	margin-right: auto;
 
-	background-color: var(--accentedBg);
-	color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 }
 
 .error .extInstallerIconWrapper {
@@ -353,9 +283,4 @@ definePageMetadata(() => ({
 .extInstallerNormDesc {
 	text-align: center;
 }
-
-.extInstallerKVList {
-	margin-top: 0;
-	margin-bottom: 0;
-}
 </style>
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index c69530b34349..6cec3f9d456c 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -51,6 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
 						<MkTextarea v-model="moderationNote" manualSave>
 							<template #label>{{ i18n.ts.moderationNote }}</template>
+							<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
 						</MkTextarea>
 					</div>
 				</FormSection>
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index 25e56d2b8df3..3f6ae27b8903 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -5,10 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header>
-		<MkPageHeader/>
-	</template>
-	<MKSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200">
+	<template #header><MkPageHeader/></template>
+	<MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200">
 		<div :class="$style.root">
 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
 			<div :class="$style.text">
@@ -16,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				{{ i18n.ts.nothing }}
 			</div>
 		</div>
-	</MKSpacer>
+	</MkSpacer>
 	<MkSpacer v-else :contentMax="800">
 		<div class="_gaps_m" style="text-align: center;">
 			<div v-if="resetCycle && inviteLimit">{{ i18n.tsx.inviteLimitResetCycle({ time: resetCycle, limit: inviteLimit }) }}</div>
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
index 954246ff93f9..0ff185415463 100644
--- a/packages/frontend/src/pages/list.vue
+++ b/packages/frontend/src/pages/list.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
+	<MkSpacer v-if="error != null" :contentMax="1200">
 		<div :class="$style.root">
 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
 			<p :class="$style.text">
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				{{ i18n.ts.nothing }}
 			</p>
 		</div>
-	</MKSpacer>
+	</MkSpacer>
 	<MkSpacer v-else-if="list" :contentMax="700" :class="$style.main">
 		<div v-if="list" class="members _margin">
 			<div :class="$style.member_text">{{ i18n.ts.members }}</div>
@@ -50,7 +50,7 @@ const props = defineProps<{
 }>();
 
 const list = ref<Misskey.entities.UserList | null>(null);
-const error = ref();
+const error = ref<unknown | null>(null);
 const users = ref<Misskey.entities.UserDetailed[]>([]);
 
 function fetchList(): void {
@@ -108,7 +108,7 @@ definePageMetadata(() => ({
 </script>
 <style lang="scss" module>
 .main {
-	min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+	min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
 }
 
 .userItem {
diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue
index 323395394239..6f10c69640e2 100644
--- a/packages/frontend/src/pages/lookup.vue
+++ b/packages/frontend/src/pages/lookup.vue
@@ -40,7 +40,7 @@ function fetch() {
 		return;
 	}
 
-	let promise: Promise<any>;
+	let promise: Promise<unknown>;
 
 	if (uri.startsWith('https://')) {
 		promise = misskeyApi('ap/show', {
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index ffaf739ed0f4..e85d2c29c176 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -4,95 +4,79 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkStickyContainer>
-	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :contentMax="800">
-		<div v-if="$i">
-			<div v-if="state == 'waiting'">
-				<MkLoading/>
-			</div>
-			<div v-if="state == 'denied'">
-				<p>{{ i18n.ts._auth.denied }}</p>
-			</div>
-			<div v-else-if="state == 'accepted'" class="accepted">
-				<p v-if="callback">{{ i18n.ts._auth.callback }}<MkEllipsis/></p>
-				<p v-else>{{ i18n.ts._auth.pleaseGoBack }}</p>
-			</div>
-			<div v-else>
-				<div v-if="_permissions.length > 0">
-					<p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p>
-					<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
-					<ul>
-						<li v-for="p in _permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
-					</ul>
-				</div>
-				<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
-				<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
-				<div :class="$style.buttons">
-					<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
-					<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
-				</div>
-			</div>
+<div>
+	<MkAnimBg style="position: fixed; top: 0;"/>
+	<div :class="$style.formContainer">
+		<div :class="$style.form">
+			<MkAuthConfirm
+				ref="authRoot"
+				:name="name"
+				:icon="icon || undefined"
+				:permissions="_permissions"
+				@accept="onAccept"
+				@deny="onDeny"
+			>
+				<template #consentAdditionalInfo>
+					<div v-if="callback != null" class="_gaps_s" :class="$style.redirectRoot">
+						<div>{{ i18n.ts._auth.byClickingYouWillBeRedirectedToThisUrl }}</div>
+						<div class="_monospace" :class="$style.redirectUrl">{{ callback }}</div>
+					</div>
+				</template>
+			</MkAuthConfirm>
 		</div>
-		<div v-else>
-			<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
-			<MkSignin @login="onLogin"/>
-		</div>
-	</MkSpacer>
-</MkStickyContainer>
+	</div>
+</div>
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
-import MkSignin from '@/components/MkSignin.vue';
-import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/scripts/misskey-api.js';
-import { $i, login } from '@/account.js';
+import { computed, useTemplateRef } from 'vue';
+import * as Misskey from 'misskey-js';
+
+import MkAnimBg from '@/components/MkAnimBg.vue';
+import MkAuthConfirm from '@/components/MkAuthConfirm.vue';
+
 import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const props = defineProps<{
 	session: string;
 	callback?: string;
-	name: string;
-	icon: string;
-	permission: string; // コンマ区切り
+	name?: string;
+	icon?: string;
+	permission?: string; // コンマ区切り
 }>();
 
-const _permissions = props.permission ? props.permission.split(',') : [];
+const _permissions = computed(() => {
+	return (props.permission ? props.permission.split(',').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) : []);
+});
 
-const state = ref<string | null>(null);
+const authRoot = useTemplateRef('authRoot');
 
-async function accept(): Promise<void> {
-	state.value = 'waiting';
+async function onAccept(token: string) {
 	await misskeyApi('miauth/gen-token', {
 		session: props.session,
 		name: props.name,
 		iconUrl: props.icon,
-		permission: _permissions,
+		permission: _permissions.value,
+	}, token).catch(() => {
+		authRoot.value?.showUI('failed');
 	});
 
-	state.value = 'accepted';
-	if (props.callback) {
+	if (props.callback && props.callback !== '') {
 		const cbUrl = new URL(props.callback);
-		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
+		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url');
 		cbUrl.searchParams.set('session', props.session);
-		location.href = cbUrl.href;
+		location.href = cbUrl.toString();
+	} else {
+		authRoot.value?.showUI('success');
 	}
 }
 
-function deny(): void {
-	state.value = 'denied';
-}
-
-function onLogin(res): void {
-	login(res.i);
+function onDeny() {
+	authRoot.value?.showUI('denied');
 }
 
-const headerActions = computed(() => []);
-
-const headerTabs = computed(() => []);
-
 definePageMetadata(() => ({
 	title: 'MiAuth',
 	icon: 'ti ti-apps',
@@ -100,15 +84,38 @@ definePageMetadata(() => ({
 </script>
 
 <style lang="scss" module>
-.buttons {
-	margin-top: 16px;
-	display: flex;
-	gap: 8px;
-	flex-wrap: wrap;
+.formContainer {
+	min-height: 100svh;
+	padding: 32px 32px calc(env(safe-area-inset-bottom, 0px) + 32px) 32px;
+	box-sizing: border-box;
+	display: grid;
+	place-content: center;
+}
+
+.form {
+	position: relative;
+	z-index: 10;
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-panel);
+	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
+	overflow: clip;
+	max-width: 500px;
+	width: calc(100vw - 64px);
+	height: min(65svh, calc(100svh - calc(env(safe-area-inset-bottom, 0px) + 64px)));
+	overflow-y: scroll;
+}
+
+.redirectRoot {
+	padding: 16px;
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-bg);
 }
 
-.loginMessage {
-	text-align: center;
-	margin: 8px 0 24px;
+.redirectUrl {
+	font-size: 90%;
+	padding: 12px;
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-panel);
+	overflow-x: scroll;
 }
 </style>
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index 21c96348f0aa..f3877407282e 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -73,11 +73,11 @@ onActivated(() => {
 .antenna {
 	display: block;
 	padding: 16px;
-	border: solid 1px var(--divider);
+	border: solid 1px var(--MI_THEME-divider);
 	border-radius: 6px;
 
 	&:hover {
-		border: solid 1px var(--accent);
+		border: solid 1px var(--MI_THEME-accent);
 		text-decoration: none;
 	}
 }
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index ece998a7a552..acf37a9a2fe9 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -77,15 +77,15 @@ async function create() {
 
 	clipsCache.delete();
 
-	pagingComponent.value.reload();
+	pagingComponent.value?.reload();
 }
 
 function onClipCreated() {
-	pagingComponent.value.reload();
+	pagingComponent.value?.reload();
 }
 
 function onClipDeleted() {
-	pagingComponent.value.reload();
+	pagingComponent.value?.reload();
 }
 
 const headerActions = computed(() => []);
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 82fde284c16b..6cbcca73c2ae 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -85,12 +85,12 @@ onActivated(() => {
 .list {
 	display: block;
 	padding: 16px;
-	border: solid 1px var(--divider);
+	border: solid 1px var(--MI_THEME-divider);
 	border-radius: 6px;
 	margin-bottom: 8px;
 
 	&:hover {
-		border: solid 1px var(--accent);
+		border: solid 1px var(--MI_THEME-accent);
 		text-decoration: none;
 	}
 }
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index a2ceb222feea..69e404bd85e8 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -110,7 +110,7 @@ function addUser() {
 			listId: list.value.id,
 			userId: user.id,
 		}).then(() => {
-			paginationEl.value.reload();
+			paginationEl.value?.reload();
 		});
 	});
 }
@@ -126,7 +126,7 @@ async function removeUser(item, ev) {
 				listId: list.value.id,
 				userId: item.userId,
 			}).then(() => {
-				paginationEl.value.removeItem(item.id);
+				paginationEl.value?.removeItem(item.id);
 			});
 		},
 	}], ev.currentTarget ?? ev.target);
@@ -134,12 +134,14 @@ async function removeUser(item, ev) {
 
 async function showMembershipMenu(item, ev) {
 	const withRepliesRef = ref(item.withReplies);
+
 	os.popupMenu([{
 		type: 'switch',
 		text: i18n.ts.showRepliesToOthersInTimeline,
 		icon: 'ti ti-messages',
 		ref: withRepliesRef,
 	}], ev.currentTarget ?? ev.target);
+
 	watch(withRepliesRef, withReplies => {
 		misskeyApi('users/lists/update-membership', {
 			listId: list.value!.id,
@@ -197,7 +199,7 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .main {
-	min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+	min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px)));
 }
 
 .userItem {
@@ -232,8 +234,8 @@ definePageMetadata(() => ({
 }
 
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue
index 93a792c42f8c..6a2d01b6fa66 100644
--- a/packages/frontend/src/pages/not-found.vue
+++ b/packages/frontend/src/pages/not-found.vue
@@ -24,7 +24,7 @@ const props = defineProps<{
 }>();
 
 if (props.showLoginPopup) {
-	pleaseLogin('/');
+	pleaseLogin({ path: '/' });
 }
 
 const headerActions = computed(() => []);
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 97f32d35cd6f..454ee3c6bcf3 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -61,6 +61,7 @@ import { i18n } from '@/i18n.js';
 import { dateString } from '@/filters/date.js';
 import MkClipPreview from '@/components/MkClipPreview.vue';
 import { defaultStore } from '@/store.js';
+import { pleaseLogin } from '@/scripts/please-login.js';
 
 const props = defineProps<{
 	noteId: string;
@@ -128,6 +129,11 @@ function fetchNote() {
 			});
 		}
 	}).catch(err => {
+		if (err.id === '8e75455b-738c-471d-9f80-62693f33372e') {
+			pleaseLogin({
+				message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor,
+			});
+		}
 		error.value = err;
 	});
 }
@@ -170,11 +176,11 @@ definePageMetadata(() => ({
 }
 
 .loadNext {
-	margin-bottom: var(--margin);
+	margin-bottom: var(--MI-margin);
 }
 
 .loadPrev {
-	margin-top: var(--margin);
+	margin-top: var(--MI-margin);
 }
 
 .loadButton {
@@ -182,7 +188,7 @@ definePageMetadata(() => ({
 }
 
 .note {
-	border-radius: var(--radius);
-	background: var(--panel);
+	border-radius: var(--MI-radius);
+	background: var(--MI_THEME-panel);
 }
 </style>
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index bd93fc836965..46ee501c769c 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -102,7 +102,7 @@ definePageMetadata(() => ({
 
 <style module lang="scss">
 .notifications {
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue
index 733e34eb2cd6..8719a769e59f 100644
--- a/packages/frontend/src/pages/oauth.vue
+++ b/packages/frontend/src/pages/oauth.vue
@@ -4,40 +4,28 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkStickyContainer>
-	<template #header><MkPageHeader/></template>
-	<MkSpacer :contentMax="800">
-		<div v-if="$i">
-			<div v-if="permissions.length > 0">
-				<p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p>
-				<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
-				<ul>
-					<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
-				</ul>
-			</div>
-			<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
-			<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
-			<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
-				<input name="login_token" type="hidden" :value="$i.token"/>
-				<input name="transaction_id" type="hidden" :value="transactionIdMeta?.content"/>
-				<MkButton inline name="cancel" value="cancel">{{ i18n.ts.cancel }}</MkButton>
-				<MkButton inline primary>{{ i18n.ts.accept }}</MkButton>
-			</form>
+<div>
+	<MkAnimBg style="position: fixed; top: 0;"/>
+	<div :class="$style.formContainer">
+		<div :class="$style.form">
+			<MkAuthConfirm
+				ref="authRoot"
+				:name="name"
+				:permissions="permissions"
+				:waitOnDeny="true"
+				@accept="onAccept"
+				@deny="onDeny"
+			/>
 		</div>
-		<div v-else>
-			<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
-			<MkSignin @login="onLogin"/>
-		</div>
-	</MkSpacer>
-</MkStickyContainer>
+	</div>
+</div>
 </template>
 
 <script lang="ts" setup>
-import MkSignin from '@/components/MkSignin.vue';
-import MkButton from '@/components/MkButton.vue';
-import { $i, login } from '@/account.js';
-import { i18n } from '@/i18n.js';
+import * as Misskey from 'misskey-js';
+import MkAnimBg from '@/components/MkAnimBg.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkAuthConfirm from '@/components/MkAuthConfirm.vue';
 
 const transactionIdMeta = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:transaction-id"]');
 if (transactionIdMeta) {
@@ -45,10 +33,44 @@ if (transactionIdMeta) {
 }
 
 const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content;
-const permissions = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:scope"]')?.content.split(' ') ?? [];
+const permissions = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:scope"]')?.content.split(' ').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) ?? [];
+
+function doPost(token: string, decision: 'accept' | 'deny') {
+	const form = document.createElement('form');
+	form.action = '/oauth/decision';
+	form.method = 'post';
+	form.acceptCharset = 'utf-8';
+
+	const loginToken = document.createElement('input');
+	loginToken.type = 'hidden';
+	loginToken.name = 'login_token';
+	loginToken.value = token;
+	form.appendChild(loginToken);
+
+	const transactionId = document.createElement('input');
+	transactionId.type = 'hidden';
+	transactionId.name = 'transaction_id';
+	transactionId.value = transactionIdMeta?.content ?? '';
+	form.appendChild(transactionId);
+
+	if (decision === 'deny') {
+		const cancel = document.createElement('input');
+		cancel.type = 'hidden';
+		cancel.name = 'cancel';
+		cancel.value = 'cancel';
+		form.appendChild(cancel);
+	}
+
+	document.body.appendChild(form);
+	form.submit();
+}
+
+function onAccept(token: string) {
+	doPost(token, 'accept');
+}
 
-function onLogin(res): void {
-	login(res.i);
+function onDeny(token: string) {
+	doPost(token, 'deny');
 }
 
 definePageMetadata(() => ({
@@ -58,15 +80,24 @@ definePageMetadata(() => ({
 </script>
 
 <style lang="scss" module>
-.buttons {
-	margin-top: 16px;
-	display: flex;
-	gap: 8px;
-	flex-wrap: wrap;
+.formContainer {
+	min-height: 100svh;
+	padding: 32px 32px calc(env(safe-area-inset-bottom, 0px) + 32px) 32px;
+	box-sizing: border-box;
+	display: grid;
+	place-content: center;
 }
 
-.loginMessage {
-	text-align: center;
-	margin: 8px 0 24px;
+.form {
+	position: relative;
+	z-index: 10;
+	border-radius: var(--MI-radius);
+	background-color: var(--MI_THEME-panel);
+	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
+	overflow: clip;
+	max-width: 500px;
+	width: calc(100vw - 64px);
+	height: min(65svh, calc(100svh - calc(env(safe-area-inset-bottom, 0px) + 64px)));
+	overflow-y: scroll;
 }
 </style>
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
index 1cfe7a6d2d67..c3ad6657b04b 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <!-- eslint-disable vue/no-mutating-props -->
-<XContainer :draggable="true" @remove="() => $emit('remove')">
+<XContainer :draggable="true" @remove="() => emit('remove')">
 	<template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template>
 	<template #func>
 		<button @click="choose()">
@@ -30,11 +30,12 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
-	modelValue: any
+	modelValue: Misskey.entities.PageBlock & { type: 'image' };
 }>();
 
 const emit = defineEmits<{
-	(ev: 'update:modelValue', value: any): void;
+	(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'image' }): void;
+	(ev: 'remove'): void;
 }>();
 
 const file = ref<Misskey.entities.DriveFile | null>(null);
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
index 0a28386986dc..36e03b47901f 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <!-- eslint-disable vue/no-mutating-props -->
-<XContainer :draggable="true" @remove="() => $emit('remove')">
+<XContainer :draggable="true" @remove="() => emit('remove')">
 	<template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template>
 
 	<section style="padding: 16px;" class="_gaps_s">
@@ -34,19 +34,24 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
-	modelValue: any
+	modelValue: Misskey.entities.PageBlock & { type: 'note' };
 }>();
 
 const emit = defineEmits<{
-	(ev: 'update:modelValue', value: any): void;
+	(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void;
 }>();
 
-const id = ref<any>(props.modelValue.note);
+const id = ref(props.modelValue.note);
 const note = ref<Misskey.entities.Note | null>(null);
 
 watch(id, async () => {
 	if (id.value && (id.value.startsWith('http://') || id.value.startsWith('https://'))) {
-		id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop();
+		id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop() ?? null;
+	}
+
+	if (!id.value) {
+		note.value = null;
+		return;
 	}
 
 	emit('update:modelValue', {
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
index 0f8dc33143ea..3fed07f7e898 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <!-- eslint-disable vue/no-mutating-props -->
-<XContainer :draggable="true" @remove="() => $emit('remove')">
+<XContainer :draggable="true" @remove="() => emit('remove')">
 	<template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template>
 	<template #func>
 		<button class="_button" @click="rename()">
@@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-/* eslint-disable vue/no-mutating-props */
+ 
 import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
+import * as Misskey from 'misskey-js';
 import { v4 as uuid } from 'uuid';
 import XContainer from '../page-editor.container.vue';
 import * as os from '@/os.js';
@@ -33,14 +34,13 @@ import { getPageBlockList } from '@/pages/page-editor/common.js';
 
 const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue'));
 
-const props = withDefaults(defineProps<{
-	modelValue: any,
-}>(), {
-	modelValue: {},
-});
+const props = defineProps<{
+	modelValue: Misskey.entities.PageBlock & { type: 'section'; },
+}>();
 
 const emit = defineEmits<{
-	(ev: 'update:modelValue', value: any): void;
+	(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void;
+	(ev: 'remove'): void;
 }>();
 
 const children = ref(deepClone(props.modelValue.children ?? []));
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
index 14c3e6845e9a..5795b46c0082 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <!-- eslint-disable vue/no-mutating-props -->
-<XContainer :draggable="true" @remove="() => $emit('remove')">
+<XContainer :draggable="true" @remove="() => emit('remove')">
 	<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
 
 	<section>
@@ -15,18 +15,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-/* eslint-disable vue/no-mutating-props */
+ 
 import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
+import * as Misskey from 'misskey-js';
 import XContainer from '../page-editor.container.vue';
 import { i18n } from '@/i18n.js';
 import { Autocomplete } from '@/scripts/autocomplete.js';
 
 const props = defineProps<{
-	modelValue: any
+	modelValue: Misskey.entities.PageBlock & { type: 'text' }
 }>();
 
 const emit = defineEmits<{
-	(ev: 'update:modelValue', value: any): void;
+	(ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void;
 }>();
 
 let autocomplete: Autocomplete;
@@ -63,7 +64,7 @@ onUnmounted(() => {
 	box-shadow: none;
 	padding: 16px;
 	background: transparent;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	font-size: 14px;
 	box-sizing: border-box;
 }
diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
index 4967e7300081..f1913201805e 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => $emit('update:modelValue', v)">
+<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => emit('update:modelValue', v)">
 	<template #item="{element}">
 		<div :class="$style.item">
 			<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue
index f2081c452cd5..a96c2c2a772e 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.container.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue
@@ -60,12 +60,12 @@ function remove() {
 .cpjygsrt {
 	position: relative;
 	overflow: hidden;
-	background: var(--panel);
-	border: solid 2px var(--X12);
+	background: var(--MI_THEME-panel);
+	border: solid 2px var(--MI_THEME-X12);
 	border-radius: 8px;
 
 	&:hover {
-		border: solid 2px var(--X13);
+		border: solid 2px var(--MI_THEME-X13);
 	}
 
 	&.warn {
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 381b80cd2952..a1bec52f1810 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -121,7 +121,7 @@ import { instance } from '@/instance.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import { useRouter } from '@/router/supplier.js';
-import { MenuItem } from '@/types/menu';
+import type { MenuItem } from '@/types/menu.js';
 
 const router = useRouter();
 
@@ -165,18 +165,23 @@ function fetchPage() {
 function share(ev: MouseEvent) {
 	if (!page.value) return;
 
-	os.popupMenu([
-		{
-			text: i18n.ts.shareWithNote,
-			icon: 'ti ti-pencil',
-			action: shareWithNote,
-		},
-		...(isSupportShare() ? [{
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
+		text: i18n.ts.shareWithNote,
+		icon: 'ti ti-pencil',
+		action: shareWithNote,
+	});
+
+	if (isSupportShare()) {
+		menuItems.push({
 			text: i18n.ts.share,
 			icon: 'ti ti-share',
 			action: shareWithNavigator,
-		}] : []),
-	], ev.currentTarget ?? ev.target);
+		});
+	}
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 }
 
 function copyLink() {
@@ -256,51 +261,59 @@ function reportAbuse() {
 function showMenu(ev: MouseEvent) {
 	if (!page.value) return;
 
-	const menu: MenuItem[] = [
-		...($i && $i.id === page.value.userId ? [
-			{
-				icon: 'ti ti-code',
-				text: i18n.ts._pages.viewSource,
-				action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`),
-			},
-			...($i.pinnedPageId === page.value.id ? [{
+	const menuItems: MenuItem[] = [];
+
+	if ($i && $i.id === page.value.userId) {
+		menuItems.push({
+			icon: 'ti ti-pencil',
+			text: i18n.ts.editThisPage,
+			action: () => router.push(`/pages/edit/${page.value.id}`),
+		});
+
+		if ($i.pinnedPageId === page.value.id) {
+			menuItems.push({
 				icon: 'ti ti-pinned-off',
 				text: i18n.ts.unpin,
 				action: () => pin(false),
-			}] : [{
+			});
+		} else {
+			menuItems.push({
 				icon: 'ti ti-pin',
 				text: i18n.ts.pin,
 				action: () => pin(true),
-			}]),
-		] : []),
-		...($i && $i.id !== page.value.userId ? [
-			{
-				icon: 'ti ti-exclamation-circle',
-				text: i18n.ts.reportAbuse,
-				action: reportAbuse,
-			},
-			...($i.isModerator || $i.isAdmin ? [
-				{
-					type: 'divider' as const,
-				},
-				{
-					icon: 'ti ti-trash',
-					text: i18n.ts.delete,
-					danger: true,
-					action: () => os.confirm({
-						type: 'warning',
-						text: i18n.ts.deleteConfirm,
-					}).then(({ canceled }) => {
-						if (canceled || !page.value) return;
-
-						os.apiWithDialog('pages/delete', { pageId: page.value.id });
-					}),
-				},
-			] : []),
-		] : []),
-	];
-
-	os.popupMenu(menu, ev.currentTarget ?? ev.target);
+			});
+		}
+	} else if ($i && $i.id !== page.value.userId) {
+		menuItems.push({
+				icon: 'ti ti-code',
+				text: i18n.ts._pages.viewSource,
+				action: () => router.push(`/@${props.username}/pages/${props.pageName}/view-source`),
+		}, {
+			icon: 'ti ti-exclamation-circle',
+			text: i18n.ts.reportAbuse,
+			action: reportAbuse,
+		});
+
+		if ($i.isModerator || $i.isAdmin) {
+			menuItems.push({
+				type: 'divider',
+			}, {
+				icon: 'ti ti-trash',
+				text: i18n.ts.delete,
+				danger: true,
+				action: () => os.confirm({
+					type: 'warning',
+					text: i18n.ts.deleteConfirm,
+				}).then(({ canceled }) => {
+					if (canceled || !page.value) return;
+
+					os.apiWithDialog('pages/delete', { pageId: page.value.id });
+				}),
+			});
+		}
+	}
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 }
 
 watch(() => path.value, fetchPage, { immediate: true });
@@ -344,24 +357,24 @@ definePageMetadata(() => ({
 
 	&:hover,
 	&:focus-visible {
-		background-color: var(--accentedBg);
-		color: var(--accent);
+		background-color: var(--MI_THEME-accentedBg);
+		color: var(--MI_THEME-accent);
 		text-decoration: none;
 		outline: none;
 	}
 }
 
 .pageMain {
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 	padding: 2rem;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	box-sizing: border-box;
 }
 
 .pageBanner {
 	width: calc(100% + 4rem);
 	margin: -2rem -2rem 1.5rem;
-	border-radius: var(--radius) var(--radius) 0 0;
+	border-radius: var(--MI-radius) var(--MI-radius) 0 0;
 	overflow: hidden;
 	position: relative;
 
@@ -386,7 +399,7 @@ definePageMetadata(() => ({
 		}
 
 		.pageBannerBgFallback2 {
-			background-color: var(--accentedBg);
+			background-color: var(--MI_THEME-accentedBg);
 		}
 
 		&::after {
@@ -396,7 +409,7 @@ definePageMetadata(() => ({
 			bottom: 0;
 			width: 100%;
 			height: 100px;
-			background: linear-gradient(0deg, var(--panel), transparent);
+			background: linear-gradient(0deg, var(--MI_THEME-panel), transparent);
 		}
 	}
 
@@ -420,7 +433,7 @@ definePageMetadata(() => ({
 		h1 {
 			font-size: 2rem;
 			font-weight: 700;
-			color: var(--fg);
+			color: var(--MI_THEME-fg);
 			margin: 0;
 		}
 
@@ -445,7 +458,7 @@ definePageMetadata(() => ({
 			flex-shrink: 0;
 			display: flex;
 			align-items: center;
-			gap: var(--marginHalf);
+			gap: var(--MI-marginHalf);
 			margin-left: auto;
 		}
 	}
@@ -459,14 +472,14 @@ definePageMetadata(() => ({
 	display: flex;
 	align-items: center;
 
-	border-top: 1px solid var(--divider);
+	border-top: 1px solid var(--MI_THEME-divider);
 	padding-top: 1.5rem;
 	margin-bottom: 1.5rem;
 
 	> .other {
 		margin-left: auto;
 		display: flex;
-		gap: var(--marginHalf);
+		gap: var(--MI-marginHalf);
 	}
 }
 
@@ -474,7 +487,7 @@ definePageMetadata(() => ({
 	display: flex;
 	align-items: center;
 
-	border-top: 1px solid var(--divider);
+	border-top: 1px solid var(--MI_THEME-divider);
 	padding-top: 1.5rem;
 	margin-bottom: 1.5rem;
 
@@ -513,14 +526,14 @@ definePageMetadata(() => ({
 	display: flex;
 	align-items: center;
 	flex-wrap: wrap;
-	gap: var(--marginHalf);
+	gap: var(--MI-marginHalf);
 }
 
 .relatedPagesRoot {
-	padding: var(--margin);
+	padding: var(--MI-margin);
 }
 
 .relatedPagesItem > article {
-	background-color: var(--panelHighlight) !important;
+	background-color: var(--MI_THEME-panelHighlight) !important;
 }
 </style>
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index bac1d2bb7007..4cacbd090664 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -52,7 +52,7 @@ const props = defineProps<{
 
 const scope = computed(() => props.path ? props.path.split('/') : []);
 
-const keys = ref<any>(null);
+const keys = ref<[string, string][]>([]);
 
 function fetchKeys() {
 	misskeyApi('i/registry/keys-with-type', {
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 54e66f6e169b..429f502133e5 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -504,7 +504,7 @@ $gap: 4px;
 .boardInner {
 	padding: 32px;
 
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
 	border-radius: 8px;
 }
@@ -574,34 +574,34 @@ $gap: 4px;
 	transition: border 0.25s ease, opacity 0.25s ease;
 
 	&.boardCell_empty {
-		border: solid 2px var(--divider);
+		border: solid 2px var(--MI_THEME-divider);
 	}
 
 	&.boardCell_empty.boardCell_can {
-		border-color: var(--accent);
+		border-color: var(--MI_THEME-accent);
 		opacity: 0.5;
 	}
 
 	&.boardCell_empty.boardCell_myTurn {
-		border-color: var(--divider);
+		border-color: var(--MI_THEME-divider);
 		opacity: 1;
 
 		&.boardCell_can {
-			border-color: var(--accent);
+			border-color: var(--MI_THEME-accent);
 			cursor: pointer;
 
 			&:hover {
-				background: var(--accent);
+				background: var(--MI_THEME-accent);
 			}
 		}
 	}
 
 	&.boardCell_prev {
-		box-shadow: 0 0 0 4px var(--accent);
+		box-shadow: 0 0 0 4px var(--MI_THEME-accent);
 	}
 
 	&.boardCell_isEnded {
-		border-color: var(--divider);
+		border-color: var(--MI_THEME-divider);
 	}
 
 	&.boardCell_none {
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 31c0003130ca..437a1a2294ae 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</template>
 				<template v-else>
 					<div class="_panel">
-						<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
+						<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--MI_THEME-divider);">
 							<div>{{ mapName }}</div>
 							<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
 						</div>
@@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.footer">
 			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 				<div style="text-align: center;" class="_gaps_s">
-					<div v-if="opponentHasSettingsChanged" style="color: var(--warn);">{{ i18n.ts._reversi.opponentHasSettingsChanged }}</div>
+					<div v-if="opponentHasSettingsChanged" style="color: var(--MI_THEME-warn);">{{ i18n.ts._reversi.opponentHasSettingsChanged }}</div>
 					<div>
 						<template v-if="isReady && isOpReady">{{ i18n.ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template>
 						<template v-if="isReady && !isOpReady">{{ i18n.ts._reversi.waitingForOther }}<MkEllipsis/></template>
@@ -121,7 +121,7 @@ import MkRadios from '@/components/MkRadios.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { useRouter } from '@/router/supplier.js';
 
 const $i = signinRequired();
@@ -132,7 +132,7 @@ const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.
 
 const props = defineProps<{
 	game: Misskey.entities.ReversiGameDetailed;
-	connection: Misskey.ChannelConnection;
+	connection: Misskey.ChannelConnection<Misskey.Channels['reversiGame']>;
 }>();
 
 const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
@@ -217,14 +217,14 @@ function onChangeReadyStates(states) {
 	game.value.user2Ready = states.user2;
 }
 
-function updateSettings(key: keyof Misskey.entities.ReversiGameDetailed) {
+function updateSettings(key: typeof Misskey.reversiUpdateKeys[number]) {
 	props.connection.send('updateSettings', {
 		key: key,
 		value: game.value[key],
 	});
 }
 
-function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof Misskey.entities.ReversiGameDetailed; value: any; }) {
+function onUpdateSettings<K extends typeof Misskey.reversiUpdateKeys[number]>({ userId, key, value }: { userId: string; key: K; value: Misskey.entities.ReversiGameDetailed[K]; }) {
 	if (userId === $i.id) return;
 	if (game.value[key] === value) return;
 	game.value[key] = value;
@@ -273,14 +273,14 @@ onUnmounted(() => {
 	width: 300px;
 	height: 300px;
 	margin: 0 auto;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 
 .boardCell {
 	display: grid;
 	place-items: center;
 	background: transparent;
-	border: solid 2px var(--divider);
+	border: solid 2px var(--MI_THEME-divider);
 	border-radius: 6px;
 	overflow: clip;
 	cursor: pointer;
@@ -290,9 +290,9 @@ onUnmounted(() => {
 }
 
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	background: var(--acrylicBg);
-	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	background: var(--MI_THEME-acrylicBg);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index d823861b4ac6..d608a2411c08 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div :class="$style.gamePreviews">
 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
 							<div :class="$style.gamePreviewPlayers">
-								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
+								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
 								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/>
 								<span style="margin: 0 1em;">vs</span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
 								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
-								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
 							</div>
 							<div :class="$style.gamePreviewFooter">
 								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span>
@@ -63,13 +63,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div :class="$style.gamePreviews">
 						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
 							<div :class="$style.gamePreviewPlayers">
-								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
+								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
 								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/>
 								<span style="margin: 0 1em;">vs</span>
 								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
 								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span>
-								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span>
 							</div>
 							<div :class="$style.gamePreviewFooter">
 								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span>
@@ -285,7 +285,7 @@ definePageMetadata(() => ({
 .gamePreviews {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
-	grid-gap: var(--margin);
+	grid-gap: var(--MI-margin);
 }
 
 .gamePreview {
@@ -295,11 +295,11 @@ definePageMetadata(() => ({
 }
 
 .gamePreviewActive {
-	box-shadow: inset 0 0 8px 0px var(--accent);
+	box-shadow: inset 0 0 8px 0px var(--MI_THEME-accent);
 }
 
 .gamePreviewWaiting {
-	box-shadow: inset 0 0 8px 0px var(--warn);
+	box-shadow: inset 0 0 8px 0px var(--MI_THEME-warn);
 }
 
 .gamePreviewPlayers {
@@ -324,19 +324,19 @@ definePageMetadata(() => ({
 .gamePreviewFooter {
 	display: flex;
 	align-items: baseline;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 	padding: 6px 10px;
 	font-size: 0.9em;
 }
 
 .gamePreviewStatusActive {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	font-weight: bold;
 	animation: blink 2s infinite;
 }
 
 .gamePreviewStatusWaiting {
-	color: var(--warn);
+	color: var(--MI_THEME-warn);
 	font-weight: bold;
 	animation: blink 2s infinite;
 }
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index 45f8ef21ed00..46e510b49b52 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
-	<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
+	<MkSpacer v-if="error != null" :contentMax="1200">
 		<div :class="$style.root">
 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
 			<p :class="$style.text">
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				{{ error }}
 			</p>
 		</div>
-	</MKSpacer>
+	</MkSpacer>
 	<MkSpacer v-else-if="tab === 'users'" :contentMax="1200">
 		<div class="_gaps_s">
 			<div v-if="role">{{ role.description }}</div>
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</MkSpacer>
 	<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700">
-		<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.role"/>
+		<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
 		<div v-else-if="!visible" class="_fullinfo">
 			<img :src="infoImageUrl" class="_ghost"/>
 			<div>{{ i18n.ts.nothing }}</div>
@@ -47,23 +47,24 @@ import { instanceName } from '@@/js/config.js';
 import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
 
 const props = withDefaults(defineProps<{
-	role: string;
+	roleId: string;
 	initialTab?: string;
 }>(), {
 	initialTab: 'users',
 });
 
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
 const tab = ref(props.initialTab);
-const role = ref<Misskey.entities.Role>();
-const error = ref();
+const role = ref<Misskey.entities.Role | null>(null);
+const error = ref<string | null>(null);
 const visible = ref(false);
 
-watch(() => props.role, () => {
+watch(() => props.roleId, () => {
 	misskeyApi('roles/show', {
-		roleId: props.role,
+		roleId: props.roleId,
 	}).then(res => {
 		role.value = res;
-		document.title = `${role.value.name} | ${instanceName}`;
+		error.value = null;
 		visible.value = res.isExplorable && res.isPublic;
 	}).catch((err) => {
 		if (err.code === 'NO_SUCH_ROLE') {
@@ -71,7 +72,6 @@ watch(() => props.role, () => {
 		} else {
 			error.value = i18n.ts.somethingHappened;
 		}
-		document.title = `${error.value} | ${instanceName}`;
 	});
 }, { immediate: true });
 
@@ -79,7 +79,7 @@ const users = computed(() => ({
 	endpoint: 'roles/users' as const,
 	limit: 30,
 	params: {
-		roleId: props.role,
+		roleId: props.roleId,
 	},
 }));
 
@@ -94,7 +94,7 @@ const headerTabs = computed(() => [{
 }]);
 
 definePageMetadata(() => ({
-	title: role.value ? role.value.name : i18n.ts.role,
+	title: role.value ? role.value.name : (error.value ?? i18n.ts.role),
 	icon: 'ti ti-badge',
 }));
 </script>
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index 9aaa8ff9c633..88171f7d704a 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -30,6 +30,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</MkContainer>
 
+			<MkContainer :foldable="true" :expanded="false">
+				<template #header>{{ i18n.ts.uiInspector }}</template>
+				<div :class="$style.uiInspector">
+					<div v-for="c in components" :key="c.value.id" :class="{ [$style.uiInspectorUnShown]: !showns.has(c.value.id) }">
+						<div :class="$style.uiInspectorType">{{ c.value.type }}</div>
+						<div :class="$style.uiInspectorId">{{ c.value.id }}</div>
+						<button :class="$style.uiInspectorPropsToggle" @click="() => uiInspectorOpenedComponents.set(c, !uiInspectorOpenedComponents.get(c))">
+							<i v-if="uiInspectorOpenedComponents.get(c)" class="ti ti-chevron-up icon"></i>
+							<i v-else class="ti ti-chevron-down icon"></i>
+						</button>
+						<div v-if="uiInspectorOpenedComponents.get(c)">
+							<MkTextarea :modelValue="stringifyUiProps(c.value)" code readonly></MkTextarea>
+						</div>
+					</div>
+					<div :class="$style.uiInspectorDescription">{{ i18n.ts.uiInspectorDescription }}</div>
+				</div>
+			</MkContainer>
+
 			<div class="">
 				{{ i18n.ts.scratchpadDescription }}
 			</div>
@@ -43,6 +61,7 @@ import { onDeactivated, onUnmounted, Ref, ref, watch, computed } from 'vue';
 import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import MkContainer from '@/components/MkContainer.vue';
 import MkButton from '@/components/MkButton.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
 import MkCodeEditor from '@/components/MkCodeEditor.vue';
 import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import * as os from '@/os.js';
@@ -57,10 +76,15 @@ import { claimAchievement } from '@/scripts/achievements.js';
 const parser = new Parser();
 let aiscript: Interpreter;
 const code = ref('');
-const logs = ref<any[]>([]);
+const logs = ref<{
+	id: number;
+	text: string;
+	print: boolean;
+}[]>([]);
 const root = ref<AsUiRoot>();
 const components = ref<Ref<AsUiComponent>[]>([]);
 const uiKey = ref(0);
+const uiInspectorOpenedComponents = ref(new Map<string, boolean>);
 
 const saved = miLocalStorage.getItem('scratchpad');
 if (saved) {
@@ -71,6 +95,14 @@ watch(code, () => {
 	miLocalStorage.setItem('scratchpad', code.value);
 });
 
+function stringifyUiProps(uiProps) {
+	return JSON.stringify(
+		{ ...uiProps, type: undefined, id: undefined },
+		(k, v) => typeof v === 'function' ? '<function>' : v,
+		2
+	);
+}
+
 async function run() {
 	if (aiscript) aiscript.abort();
 	root.value = undefined;
@@ -152,6 +184,20 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
+const showns = computed(() => {
+	const result = new Set<string>();
+	(function addChildrenToResult(c: AsUiComponent) {
+		result.add(c.id);
+		if (c.children) {
+			const childComponents = components.value.filter(v => c.children.includes(v.value.id));
+			for (const child of childComponents) {
+				addChildrenToResult(child.value);
+			}
+		}
+	})(root.value);
+	return result;
+});
+
 definePageMetadata(() => ({
 	title: i18n.ts.scratchpad,
 	icon: 'ti ti-terminal-2',
@@ -162,7 +208,7 @@ definePageMetadata(() => ({
 .root {
 	display: flex;
 	flex-direction: column;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 }
 
 .editor {
@@ -192,4 +238,39 @@ definePageMetadata(() => ({
 		}
 	}
 }
+
+.uiInspector {
+	display: grid;
+	gap: 8px;
+	padding: 16px;
+}
+
+.uiInspectorUnShown {
+	color: var(--MI_THEME-fgTransparent);
+}
+
+.uiInspectorType {
+	display: inline-block;
+	border: hidden;
+	border-radius: 10px;
+	background-color: var(--MI_THEME-panelHighlight);
+	padding: 2px 8px;
+	font-size: 12px;
+}
+
+.uiInspectorId {
+	display: inline-block;
+	padding-left: 8px;
+}
+
+.uiInspectorDescription {
+	display: block;
+	font-size: 12px;
+	padding-top: 16px;
+}
+
+.uiInspectorPropsToggle {
+	background: none;
+	border: none;
+}
 </style>
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index 9cf7fbe8d8bf..105c947d2543 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -211,12 +211,12 @@ async function search() {
 	justify-content: center;
 }
 .addMeButton {
-  border: 2px dashed var(--fgTransparent);
+  border: 2px dashed var(--MI_THEME-fgTransparent);
 	padding: 12px;
 	margin-right: 16px;
 }
 .addUserButton {
-  border: 2px dashed var(--fgTransparent);
+  border: 2px dashed var(--MI_THEME-fgTransparent);
 	padding: 12px;
 	flex-grow: 1;
 }
diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
index 2244047b3165..18c82ffdf697 100644
--- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue
+++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
@@ -138,12 +138,13 @@ const token = ref<string | number | null>(null);
 const backupCodes = ref<string[]>();
 
 function cancel() {
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 async function tokenDone() {
+	if (token.value == null) return;
 	const res = await os.apiWithDialog('i/2fa/done', {
-		token: token.value.toString(),
+		token: typeof token.value === 'string' ? token.value : token.value.toString(),
 	});
 
 	backupCodes.value = res.backupCodes;
@@ -166,7 +167,7 @@ function downloadBackupCodes() {
 }
 
 function allDone() {
-	dialog.value.close();
+	dialog.value?.close();
 }
 </script>
 
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index 6a9a1e16e206..776f59dda321 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<template #icon><i class="ti ti-shield-lock"></i></template>
 			<template #label>{{ i18n.ts.totp }}</template>
 			<template #caption>{{ i18n.ts.totpDescription }}</template>
-			<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--success)"></i></template>
+			<template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template>
 
 			<div v-if="$i.twoFactorEnabled" class="_gaps_s">
 				<div v-text="i18n.ts._2fa.alreadyRegistered"/>
@@ -84,7 +84,7 @@ import FormSection from '@/components/form/section.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkLink from '@/components/MkLink.vue';
 import * as os from '@/os.js';
-import { signinRequired, updateAccount } from '@/account.js';
+import { signinRequired, updateAccountPartial } from '@/account.js';
 import { i18n } from '@/i18n.js';
 
 const $i = signinRequired();
@@ -123,7 +123,7 @@ async function unregisterTOTP(): Promise<void> {
 		password: auth.result.password,
 		token: auth.result.token,
 	}).then(res => {
-		updateAccount({
+		updateAccountPartial({
 			twoFactorEnabled: false,
 		});
 	}).catch(error => {
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index 08c9261dcf09..97e960675faf 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -19,13 +19,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, ref, computed } from 'vue';
+import { ref, computed } from 'vue';
 import type * as Misskey from 'misskey-js';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account.js';
+import { getAccounts, removeAccount as _removeAccount, login, $i, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -45,7 +45,7 @@ const init = async () => {
 	});
 };
 
-function menu(account, ev) {
+function menu(account: Misskey.entities.UserDetailed, ev: MouseEvent) {
 	os.popupMenu([{
 		text: i18n.ts.switch,
 		icon: 'ti ti-switch-horizontal',
@@ -58,7 +58,7 @@ function menu(account, ev) {
 	}], ev.currentTarget ?? ev.target);
 }
 
-function addAccount(ev) {
+function addAccount(ev: MouseEvent) {
 	os.popupMenu([{
 		text: i18n.ts.existingAccount,
 		action: () => { addExistingAccount(); },
@@ -68,35 +68,31 @@ function addAccount(ev) {
 	}], ev.currentTarget ?? ev.target);
 }
 
-async function removeAccount(account) {
+async function removeAccount(account: Misskey.entities.UserDetailed) {
 	await _removeAccount(account.id);
 	accounts.value = accounts.value.filter(x => x.id !== account.id);
 }
 
 function addExistingAccount() {
-	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, {
-		done: async res => {
-			await addAccounts(res.id, res.i);
+	getAccountWithSigninDialog().then((res) => {
+		if (res != null) {
 			os.success();
 			init();
-		},
-		closed: () => dispose(),
+		}
 	});
 }
 
 function createAccount() {
-	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
-		done: async res => {
-			await addAccounts(res.id, res.i);
-			switchAccountWithToken(res.i);
-		},
-		closed: () => dispose(),
+	getAccountWithSignupDialog().then((res) => {
+		if (res != null) {
+			switchAccountWithToken(res.token);
+		}
 	});
 }
 
-async function switchAccount(account: any) {
-	const fetchedAccounts: any[] = await getAccounts();
-	const token = fetchedAccounts.find(x => x.id === account.id).token;
+async function switchAccount(account: Misskey.entities.UserDetailed) {
+	const fetchedAccounts = await getAccounts();
+	const token = fetchedAccounts.find(x => x.id === account.id)!.token;
 	switchAccountWithToken(token);
 }
 
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index 0e0c1f4c0ccd..65155035053b 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -14,30 +14,39 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</template>
 		<template #default="{items}">
 			<div class="_gaps">
-				<div v-for="token in items" :key="token.id" class="_panel" :class="$style.app">
-					<img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/>
-					<div :class="$style.appBody">
-						<div :class="$style.appName">{{ token.name }}</div>
-						<div>{{ token.description }}</div>
-						<MkKeyValue oneline>
-							<template #key>{{ i18n.ts.installedDate }}</template>
-							<template #value><MkTime :time="token.createdAt"/></template>
-						</MkKeyValue>
-						<MkKeyValue oneline>
-							<template #key>{{ i18n.ts.lastUsedDate }}</template>
-							<template #value><MkTime :time="token.lastUsedAt"/></template>
-						</MkKeyValue>
-						<details>
-							<summary>{{ i18n.ts.details }}</summary>
+				<MkFolder v-for="token in items" :key="token.id" :defaultOpen="true">
+					<template #icon>
+						<img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/>
+						<i v-else class="ti ti-plug"/>
+					</template>
+					<template #label>{{ token.name }}</template>
+					<template #caption>{{ token.description }}</template>
+					<template #suffix><MkTime :time="token.lastUsedAt"/></template>
+					<template #footer>
+						<MkButton danger @click="revoke(token)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+					</template>
+
+					<div class="_gaps_s">
+						<div v-if="token.description">{{ token.description }}</div>
+						<div>
+							<MkKeyValue oneline>
+								<template #key>{{ i18n.ts.installedDate }}</template>
+								<template #value><MkTime :time="token.createdAt" :mode="'detail'"/></template>
+							</MkKeyValue>
+							<MkKeyValue oneline>
+								<template #key>{{ i18n.ts.lastUsedDate }}</template>
+								<template #value><MkTime :time="token.lastUsedAt" :mode="'detail'"/></template>
+							</MkKeyValue>
+						</div>
+						<MkFolder>
+							<template #label>{{ i18n.ts.permission }}</template>
+							<template #suffix>{{ Object.keys(token.permission).length === 0 ? i18n.ts.none : Object.keys(token.permission).length }}</template>
 							<ul>
 								<li v-for="p in token.permission" :key="p">{{ i18n.ts._permissions[p] }}</li>
 							</ul>
-						</details>
-						<div>
-							<MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton>
-						</div>
+						</MkFolder>
 					</div>
-				</div>
+				</MkFolder>
 			</div>
 		</template>
 	</FormPagination>
@@ -46,12 +55,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
+import * as Misskey from 'misskey-js';
 import FormPagination from '@/components/MkPagination.vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import { infoImageUrl } from '@/instance.js';
 
 const list = ref<InstanceType<typeof FormPagination>>();
@@ -67,7 +78,7 @@ const pagination = {
 
 function revoke(token) {
 	misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
-		list.value.reload();
+		list.value?.reload();
 	});
 }
 
@@ -82,26 +93,9 @@ definePageMetadata(() => ({
 </script>
 
 <style lang="scss" module>
-.app {
-	display: flex;
-	padding: 16px;
-}
-
 .appIcon {
-	display: block;
-	flex-shrink: 0;
-	margin: 0 12px 0 0;
-	width: 50px;
-	height: 50px;
-	border-radius: 8px;
-}
-
-.appBody {
-	width: calc(100% - 62px);
-	position: relative;
-}
-
-.appName {
-	font-weight: bold;
+	width: 20px;
+	height: 20px;
+	border-radius: 4px;
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
index f37b53aebbbf..3c9914b4e291 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
@@ -10,12 +10,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 >
 	<div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ decoration.name }}</MkCondensedLine></div>
 	<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH, offsetX, offsetY }]" forceShowDecoration/>
-	<i v-if="decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.lock" class="ti ti-lock"></i>
+	<i v-if="locked" :class="$style.lock" class="ti ti-lock"></i>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import { signinRequired } from '@/account.js';
 
 const $i = signinRequired();
@@ -37,13 +37,15 @@ const props = defineProps<{
 const emit = defineEmits<{
 	(ev: 'click'): void;
 }>();
+
+const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id)));
 </script>
 
 <style lang="scss" module>
 .root {
 	cursor: pointer;
 	padding: 16px 16px 28px 16px;
-	border: solid 2px var(--divider);
+	border: solid 2px var(--MI_THEME-divider);
 	border-radius: 8px;
 	text-align: center;
 	font-size: 90%;
@@ -52,8 +54,8 @@ const emit = defineEmits<{
 }
 
 .active {
-	background-color: var(--accentedBg);
-	border-color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	border-color: var(--MI_THEME-accent);
 }
 
 .name {
@@ -67,5 +69,6 @@ const emit = defineEmits<{
 	position: absolute;
 	bottom: 12px;
 	right: 12px;
+	color: var(--MI_THEME-warn);
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
index 1938b8d48d9c..40542ad5b285 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.footer" class="_buttonsCenter">
 			<MkButton v-if="usingIndex != null" primary rounded @click="update"><i class="ti ti-check"></i> {{ i18n.ts.update }}</MkButton>
 			<MkButton v-if="usingIndex != null" rounded @click="detach"><i class="ti ti-x"></i> {{ i18n.ts.detach }}</MkButton>
-			<MkButton v-else :disabled="exceeded" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton>
+			<MkButton v-else :disabled="exceeded || locked" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton>
 		</div>
 	</div>
 </MkModalWindow>
@@ -61,6 +61,7 @@ const props = defineProps<{
 		id: string;
 		url: string;
 		name: string;
+		roleIdsThatCanBeUsedThisDecoration: string[];
 	};
 }>();
 
@@ -83,6 +84,7 @@ const emit = defineEmits<{
 
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0);
+const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id)));
 const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0);
 const flipH = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].flipH : null) ?? false);
 const offsetX = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetX : null) ?? 0);
@@ -108,7 +110,7 @@ const decorationsForPreview = computed(() => {
 });
 
 function cancel() {
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 async function update() {
@@ -118,7 +120,7 @@ async function update() {
 		offsetX: offsetX.value,
 		offsetY: offsetY.value,
 	});
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 async function attach() {
@@ -128,12 +130,12 @@ async function attach() {
 		offsetX: offsetX.value,
 		offsetY: offsetY.value,
 	});
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 async function detach() {
 	emit('detach');
-	dialog.value.close();
+	dialog.value?.close();
 }
 </script>
 
@@ -150,8 +152,8 @@ async function detach() {
 	bottom: 0;
 	left: 0;
 	padding: 12px;
-	border-top: solid 0.5px var(--divider);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	border-top: solid 0.5px var(--MI_THEME-divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue
index 77229d3349d0..9fca306f9f26 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.vue
@@ -145,7 +145,7 @@ definePageMetadata(() => ({
 
 .current {
 	padding: 16px;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 }
 
 .decorations {
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 8d2946db63f9..6a13984dd7a6 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -132,7 +132,7 @@ definePageMetadata(() => ({
 	align-items: center;
 
 	&:hover {
-		color: var(--accent);
+		color: var(--MI_THEME-accent);
 	}
 }
 
diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue
index f226647569e7..d452f249b69b 100644
--- a/packages/frontend/src/pages/settings/email.vue
+++ b/packages/frontend/src/pages/settings/email.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkInput v-model="emailAddress" type="email" manualSave>
 			<template #prefix><i class="ti ti-mail"></i></template>
 			<template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template>
-			<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--success);"></i> {{ i18n.ts.emailVerified }}</template>
+			<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i> {{ i18n.ts.emailVerified }}</template>
 		</MkInput>
 	</FormSection>
 
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index dc3e3ee503fe..fd3581d11431 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -113,10 +113,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<option :value="4">{{ i18n.ts.large }}+</option>
 			</MkRadios>
 
-			<MkSwitch v-model="emojiPickerUseDrawerForMobile">
-				{{ i18n.ts.useDrawerReactionPickerForMobile }}
+			<MkSelect v-model="emojiPickerStyle">
+				<template #label>{{ i18n.ts.style }}</template>
 				<template #caption>{{ i18n.ts.needReloadToApply }}</template>
-			</MkSwitch>
+				<option value="auto">{{ i18n.ts.auto }}</option>
+				<option value="popup">{{ i18n.ts.popup }}</option>
+				<option value="drawer">{{ i18n.ts.drawer }}</option>
+			</MkSelect>
 		</div>
 	</FormSection>
 </div>
@@ -128,7 +131,7 @@ import Sortable from 'vuedraggable';
 import MkRadios from '@/components/MkRadios.vue';
 import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
+import MkSelect from '@/components/MkSelect.vue';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
@@ -146,7 +149,7 @@ const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmoji
 const emojiPickerScale = computed(defaultStore.makeGetterSetter('emojiPickerScale'));
 const emojiPickerWidth = computed(defaultStore.makeGetterSetter('emojiPickerWidth'));
 const emojiPickerHeight = computed(defaultStore.makeGetterSetter('emojiPickerHeight'));
-const emojiPickerUseDrawerForMobile = computed(defaultStore.makeGetterSetter('emojiPickerUseDrawerForMobile'));
+const emojiPickerStyle = computed(defaultStore.makeGetterSetter('emojiPickerStyle'));
 
 const removeReaction = (reaction: string, ev: MouseEvent) => remove(pinnedEmojisForReaction, reaction, ev);
 const chooseReaction = (ev: MouseEvent) => pickEmoji(pinnedEmojisForReaction, ev);
@@ -245,9 +248,9 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .tab {
-	margin: calc(var(--margin) / 2) 0;
-	padding: calc(var(--margin) / 2) 0;
-	background: var(--bg);
+	margin: calc(var(--MI-margin) / 2) 0;
+	padding: calc(var(--MI-margin) / 2) 0;
+	background: var(--MI_THEME-bg);
 }
 
 .emojis {
@@ -269,6 +272,6 @@ definePageMetadata(() => ({
 .editorCaption {
 	font-size: 0.85em;
 	padding: 8px 0 0 0;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 15af5617cc5a..1bfdfd0e7665 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -17,13 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</template>
 	</MkSelect>
 
-	<MkRadios v-model="hemisphere">
-		<template #label>{{ i18n.ts.hemisphere }}</template>
-		<option value="N">{{ i18n.ts._hemisphere.N }}</option>
-		<option value="S">{{ i18n.ts._hemisphere.S }}</option>
-		<template #caption>{{ i18n.ts._hemisphere.caption }}</template>
-	</MkRadios>
-
 	<MkRadios v-model="overridedDeviceKind">
 		<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
 		<option :value="null">{{ i18n.ts.auto }}</option>
@@ -132,11 +125,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
 				<MkSwitch v-model="showAvatarDecorations">{{ i18n.ts.showAvatarDecorations }}</MkSwitch>
 				<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
-				<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
 				<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
 				<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
 				<MkSwitch v-model="useNativeUIForVideoAudioPlayer">{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</MkSwitch>
 			</div>
+
+			<MkSelect v-model="menuStyle">
+				<template #label>{{ i18n.ts.menuStyle }}</template>
+				<option value="auto">{{ i18n.ts.auto }}</option>
+				<option value="popup">{{ i18n.ts.popup }}</option>
+				<option value="drawer">{{ i18n.ts.drawer }}</option>
+			</MkSelect>
+
 			<div>
 				<MkRadios v-model="emojiStyle">
 					<template #label>{{ i18n.ts.emojiStyle }}</template>
@@ -225,6 +225,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #label>{{ i18n.ts.other }}</template>
 
 		<div class="_gaps">
+			<MkRadios v-model="hemisphere">
+				<template #label>{{ i18n.ts.hemisphere }}</template>
+				<option value="N">{{ i18n.ts._hemisphere.N }}</option>
+				<option value="S">{{ i18n.ts._hemisphere.S }}</option>
+				<template #caption>{{ i18n.ts._hemisphere.caption }}</template>
+			</MkRadios>
 			<MkFolder>
 				<template #label>{{ i18n.ts.additionalEmojiDictionary }}</template>
 				<div class="_buttons">
@@ -244,6 +250,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
+import { langs } from '@@/js/config.js';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkRadios from '@/components/MkRadios.vue';
@@ -254,11 +261,10 @@ import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/MkLink.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import { langs } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { reloadAsk } from '@/scripts/reload-ask.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
@@ -270,16 +276,6 @@ const fontSize = ref(miLocalStorage.getItem('fontSize'));
 const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
 const dataSaver = ref(defaultStore.state.dataSaver);
 
-async function reloadAsk() {
-	const { canceled } = await os.confirm({
-		type: 'info',
-		text: i18n.ts.reloadToApplySetting,
-	});
-	if (canceled) return;
-
-	unisonReload();
-}
-
 const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
 const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
 const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
@@ -297,7 +293,7 @@ const advancedMfm = computed(defaultStore.makeGetterSetter('advancedMfm'));
 const showReactionsCount = computed(defaultStore.makeGetterSetter('showReactionsCount'));
 const enableQuickAddMfmFunction = computed(defaultStore.makeGetterSetter('enableQuickAddMfmFunction'));
 const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
-const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
+const menuStyle = computed(defaultStore.makeGetterSetter('menuStyle'));
 const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
 const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
 const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
@@ -369,7 +365,7 @@ watch([
 	confirmWhenRevealingSensitiveMedia,
 	contextMenu,
 ], async () => {
-	await reloadAsk();
+	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
 });
 
 const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index 9bb3957a84f9..5acbc5075615 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 				</div>
 			</MkFolder>
-			<MkFolder v-if="$i && !$i.movedTo">
+			<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportFollowing">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkSwitch v-model="withReplies">
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder v-if="$i && !$i.movedTo">
+			<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportUserLists">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
@@ -78,7 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder v-if="$i && !$i.movedTo">
+			<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportMuting">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
@@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder v-if="$i && !$i.movedTo">
+			<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportBlocking">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
@@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder v-if="$i && !$i.movedTo">
+			<MkFolder v-if="$i && !$i.movedTo && $i.policies.canImportAntennas">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 7d16740a3e68..96a95f163586 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 				<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
 					<div class="bkzroven" style="container-type: inline-size;">
-						<RouterView/>
+						<RouterView nested/>
 					</div>
 				</div>
 			</div>
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index f4ee7dffbf0d..4d413d53ab5d 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -244,7 +244,7 @@ definePageMetadata(() => ({
 .userItemSub {
 	padding: 6px 12px;
 	font-size: 85%;
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 
 .userItemMainBody {
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index 7f8460e31640..b189db0f8fd4 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -54,7 +54,7 @@ import MkContainer from '@/components/MkContainer.vue';
 import * as os from '@/os.js';
 import { navbarItemDef } from '@/navbar.js';
 import { defaultStore } from '@/store.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { reloadAsk } from '@/scripts/reload-ask.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -67,16 +67,6 @@ const items = ref(defaultStore.state.menu.map(x => ({
 
 const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
 
-async function reloadAsk() {
-	const { canceled } = await os.confirm({
-		type: 'info',
-		text: i18n.ts.reloadToApplySetting,
-	});
-	if (canceled) return;
-
-	unisonReload();
-}
-
 async function addItem() {
 	const menu = Object.keys(navbarItemDef).filter(k => !defaultStore.state.menu.includes(k));
 	const { canceled, result: item } = await os.select({
@@ -100,7 +90,7 @@ function removeItem(index: number) {
 
 async function save() {
 	defaultStore.set('menu', items.value.map(x => x.type));
-	await reloadAsk();
+	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
 }
 
 function reset() {
@@ -111,7 +101,7 @@ function reset() {
 }
 
 watch(menuDisplay, async () => {
-	await reloadAsk();
+	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
 });
 
 const headerActions = computed(() => []);
@@ -132,7 +122,7 @@ definePageMetadata(() => ({
 	text-overflow: ellipsis;
 	overflow: hidden;
 	white-space: nowrap;
-	color: var(--navFg);
+	color: var(--MI_THEME-navFg);
 }
 
 .itemIcon {
diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue
index a36f036303e4..0ea415f673f0 100644
--- a/packages/frontend/src/pages/settings/notifications.notification-config.vue
+++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue
@@ -6,13 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div class="_gaps_m">
 	<MkSelect v-model="type">
-		<option value="all">{{ i18n.ts.all }}</option>
-		<option value="following">{{ i18n.ts.following }}</option>
-		<option value="follower">{{ i18n.ts.followers }}</option>
-		<option value="mutualFollow">{{ i18n.ts.mutualFollow }}</option>
-		<option value="followingOrFollower">{{ i18n.ts.followingOrFollower }}</option>
-		<option value="list">{{ i18n.ts.userList }}</option>
-		<option value="never">{{ i18n.ts.none }}</option>
+		<option v-for="type in props.configurableTypes ?? notificationConfigTypes" :key="type" :value="type">{{ notificationConfigTypesI18nMap[type] }}</option>
 	</MkSelect>
 
 	<MkSelect v-if="type === 'list'" v-model="userListId">
@@ -21,31 +15,61 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</MkSelect>
 
 	<div class="_buttons">
-		<MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+		<MkButton inline primary :disabled="type === 'list' && userListId === null" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 	</div>
 </div>
 </template>
 
+<script lang="ts">
+const notificationConfigTypes = [
+	'all',
+	'following',
+	'follower',
+	'mutualFollow',
+	'followingOrFollower',
+	'list',
+	'never'
+] as const;
+
+export type NotificationConfig = {
+	type: Exclude<typeof notificationConfigTypes[number], 'list'>;
+} | {
+	type: 'list';
+	userListId: string;
+};
+</script>
+
 <script lang="ts" setup>
-import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { ref } from 'vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
-	value: any;
+	value: NotificationConfig;
 	userLists: Misskey.entities.UserList[];
+	configurableTypes?: NotificationConfig['type'][]; // If not specified, all types are configurable
 }>();
 
 const emit = defineEmits<{
-	(ev: 'update', result: any): void;
+	(ev: 'update', result: NotificationConfig): void;
 }>();
 
+const notificationConfigTypesI18nMap: Record<typeof notificationConfigTypes[number], string> = {
+	all: i18n.ts.all,
+	following: i18n.ts.following,
+	follower: i18n.ts.followers,
+	mutualFollow: i18n.ts.mutualFollow,
+	followingOrFollower: i18n.ts.followingOrFollower,
+	list: i18n.ts.userList,
+	never: i18n.ts.none,
+};
+
 const type = ref(props.value.type);
-const userListId = ref(props.value.userListId);
+const userListId = ref(props.value.type === 'list' ? props.value.userListId : null);
 
 function save() {
-	emit('update', { type: type.value, userListId: userListId.value });
+	emit('update', type.value === 'list' ? { type: type.value, userListId: userListId.value! } : { type: type.value });
 }
 </script>
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index cce671a7cb02..8ffe0d6a7a60 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -22,7 +22,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 					}}
 				</template>
 
-				<XNotificationConfig :userLists="userLists" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" @update="(res) => updateReceiveConfig(type, res)"/>
+				<XNotificationConfig
+					:userLists="userLists"
+					:value="$i.notificationRecieveConfig[type] ?? { type: 'all' }"
+					:configurableTypes="onlyOnOrOffNotificationTypes.includes(type) ? ['all', 'never'] : undefined"
+					@update="(res) => updateReceiveConfig(type, res)"
+				/>
 			</MkFolder>
 		</div>
 	</FormSection>
@@ -58,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { shallowRef, computed } from 'vue';
-import XNotificationConfig from './notifications.notification-config.vue';
+import XNotificationConfig, { type NotificationConfig } from './notifications.notification-config.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -73,7 +78,9 @@ import { notificationTypes } from '@@/js/const.js';
 
 const $i = signinRequired();
 
-const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned'];
+const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[];
+
+const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login'] satisfies (typeof notificationTypes[number])[] as string[];
 
 const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
 const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
@@ -88,7 +95,7 @@ async function readAllNotifications() {
 	await os.apiWithDialog('notifications/mark-all-as-read');
 }
 
-async function updateReceiveConfig(type, value) {
+async function updateReceiveConfig(type: typeof notificationTypes[number], value: NotificationConfig) {
 	await os.apiWithDialog('i/update', {
 		notificationRecieveConfig: {
 			...$i.notificationRecieveConfig,
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index a1cb2ea1c428..4a52e59d022e 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -51,8 +51,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #label>{{ i18n.ts.experimentalFeatures }}</template>
 
 				<div class="_gaps_m">
-					<MkSwitch v-model="enableCondensedLineForAcct">
-						<template #label>Enable condensed line for acct</template>
+					<MkSwitch v-model="enableCondensedLine">
+						<template #label>Enable condensed line</template>
+					</MkSwitch>
+					<MkSwitch v-model="skipNoteRender">
+						<template #label>Enable note render skipping</template>
 					</MkSwitch>
 				</div>
 			</MkFolder>
@@ -98,16 +101,21 @@ import { defaultStore } from '@/store.js';
 import { signout, signinRequired } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { reloadAsk } from '@/scripts/reload-ask.js';
 import FormSection from '@/components/form/section.vue';
 
 const $i = signinRequired();
 
 const reportError = computed(defaultStore.makeGetterSetter('reportError'));
-const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct'));
+const enableCondensedLine = computed(defaultStore.makeGetterSetter('enableCondensedLine'));
+const skipNoteRender = computed(defaultStore.makeGetterSetter('skipNoteRender'));
 const devMode = computed(defaultStore.makeGetterSetter('devMode'));
 const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
 
+watch(skipNoteRender, async () => {
+	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
+});
+
 async function deleteAccount() {
 	{
 		const { canceled } = await os.confirm({
@@ -132,16 +140,6 @@ async function deleteAccount() {
 	await signout();
 }
 
-async function reloadAsk() {
-	const { canceled } = await os.confirm({
-		type: 'info',
-		text: i18n.ts.reloadToApplySetting,
-	});
-	if (canceled) return;
-
-	unisonReload();
-}
-
 async function updateRepliesAll(withReplies: boolean) {
 	const { canceled } = await os.confirm({
 		type: 'warning',
@@ -152,12 +150,6 @@ async function updateRepliesAll(withReplies: boolean) {
 	misskeyApi('following/update-all', { withReplies });
 }
 
-watch([
-	enableCondensedLineForAcct,
-], async () => {
-	await reloadAsk();
-});
-
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 86a044490d64..7388e014ed5d 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -6,12 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div class="_gaps_m">
 	<div :class="$style.buttons">
-		<MkButton inline primary @click="saveNew">{{ ts._preferencesBackups.saveNew }}</MkButton>
-		<MkButton inline @click="loadFile">{{ ts._preferencesBackups.loadFile }}</MkButton>
+		<MkButton inline primary @click="saveNew">{{ i18n.ts._preferencesBackups.saveNew }}</MkButton>
+		<MkButton inline @click="loadFile">{{ i18n.ts._preferencesBackups.loadFile }}</MkButton>
 	</div>
 
 	<FormSection>
-		<template #label>{{ ts._preferencesBackups.list }}</template>
+		<template #label>{{ i18n.ts._preferencesBackups.list }}</template>
 		<template v-if="profiles && Object.keys(profiles).length > 0">
 			<div class="_gaps_s">
 				<div
@@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 					@contextmenu.prevent.stop="$event => menu($event, id)"
 				>
 					<div :class="$style.profileName">{{ profile.name }}</div>
-					<div :class="$style.profileTime">{{ t('_preferencesBackups.createdAt', { date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
-					<div v-if="profile.updatedAt" :class="$style.profileTime">{{ t('_preferencesBackups.updatedAt', { date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
+					<div :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.createdAt({ date: (new Date(profile.createdAt)).toLocaleDateString(), time: (new Date(profile.createdAt)).toLocaleTimeString() }) }}</div>
+					<div v-if="profile.updatedAt" :class="$style.profileTime">{{ i18n.tsx._preferencesBackups.updatedAt({ date: (new Date(profile.updatedAt)).toLocaleDateString(), time: (new Date(profile.updatedAt)).toLocaleTimeString() }) }}</div>
 				</div>
 			</div>
 		</template>
 		<div v-else-if="profiles">
-			<MkInfo>{{ ts._preferencesBackups.noBackups }}</MkInfo>
+			<MkInfo>{{ i18n.ts._preferencesBackups.noBackups }}</MkInfo>
 		</div>
 		<MkLoading v-else/>
 	</FormSection>
@@ -39,6 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
+import { version, host } from '@@/js/config.js';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -49,10 +50,8 @@ import { unisonReload } from '@/scripts/unison-reload.js';
 import { useStream } from '@/stream.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { version, host } from '@@/js/config.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
-const { t, ts } = i18n;
 
 const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'collapseRenotes',
@@ -76,7 +75,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'dataSaver',
 	'disableShowingAnimatedImages',
 	'emojiStyle',
-	'disableDrawer',
+	'menuStyle',
 	'useBlurEffectForModal',
 	'useBlurEffect',
 	'showFixedPostForm',
@@ -88,7 +87,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'emojiPickerScale',
 	'emojiPickerWidth',
 	'emojiPickerHeight',
-	'emojiPickerUseDrawerForMobile',
+	'emojiPickerStyle',
 	'defaultSideView',
 	'menuDisplay',
 	'reportError',
@@ -104,7 +103,6 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'mediaListWithOneImageAppearance',
 	'notificationPosition',
 	'notificationStackAxis',
-	'enableCondensedLineForAcct',
 	'keepScreenOn',
 	'defaultWithReplies',
 	'disableStreamingTimeline',
@@ -201,15 +199,15 @@ async function saveNew(): Promise<void> {
 	if (!profiles.value) return;
 
 	const { canceled, result: name } = await os.inputText({
-		title: ts._preferencesBackups.inputName,
+		title: i18n.ts._preferencesBackups.inputName,
 		default: '',
 	});
 	if (canceled) return;
 
 	if (Object.values(profiles.value).some(x => x.name === name)) {
 		return os.alert({
-			title: ts._preferencesBackups.cannotSave,
-			text: t('_preferencesBackups.nameAlreadyExists', { name }),
+			title: i18n.ts._preferencesBackups.cannotSave,
+			text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
 		});
 	}
 
@@ -238,8 +236,8 @@ function loadFile(): void {
 		if (file.type !== 'application/json') {
 			return os.alert({
 				type: 'error',
-				title: ts._preferencesBackups.cannotLoad,
-				text: ts._preferencesBackups.invalidFile,
+				title: i18n.ts._preferencesBackups.cannotLoad,
+				text: i18n.ts._preferencesBackups.invalidFile,
 			});
 		}
 
@@ -250,7 +248,7 @@ function loadFile(): void {
 		} catch (err) {
 			return os.alert({
 				type: 'error',
-				title: ts._preferencesBackups.cannotLoad,
+				title: i18n.ts._preferencesBackups.cannotLoad,
 				text: (err as any)?.message ?? '',
 			});
 		}
@@ -276,8 +274,8 @@ async function applyProfile(id: string): Promise<void> {
 
 	const { canceled: cancel1 } = await os.confirm({
 		type: 'warning',
-		title: ts._preferencesBackups.apply,
-		text: t('_preferencesBackups.applyConfirm', { name: profile.name }),
+		title: i18n.ts._preferencesBackups.apply,
+		text: i18n.tsx._preferencesBackups.applyConfirm({ name: profile.name }),
 	});
 	if (cancel1) return;
 
@@ -322,7 +320,7 @@ async function applyProfile(id: string): Promise<void> {
 
 	const { canceled: cancel2 } = await os.confirm({
 		type: 'info',
-		text: ts.reloadToApplySetting,
+		text: i18n.ts.reloadToApplySetting,
 	});
 	if (cancel2) return;
 
@@ -334,8 +332,8 @@ async function deleteProfile(id: string): Promise<void> {
 
 	const { canceled } = await os.confirm({
 		type: 'info',
-		title: ts.delete,
-		text: t('deleteAreYouSure', { x: profiles.value[id].name }),
+		title: i18n.ts.delete,
+		text: i18n.tsx.deleteAreYouSure({ x: profiles.value[id].name }),
 	});
 	if (canceled) return;
 
@@ -350,8 +348,8 @@ async function save(id: string): Promise<void> {
 
 	const { canceled } = await os.confirm({
 		type: 'info',
-		title: ts._preferencesBackups.save,
-		text: t('_preferencesBackups.saveConfirm', { name }),
+		title: i18n.ts._preferencesBackups.save,
+		text: i18n.tsx._preferencesBackups.saveConfirm({ name }),
 	});
 	if (canceled) return;
 
@@ -370,15 +368,15 @@ async function rename(id: string): Promise<void> {
 	if (!profiles.value) return;
 
 	const { canceled: cancel1, result: name } = await os.inputText({
-		title: ts._preferencesBackups.inputName,
+		title: i18n.ts._preferencesBackups.inputName,
 		default: '',
 	});
 	if (cancel1 || profiles.value[id].name === name) return;
 
 	if (Object.values(profiles.value).some(x => x.name === name)) {
 		return os.alert({
-			title: ts._preferencesBackups.cannotSave,
-			text: t('_preferencesBackups.nameAlreadyExists', { name }),
+			title: i18n.ts._preferencesBackups.cannotSave,
+			text: i18n.tsx._preferencesBackups.nameAlreadyExists({ name }),
 		});
 	}
 
@@ -386,8 +384,8 @@ async function rename(id: string): Promise<void> {
 
 	const { canceled: cancel2 } = await os.confirm({
 		type: 'info',
-		title: ts.rename,
-		text: t('_preferencesBackups.renameConfirm', { old: registry.name, new: name }),
+		title: i18n.ts.rename,
+		text: i18n.tsx._preferencesBackups.renameConfirm({ old: registry.name, new: name }),
 	});
 	if (cancel2) return;
 
@@ -399,25 +397,25 @@ function menu(ev: MouseEvent, profileId: string) {
 	if (!profiles.value) return;
 
 	return os.popupMenu([{
-		text: ts._preferencesBackups.apply,
+		text: i18n.ts._preferencesBackups.apply,
 		icon: 'ti ti-check',
 		action: () => applyProfile(profileId),
 	}, {
 		type: 'a',
-		text: ts.download,
+		text: i18n.ts.download,
 		icon: 'ti ti-download',
 		href: URL.createObjectURL(new Blob([JSON.stringify(profiles.value[profileId], null, 2)], { type: 'application/json' })),
 		download: `${profiles.value[profileId].name}.json`,
 	}, { type: 'divider' }, {
-		text: ts.rename,
+		text: i18n.ts.rename,
 		icon: 'ti ti-forms',
 		action: () => rename(profileId),
 	}, {
-		text: ts._preferencesBackups.save,
+		text: i18n.ts._preferencesBackups.save,
 		icon: 'ti ti-device-floppy',
 		action: () => save(profileId),
 	}, { type: 'divider' }, {
-		text: ts.delete,
+		text: i18n.ts.delete,
 		icon: 'ti ti-trash',
 		action: () => deleteProfile(profileId),
 		danger: true,
@@ -439,7 +437,7 @@ onUnmounted(() => {
 });
 
 definePageMetadata(() => ({
-	title: ts.preferencesBackups,
+	title: i18n.ts.preferencesBackups,
 	icon: 'ti ti-device-floppy',
 }));
 </script>
@@ -447,7 +445,7 @@ definePageMetadata(() => ({
 <style lang="scss" module>
 .buttons {
 	display: flex;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 	flex-wrap: wrap;
 }
 
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index d418be624ef2..da3d36b31add 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
 	</MkSwitch>
 	<MkSwitch v-model="preventAiLearning" @update:modelValue="save()">
-		{{ i18n.ts.preventAiLearning }}<span class="_beta">{{ i18n.ts.beta }}</span>
+		{{ i18n.ts.preventAiLearning }}
 		<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template>
 	</MkSwitch>
 	<MkSwitch v-model="isExplorable" @update:modelValue="save()">
@@ -44,6 +44,93 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
 	</MkSwitch>
 
+	<FormSection>
+		<template #label>{{ i18n.ts.lockdown }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
+
+		<div class="_gaps_m">
+			<MkSwitch v-model="requireSigninToViewContents" @update:modelValue="save()">
+				{{ i18n.ts._accountSettings.requireSigninToViewContents }}
+				<template #caption>
+					<div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div>
+					<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div>
+					<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div>
+				</template>
+			</MkSwitch>
+
+			<FormSlot>
+				<template #label>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</template>
+
+				<div class="_gaps_s">
+					<MkSelect :modelValue="makeNotesFollowersOnlyBefore_type" @update:modelValue="makeNotesFollowersOnlyBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
+						<option :value="null">{{ i18n.ts.none }}</option>
+						<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
+						<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
+					</MkSelect>
+
+					<MkSelect v-if="makeNotesFollowersOnlyBefore_type === 'relative'" v-model="makeNotesFollowersOnlyBefore">
+						<option :value="-3600">{{ i18n.ts.oneHour }}</option>
+						<option :value="-86400">{{ i18n.ts.oneDay }}</option>
+						<option :value="-259200">{{ i18n.ts.threeDays }}</option>
+						<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
+						<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
+						<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
+						<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
+					</MkSelect>
+
+					<MkInput
+						v-if="makeNotesFollowersOnlyBefore_type === 'absolute'"
+						:modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')"
+						type="date"
+						:manualSave="true"
+						@update:modelValue="makeNotesFollowersOnlyBefore = Math.floor(new Date($event).getTime() / 1000)"
+					>
+					</MkInput>
+				</div>
+
+				<template #caption>
+					<div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div>
+					<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
+				</template>
+			</FormSlot>
+
+			<FormSlot>
+				<template #label>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</template>
+
+				<div class="_gaps_s">
+					<MkSelect :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
+						<option :value="null">{{ i18n.ts.none }}</option>
+						<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
+						<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
+					</MkSelect>
+
+					<MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore">
+						<option :value="-3600">{{ i18n.ts.oneHour }}</option>
+						<option :value="-86400">{{ i18n.ts.oneDay }}</option>
+						<option :value="-259200">{{ i18n.ts.threeDays }}</option>
+						<option :value="-604800">{{ i18n.ts.oneWeek }}</option>
+						<option :value="-2592000">{{ i18n.ts.oneMonth }}</option>
+						<option :value="-7776000">{{ i18n.ts.threeMonths }}</option>
+						<option :value="-31104000">{{ i18n.ts.oneYear }}</option>
+					</MkSelect>
+
+					<MkInput
+						v-if="makeNotesHiddenBefore_type === 'absolute'"
+						:modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')"
+						type="date"
+						:manualSave="true"
+						@update:modelValue="makeNotesHiddenBefore = Math.floor(new Date($event).getTime() / 1000)"
+					>
+					</MkInput>
+				</div>
+
+				<template #caption>
+					<div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div>
+					<div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div>
+				</template>
+			</FormSlot>
+		</div>
+	</FormSection>
+
 	<FormSection>
 		<div class="_gaps_m">
 			<MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
@@ -72,7 +159,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSection from '@/components/form/section.vue';
@@ -82,6 +169,9 @@ import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { signinRequired } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import FormSlot from '@/components/form/slot.vue';
+import { formatDateTimeString } from '@/scripts/format-time-string.js';
+import MkInput from '@/components/MkInput.vue';
 
 const $i = signinRequired();
 
@@ -90,6 +180,9 @@ const autoAcceptFollowed = ref($i.autoAcceptFollowed);
 const noCrawle = ref($i.noCrawle);
 const preventAiLearning = ref($i.preventAiLearning);
 const isExplorable = ref($i.isExplorable);
+const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false);
+const makeNotesFollowersOnlyBefore = ref($i.makeNotesFollowersOnlyBefore ?? null);
+const makeNotesHiddenBefore = ref($i.makeNotesHiddenBefore ?? null);
 const hideOnlineStatus = ref($i.hideOnlineStatus);
 const publicReactions = ref($i.publicReactions);
 const followingVisibility = ref($i.followingVisibility);
@@ -100,6 +193,30 @@ const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNote
 const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
 const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
 
+const makeNotesFollowersOnlyBefore_type = computed(() => {
+	if (makeNotesFollowersOnlyBefore.value == null) {
+		return null;
+	} else if (makeNotesFollowersOnlyBefore.value >= 0) {
+		return 'absolute';
+	} else {
+		return 'relative';
+	}
+});
+
+const makeNotesHiddenBefore_type = computed(() => {
+	if (makeNotesHiddenBefore.value == null) {
+		return null;
+	} else if (makeNotesHiddenBefore.value >= 0) {
+		return 'absolute';
+	} else {
+		return 'relative';
+	}
+});
+
+watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => {
+	save();
+});
+
 function save() {
 	misskeyApi('i/update', {
 		isLocked: !!isLocked.value,
@@ -107,6 +224,9 @@ function save() {
 		noCrawle: !!noCrawle.value,
 		preventAiLearning: !!preventAiLearning.value,
 		isExplorable: !!isExplorable.value,
+		requireSigninToViewContents: !!requireSigninToViewContents.value,
+		makeNotesFollowersOnlyBefore: makeNotesFollowersOnlyBefore.value,
+		makeNotesHiddenBefore: makeNotesHiddenBefore.value,
 		hideOnlineStatus: !!hideOnlineStatus.value,
 		publicReactions: !!publicReactions.value,
 		followingVisibility: followingVisibility.value,
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index a32893368688..561894d2b77b 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -46,14 +46,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkFolder>
 			<template #icon><i class="ti ti-list"></i></template>
 			<template #label>{{ i18n.ts._profile.metadataEdit }}</template>
-
-			<div :class="$style.metadataRoot">
-				<div :class="$style.metadataMargin">
-					<MkButton :disabled="fields.length >= 16" inline style="margin-right: 8px;" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
-					<MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" inline danger style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
-					<MkButton v-else inline style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
-					<MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+			<template #footer>
+				<div class="_buttons">
+					<MkButton primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+					<MkButton :disabled="fields.length >= 16" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+					<MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" danger @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+					<MkButton v-else @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
 				</div>
+			</template>
+
+			<div :class="$style.metadataRoot" class="_gaps_s">
+				<MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
 
 				<Sortable
 					v-model="fields"
@@ -65,37 +68,32 @@ SPDX-License-Identifier: AGPL-3.0-only
 					@end="e => e.item.classList.remove('active')"
 				>
 					<template #item="{element, index}">
-						<div :class="$style.fieldDragItem">
+						<div v-panel :class="$style.fieldDragItem">
 							<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
 							<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
 							<div :class="$style.dragItemForm">
 								<FormSplit :minWidth="200">
-									<MkInput v-model="element.name" small>
-										<template #label>{{ i18n.ts._profile.metadataLabel }}</template>
+									<MkInput v-model="element.name" small :placeholder="i18n.ts._profile.metadataLabel">
 									</MkInput>
-									<MkInput v-model="element.value" small>
-										<template #label>{{ i18n.ts._profile.metadataContent }}</template>
+									<MkInput v-model="element.value" small :placeholder="i18n.ts._profile.metadataContent">
 									</MkInput>
 								</FormSplit>
 							</div>
 						</div>
 					</template>
 				</Sortable>
-
-				<MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo>
 			</div>
 		</MkFolder>
 		<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
 	</FormSlot>
 
-	<MkFolder>
-		<template #label>{{ i18n.ts.advancedSettings }}</template>
-
-		<div class="_gaps_m">
-			<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
-			<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
-		</div>
-	</MkFolder>
+	<MkInput v-model="profile.followedMessage" :max="200" manualSave :mfmPreview="false">
+		<template #label>{{ i18n.ts._profile.followedMessage }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
+		<template #caption>
+			<div>{{ i18n.ts._profile.followedMessageDescription }}</div>
+			<div>{{ i18n.ts._profile.followedMessageDescriptionForLockedAccount }}</div>
+		</template>
+	</MkInput>
 
 	<MkSelect v-model="reactionAcceptance">
 		<template #label>{{ i18n.ts.reactionAcceptance }}</template>
@@ -105,6 +103,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
 		<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
 	</MkSelect>
+
+	<MkFolder>
+		<template #label>{{ i18n.ts.advancedSettings }}</template>
+
+		<div class="_gaps_m">
+			<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
+			<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
+		</div>
+	</MkFolder>
 </div>
 </template>
 
@@ -135,12 +142,17 @@ const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.d
 
 const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
 
+function assertVaildLang(lang: string | null): lang is keyof typeof langmap {
+	return lang != null && lang in langmap;
+}
+
 const profile = reactive({
 	name: $i.name,
 	description: $i.description,
+	followedMessage: $i.followedMessage,
 	location: $i.location,
 	birthday: $i.birthday,
-	lang: $i.lang,
+	lang: assertVaildLang($i.lang) ? $i.lang : null,
 	isBot: $i.isBot ?? false,
 	isCat: $i.isCat ?? false,
 });
@@ -185,6 +197,8 @@ function save() {
 		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
 		description: profile.description || null,
 		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+		followedMessage: profile.followedMessage || null,
+		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
 		location: profile.location || null,
 		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
 		birthday: profile.birthday || null,
@@ -192,6 +206,11 @@ function save() {
 		lang: profile.lang || null,
 		isBot: !!profile.isBot,
 		isCat: !!profile.isCat,
+	}, undefined, {
+		'0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191': {
+			title: i18n.ts.yourNameContainsProhibitedWords,
+			text: i18n.ts.yourNameContainsProhibitedWordsDescription,
+		},
 	});
 	globalEvents.emit('requestClearPageCache');
 	claimAchievement('profileFilled');
@@ -272,7 +291,7 @@ definePageMetadata(() => ({
 	height: 130px;
 	background-size: cover;
 	background-position: center;
-	border-bottom: solid 1px var(--divider);
+	border-bottom: solid 1px var(--MI_THEME-divider);
 	overflow: clip;
 }
 
@@ -299,19 +318,11 @@ definePageMetadata(() => ({
 	container-type: inline-size;
 }
 
-.metadataMargin {
-	margin-bottom: 1.5em;
-}
-
 .fieldDragItem {
 	display: flex;
-	padding-bottom: .75em;
+	padding: 10px;
 	align-items: flex-end;
-	border-bottom: solid 0.5px var(--divider);
-
-	&:last-child {
-		border-bottom: 0;
-	}
+	border-radius: 6px;
 
 	/* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */
 	@container (max-width: 452px) {
diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue
index de0f63630edd..8f9d4f858b72 100644
--- a/packages/frontend/src/pages/settings/security.vue
+++ b/packages/frontend/src/pages/settings/security.vue
@@ -124,7 +124,7 @@ definePageMetadata(() => ({
 	}
 
 	&:not(:last-child) {
-		border-bottom: solid 0.5px var(--divider);
+		border-bottom: solid 0.5px var(--MI_THEME-divider);
 	}
 
 	> header {
@@ -136,11 +136,11 @@ definePageMetadata(() => ({
 			margin-right: 0.75em;
 
 			&.succ {
-				color: var(--success);
+				color: var(--MI_THEME-success);
 			}
 
 			&.fail {
-				color: var(--error);
+				color: var(--MI_THEME-error);
 			}
 		}
 
diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue
index 81478fede58f..56f65e230996 100644
--- a/packages/frontend/src/pages/settings/sounds.sound.vue
+++ b/packages/frontend/src/pages/settings/sounds.sound.vue
@@ -194,7 +194,7 @@ function save() {
 	flex-grow: 1;
 	min-width: 0;
 	font-weight: 700;
-	color: var(--error);
+	color: var(--MI_THEME-error);
 }
 
 .fileSelectorButton {
@@ -203,6 +203,6 @@ function save() {
 
 .fileNotSelected {
 	font-weight: 700;
-	color: var(--infoWarnFg);
+	color: var(--MI_THEME-infoWarnFg);
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index 7d192bcbea6e..f1ec23158826 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -88,19 +88,9 @@ import { uniqueBy } from '@/scripts/array.js';
 import { fetchThemes, getThemes } from '@/theme-store.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
-import { unisonReload } from '@/scripts/unison-reload.js';
+import { reloadAsk } from '@/scripts/reload-ask.js';
 import * as os from '@/os.js';
 
-async function reloadAsk() {
-	const { canceled } = await os.confirm({
-		type: 'info',
-		text: i18n.ts.reloadToApplySetting,
-	});
-	if (canceled) return;
-
-	unisonReload();
-}
-
 const installedThemes = ref(getThemes());
 const builtinThemes = getBuiltinThemesRef();
 
@@ -148,13 +138,13 @@ watch(syncDeviceDarkMode, () => {
 	}
 });
 
-watch(wallpaper, () => {
+watch(wallpaper, async () => {
 	if (wallpaper.value == null) {
 		miLocalStorage.removeItem('wallpaper');
 	} else {
 		miLocalStorage.setItem('wallpaper', wallpaper.value);
 	}
-	reloadAsk();
+	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
 });
 
 onActivated(() => {
@@ -214,7 +204,7 @@ definePageMetadata(() => ({
 		}
 
 		.dn:focus-visible ~ .toggle {
-			outline: 2px solid var(--focus);
+			outline: 2px solid var(--MI_THEME-focus);
 			outline-offset: 2px;
 		}
 
@@ -237,12 +227,12 @@ definePageMetadata(() => ({
 
 			> .before {
 				left: -70px;
-				color: var(--accent);
+				color: var(--MI_THEME-accent);
 			}
 
 			> .after {
 				right: -68px;
-				color: var(--fg);
+				color: var(--MI_THEME-fg);
 			}
 		}
 
@@ -360,11 +350,11 @@ definePageMetadata(() => ({
 				background-color: #749DD6;
 
 				> .before {
-					color: var(--fg);
+					color: var(--MI_THEME-fg);
 				}
 
 				> .after {
-					color: var(--accent);
+					color: var(--MI_THEME-accent);
 				}
 
 				.toggle__handler {
@@ -415,14 +405,14 @@ definePageMetadata(() => ({
 
 	> .sync {
 		padding: 14px 16px;
-		border-top: solid 0.5px var(--divider);
+		border-top: solid 0.5px var(--MI_THEME-divider);
 	}
 }
 
 .rsljpzjq {
 	> .selects {
 		display: flex;
-		gap: 1.5em var(--margin);
+		gap: 1.5em var(--MI-margin);
 		flex-wrap: wrap;
 
 		> .select {
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 058ef69c35e3..40d23e36c523 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -21,14 +21,41 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<FormSection>
 		<template #label>{{ i18n.ts._webhookSettings.trigger }}</template>
 
-		<div class="_gaps_s">
-			<MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch>
-			<MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch>
-			<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
-			<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
-			<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
-			<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
-			<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
+		<div class="_gaps">
+			<div class="_gaps_s">
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_follow">{{ i18n.ts._webhookSettings._events.follow }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_follow)" @click="test('follow')"><i class="ti ti-send"></i></MkButton>
+				</div>
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_followed">{{ i18n.ts._webhookSettings._events.followed }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_followed)" @click="test('followed')"><i class="ti ti-send"></i></MkButton>
+				</div>
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_note)" @click="test('note')"><i class="ti ti-send"></i></MkButton>
+				</div>
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reply)" @click="test('reply')"><i class="ti ti-send"></i></MkButton>
+				</div>
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton>
+				</div>
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton>
+				</div>
+				<div :class="$style.switchBox">
+					<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
+					<MkButton transparent :class="$style.testButton" :disabled="!(active && event_mention)" @click="test('mention')"><i class="ti ti-send"></i></MkButton>
+				</div>
+			</div>
+
+			<div :class="$style.description">
+				{{ i18n.ts._webhookSettings.testRemarks }}
+			</div>
 		</div>
 	</FormSection>
 
@@ -43,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkInput from '@/components/MkInput.vue';
 import FormSection from '@/components/form/section.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -76,8 +104,8 @@ const event_renote = ref(webhook.on.includes('renote'));
 const event_reaction = ref(webhook.on.includes('reaction'));
 const event_mention = ref(webhook.on.includes('mention'));
 
-async function save(): Promise<void> {
-	const events = [];
+function save() {
+	const events: Misskey.entities.UserWebhook['on'] = [];
 	if (event_follow.value) events.push('follow');
 	if (event_followed.value) events.push('followed');
 	if (event_note.value) events.push('note');
@@ -110,8 +138,21 @@ async function del(): Promise<void> {
 	router.push('/settings/webhook');
 }
 
+async function test(type: Misskey.entities.UserWebhook['on'][number]): Promise<void> {
+	await os.apiWithDialog('i/webhooks/test', {
+		webhookId: props.webhookId,
+		type,
+		override: {
+			secret: secret.value,
+			url: url.value,
+		},
+	});
+}
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 const headerActions = computed(() => []);
 
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
 const headerTabs = computed(() => []);
 
 definePageMetadata(() => ({
@@ -119,3 +160,30 @@ definePageMetadata(() => ({
 	icon: 'ti ti-webhook',
 }));
 </script>
+
+<style module lang="scss">
+.switchBox {
+	display: flex;
+	align-items: center;
+	justify-content: start;
+
+	.testButton {
+		$buttonSize: 28px;
+		padding: 0;
+		width: $buttonSize;
+		min-width: $buttonSize;
+		max-width: $buttonSize;
+		height: $buttonSize;
+		margin-left: auto;
+		line-height: inherit;
+		font-size: 90%;
+		border-radius: 9999px;
+	}
+}
+
+.description {
+	font-size: 0.85em;
+	padding: 8px 0 0 0;
+	color: var(--MI_THEME-fgTransparentWeak);
+}
+</style>
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
index 0d11b00c97d0..af8b7ca945f6 100644
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ b/packages/frontend/src/pages/settings/webhook.vue
@@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #icon>
 							<i v-if="webhook.active === false" class="ti ti-player-pause"></i>
 							<i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i>
-							<i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--success)' }"></i>
-							<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"></i>
+							<i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--MI_THEME-success)' }"></i>
+							<i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i>
 						</template>
 						{{ webhook.name || webhook.url }}
 						<template #suffix>
diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue
index 8c2f7042cdea..14fb96d4f18a 100644
--- a/packages/frontend/src/pages/signup-complete.vue
+++ b/packages/frontend/src/pages/signup-complete.vue
@@ -71,7 +71,7 @@ place-content: center;
 .form {
 	position: relative;
 	z-index: 10;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
 	overflow: clip;
 	max-width: 500px;
@@ -81,7 +81,7 @@ place-content: center;
 	padding: 16px;
 	text-align: center;
 	font-size: 26px;
-	background-color: var(--accentedBg);
-	color: var(--accent);
+	background-color: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 }
 </style>
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index 1b3e1ecaee67..b669e251793b 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -76,10 +76,10 @@ definePageMetadata(() => ({
 
 <style lang="scss" module>
 .footer {
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	background: var(--acrylicBg);
-	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
+	background: var(--MI_THEME-acrylicBg);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 	display: flex;
 }
 
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index a62fe5d581d0..b2e084f53f0f 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -268,7 +268,7 @@ definePageMetadata(() => ({
 				}
 
 				&.active {
-					box-shadow: 0 0 0 2px var(--divider) inset;
+					box-shadow: 0 0 0 2px var(--MI_THEME-divider) inset;
 				}
 
 				&.rounded {
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index cc1ed3d01f29..044a1908ab41 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -9,19 +9,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkSpacer :contentMax="800">
 		<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
 			<div :key="src" ref="rootEl">
-				<MkInfo v-if="isBasicTimeline(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
+				<MkInfo v-if="isBasicTimeline(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
 					{{ i18n.ts._timelineDescription[src] }}
 				</MkInfo>
-				<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
+				<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/>
 				<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
 				<div :class="$style.tl">
 					<MkTimeline
 						ref="tlComponent"
-						:key="src + withRenotes + withReplies + onlyFiles"
+						:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
 						:src="src.split(':')[0]"
 						:list="src.split(':')[1]"
 						:withRenotes="withRenotes"
 						:withReplies="withReplies"
+						:withSensitive="withSensitive"
 						:onlyFiles="onlyFiles"
 						:sound="true"
 						@queue="queueUpdated"
@@ -50,7 +51,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { deepMerge } from '@/scripts/merge.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
 import type { BasicTimelineType } from '@/timelines.js';
@@ -121,11 +122,6 @@ watch(src, () => {
 	queue.value = 0;
 });
 
-watch(withSensitive, () => {
-	// これだけはクライアント側で完結する処理なので手動でリロード
-	tlComponent.value?.reloadTimeline();
-});
-
 function queueUpdated(q: number): void {
 	queue.value = q;
 }
@@ -189,7 +185,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
 		}),
 		(channels.length === 0 ? undefined : { type: 'divider' }),
 		{
-			type: 'link' as const,
+			type: 'link',
 			icon: 'ti ti-plus',
 			text: i18n.ts.createNew,
 			to: '/channels',
@@ -258,16 +254,24 @@ const headerActions = computed(() => {
 			icon: 'ti ti-dots',
 			text: i18n.ts.options,
 			handler: (ev) => {
-				os.popupMenu([{
+				const menuItems: MenuItem[] = [];
+
+				menuItems.push({
 					type: 'switch',
 					text: i18n.ts.showRenotes,
 					ref: withRenotes,
-				}, isBasicTimeline(src.value) && hasWithReplies(src.value) ? {
-					type: 'switch',
-					text: i18n.ts.showRepliesToOthersInTimeline,
-					ref: withReplies,
-					disabled: onlyFiles,
-				} : undefined, {
+				});
+
+				if (isBasicTimeline(src.value) && hasWithReplies(src.value)) {
+					menuItems.push({
+						type: 'switch',
+						text: i18n.ts.showRepliesToOthersInTimeline,
+						ref: withReplies,
+						disabled: onlyFiles,
+					});
+				}
+
+				menuItems.push({
 					type: 'switch',
 					text: i18n.ts.withSensitive,
 					ref: withSensitive,
@@ -276,7 +280,9 @@ const headerActions = computed(() => {
 					text: i18n.ts.fileAttachedOnly,
 					ref: onlyFiles,
 					disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false,
-				}], ev.currentTarget ?? ev.target);
+				});
+
+				os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 			},
 		},
 	];
@@ -335,30 +341,30 @@ definePageMetadata(() => ({
 <style lang="scss" module>
 .new {
 	position: sticky;
-	top: calc(var(--stickyTop, 0px) + 16px);
+	top: calc(var(--MI-stickyTop, 0px) + 16px);
 	z-index: 1000;
 	width: 100%;
 	margin: calc(-0.675em - 8px) 0;
 
 	&:first-child {
-		margin-top: calc(-0.675em - 8px - var(--margin));
+		margin-top: calc(-0.675em - 8px - var(--MI-margin));
 	}
 }
 
 .newButton {
 	display: block;
-	margin: var(--margin) auto 0 auto;
+	margin: var(--MI-margin) auto 0 auto;
 	padding: 8px 16px;
 	border-radius: 32px;
 }
 
 .postForm {
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 }
 
 .tl {
-	background: var(--bg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-bg);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 31a3f1b0607b..3efeb46c0a86 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -79,26 +79,26 @@ definePageMetadata(() => ({
 <style lang="scss" module>
 .new {
 	position: sticky;
-	top: calc(var(--stickyTop, 0px) + 16px);
+	top: calc(var(--MI-stickyTop, 0px) + 16px);
 	z-index: 1000;
 	width: 100%;
 	margin: calc(-0.675em - 8px) 0;
 
 	&:first-child {
-		margin-top: calc(-0.675em - 8px - var(--margin));
+		margin-top: calc(-0.675em - 8px - var(--MI-margin));
 	}
 }
 
 .newButton {
 	display: block;
-	margin: var(--margin) auto 0 auto;
+	margin: var(--MI-margin) auto 0 auto;
 	padding: 8px 16px;
 	border-radius: 32px;
 }
 
 .tl {
-	background: var(--bg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-bg);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/user/clips.vue b/packages/frontend/src/pages/user/clips.vue
index ac01cff8cdb7..38ce78e8d538 100644
--- a/packages/frontend/src/pages/user/clips.vue
+++ b/packages/frontend/src/pages/user/clips.vue
@@ -43,6 +43,6 @@ const pagination = {
 .description {
 	margin-top: 8px;
 	padding-top: 8px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/pages/user/follow-list.vue b/packages/frontend/src/pages/user/follow-list.vue
index e60dccec1766..868767e8f470 100644
--- a/packages/frontend/src/pages/user/follow-list.vue
+++ b/packages/frontend/src/pages/user/follow-list.vue
@@ -45,6 +45,6 @@ const followersPagination = {
 .users {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
-	grid-gap: var(--margin);
+	grid-gap: var(--MI-margin);
 }
 </style>
diff --git a/packages/frontend/src/pages/user/gallery.vue b/packages/frontend/src/pages/user/gallery.vue
index 9ba81322ba3b..0bc562852842 100644
--- a/packages/frontend/src/pages/user/gallery.vue
+++ b/packages/frontend/src/pages/user/gallery.vue
@@ -38,6 +38,6 @@ const pagination = {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
 	grid-gap: 12px;
-	margin: var(--margin);
+	margin: var(--MI-margin);
 }
 </style>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 8e0292c7fe38..2794db2821ab 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<MkUserName class="name" :user="user" :nowrap="true"/>
 							<div class="bottom">
 								<span class="username"><MkAcct :user="user" :detail="true"/></span>
-								<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span>
+								<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span>
 								<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
 								<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
 								<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
@@ -42,11 +42,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<MkUserName :user="user" :nowrap="false" class="name"/>
 						<div class="bottom">
 							<span class="username"><MkAcct :user="user" :detail="true"/></span>
-							<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span>
+							<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span>
 							<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
 							<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
 						</div>
 					</div>
+					<div v-if="user.followedMessage != null" class="followedMessage">
+						<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin shadow>
+							<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
+							<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div>
+						</MkFukidashi>
+					</div>
 					<div v-if="user.roles.length > 0" class="roles">
 						<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
 							<MkA v-adaptive-bg :to="`/roles/${role.id}`">
@@ -58,6 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div v-if="iAmModerator" class="moderationNote">
 						<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave>
 							<template #label>{{ i18n.ts.moderationNote }}</template>
+							<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
 						</MkTextarea>
 						<div v-else>
 							<MkButton small @click="editModerationNote = true">{{ i18n.ts.addModerationNote }}</MkButton>
@@ -153,15 +160,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { getScrollPosition } from '@@/js/scroll.js';
 import MkNote from '@/components/MkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import MkAccountMoved from '@/components/MkAccountMoved.vue';
+import MkFukidashi from '@/components/MkFukidashi.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkOmit from '@/components/MkOmit.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkButton from '@/components/MkButton.vue';
-import { getScrollPosition } from '@@/js/scroll.js';
 import { getUserMenu } from '@/scripts/get-user-menu.js';
 import number from '@/filters/number.js';
 import { userPage } from '@/filters/user.js';
@@ -175,6 +183,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
 import { useRouter } from '@/router/supplier.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
+import MkSparkle from '@/components/MkSparkle.vue';
 
 function calcAge(birthdate: string): number {
 	const date = new Date(birthdate);
@@ -248,7 +257,7 @@ function parallaxLoop() {
 }
 
 function parallax() {
-	const banner = bannerEl.value as any;
+	const banner = bannerEl.value;
 	if (banner == null) return;
 
 	const top = getScrollPosition(rootEl.value);
@@ -368,8 +377,8 @@ onUnmounted(() => {
 						position: absolute;
 						top: 12px;
 						right: 12px;
-						-webkit-backdrop-filter: var(--blur, blur(8px));
-						backdrop-filter: var(--blur, blur(8px));
+						-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+						backdrop-filter: var(--MI-blur, blur(8px));
 						background: rgba(0, 0, 0, 0.2);
 						padding: 8px;
 						border-radius: 24px;
@@ -423,8 +432,8 @@ onUnmounted(() => {
 							> .add-note-button {
 								background: rgba(0, 0, 0, 0.2);
 								color: #fff;
-								-webkit-backdrop-filter: var(--blur, blur(8px));
-								backdrop-filter: var(--blur, blur(8px));
+								-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+								backdrop-filter: var(--MI-blur, blur(8px));
 								border-radius: 24px;
 								padding: 4px 8px;
 								font-size: 80%;
@@ -438,7 +447,7 @@ onUnmounted(() => {
 					text-align: center;
 					padding: 50px 8px 16px 8px;
 					font-weight: bold;
-					border-bottom: solid 0.5px var(--divider);
+					border-bottom: solid 0.5px var(--MI_THEME-divider);
 
 					> .bottom {
 						> * {
@@ -460,6 +469,22 @@ onUnmounted(() => {
 					box-shadow: 1px 1px 3px rgba(#000, 0.2);
 				}
 
+				> .followedMessage {
+					padding: 24px 24px 0 154px;
+
+					> .fukidashi {
+						display: block;
+						--fukidashi-bg: color-mix(in srgb, var(--MI_THEME-accent), var(--MI_THEME-panel) 85%);
+						--fukidashi-radius: 16px;
+						font-size: 0.9em;
+
+						.messageHeader {
+							opacity: 0.7;
+							font-size: 0.85em;
+						}
+					}
+				}
+
 				> .roles {
 					padding: 24px 24px 0 154px;
 					font-size: 0.95em;
@@ -468,7 +493,7 @@ onUnmounted(() => {
 					gap: 8px;
 
 					> .role {
-						border: solid 1px var(--color, var(--divider));
+						border: solid 1px var(--color, var(--MI_THEME-divider));
 						border-radius: 999px;
 						margin-right: 4px;
 						padding: 3px 8px;
@@ -482,15 +507,15 @@ onUnmounted(() => {
 				> .memo {
 					margin: 12px 24px 0 154px;
 					background: transparent;
-					color: var(--fg);
-					border: 1px solid var(--divider);
+					color: var(--MI_THEME-fg);
+					border: 1px solid var(--MI_THEME-divider);
 					border-radius: 8px;
 					padding: 8px;
 					line-height: 0;
 
 					> .heading {
 						text-align: left;
-						color: var(--fgTransparent);
+						color: var(--MI_THEME-fgTransparent);
 						line-height: 1.5;
 						font-size: 85%;
 					}
@@ -505,7 +530,7 @@ onUnmounted(() => {
 						height: auto;
 						min-height: 0;
 						line-height: 1.5;
-						color: var(--fg);
+						color: var(--MI_THEME-fg);
 						overflow: hidden;
 						background: transparent;
 						font-family: inherit;
@@ -525,7 +550,7 @@ onUnmounted(() => {
 				> .fields {
 					padding: 24px;
 					font-size: 0.9em;
-					border-top: solid 0.5px var(--divider);
+					border-top: solid 0.5px var(--MI_THEME-divider);
 
 					> .field {
 						display: flex;
@@ -562,14 +587,14 @@ onUnmounted(() => {
 				> .status {
 					display: flex;
 					padding: 24px;
-					border-top: solid 0.5px var(--divider);
+					border-top: solid 0.5px var(--MI_THEME-divider);
 
 					> a {
 						flex: 1;
 						text-align: center;
 
 						&.active {
-							color: var(--accent);
+							color: var(--MI_THEME-accent);
 						}
 
 						&:hover {
@@ -591,7 +616,7 @@ onUnmounted(() => {
 
 		> .contents {
 			> .content {
-				margin-bottom: var(--margin);
+				margin-bottom: var(--MI-margin);
 			}
 		}
 	}
@@ -608,7 +633,7 @@ onUnmounted(() => {
 		> .sub {
 			max-width: 350px;
 			min-width: 350px;
-			margin-left: var(--margin);
+			margin-left: var(--MI-margin);
 		}
 	}
 }
@@ -642,6 +667,10 @@ onUnmounted(() => {
 					margin: auto;
 				}
 
+				> .followedMessage {
+					padding: 16px 16px 0 16px;
+				}
+
 				> .roles {
 					padding: 16px 16px 0 16px;
 					justify-content: center;
@@ -681,13 +710,13 @@ onUnmounted(() => {
 
 <style lang="scss" module>
 .tl {
-	background: var(--bg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-bg);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 
 .verifiedLink {
 	margin-left: 4px;
-	color: var(--success);
+	color: var(--MI_THEME-success);
 }
 </style>
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index 8dbf90f344ca..49d015a530db 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -51,13 +51,13 @@ const pagination = computed(() => tab.value === 'featured' ? {
 
 <style lang="scss" module>
 .tab {
-	padding: calc(var(--margin) / 2) 0;
-	background: var(--bg);
+	padding: calc(var(--MI-margin) / 2) 0;
+	background: var(--MI_THEME-bg);
 }
 
 .tl {
-	background: var(--bg);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-bg);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue
index 5e9b95eb74a9..00de3e913298 100644
--- a/packages/frontend/src/pages/user/lists.vue
+++ b/packages/frontend/src/pages/user/lists.vue
@@ -44,12 +44,12 @@ const pagination = {
 .list {
 	display: block;
 	padding: 16px;
-	border: solid 1px var(--divider);
+	border: solid 1px var(--MI_THEME-divider);
 	border-radius: 6px;
 	margin-bottom: 8px;
 
 	&:hover {
-		border: solid 1px var(--accent);
+		border: solid 1px var(--MI_THEME-accent);
 		text-decoration: none;
 	}
 }
diff --git a/packages/frontend/src/pages/user/raw.vue b/packages/frontend/src/pages/user/raw.vue
index dd5704840968..e6e66bd6afb9 100644
--- a/packages/frontend/src/pages/user/raw.vue
+++ b/packages/frontend/src/pages/user/raw.vue
@@ -113,18 +113,18 @@ const suspended = computed(() => props.user.isSuspended ?? false);
 	}
 
 	> .suspended {
-		color: var(--error);
-		border-color: var(--error);
+		color: var(--MI_THEME-error);
+		border-color: var(--MI_THEME-error);
 	}
 
 	> .silenced {
-		color: var(--warn);
-		border-color: var(--warn);
+		color: var(--MI_THEME-warn);
+		border-color: var(--MI_THEME-warn);
 	}
 
 	> .moderator {
-		color: var(--success);
-		border-color: var(--success);
+		color: var(--MI_THEME-success);
+		border-color: var(--MI_THEME-success);
 	}
 }
 </style>
diff --git a/packages/frontend/src/pages/user/reactions.vue b/packages/frontend/src/pages/user/reactions.vue
index 3671decc1891..7168778e121a 100644
--- a/packages/frontend/src/pages/user/reactions.vue
+++ b/packages/frontend/src/pages/user/reactions.vue
@@ -44,7 +44,7 @@ const pagination = {
 	align-items: center;
 	padding: 8px 16px;
 	margin-bottom: 8px;
-	border-bottom: solid 2px var(--divider);
+	border-bottom: solid 2px var(--MI_THEME-divider);
 }
 
 .avatar {
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index d6ba397f1b65..f0e4a852c971 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -98,7 +98,7 @@ misskeyApiGet('federation/instances', {
 		left: 0;
 		width: 100vw;
 		height: 100vh;
-		background: var(--accent);
+		background: var(--MI_THEME-accent);
 		clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%);
 	}
 	> .shape2 {
@@ -107,7 +107,7 @@ misskeyApiGet('federation/instances', {
 		left: 0;
 		width: 100vw;
 		height: 100vh;
-		background: var(--accent);
+		background: var(--MI_THEME-accent);
 		clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%);
 		opacity: 0.5;
 	}
@@ -164,9 +164,9 @@ misskeyApiGet('federation/instances', {
 		left: 0;
 		right: 0;
 		margin: auto;
-		background: var(--acrylicPanel);
-		-webkit-backdrop-filter: var(--blur, blur(15px));
-		backdrop-filter: var(--blur, blur(15px));
+		background: var(--MI_THEME-acrylicPanel);
+		-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+		backdrop-filter: var(--MI-blur, blur(15px));
 		border-radius: 999px;
 		overflow: clip;
 		width: 800px;
@@ -186,7 +186,7 @@ misskeyApiGet('federation/instances', {
 	vertical-align: bottom;
 	padding: 6px 12px 6px 6px;
 	margin: 0 10px 0 0;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 	border-radius: 999px;
 
 	> :global(.icon) {
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index a227c7c4bc16..33cc139a4592 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -14,6 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<div class="_gaps_m" style="padding: 32px;">
 				<div>{{ i18n.ts.intro }}</div>
+				<MkInput v-model="setupPassword" type="password" data-cy-admin-initial-password>
+					<template #label>{{ i18n.ts.initialPasswordForSetup }} <div v-tooltip:dialog="i18n.ts.initialPasswordForSetupDescription" class="_button _help"><i class="ti ti-help-circle"></i></div></template>
+					<template #prefix><i class="ti ti-lock"></i></template>
+				</MkInput>
 				<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
 					<template #label>{{ i18n.ts.username }}</template>
 					<template #prefix>@</template>
@@ -36,9 +40,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref } from 'vue';
+import { host, version } from '@@/js/config.js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
-import { host, version } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
@@ -47,6 +51,7 @@ import MkAnimBg from '@/components/MkAnimBg.vue';
 
 const username = ref('');
 const password = ref('');
+const setupPassword = ref('');
 const submitting = ref(false);
 
 function submit() {
@@ -56,14 +61,27 @@ function submit() {
 	misskeyApi('admin/accounts/create', {
 		username: username.value,
 		password: password.value,
+		setupPassword: setupPassword.value === '' ? null : setupPassword.value,
 	}).then(res => {
 		return login(res.token);
-	}).catch(() => {
+	}).catch((err) => {
 		submitting.value = false;
 
+		let title = i18n.ts.somethingHappened;
+		let text = err.message + '\n' + err.id;
+
+		if (err.code === 'ACCESS_DENIED') {
+			title = i18n.ts.permissionDeniedError;
+			text = i18n.ts.operationForbidden;
+		} else if (err.code === 'INCORRECT_INITIAL_PASSWORD') {
+			title = i18n.ts.permissionDeniedError;
+			text = i18n.ts.incorrectPassword;
+		}
+
 		os.alert({
 			type: 'error',
-			text: i18n.ts.somethingHappened,
+			title,
+			text,
 		});
 	});
 }
@@ -74,14 +92,14 @@ function submit() {
 	min-height: 100svh;
 	padding: 32px 32px 64px 32px;
 	box-sizing: border-box;
-display: grid;
-place-content: center;
+	display: grid;
+	place-content: center;
 }
 
 .form {
 	position: relative;
 	z-index: 10;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
 	overflow: clip;
 	max-width: 500px;
@@ -92,8 +110,8 @@ place-content: center;
 	font-size: 1.5em;
 	text-align: center;
 	padding: 32px;
-	background: var(--accentedBg);
-	color: var(--accent);
+	background: var(--MI_THEME-accentedBg);
+	color: var(--MI_THEME-accent);
 	font-weight: bold;
 }
 
diff --git a/packages/frontend/src/pages/welcome.timeline.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue
index 6a9ecd9a6215..8fb84fd58ff3 100644
--- a/packages/frontend/src/pages/welcome.timeline.note.vue
+++ b/packages/frontend/src/pages/welcome.timeline.note.vue
@@ -84,7 +84,7 @@ onUpdated(() => {
 		left: 0;
 		width: 100%;
 		height: 64px;
-		background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+		background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 	}
 }
 
@@ -100,7 +100,7 @@ onUpdated(() => {
 	margin: 8px -16px -8px;
 	padding: 8px 16px 0;
 	width: calc(100% + 32px);
-	border-top: 1px solid var(--divider);
+	border-top: 1px solid var(--MI_THEME-divider);
 }
 
 .richcontent {
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 732d483615bf..9be3a80a9e95 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -60,7 +60,7 @@ onUpdated(() => {
 		transform: translate3d(0, 0, 0);
 	}
 	100% {
-		transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0);
+		transform: translate3d(0, calc(calc(-100% - 128px) - var(--MI-margin)), 0);
 	}
 }
 
@@ -69,7 +69,7 @@ onUpdated(() => {
 		transform: translate3d(0, -128px, 0);
 	}
 	100% {
-		transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0);
+		transform: translate3d(0, calc(calc(-100% - 128px) - var(--MI-margin)), 0);
 	}
 }
 
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index ac325e923f36..7740fe0d39b4 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -241,9 +241,13 @@ export class Storage<T extends StateDef> {
 	 * 特定のキーの、簡易的なgetter/setterを作ります
 	 * 主にvue上で設定コントロールのmodelとして使う用
 	 */
-	public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
-		get: () => T[K]['default'];
-		set: (value: T[K]['default']) => void;
+	public makeGetterSetter<K extends keyof T, R = T[K]['default']>(
+		key: K,
+		getter?: (v: T[K]['default']) => R,
+		setter?: (v: R) => T[K]['default'],
+	): {
+		get: () => R;
+		set: (value: R) => void;
 	} {
 		const valueRef = ref(this.state[key]);
 
@@ -265,7 +269,7 @@ export class Storage<T extends StateDef> {
 					return valueRef.value;
 				}
 			},
-			set: (value: unknown) => {
+			set: (value) => {
 				const val = setter ? setter(value) : value;
 				this.set(key, val);
 				valueRef.value = val;
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts
index 8a29fd677ec9..e98e0b59b119 100644
--- a/packages/frontend/src/router/definition.ts
+++ b/packages/frontend/src/router/definition.ts
@@ -10,7 +10,7 @@ import { $i, iAmModerator } from '@/account.js';
 import MkLoading from '@/pages/_loading_.vue';
 import MkError from '@/pages/_error_.vue';
 
-export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
+export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({
 	loader: loader,
 	loadingComponent: MkLoading,
 	errorComponent: MkError,
@@ -217,7 +217,7 @@ const routes: RouteDef[] = [{
 	component: page(() => import('@/pages/theme-editor.vue')),
 	loginRequired: true,
 }, {
-	path: '/roles/:role',
+	path: '/roles/:roleId',
 	component: page(() => import('@/pages/role.vue')),
 }, {
 	path: '/user-tags/:tag',
@@ -462,22 +462,14 @@ const routes: RouteDef[] = [{
 		path: '/relays',
 		name: 'relays',
 		component: page(() => import('@/pages/admin/relays.vue')),
-	}, {
-		path: '/instance-block',
-		name: 'instance-block',
-		component: page(() => import('@/pages/admin/instance-block.vue')),
-	}, {
-		path: '/proxy-account',
-		name: 'proxy-account',
-		component: page(() => import('@/pages/admin/proxy-account.vue')),
 	}, {
 		path: '/external-services',
 		name: 'external-services',
 		component: page(() => import('@/pages/admin/external-services.vue')),
 	}, {
-		path: '/other-settings',
-		name: 'other-settings',
-		component: page(() => import('@/pages/admin/other-settings.vue')),
+		path: '/performance',
+		name: 'performance',
+		component: page(() => import('@/pages/admin/performance.vue')),
 	}, {
 		path: '/server-rules',
 		name: 'server-rules',
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
index 6ee967e6f443..3c25e80d12e7 100644
--- a/packages/frontend/src/router/main.ts
+++ b/packages/frontend/src/router/main.ts
@@ -4,7 +4,7 @@
  */
 
 import { EventEmitter } from 'eventemitter3';
-import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
+import { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js';
 
 import type { App, ShallowRef } from 'vue';
 
@@ -79,7 +79,7 @@ class MainRouterProxy implements IRouter {
 		return this.supplier().currentRoute;
 	}
 
-	get navHook(): ((path: string, flag?: any) => boolean) | null {
+	get navHook(): ((path: string, flag?: RouterFlag) => boolean) | null {
 		return this.supplier().navHook;
 	}
 
@@ -91,11 +91,11 @@ class MainRouterProxy implements IRouter {
 		return this.supplier().getCurrentKey();
 	}
 
-	getCurrentPath(): any {
+	getCurrentPath(): string {
 		return this.supplier().getCurrentPath();
 	}
 
-	push(path: string, flag?: any): void {
+	push(path: string, flag?: RouterFlag): void {
 		this.supplier().push(path, flag);
 	}
 
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index fa3fcac2e79a..2b386bebb81a 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -27,6 +27,8 @@ export type AsUiContainer = AsUiComponentBase & {
 	font?: 'serif' | 'sans-serif' | 'monospace';
 	borderWidth?: number;
 	borderColor?: string;
+	borderStyle?: 'hidden' | 'dotted' | 'dashed' | 'solid' | 'double' | 'groove' | 'ridge' | 'inset' | 'outset';
+	borderRadius?: number;
 	padding?: number;
 	rounded?: boolean;
 	hidden?: boolean;
@@ -173,6 +175,10 @@ function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer,
 	if (borderWidth) utils.assertNumber(borderWidth);
 	const borderColor = def.value.get('borderColor');
 	if (borderColor) utils.assertString(borderColor);
+	const borderStyle = def.value.get('borderStyle');
+	if (borderStyle) utils.assertString(borderStyle);
+	const borderRadius = def.value.get('borderRadius');
+	if (borderRadius) utils.assertNumber(borderRadius);
 	const padding = def.value.get('padding');
 	if (padding) utils.assertNumber(padding);
 	const rounded = def.value.get('rounded');
@@ -191,6 +197,8 @@ function getContainerOptions(def: values.Value | undefined): Omit<AsUiContainer,
 		font: font?.value,
 		borderWidth: borderWidth?.value,
 		borderColor: borderColor?.value,
+		borderStyle: borderStyle?.value,
+		borderRadius: borderRadius?.value,
 		padding: padding?.value,
 		rounded: rounded?.value,
 		hidden: hidden?.value,
diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts
index 67e896b4b9d0..0a37a08bf0c2 100644
--- a/packages/frontend/src/scripts/check-word-mute.ts
+++ b/packages/frontend/src/scripts/check-word-mute.ts
@@ -2,8 +2,9 @@
  * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
+import * as Misskey from 'misskey-js';
 
-export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: Array<string | string[]>): boolean {
+export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean {
 	// 自分自身
 	if (me && (note.userId === me.id)) return false;
 
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index b0ffac93d70e..6710d9826e8d 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { getHighlighterCore, loadWasm } from 'shiki/core';
+import { createHighlighterCore, loadWasm } from 'shiki/core';
 import darkPlus from 'shiki/themes/dark-plus.mjs';
 import { bundledThemesInfo } from 'shiki/themes';
 import { bundledLanguagesInfo } from 'shiki/langs';
@@ -69,7 +69,7 @@ async function initHighlighter() {
 	]);
 
 	const jsLangInfo = bundledLanguagesInfo.find(t => t.id === 'javascript');
-	const highlighter = await getHighlighterCore({
+	const highlighter = await createHighlighterCore({
 		themes,
 		langs: [
 			...(jsLangInfo ? [async () => await jsLangInfo.import()] : []),
diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/scripts/device-kind.ts
index 7c33f8ccee27..7aadb617ca3b 100644
--- a/packages/frontend/src/scripts/device-kind.ts
+++ b/packages/frontend/src/scripts/device-kind.ts
@@ -3,22 +3,22 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { defaultStore } from '@/store.js';
-
-await defaultStore.ready;
+export type DeviceKind = 'smartphone' | 'tablet' | 'desktop';
 
 const ua = navigator.userAgent.toLowerCase();
 const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
 const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
 
-const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
-// navigator.platform may be deprecated but this check is still required
-const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
-const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
+export const DEFAULT_DEVICE_KIND: DeviceKind = (
+	isSmartphone
+		? 'smartphone'
+		: isTablet
+			? 'tablet'
+			: 'desktop'
+);
 
-export const isFullscreenNotSupported = isIPhone || isIos;
+export let deviceKind: DeviceKind = DEFAULT_DEVICE_KIND;
 
-export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind
-	: isSmartphone ? 'smartphone'
-	: isTablet ? 'tablet'
-	: 'desktop';
+export function updateDeviceKind(kind: DeviceKind | null) {
+	deviceKind = kind ?? DEFAULT_DEVICE_KIND;
+}
diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts
index 242a504c3b5f..1032e97ac9e0 100644
--- a/packages/frontend/src/scripts/form.ts
+++ b/packages/frontend/src/scripts/form.ts
@@ -15,7 +15,7 @@ type Hidden = boolean | ((v: any) => boolean);
 export type FormItem = {
 	label?: string;
 	type: 'string';
-	default: string | null;
+	default?: string | null;
 	description?: string;
 	required?: boolean;
 	hidden?: Hidden;
@@ -24,7 +24,7 @@ export type FormItem = {
 } | {
 	label?: string;
 	type: 'number';
-	default: number | null;
+	default?: number | null;
 	description?: string;
 	required?: boolean;
 	hidden?: Hidden;
@@ -32,20 +32,20 @@ export type FormItem = {
 } | {
 	label?: string;
 	type: 'boolean';
-	default: boolean | null;
+	default?: boolean | null;
 	description?: string;
 	hidden?: Hidden;
 } | {
 	label?: string;
 	type: 'enum';
-	default: string | null;
+	default?: string | null;
 	required?: boolean;
 	hidden?: Hidden;
 	enum: EnumItem[];
 } | {
 	label?: string;
 	type: 'radio';
-	default: unknown | null;
+	default?: unknown | null;
 	required?: boolean;
 	hidden?: Hidden;
 	options: {
@@ -55,7 +55,7 @@ export type FormItem = {
 } | {
 	label?: string;
 	type: 'range';
-	default: number | null;
+	default?: number | null;
 	description?: string;
 	required?: boolean;
 	step?: number;
@@ -66,12 +66,12 @@ export type FormItem = {
 } | {
 	label?: string;
 	type: 'object';
-	default: Record<string, unknown> | null;
+	default?: Record<string, unknown> | null;
 	hidden: Hidden;
 } | {
 	label?: string;
 	type: 'array';
-	default: unknown[] | null;
+	default?: unknown[] | null;
 	hidden: Hidden;
 } | {
 	type: 'button';
diff --git a/packages/frontend/src/scripts/fullscreen.ts b/packages/frontend/src/scripts/fullscreen.ts
new file mode 100644
index 000000000000..7a0a018ef369
--- /dev/null
+++ b/packages/frontend/src/scripts/fullscreen.ts
@@ -0,0 +1,46 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+type PartiallyPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
+
+type VideoEl = PartiallyPartial<HTMLVideoElement, 'requestFullscreen'> & {
+	webkitEnterFullscreen?(): void;
+	webkitExitFullscreen?(): void;
+};
+
+type PlayerEl = PartiallyPartial<HTMLElement, 'requestFullscreen'>;
+
+type RequestFullscreenProps = {
+	readonly videoEl: VideoEl;
+	readonly playerEl: PlayerEl;
+	readonly options?: FullscreenOptions | null;
+};
+
+type ExitFullscreenProps = {
+	readonly videoEl: VideoEl;
+};
+
+export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscreenProps) => {
+	if (playerEl.requestFullscreen != null) {
+		playerEl.requestFullscreen(options ?? undefined);
+		return;
+	}
+	if (videoEl.webkitEnterFullscreen != null) {
+		videoEl.webkitEnterFullscreen();
+		return;
+	}
+};
+
+export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => {
+	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+	if (document.exitFullscreen != null) {
+		document.exitFullscreen();
+		return;
+	}
+	if (videoEl.webkitExitFullscreen != null) {
+		videoEl.webkitExitFullscreen();
+		return;
+	}
+};
diff --git a/packages/frontend/src/scripts/get-bg-color.ts b/packages/frontend/src/scripts/get-bg-color.ts
new file mode 100644
index 000000000000..ccf60b454fd4
--- /dev/null
+++ b/packages/frontend/src/scripts/get-bg-color.ts
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import tinycolor from 'tinycolor2';
+
+export const getBgColor = (elem?: Element | null | undefined): string | null => {
+	if (elem == null) return null;
+
+	const { backgroundColor: bg } = window.getComputedStyle(elem);
+
+	if (bg && tinycolor(bg).getAlpha() !== 0) {
+		return bg;
+	}
+
+	return getBgColor(elem.parentElement);
+};
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index 108648d640ad..c8ab9238d3b7 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -9,7 +9,7 @@ import { i18n } from '@/i18n.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { defaultStore } from '@/store.js';
 
 function rename(file: Misskey.entities.DriveFile) {
@@ -87,8 +87,10 @@ async function deleteFile(file: Misskey.entities.DriveFile) {
 
 export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Misskey.entities.DriveFolder | null): MenuItem[] {
 	const isImage = file.type.startsWith('image/');
-	let menu;
-	menu = [{
+
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
 		type: 'link',
 		to: `/my/drive/file/${file.id}`,
 		text: i18n.ts._fileViewer.title,
@@ -109,14 +111,20 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
 		text: i18n.ts.describeFile,
 		icon: 'ti ti-text-caption',
 		action: () => describe(file),
-	}, ...isImage ? [{
-		text: i18n.ts.cropImage,
-		icon: 'ti ti-crop',
-		action: () => os.cropImage(file, {
-			aspectRatio: NaN,
-			uploadFolder: folder ? folder.id : folder,
-		}),
-	}] : [], { type: 'divider' }, {
+	});
+
+	if (isImage) {
+		menuItems.push({
+			text: i18n.ts.cropImage,
+			icon: 'ti ti-crop',
+			action: () => os.cropImage(file, {
+				aspectRatio: NaN,
+				uploadFolder: folder ? folder.id : folder,
+			}),
+		});
+	}
+
+	menuItems.push({ type: 'divider' }, {
 		text: i18n.ts.createNoteFromTheFile,
 		icon: 'ti ti-pencil',
 		action: () => os.post({
@@ -138,17 +146,17 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
 		icon: 'ti ti-trash',
 		danger: true,
 		action: () => deleteFile(file),
-	}];
+	});
 
 	if (defaultStore.state.devMode) {
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: 'ti ti-id',
 			text: i18n.ts.copyFileId,
 			action: () => {
 				copyToClipboard(file.id);
 			},
-		}]);
+		});
 	}
 
-	return menu;
+	return menuItems;
 }
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 49f3199887f6..c1846b058957 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -17,7 +17,7 @@ import { defaultStore, noteActions } from '@/store.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { getUserMenu } from '@/scripts/get-user-menu.js';
 import { clipsCache, favoritedChannelsCache } from '@/cache.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { isSupportShare } from '@/scripts/navigator.js';
 import { getAppearNote } from '@/scripts/get-appear-note.js';
@@ -99,11 +99,13 @@ export async function getNoteClipMenu(props: {
 			const { canceled, result } = await os.form(i18n.ts.createNewClip, {
 				name: {
 					type: 'string',
+					default: null,
 					label: i18n.ts.name,
 				},
 				description: {
 					type: 'string',
 					required: false,
+					default: null,
 					multiline: true,
 					label: i18n.ts.description,
 				},
@@ -243,13 +245,10 @@ export function getNoteMenu(props: {
 	function togglePin(pin: boolean): void {
 		os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', {
 			noteId: appearNote.id,
-		}, undefined, null, res => {
-			if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
-				os.alert({
-					type: 'error',
-					text: i18n.ts.pinLimitExceeded,
-				});
-			}
+		}, undefined, {
+			'72dab508-c64d-498f-8740-a8eec1ba385a': {
+				text: i18n.ts.pinLimitExceeded,
+			},
 		});
 	}
 
@@ -264,7 +263,7 @@ export function getNoteMenu(props: {
 			title: i18n.ts.numberOfDays,
 		});
 
-		if (canceled) return;
+		if (canceled || days == null) return;
 
 		os.apiWithDialog('admin/promo/create', {
 			noteId: appearNote.id,
@@ -295,161 +294,175 @@ export function getNoteMenu(props: {
 		props.translation.value = res;
 	}
 
-	let menu: MenuItem[];
+	const menuItems: MenuItem[] = [];
+
 	if ($i) {
 		const statePromise = misskeyApi('notes/state', {
 			noteId: appearNote.id,
 		});
 
-		menu = [
-			...(
-				props.currentClip?.userId === $i.id ? [{
-					icon: 'ti ti-backspace',
-					text: i18n.ts.unclip,
-					danger: true,
-					action: unclip,
-				}, { type: 'divider' }] : []
-			), {
-				icon: 'ti ti-info-circle',
-				text: i18n.ts.details,
-				action: openDetail,
-			}, {
-				icon: 'ti ti-copy',
-				text: i18n.ts.copyContent,
-				action: copyContent,
-			}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)
-			, (appearNote.url || appearNote.uri) ? {
+		if (props.currentClip?.userId === $i.id) {
+			menuItems.push({
+				icon: 'ti ti-backspace',
+				text: i18n.ts.unclip,
+				danger: true,
+				action: unclip,
+			}, { type: 'divider' });
+		}
+
+		menuItems.push({
+			icon: 'ti ti-info-circle',
+			text: i18n.ts.details,
+			action: openDetail,
+		}, {
+			icon: 'ti ti-copy',
+			text: i18n.ts.copyContent,
+			action: copyContent,
+		}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink));
+
+		if (appearNote.url || appearNote.uri) {
+			menuItems.push({
 				icon: 'ti ti-external-link',
 				text: i18n.ts.showOnRemote,
 				action: () => {
 					window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
 				},
-			} : getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode),
-			...(isSupportShare() ? [{
+			});
+		} else {
+			menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode));
+		}
+
+		if (isSupportShare()) {
+			menuItems.push({
 				icon: 'ti ti-share',
 				text: i18n.ts.share,
 				action: share,
-			}] : []),
-			$i && $i.policies.canUseTranslator && instance.translatorAvailable ? {
+			});
+		}
+
+		if ($i.policies.canUseTranslator && instance.translatorAvailable) {
+			menuItems.push({
 				icon: 'ti ti-language-hiragana',
 				text: i18n.ts.translate,
 				action: translate,
-			} : undefined,
-			{ type: 'divider' },
-			statePromise.then(state => state.isFavorited ? {
-				icon: 'ti ti-star-off',
-				text: i18n.ts.unfavorite,
-				action: () => toggleFavorite(false),
-			} : {
-				icon: 'ti ti-star',
-				text: i18n.ts.favorite,
-				action: () => toggleFavorite(true),
-			}),
-			{
-				type: 'parent' as const,
-				icon: 'ti ti-paperclip',
-				text: i18n.ts.clip,
-				children: () => getNoteClipMenu(props),
+			});
+		}
+
+		menuItems.push({ type: 'divider' });
+
+		menuItems.push(statePromise.then(state => state.isFavorited ? {
+			icon: 'ti ti-star-off',
+			text: i18n.ts.unfavorite,
+			action: () => toggleFavorite(false),
+		} : {
+			icon: 'ti ti-star',
+			text: i18n.ts.favorite,
+			action: () => toggleFavorite(true),
+		}));
+
+		menuItems.push({
+			type: 'parent',
+			icon: 'ti ti-paperclip',
+			text: i18n.ts.clip,
+			children: () => getNoteClipMenu(props),
+		});
+
+		menuItems.push(statePromise.then(state => state.isMutedThread ? {
+			icon: 'ti ti-message-off',
+			text: i18n.ts.unmuteThread,
+			action: () => toggleThreadMute(false),
+		} : {
+			icon: 'ti ti-message-off',
+			text: i18n.ts.muteThread,
+			action: () => toggleThreadMute(true),
+		}));
+
+		if (appearNote.userId === $i.id) {
+			if (($i.pinnedNoteIds ?? []).includes(appearNote.id)) {
+				menuItems.push({
+					icon: 'ti ti-pinned-off',
+					text: i18n.ts.unpin,
+					action: () => togglePin(false),
+				});
+			} else {
+				menuItems.push({
+					icon: 'ti ti-pin',
+					text: i18n.ts.pin,
+					action: () => togglePin(true),
+				});
+			}
+		}
+
+		menuItems.push({
+			type: 'parent',
+			icon: 'ti ti-user',
+			text: i18n.ts.user,
+			children: async () => {
+				const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId });
+				const { menu, cleanup } = getUserMenu(user);
+				cleanups.push(cleanup);
+				return menu;
 			},
-			statePromise.then(state => state.isMutedThread ? {
-				icon: 'ti ti-message-off',
-				text: i18n.ts.unmuteThread,
-				action: () => toggleThreadMute(false),
-			} : {
-				icon: 'ti ti-message-off',
-				text: i18n.ts.muteThread,
-				action: () => toggleThreadMute(true),
-			}),
-			appearNote.userId === $i.id ? ($i.pinnedNoteIds ?? []).includes(appearNote.id) ? {
-				icon: 'ti ti-pinned-off',
-				text: i18n.ts.unpin,
-				action: () => togglePin(false),
-			} : {
-				icon: 'ti ti-pin',
-				text: i18n.ts.pin,
-				action: () => togglePin(true),
-			} : undefined,
-			{
-				type: 'parent' as const,
-				icon: 'ti ti-user',
-				text: i18n.ts.user,
+		});
+
+		if (appearNote.userId !== $i.id) {
+			menuItems.push({ type: 'divider' });
+			menuItems.push(getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse));
+		}
+
+		if (appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin)) {
+			menuItems.push({ type: 'divider' });
+			menuItems.push({
+				type: 'parent',
+				icon: 'ti ti-device-tv',
+				text: i18n.ts.channel,
 				children: async () => {
-					const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId });
-					const { menu, cleanup } = getUserMenu(user);
-					cleanups.push(cleanup);
-					return menu;
-				},
-			},
-			/*
-		...($i.isModerator || $i.isAdmin ? [
-			{ type: 'divider' },
-			{
-				icon: 'ti ti-speakerphone',
-				text: i18n.ts.promote,
-				action: promote
-			}]
-			: []
-		),*/
-			...(appearNote.userId !== $i.id ? [
-				{ type: 'divider' },
-				appearNote.userId !== $i.id ? getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse) : undefined,
-			]
-			: []
-			),
-			...(appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin) ? [
-				{ type: 'divider' },
-				{
-					type: 'parent' as const,
-					icon: 'ti ti-device-tv',
-					text: i18n.ts.channel,
-					children: async () => {
-						const channelChildMenu = [] as MenuItem[];
-
-						const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id });
-
-						if (channel.pinnedNoteIds.includes(appearNote.id)) {
-							channelChildMenu.push({
-								icon: 'ti ti-pinned-off',
-								text: i18n.ts.unpin,
-								action: () => os.apiWithDialog('channels/update', {
-									channelId: appearNote.channel!.id,
-									pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id),
-								}),
-							});
-						} else {
-							channelChildMenu.push({
-								icon: 'ti ti-pin',
-								text: i18n.ts.pin,
-								action: () => os.apiWithDialog('channels/update', {
-									channelId: appearNote.channel!.id,
-									pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id],
-								}),
-							});
-						}
-						return channelChildMenu;
-					},
+					const channelChildMenu = [] as MenuItem[];
+
+					const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id });
+
+					if (channel.pinnedNoteIds.includes(appearNote.id)) {
+						channelChildMenu.push({
+							icon: 'ti ti-pinned-off',
+							text: i18n.ts.unpin,
+							action: () => os.apiWithDialog('channels/update', {
+								channelId: appearNote.channel!.id,
+								pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id),
+							}),
+						});
+					} else {
+						channelChildMenu.push({
+							icon: 'ti ti-pin',
+							text: i18n.ts.pin,
+							action: () => os.apiWithDialog('channels/update', {
+								channelId: appearNote.channel!.id,
+								pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id],
+							}),
+						});
+					}
+					return channelChildMenu;
 				},
-			]
-			: []
-			),
-			...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
-				{ type: 'divider' },
-				appearNote.userId === $i.id ? {
+			});
+		}
+
+		if (appearNote.userId === $i.id || $i.isModerator || $i.isAdmin) {
+			menuItems.push({ type: 'divider' });
+			if (appearNote.userId === $i.id) {
+				menuItems.push({
 					icon: 'ti ti-edit',
 					text: i18n.ts.deleteAndEdit,
 					action: delEdit,
-				} : undefined,
-				{
-					icon: 'ti ti-trash',
-					text: i18n.ts.delete,
-					danger: true,
-					action: del,
-				}]
-			: []
-			)]
-			.filter(x => x !== undefined);
+				});
+			}
+			menuItems.push({
+				icon: 'ti ti-trash',
+				text: i18n.ts.delete,
+				danger: true,
+				action: del,
+			});
+		}
 	} else {
-		menu = [{
+		menuItems.push({
 			icon: 'ti ti-info-circle',
 			text: i18n.ts.details,
 			action: openDetail,
@@ -457,35 +470,42 @@ export function getNoteMenu(props: {
 			icon: 'ti ti-copy',
 			text: i18n.ts.copyContent,
 			action: copyContent,
-		}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink),
-		(appearNote.url || appearNote.uri) ? {
-			icon: 'ti ti-external-link',
-			text: i18n.ts.showOnRemote,
-			action: () => {
-				window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
-			},
-		} : getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode)]
-			.filter(x => x !== undefined);
+		}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink));
+
+		if (appearNote.url || appearNote.uri) {
+			menuItems.push({
+				icon: 'ti ti-external-link',
+				text: i18n.ts.showOnRemote,
+				action: () => {
+					window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
+				},
+			});
+		} else {
+			menuItems.push(getNoteEmbedCodeMenu(appearNote, i18n.ts.genEmbedCode));
+		}
 	}
 
 	if (noteActions.length > 0) {
-		menu = menu.concat([{ type: 'divider' }, ...noteActions.map(action => ({
+		menuItems.push({ type: 'divider' });
+
+		menuItems.push(...noteActions.map(action => ({
 			icon: 'ti ti-plug',
 			text: action.title,
 			action: () => {
 				action.handler(appearNote);
 			},
-		}))]);
+		})));
 	}
 
 	if (defaultStore.state.devMode) {
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: 'ti ti-id',
 			text: i18n.ts.copyNoteId,
 			action: () => {
 				copyToClipboard(appearNote.id);
+				os.success();
 			},
-		}]);
+		});
 	}
 
 	const cleanup = () => {
@@ -496,7 +516,7 @@ export function getNoteMenu(props: {
 	};
 
 	return {
-		menu,
+		menu: menuItems,
 		cleanup,
 	};
 }
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 33316b4ab61e..d15279d63339 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -18,7 +18,7 @@ import { IRouter } from '@/nirax.js';
 import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
 import { mainRouter } from '@/router/main.js';
 import { genEmbedCode } from '@/scripts/get-embed-code.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 
 export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
 	const meId = $i ? $i.id : null;
@@ -148,133 +148,154 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
 		});
 	}
 
-	let menu: MenuItem[] = [{
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
 		icon: 'ti ti-at',
 		text: i18n.ts.copyUsername,
 		action: () => {
 			copyToClipboard(`@${user.username}@${user.host ?? host}`);
 		},
-	}, ...( notesSearchAvailable && (user.host == null || canSearchNonLocalNotes) ? [{
-		icon: 'ti ti-search',
-		text: i18n.ts.searchThisUsersNotes,
-		action: () => {
-			router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
-		},
-	}] : [])
-	, ...(iAmModerator ? [{
-		icon: 'ti ti-user-exclamation',
-		text: i18n.ts.moderation,
-		action: () => {
-			router.push(`/admin/user/${user.id}`);
-		},
-	}] : []), {
+	});
+
+	if (notesSearchAvailable && (user.host == null || canSearchNonLocalNotes)) {
+		menuItems.push({
+			icon: 'ti ti-search',
+			text: i18n.ts.searchThisUsersNotes,
+			action: () => {
+				router.push(`/search?username=${encodeURIComponent(user.username)}${user.host != null ? '&host=' + encodeURIComponent(user.host) : ''}`);
+			},
+		});
+	}
+
+	if (iAmModerator) {
+		menuItems.push({
+			icon: 'ti ti-user-exclamation',
+			text: i18n.ts.moderation,
+			action: () => {
+				router.push(`/admin/user/${user.id}`);
+			},
+		});
+	}
+
+	menuItems.push({
 		icon: 'ti ti-rss',
 		text: i18n.ts.copyRSS,
 		action: () => {
 			copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
 		},
-	}, ...(user.host != null && user.url != null ? [{
-		icon: 'ti ti-external-link',
-		text: i18n.ts.showOnRemote,
-		action: () => {
-			if (user.url == null) return;
-			window.open(user.url, '_blank', 'noopener');
-		},
-	}] : [{
-		icon: 'ti ti-code',
-		text: i18n.ts.genEmbedCode,
-		type: 'parent' as const,
-		children: [{
-			text: i18n.ts.noteOfThisUser,
+	});
+
+	if (user.host != null && user.url != null) {
+		menuItems.push({
+			icon: 'ti ti-external-link',
+			text: i18n.ts.showOnRemote,
 			action: () => {
-				genEmbedCode('user-timeline', user.id);
+				if (user.url == null) return;
+				window.open(user.url, '_blank', 'noopener');
 			},
-		}], // TODO: ユーザーカードの埋め込みなど
-	}]), {
+		});
+	} else {
+		menuItems.push({
+			icon: 'ti ti-code',
+			text: i18n.ts.genEmbedCode,
+			type: 'parent',
+			children: [{
+				text: i18n.ts.noteOfThisUser,
+				action: () => {
+					genEmbedCode('user-timeline', user.id);
+				},
+			}], // TODO: ユーザーカードの埋め込みなど
+		});
+	}
+
+	menuItems.push({
 		icon: 'ti ti-share',
 		text: i18n.ts.copyProfileUrl,
 		action: () => {
 			const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
 			copyToClipboard(`${url}/${canonical}`);
 		},
-	}, ...($i ? [{
-		icon: 'ti ti-mail',
-		text: i18n.ts.sendMessage,
-		action: () => {
-			const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
-			os.post({ specified: user, initialText: `${canonical} ` });
-		},
-	}, { type: 'divider' }, {
-		icon: 'ti ti-pencil',
-		text: i18n.ts.editMemo,
-		action: () => {
-			editMemo();
-		},
-	}, {
-		type: 'parent',
-		icon: 'ti ti-list',
-		text: i18n.ts.addToList,
-		children: async () => {
-			const lists = await userListsCache.fetch();
-			return lists.map(list => {
-				const isListed = ref(list.userIds.includes(user.id));
-				cleanups.push(watch(isListed, () => {
-					if (isListed.value) {
-						os.apiWithDialog('users/lists/push', {
-							listId: list.id,
-							userId: user.id,
-						}).then(() => {
-							list.userIds.push(user.id);
-						});
-					} else {
-						os.apiWithDialog('users/lists/pull', {
-							listId: list.id,
-							userId: user.id,
-						}).then(() => {
-							list.userIds.splice(list.userIds.indexOf(user.id), 1);
+	});
+
+	if ($i) {
+		menuItems.push({
+			icon: 'ti ti-mail',
+			text: i18n.ts.sendMessage,
+			action: () => {
+				const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
+				os.post({ specified: user, initialText: `${canonical} ` });
+			},
+		}, { type: 'divider' }, {
+			icon: 'ti ti-pencil',
+			text: i18n.ts.editMemo,
+			action: editMemo,
+		}, {
+			type: 'parent',
+			icon: 'ti ti-list',
+			text: i18n.ts.addToList,
+			children: async () => {
+				const lists = await userListsCache.fetch();
+				return lists.map(list => {
+					const isListed = ref(list.userIds?.includes(user.id) ?? false);
+					cleanups.push(watch(isListed, () => {
+						if (isListed.value) {
+							os.apiWithDialog('users/lists/push', {
+								listId: list.id,
+								userId: user.id,
+							}).then(() => {
+								list.userIds?.push(user.id);
+							});
+						} else {
+							os.apiWithDialog('users/lists/pull', {
+								listId: list.id,
+								userId: user.id,
+							}).then(() => {
+								list.userIds?.splice(list.userIds?.indexOf(user.id), 1);
+							});
+						}
+					}));
+
+					return {
+						type: 'switch',
+						text: list.name,
+						ref: isListed,
+					};
+				});
+			},
+		}, {
+			type: 'parent',
+			icon: 'ti ti-antenna',
+			text: i18n.ts.addToAntenna,
+			children: async () => {
+				const antennas = await antennasCache.fetch();
+				const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
+				return antennas.filter((a) => a.src === 'users').map(antenna => ({
+					text: antenna.name,
+					action: async () => {
+						await os.apiWithDialog('antennas/update', {
+							antennaId: antenna.id,
+							name: antenna.name,
+							keywords: antenna.keywords,
+							excludeKeywords: antenna.excludeKeywords,
+							src: antenna.src,
+							userListId: antenna.userListId,
+							users: [...antenna.users, canonical],
+							caseSensitive: antenna.caseSensitive,
+							withReplies: antenna.withReplies,
+							withFile: antenna.withFile,
+							notify: antenna.notify,
 						});
-					}
+						antennasCache.delete();
+					},
 				}));
-
-				return {
-					type: 'switch',
-					text: list.name,
-					ref: isListed,
-				};
-			});
-		},
-	}, {
-		type: 'parent',
-		icon: 'ti ti-antenna',
-		text: i18n.ts.addToAntenna,
-		children: async () => {
-			const antennas = await antennasCache.fetch();
-			const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
-			return antennas.filter((a) => a.src === 'users').map(antenna => ({
-				text: antenna.name,
-				action: async () => {
-					await os.apiWithDialog('antennas/update', {
-						antennaId: antenna.id,
-						name: antenna.name,
-						keywords: antenna.keywords,
-						excludeKeywords: antenna.excludeKeywords,
-						src: antenna.src,
-						userListId: antenna.userListId,
-						users: [...antenna.users, canonical],
-						caseSensitive: antenna.caseSensitive,
-						withReplies: antenna.withReplies,
-						withFile: antenna.withFile,
-						notify: antenna.notify,
-					});
-					antennasCache.delete();
-				},
-			}));
-		},
-	}] : [])] as any;
+			},
+		});
+	}
 
 	if ($i && meId !== user.id) {
 		if (iAmModerator) {
-			menu = menu.concat([{
+			menuItems.push({
 				type: 'parent',
 				icon: 'ti ti-badges',
 				text: i18n.ts.roles,
@@ -312,13 +333,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
 						},
 					}));
 				},
-			}]);
+			});
 		}
 
 		// フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
 		//if (user.isFollowing) {
-		const withRepliesRef = ref(user.withReplies);
-		menu = menu.concat([{
+		const withRepliesRef = ref(user.withReplies ?? false);
+
+		menuItems.push({
 			type: 'switch',
 			icon: 'ti ti-messages',
 			text: i18n.ts.showRepliesToOthersInTimeline,
@@ -327,7 +349,8 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
 			icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off',
 			text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
 			action: toggleNotify,
-		}]);
+		});
+
 		watch(withRepliesRef, (withReplies) => {
 			misskeyApi('following/update', {
 				userId: user.id,
@@ -338,7 +361,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
 		});
 		//}
 
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
 			text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
 			action: toggleMute,
@@ -350,70 +373,68 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
 			icon: 'ti ti-ban',
 			text: user.isBlocking ? i18n.ts.unblock : i18n.ts.block,
 			action: toggleBlock,
-		}]);
+		});
 
 		if (user.isFollowed) {
-			menu = menu.concat([{
+			menuItems.push({
 				icon: 'ti ti-link-off',
 				text: i18n.ts.breakFollow,
 				action: invalidateFollow,
-			}]);
+			});
 		}
 
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: 'ti ti-exclamation-circle',
 			text: i18n.ts.reportAbuse,
 			action: reportAbuse,
-		}]);
+		});
 	}
 
 	if (user.host !== null) {
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: 'ti ti-refresh',
 			text: i18n.ts.updateRemoteUser,
 			action: userInfoUpdate,
-		}]);
+		});
 	}
 
 	if (defaultStore.state.devMode) {
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: 'ti ti-id',
 			text: i18n.ts.copyUserId,
 			action: () => {
 				copyToClipboard(user.id);
 			},
-		}]);
+		});
 	}
 
 	if ($i && meId === user.id) {
-		menu = menu.concat([{ type: 'divider' }, {
+		menuItems.push({ type: 'divider' }, {
 			icon: 'ti ti-pencil',
 			text: i18n.ts.editProfile,
 			action: () => {
 				router.push('/settings/profile');
 			},
-		}]);
+		});
 	}
 
 	if (userActions.length > 0) {
-		menu = menu.concat([{ type: 'divider' }, ...userActions.map(action => ({
+		menuItems.push({ type: 'divider' }, ...userActions.map(action => ({
 			icon: 'ti ti-plug',
 			text: action.title,
 			action: () => {
 				action.handler(user);
 			},
-		}))]);
+		})));
 	}
 
-	const cleanup = () => {
-		if (_DEV_) console.log('user menu cleanup', cleanups);
-		for (const cl of cleanups) {
-			cl();
-		}
-	};
-
 	return {
-		menu,
-		cleanup,
+		menu: menuItems,
+		cleanup: () => {
+			if (_DEV_) console.log('user menu cleanup', cleanups);
+			for (const cl of cleanups) {
+				cl();
+			}
+		},
 	};
 }
diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts
index 2465a14703dd..41e1636aa70d 100644
--- a/packages/frontend/src/scripts/init-chart.ts
+++ b/packages/frontend/src/scripts/init-chart.ts
@@ -50,7 +50,7 @@ export function initChart() {
 	);
 
 	// フォントカラー
-	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
+	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg');
 
 	Chart.defaults.borderColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
 
diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts
index 1b1159fd015a..e7a92e2d5cda 100644
--- a/packages/frontend/src/scripts/misskey-api.ts
+++ b/packages/frontend/src/scripts/misskey-api.ts
@@ -17,7 +17,7 @@ export function misskeyApi<
 	_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
 >(
 	endpoint: E,
-	data: P = {} as any,
+	data: P & { i?: string | null; } = {} as any,
 	token?: string | null | undefined,
 	signal?: AbortSignal,
 ): Promise<_ResT> {
@@ -30,8 +30,8 @@ export function misskeyApi<
 
 	const promise = new Promise<_ResT>((resolve, reject) => {
 		// Append a credential
-		if ($i) (data as any).i = $i.token;
-		if (token !== undefined) (data as any).i = token;
+		if ($i) data.i = $i.token;
+		if (token !== undefined) data.i = token;
 
 		// Send request
 		window.fetch(`${apiUrl}/${endpoint}`, {
diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts
index 18f05bc7f428..43dcf1193674 100644
--- a/packages/frontend/src/scripts/please-login.ts
+++ b/packages/frontend/src/scripts/please-login.ts
@@ -44,17 +44,21 @@ export type OpenOnRemoteOptions = {
 	params: Record<string, string>;
 };
 
-export function pleaseLogin(path?: string, openOnRemote?: OpenOnRemoteOptions) {
+export function pleaseLogin(opts: {
+	path?: string;
+	message?: string;
+	openOnRemote?: OpenOnRemoteOptions;
+} = {}) {
 	if ($i) return;
 
 	const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
 		autoSet: true,
-		message: openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired,
-		openOnRemote,
+		message: opts.message ?? (opts.openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired),
+		openOnRemote: opts.openOnRemote,
 	}, {
 		cancelled: () => {
-			if (path) {
-				window.location.href = path;
+			if (opts.path) {
+				window.location.href = opts.path;
 			}
 		},
 		closed: () => dispose(),
diff --git a/packages/frontend/src/scripts/reload-ask.ts b/packages/frontend/src/scripts/reload-ask.ts
new file mode 100644
index 000000000000..733d91b85ab4
--- /dev/null
+++ b/packages/frontend/src/scripts/reload-ask.ts
@@ -0,0 +1,40 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+import { unisonReload } from '@/scripts/unison-reload.js';
+
+let isReloadConfirming = false;
+
+export async function reloadAsk(opts: {
+	unison?: boolean;
+	reason?: string;
+}) {
+	if (isReloadConfirming) {
+		return;
+	}
+
+	isReloadConfirming = true;
+
+	const { canceled } = await os.confirm(opts.reason == null ? {
+		type: 'info',
+		text: i18n.ts.reloadConfirm,
+	} : {
+		type: 'info',
+		title: i18n.ts.reloadConfirm,
+		text: opts.reason,
+	}).finally(() => {
+		isReloadConfirming = false;
+	});
+
+	if (canceled) return;
+
+	if (opts.unison) {
+		unisonReload();
+	} else {
+		location.reload();
+	}
+}
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts
index 9aa38178b20e..b037aa8accf8 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/scripts/select-file.ts
@@ -80,7 +80,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
 	});
 }
 
-function select(src: any, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
+function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
 	return new Promise((res, rej) => {
 		const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
 
@@ -107,10 +107,10 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Miss
 	});
 }
 
-export function selectFile(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile> {
+export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> {
 	return select(src, label, false).then(files => files[0]);
 }
 
-export function selectFiles(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
+export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> {
 	return select(src, label, true);
 }
diff --git a/packages/frontend/src/scripts/shuffle.ts b/packages/frontend/src/scripts/shuffle.ts
index fed16bc71c6a..1f6ef1928c6e 100644
--- a/packages/frontend/src/scripts/shuffle.ts
+++ b/packages/frontend/src/scripts/shuffle.ts
@@ -6,8 +6,9 @@
 /**
  * 配列をシャッフル (破壊的)
  */
-export function shuffle<T extends any[]>(array: T): T {
-	let currentIndex = array.length, randomIndex;
+export function shuffle<T extends unknown[]>(array: T): T {
+	let currentIndex = array.length;
+	let randomIndex: number;
 
 	// While there remain elements to shuffle.
 	while (currentIndex !== 0) {
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index fc888c0908a2..1a3909c132fa 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -74,6 +74,8 @@ export function applyTheme(theme: Theme, persist = true) {
 
 	const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
 
+	document.documentElement.dataset.colorScheme = colorScheme;
+
 	// Deep copy
 	const _theme = deepClone(theme);
 
@@ -92,7 +94,7 @@ export function applyTheme(theme: Theme, persist = true) {
 	}
 
 	for (const [k, v] of Object.entries(props)) {
-		document.documentElement.style.setProperty(`--${k}`, v.toString());
+		document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
 	}
 
 	document.documentElement.style.setProperty('color-scheme', colorScheme);
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index 22dce609c692..713573a3779a 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -32,13 +32,13 @@ const mimeTypeMap = {
 
 export function uploadFile(
 	file: File,
-	folder?: any,
+	folder?: string | Misskey.entities.DriveFolder,
 	name?: string,
 	keepOriginal: boolean = defaultStore.state.keepOriginalUploading,
 ): Promise<Misskey.entities.DriveFile> {
 	if ($i == null) throw new Error('Not logged in');
 
-	if (folder && typeof folder === 'object') folder = folder.id;
+	const _folder = typeof folder === 'string' ? folder : folder?.id;
 
 	if (file.size > instance.maxFileSize) {
 		alert({
@@ -89,11 +89,11 @@ export function uploadFile(
 			}
 
 			const formData = new FormData();
-			formData.append('i', $i.token);
+			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);
+			if (_folder) formData.append('folderId', _folder);
 
 			const xhr = new XMLHttpRequest();
 			xhr.open('POST', apiUrl + '/drive/files/create', true);
diff --git a/packages/frontend/src/scripts/use-form.ts b/packages/frontend/src/scripts/use-form.ts
new file mode 100644
index 000000000000..0d505fe4668b
--- /dev/null
+++ b/packages/frontend/src/scripts/use-form.ts
@@ -0,0 +1,55 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { computed, Reactive, reactive, watch } from 'vue';
+
+function copy<T>(v: T): T {
+	return JSON.parse(JSON.stringify(v));
+}
+
+function unwrapReactive<T>(v: Reactive<T>): T {
+	return JSON.parse(JSON.stringify(v));
+}
+
+export function useForm<T extends Record<string, any>>(initialState: T, save: (newState: T) => Promise<void>) {
+	const currentState = reactive<T>(copy(initialState));
+	const previousState = reactive<T>(copy(initialState));
+
+	const modifiedStates = reactive<Record<keyof T, boolean>>({} as any);
+	for (const key in currentState) {
+		modifiedStates[key] = false;
+	}
+	const modified = computed(() => Object.values(modifiedStates).some(v => v));
+	const modifiedCount = computed(() => Object.values(modifiedStates).filter(v => v).length);
+
+	watch([currentState, previousState], () => {
+		for (const key in modifiedStates) {
+			modifiedStates[key] = currentState[key] !== previousState[key];
+		}
+	}, { deep: true });
+
+	async function _save() {
+		await save(unwrapReactive(currentState));
+		for (const key in currentState) {
+			previousState[key] = copy(currentState[key]);
+		}
+	}
+
+	function discard() {
+		for (const key in currentState) {
+			currentState[key] = copy(previousState[key]);
+		}
+	}
+
+	return {
+		state: currentState,
+		savedState: previousState,
+		modifiedStates,
+		modified,
+		modifiedCount,
+		save: _save,
+		discard,
+	};
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 40615cfc7dda..1d981e897bd6 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -5,10 +5,14 @@
 
 import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { miLocalStorage } from './local-storage.js';
+import { hemisphere } from '@@/js/intl-const.js';
+import lightTheme from '@@/themes/l-light.json5';
+import darkTheme from '@@/themes/d-green-lime.json5';
 import type { SoundType } from '@/scripts/sound.js';
+import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js';
+import { miLocalStorage } from '@/local-storage.js';
 import { Storage } from '@/pizzax.js';
-import { hemisphere } from '@@/js/intl-const.js';
+import type { Ast } from '@syuilo/aiscript';
 
 interface PostFormAction {
 	title: string,
@@ -76,6 +80,10 @@ export const defaultStore = markRaw(new Storage('base', {
 			global: false,
 		},
 	},
+	abusesTutorial: {
+		where: 'account',
+		default: false,
+	},
 	keepCw: {
 		where: 'account',
 		default: true,
@@ -200,7 +208,7 @@ export const defaultStore = markRaw(new Storage('base', {
 
 	overridedDeviceKind: {
 		where: 'device',
-		default: null as null | 'smartphone' | 'tablet' | 'desktop',
+		default: null as DeviceKind | null,
 	},
 	serverDisconnectedBehavior: {
 		where: 'device',
@@ -220,7 +228,7 @@ export const defaultStore = markRaw(new Storage('base', {
 	},
 	animatedMfm: {
 		where: 'device',
-		default: false,
+		default: !window.matchMedia('(prefers-reduced-motion)').matches,
 	},
 	advancedMfm: {
 		where: 'device',
@@ -250,17 +258,17 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: 'twemoji', // twemoji / fluentEmoji / native
 	},
-	disableDrawer: {
+	menuStyle: {
 		where: 'device',
-		default: false,
+		default: 'auto' as 'auto' | 'popup' | 'drawer',
 	},
 	useBlurEffectForModal: {
 		where: 'device',
-		default: !/mobile|iphone|android/.test(navigator.userAgent.toLowerCase()), // 循環参照するのでdevice-kind.tsは参照できない
+		default: DEFAULT_DEVICE_KIND === 'desktop',
 	},
 	useBlurEffect: {
 		where: 'device',
-		default: !/mobile|iphone|android/.test(navigator.userAgent.toLowerCase()), // 循環参照するのでdevice-kind.tsは参照できない
+		default: DEFAULT_DEVICE_KIND === 'desktop',
 	},
 	showFixedPostForm: {
 		where: 'device',
@@ -302,9 +310,9 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: 2,
 	},
-	emojiPickerUseDrawerForMobile: {
+	emojiPickerStyle: {
 		where: 'device',
-		default: true,
+		default: 'auto' as 'auto' | 'popup' | 'drawer',
 	},
 	recentlyUsedEmojis: {
 		where: 'device',
@@ -390,9 +398,9 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: 'horizontal' as 'vertical' | 'horizontal',
 	},
-	enableCondensedLineForAcct: {
+	enableCondensedLine: {
 		where: 'device',
-		default: false,
+		default: true,
 	},
 	additionalUnicodeEmojiIndexes: {
 		where: 'device',
@@ -462,6 +470,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: 'app' as 'app' | 'appWithShift' | 'native',
 	},
+	skipNoteRender: {
+		where: 'device',
+		default: true,
+	},
 
 	sound_masterVolume: {
 		where: 'device',
@@ -506,7 +518,7 @@ export type Plugin = {
 	token: string;
 	src: string | null;
 	version: string;
-	ast: any[];
+	ast: Ast.Node[];
 	author?: string;
 	description?: string;
 	permissions?: string[];
@@ -520,8 +532,6 @@ interface Watcher {
 /**
  * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
  */
-import lightTheme from '@@/themes/l-light.json5';
-import darkTheme from '@@/themes/d-green-lime.json5';
 
 export class ColdDeviceStorage {
 	public static default = {
@@ -546,13 +556,13 @@ export class ColdDeviceStorage {
 	}
 
 	public static getAll(): Partial<typeof this.default> {
-		return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce((acc, key) => {
+		return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce<Partial<typeof this.default>>((acc, key) => {
 			const value = localStorage.getItem(PREFIX + key);
 			if (value != null) {
 				acc[key] = JSON.parse(value);
 			}
 			return acc;
-		}, {} as any);
+		}, {});
 	}
 
 	public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void {
@@ -597,7 +607,7 @@ export class ColdDeviceStorage {
 			get: () => {
 				return valueRef.value;
 			},
-			set: (value: unknown) => {
+			set: (value: typeof ColdDeviceStorage.default[K]) => {
 				const val = value;
 				ColdDeviceStorage.set(key, val);
 			},
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index caaf9fca6fda..4204c5af59a5 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -7,39 +7,30 @@
  */
 
 :root {
-	--radius: 12px;
-	--marginFull: 16px;
-	--marginHalf: 10px;
+	--MI-radius: 12px;
+	--MI-marginFull: 16px;
+	--MI-marginHalf: 10px;
 
-	--margin: var(--marginFull);
+	--MI-margin: var(--MI-marginFull);
 
 	// switch dynamically
-	--minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px)));
-	--minBottomSpacing: var(--minBottomSpacingMobile);
-
-	//--ad: rgb(255 169 0 / 10%);
-	--eventFollow: #36aed2;
-	--eventRenote: #36d298;
-	--eventReply: #007aff;
-	--eventReactionHeart: #dd2e44;
-	--eventReaction: #e99a0b;
-	--eventAchievement: #cb9a11;
-	--eventOther: #88a6b7;
+	--MI-minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px)));
+	--MI-minBottomSpacing: var(--MI-minBottomSpacingMobile);
 
 	@media (max-width: 500px) {
-		--margin: var(--marginHalf);
+		--MI-margin: var(--MI-marginHalf);
 	}
 }
 
 ::selection {
-	color: var(--fgOnAccent);
-	background-color: var(--accent);
+	color: var(--MI_THEME-fgOnAccent);
+	background-color: var(--MI_THEME-accent);
 }
 
 html {
-	background-color: var(--bg);
-	color: var(--fg);
-	accent-color: var(--accent);
+	background-color: var(--MI_THEME-bg);
+	color: var(--MI_THEME-fg);
+	accent-color: var(--MI_THEME-accent);
 	overflow: auto;
 	overflow-wrap: break-word;
 	font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif;
@@ -50,7 +41,7 @@ html {
 	-webkit-text-size-adjust: 100%;
 
 	&, * {
-		scrollbar-color: var(--scrollbarHandle) transparent;
+		scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
 		scrollbar-width: thin;
 
 		&::-webkit-scrollbar {
@@ -63,14 +54,14 @@ html {
 		}
 
 		&::-webkit-scrollbar-thumb {
-			background: var(--scrollbarHandle);
+			background: var(--MI_THEME-scrollbarHandle);
 
 			&:hover {
-				background: var(--scrollbarHandleHover);
+				background: var(--MI_THEME-scrollbarHandleHover);
 			}
 
 			&:active {
-				background: var(--accent);
+				background: var(--MI_THEME-accent);
 			}
 		}
 	}
@@ -132,15 +123,15 @@ textarea, input {
 }
 
 optgroup, option {
-	background: var(--panel);
-	color: var(--fg);
+	background: var(--MI_THEME-panel);
+	color: var(--MI_THEME-fg);
 }
 
 hr {
-	margin: var(--margin) 0 var(--margin) 0;
+	margin: var(--MI-margin) 0 var(--MI-margin) 0;
 	border: none;
 	height: 1px;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 
 rt {
@@ -148,7 +139,7 @@ rt {
 }
 
 :focus-visible {
-	outline: var(--focus) solid 2px;
+	outline: var(--MI_THEME-focus) solid 2px;
 	outline-offset: -2px;
 
 	&:hover {
@@ -181,9 +172,9 @@ rt {
 
 ._indicateCounter {
 	display: inline-flex;
-	color: var(--fgOnAccent);
+	color: var(--MI_THEME-fgOnAccent);
 	font-weight: 700;
-	background: var(--indicator);
+	background: var(--MI_THEME-indicator);
 	height: 1.5em;
 	min-width: 1.5em;
 	align-items: center;
@@ -216,13 +207,13 @@ rt {
 	left: 0;
 	width: 100%;
 	height: 100%;
-	background: var(--modalBg);
-	-webkit-backdrop-filter: var(--modalBgFilter);
-	backdrop-filter: var(--modalBgFilter);
+	background: var(--MI_THEME-modalBg);
+	-webkit-backdrop-filter: var(--MI-modalBgFilter);
+	backdrop-filter: var(--MI-modalBgFilter);
 }
 
 ._shadow {
-	box-shadow: 0px 4px 32px var(--shadow) !important;
+	box-shadow: 0px 4px 32px var(--MI_THEME-shadow) !important;
 }
 
 ._button {
@@ -251,40 +242,40 @@ rt {
 
 ._buttonPrimary {
 	@extend ._button;
-	color: var(--fgOnAccent);
-	background: var(--accent);
+	color: var(--MI_THEME-fgOnAccent);
+	background: var(--MI_THEME-accent);
 
 	&:not(:disabled):hover {
-		background: hsl(from var(--accent) h s calc(l + 5));
+		background: hsl(from var(--MI_THEME-accent) h s calc(l + 5));
 	}
 
 	&:not(:disabled):active {
-		background: hsl(from var(--accent) h s calc(l - 5));
+		background: hsl(from var(--MI_THEME-accent) h s calc(l - 5));
 	}
 }
 
 ._buttonGradate {
 	@extend ._buttonPrimary;
-	color: var(--fgOnAccent);
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+	color: var(--MI_THEME-fgOnAccent);
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 
 	&:not(:disabled):hover {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 
 	&:not(:disabled):active {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 }
 
 ._help {
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 	cursor: help;
 }
 
 ._textButton {
 	@extend ._button;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 
 	&:focus-visible {
 		outline-offset: 2px;
@@ -296,13 +287,13 @@ rt {
 }
 
 ._panel {
-	background: var(--panel);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-panel);
+	border-radius: var(--MI-radius);
 	overflow: clip;
 }
 
 ._margin {
-	margin: var(--margin) 0;
+	margin: var(--MI-margin) 0;
 }
 
 ._gaps_m {
@@ -320,7 +311,7 @@ rt {
 ._gaps {
 	display: flex;
 	flex-direction: column;
-	gap: var(--margin);
+	gap: var(--MI-margin);
 }
 
 ._buttons {
@@ -342,24 +333,24 @@ rt {
 	padding: 10px;
 	box-sizing: border-box;
 	text-align: center;
-	border: solid 0.5px var(--divider);
-	border-radius: var(--radius);
+	border: solid 0.5px var(--MI_THEME-divider);
+	border-radius: var(--MI-radius);
 
 	&:active {
-		border-color: var(--accent);
+		border-color: var(--MI_THEME-accent);
 	}
 }
 
 ._popup {
-	background: var(--popup);
-	border-radius: var(--radius);
+	background: var(--MI_THEME-popup);
+	border-radius: var(--MI-radius);
 	contain: content;
 }
 
 ._acrylic {
-	background: var(--acrylicPanel);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	background: var(--MI_THEME-acrylicPanel);
+	-webkit-backdrop-filter: var(--MI-blur, blur(15px));
+	backdrop-filter: var(--MI-blur, blur(15px));
 }
 
 ._formLinksGrid {
@@ -372,8 +363,18 @@ rt {
 	margin-left: 0.7em;
 	font-size: 65%;
 	padding: 2px 3px;
-	color: var(--accent);
-	border: solid 1px var(--accent);
+	color: var(--MI_THEME-accent);
+	border: solid 1px var(--MI_THEME-accent);
+	border-radius: 4px;
+	vertical-align: top;
+}
+
+._modified {
+	margin-left: 0.7em;
+	font-size: 65%;
+	padding: 2px 3px;
+	color: var(--MI_THEME-warn);
+	border: solid 1px var(--MI_THEME-warn);
 	border-radius: 4px;
 	vertical-align: top;
 }
@@ -419,7 +420,7 @@ rt {
 }
 
 ._link {
-	color: var(--link);
+	color: var(--MI_THEME-link);
 }
 
 ._caption {
@@ -443,14 +444,14 @@ rt {
 	box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c;
 	border-radius: 10px;
 
-	--bg: #F1E8DC;
-	--fg: #693410;
+	--MI_THEME-bg: #F1E8DC;
+	--MI_THEME-fg: #693410;
 }
 
-html[data-color-mode=dark] ._woodenFrame {
-	--bg: #1d0c02;
-	--fg: #F1E8DC;
-	--panel: #192320;
+html[data-color-scheme=dark] ._woodenFrame {
+	--MI_THEME-bg: #1d0c02;
+	--MI_THEME-fg: #F1E8DC;
+	--MI_THEME-panel: #192320;
 }
 
 ._woodenFrameH {
@@ -461,10 +462,10 @@ html[data-color-mode=dark] ._woodenFrame {
 ._woodenFrameInner {
 	padding: 8px;
 	margin-top: 8px;
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 	box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
 	border-radius: 6px;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 
 	&:first-child {
 		margin-top: 0;
@@ -479,7 +480,11 @@ html[data-color-mode=dark] ._woodenFrame {
 	transform: scale(0.9);
 }
 
-@keyframes global-blink {
+._blink {
+	animation: blink 1s infinite;
+}
+
+@keyframes blink {
 	0% { opacity: 1; transform: scale(1); }
 	30% { opacity: 1; transform: scale(1); }
 	90% { opacity: 0; transform: scale(0.5); }
diff --git a/packages/frontend/src/types/post-form.ts b/packages/frontend/src/types/post-form.ts
new file mode 100644
index 000000000000..5bb04a95a06c
--- /dev/null
+++ b/packages/frontend/src/types/post-form.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+
+export interface PostFormProps {
+	reply?: Misskey.entities.Note;
+	renote?: Misskey.entities.Note;
+	channel?: Misskey.entities.Channel; // TODO
+	mention?: Misskey.entities.User;
+	specified?: Misskey.entities.UserDetailed;
+	initialText?: string;
+	initialCw?: string;
+	initialVisibility?: (typeof Misskey.noteVisibilities)[number];
+	initialFiles?: Misskey.entities.DriveFile[];
+	initialLocalOnly?: boolean;
+	initialVisibleUsers?: Misskey.entities.UserDetailed[];
+	initialNote?: Misskey.entities.Note;
+	instant?: boolean;
+};
diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue
index 374bc20b5450..d153dc872605 100644
--- a/packages/frontend/src/ui/_common_/announcements.vue
+++ b/packages/frontend/src/ui/_common_/announcements.vue
@@ -13,9 +13,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 	>
 		<span :class="$style.icon">
 			<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
-			<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i>
-			<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i>
-			<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i>
+			<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
+			<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
+			<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
 		</span>
 		<span :class="$style.title">{{ announcement.title }}</span>
 		<span :class="$style.body">{{ announcement.text }}</span>
@@ -30,7 +30,7 @@ import { $i } from '@/account.js';
 <style lang="scss" module>
 .root {
 	font-size: 15px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 }
 
 .item {
@@ -44,8 +44,8 @@ import { $i } from '@/account.js';
 	height: var(--height);
 	overflow: clip;
 	contain: strict;
-	background: var(--accent);
-	color: var(--fgOnAccent);
+	background: var(--MI_THEME-accent);
+	color: var(--MI_THEME-fgOnAccent);
 
 	@container (max-width: 1000px) {
 		display: block;
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index b067e721a594..f908803f0116 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -41,7 +41,9 @@ function toolsMenuItems(): MenuItem[] {
 }
 
 export function openInstanceMenu(ev: MouseEvent) {
-	os.popupMenu([{
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
 		text: instance.name ?? host,
 		type: 'label',
 	}, {
@@ -69,12 +71,18 @@ export function openInstanceMenu(ev: MouseEvent) {
 		text: i18n.ts.ads,
 		icon: 'ti ti-ad',
 		to: '/ads',
-	}, ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) ? {
-		type: 'link',
-		to: '/invite',
-		text: i18n.ts.invite,
-		icon: 'ti ti-user-plus',
-	} : undefined, {
+	});
+
+	if ($i && ($i.isAdmin || $i.policies.canInvite) && instance.disableRegistration) {
+		menuItems.push({
+			type: 'link',
+			to: '/invite',
+			text: i18n.ts.invite,
+			icon: 'ti ti-user-plus',
+		});
+	}
+
+	menuItems.push({
 		type: 'parent',
 		text: i18n.ts.tools,
 		icon: 'ti ti-tool',
@@ -84,43 +92,69 @@ export function openInstanceMenu(ev: MouseEvent) {
 		text: i18n.ts.inquiry,
 		icon: 'ti ti-help-circle',
 		to: '/contact',
-	}, (instance.impressumUrl) ? {
-		type: 'a',
-		text: i18n.ts.impressum,
-		icon: 'ti ti-file-invoice',
-		href: instance.impressumUrl,
-		target: '_blank',
-	} : undefined, (instance.tosUrl) ? {
-		type: 'a',
-		text: i18n.ts.termsOfService,
-		icon: 'ti ti-notebook',
-		href: instance.tosUrl,
-		target: '_blank',
-	} : undefined, (instance.privacyPolicyUrl) ? {
-		type: 'a',
-		text: i18n.ts.privacyPolicy,
-		icon: 'ti ti-shield-lock',
-		href: instance.privacyPolicyUrl,
-		target: '_blank',
-	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
+	});
+
+	if (instance.impressumUrl) {
+		menuItems.push({
+			type: 'a',
+			text: i18n.ts.impressum,
+			icon: 'ti ti-file-invoice',
+			href: instance.impressumUrl,
+			target: '_blank',
+		});
+	}
+
+	if (instance.tosUrl) {
+		menuItems.push({
+			type: 'a',
+			text: i18n.ts.termsOfService,
+			icon: 'ti ti-notebook',
+			href: instance.tosUrl,
+			target: '_blank',
+		});
+	}
+
+	if (instance.privacyPolicyUrl) {
+		menuItems.push({
+			type: 'a',
+			text: i18n.ts.privacyPolicy,
+			icon: 'ti ti-shield-lock',
+			href: instance.privacyPolicyUrl,
+			target: '_blank',
+		});
+	}
+
+	if (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) {
+		menuItems.push({ type: 'divider' });
+	}
+
+	menuItems.push({
 		type: 'a',
 		text: i18n.ts.document,
 		icon: 'ti ti-bulb',
 		href: 'https://misskey-hub.net/docs/for-users/',
 		target: '_blank',
-	}, ($i) ? {
-		text: i18n.ts._initialTutorial.launchTutorial,
-		icon: 'ti ti-presentation',
-		action: () => {
-			const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
-				closed: () => dispose(),
-			});
-		},
-	} : undefined, {
+	});
+
+	if ($i) {
+		menuItems.push({
+			text: i18n.ts._initialTutorial.launchTutorial,
+			icon: 'ti ti-presentation',
+			action: () => {
+				const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTutorialDialog.vue')), {}, {
+					closed: () => dispose(),
+				});
+			},
+		});
+	}
+
+	menuItems.push({
 		type: 'link',
 		text: i18n.ts.aboutMisskey,
 		to: '/about-misskey',
-	}], ev.currentTarget ?? ev.target, {
+	});
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target, {
 		align: 'left',
 	});
 }
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index d7df2d10f9e9..d145b9b6c61a 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -39,9 +39,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <div v-if="pendingApiRequestsCount > 0" id="wait"></div>
 
-<div v-if="dev" id="devTicker"><span>DEV BUILD</span></div>
+<div v-if="dev" id="devTicker"><span style="animation: dev-ticker-blink 2s infinite;">DEV BUILD</span></div>
 
-<div v-if="$i && $i.isBot" id="botWarn"><span>{{ i18n.ts.loggedInAsBot }}</span></div>
+<div v-if="$i && $i.isBot" id="botWarn"><span style="animation: dev-ticker-blink 2s infinite;">{{ i18n.ts.loggedInAsBot }}</span></div>
 </template>
 
 <script lang="ts" setup>
@@ -116,27 +116,27 @@ if ($i) {
 .notifications {
 	position: fixed;
 	z-index: 3900000;
-	padding: 0 var(--margin);
+	padding: 0 var(--MI-margin);
 	pointer-events: none;
 	display: flex;
 
 	&.notificationsPosition_leftTop {
-		top: var(--margin);
+		top: var(--MI-margin);
 		left: 0;
 	}
 
 	&.notificationsPosition_rightTop {
-		top: var(--margin);
+		top: var(--MI-margin);
 		right: 0;
 	}
 
 	&.notificationsPosition_leftBottom {
-		bottom: calc(var(--minBottomSpacing) + var(--margin));
+		bottom: calc(var(--MI-minBottomSpacing) + var(--MI-margin));
 		left: 0;
 	}
 
 	&.notificationsPosition_rightBottom {
-		bottom: calc(var(--minBottomSpacing) + var(--margin));
+		bottom: calc(var(--MI-minBottomSpacing) + var(--MI-margin));
 		right: 0;
 	}
 
@@ -234,8 +234,8 @@ if ($i) {
 		height: 18px;
 		box-sizing: border-box;
 		border: solid 2px transparent;
-		border-top-color: var(--accent);
-		border-left-color: var(--accent);
+		border-top-color: var(--MI_THEME-accent);
+		border-left-color: var(--MI_THEME-accent);
 		border-radius: 50%;
 		animation: progress-spinner 400ms linear infinite;
 	}
@@ -258,10 +258,6 @@ if ($i) {
 	font-size: 14px;
 	pointer-events: none;
 	user-select: none;
-
-	> span {
-		animation: dev-ticker-blink 2s infinite;
-	}
 }
 
 #devTicker {
@@ -275,9 +271,5 @@ if ($i) {
 	font-size: 14px;
 	pointer-events: none;
 	user-select: none;
-
-	> span {
-		animation: dev-ticker-blink 2s infinite;
-	}
 }
 </style>
diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
index e80d5fd3991d..44253e93bd27 100644
--- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
+++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-if="item === '-'" :class="$style.divider"></div>
 			<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
 				<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
-				<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator">
+				<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator" class="_blink">
 					<span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span>
 					<i v-else class="_indicatorCircle"></i>
 				</span>
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</MkA>
 		<button :class="$style.item" class="_button" @click="more">
 			<i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span>
-			<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span>
+			<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span>
 		</button>
 		<MkA :class="$style.item" :activeClass="$style.active" to="/settings">
 			<i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span>
@@ -82,7 +82,7 @@ function more() {
 
 <style lang="scss" module>
 .root {
-	--nav-bg-transparent: color-mix(in srgb, var(--navBg), transparent 50%);
+	--nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5);
 
 	display: flex;
 	flex-direction: column;
@@ -94,8 +94,8 @@ function more() {
 	z-index: 1;
 	padding: 20px 0;
 	background: var(--nav-bg-transparent);
-	-webkit-backdrop-filter: var(--blur, blur(8px));
-	backdrop-filter: var(--blur, blur(8px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+	backdrop-filter: var(--MI-blur, blur(8px));
 }
 
 .banner {
@@ -128,8 +128,8 @@ function more() {
 	bottom: 0;
 	padding: 20px 0;
 	background: var(--nav-bg-transparent);
-	-webkit-backdrop-filter: var(--blur, blur(8px));
-	backdrop-filter: var(--blur, blur(8px));
+	-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+	backdrop-filter: var(--MI-blur, blur(8px));
 }
 
 .post {
@@ -137,7 +137,7 @@ function more() {
 	display: block;
 	width: 100%;
 	height: 40px;
-	color: var(--fgOnAccent);
+	color: var(--MI_THEME-fgOnAccent);
 	font-weight: bold;
 	text-align: left;
 
@@ -153,12 +153,12 @@ function more() {
 		right: 0;
 		bottom: 0;
 		border-radius: 999px;
-		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 	}
 
 	&:hover, &.active {
 		&::before {
-			background: var(--accentLighten);
+			background: var(--MI_THEME-accentLighten);
 		}
 	}
 }
@@ -202,7 +202,7 @@ function more() {
 
 .divider {
 	margin: 16px 16px;
-	border-top: solid 0.5px var(--divider);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .item {
@@ -216,15 +216,15 @@ function more() {
 	width: 100%;
 	text-align: left;
 	box-sizing: border-box;
-	color: var(--navFg);
+	color: var(--MI_THEME-navFg);
 
 	&:hover {
 		text-decoration: none;
-		color: var(--navHoverFg);
+		color: var(--MI_THEME-navHoverFg);
 	}
 
 	&.active {
-		color: var(--navActive);
+		color: var(--MI_THEME-navActive);
 	}
 
 	&:hover, &.active {
@@ -240,7 +240,7 @@ function more() {
 			right: 0;
 			bottom: 0;
 			border-radius: 999px;
-			background: var(--accentedBg);
+			background: var(--MI_THEME-accentedBg);
 		}
 	}
 }
@@ -255,9 +255,8 @@ function more() {
 	position: absolute;
 	top: 0;
 	left: 20px;
-	color: var(--navIndicator);
+	color: var(--MI_THEME-navIndicator);
 	font-size: 8px;
-	animation: global-blink 1s infinite;
 
 	&:has(.itemIndicateValueIcon) {
 		animation: none;
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 2b116909ba7d..8ae11efa2c66 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"
 				>
 					<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
-					<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator">
+					<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator" class="_blink">
 						<span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span>
 						<i v-else class="_indicatorCircle"></i>
 					</span>
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 			<button class="_button" :class="$style.item" @click="more">
 				<i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span>
-				<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span>
+				<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span>
 			</button>
 			<MkA v-tooltip.noDelay.right="i18n.ts.settings" :class="$style.item" :activeClass="$style.active" to="/settings">
 				<i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span>
@@ -111,7 +111,7 @@ function more(ev: MouseEvent) {
 .root {
 	--nav-width: 250px;
 	--nav-icon-only-width: 80px;
-	--nav-bg-transparent: color-mix(in srgb, var(--navBg), transparent 50%);
+	--nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5);
 
 	flex: 0 0 var(--nav-width);
 	width: var(--nav-width);
@@ -129,7 +129,7 @@ function more(ev: MouseEvent) {
 	overflow: auto;
 	overflow-x: clip;
 	overscroll-behavior: contain;
-	background: var(--navBg);
+	background: var(--MI_THEME-navBg);
 	contain: strict;
 	display: flex;
 	flex-direction: column;
@@ -146,8 +146,8 @@ function more(ev: MouseEvent) {
 		z-index: 1;
 		padding: 20px 0;
 		background: var(--nav-bg-transparent);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(8px));
+		-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+		backdrop-filter: var(--MI-blur, blur(8px));
 	}
 
 	.banner {
@@ -172,7 +172,7 @@ function more(ev: MouseEvent) {
 			outline: none;
 
 			> .instanceIcon {
-				outline: 2px solid var(--focus);
+				outline: 2px solid var(--MI_THEME-focus);
 				outline-offset: 2px;
 			}
 		}
@@ -189,8 +189,8 @@ function more(ev: MouseEvent) {
 		bottom: 0;
 		padding-top: 20px;
 		background: var(--nav-bg-transparent);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(8px));
+		-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+		backdrop-filter: var(--MI-blur, blur(8px));
 	}
 
 	.post {
@@ -198,7 +198,7 @@ function more(ev: MouseEvent) {
 		display: block;
 		width: 100%;
 		height: 40px;
-		color: var(--fgOnAccent);
+		color: var(--MI_THEME-fgOnAccent);
 		font-weight: bold;
 		text-align: left;
 
@@ -214,21 +214,21 @@ function more(ev: MouseEvent) {
 			right: 0;
 			bottom: 0;
 			border-radius: 999px;
-			background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+			background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 		}
 
 		&:focus-visible {
 			outline: none;
 
 			&::before {
-				outline: 2px solid var(--fgOnAccent);
+				outline: 2px solid var(--MI_THEME-fgOnAccent);
 				outline-offset: -4px;
 			}
 		}
 
 		&:hover, &.active {
 			&::before {
-				background: var(--accentLighten);
+				background: var(--MI_THEME-accentLighten);
 			}
 		}
 	}
@@ -258,7 +258,7 @@ function more(ev: MouseEvent) {
 			outline: none;
 
 			> .avatar {
-				box-shadow: 0 0 0 4px var(--focus);
+				box-shadow: 0 0 0 4px var(--MI_THEME-focus);
 			}
 		}
 	}
@@ -284,7 +284,7 @@ function more(ev: MouseEvent) {
 
 	.divider {
 		margin: 16px 16px;
-		border-top: solid 0.5px var(--divider);
+		border-top: solid 0.5px var(--MI_THEME-divider);
 	}
 
 	.item {
@@ -298,28 +298,28 @@ function more(ev: MouseEvent) {
 		width: 100%;
 		text-align: left;
 		box-sizing: border-box;
-		color: var(--navFg);
+		color: var(--MI_THEME-navFg);
 
 		&:hover {
 			text-decoration: none;
-			color: var(--navHoverFg);
+			color: var(--MI_THEME-navHoverFg);
 		}
 
 		&.active {
-			color: var(--navActive);
+			color: var(--MI_THEME-navActive);
 		}
 
 		&:focus-visible {
 			outline: none;
 
 			&::before {
-				outline: 2px solid var(--focus);
+				outline: 2px solid var(--MI_THEME-focus);
 				outline-offset: -2px;
 			}
 		}
 
 		&:hover, &.active, &:focus {
-			color: var(--accent);
+			color: var(--MI_THEME-accent);
 
 			&::before {
 				content: "";
@@ -333,7 +333,7 @@ function more(ev: MouseEvent) {
 				right: 0;
 				bottom: 0;
 				border-radius: 999px;
-				background: var(--accentedBg);
+				background: var(--MI_THEME-accentedBg);
 			}
 		}
 	}
@@ -348,9 +348,8 @@ function more(ev: MouseEvent) {
 		position: absolute;
 		top: 0;
 		left: 20px;
-		color: var(--navIndicator);
+		color: var(--MI_THEME-navIndicator);
 		font-size: 8px;
-		animation: global-blink 1s infinite;
 
 		&:has(.itemIndicateValueIcon) {
 			animation: none;
@@ -380,8 +379,8 @@ function more(ev: MouseEvent) {
 		z-index: 1;
 		padding: 20px 0;
 		background: var(--nav-bg-transparent);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(8px));
+		-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+		backdrop-filter: var(--MI-blur, blur(8px));
 	}
 
 	.instance {
@@ -393,7 +392,7 @@ function more(ev: MouseEvent) {
 			outline: none;
 
 			> .instanceIcon {
-				outline: 2px solid var(--focus);
+				outline: 2px solid var(--MI_THEME-focus);
 				outline-offset: 2px;
 			}
 		}
@@ -410,8 +409,8 @@ function more(ev: MouseEvent) {
 		bottom: 0;
 		padding-top: 20px;
 		background: var(--nav-bg-transparent);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(8px));
+		-webkit-backdrop-filter: var(--MI-blur, blur(8px));
+		backdrop-filter: var(--MI-blur, blur(8px));
 	}
 
 	.post {
@@ -433,28 +432,28 @@ function more(ev: MouseEvent) {
 			width: 52px;
 			aspect-ratio: 1/1;
 			border-radius: 100%;
-			background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+			background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
 		}
 
 		&:focus-visible {
 			outline: none;
 
 			&::before {
-				outline: 2px solid var(--fgOnAccent);
+				outline: 2px solid var(--MI_THEME-fgOnAccent);
 				outline-offset: -4px;
 			}
 		}
 
 		&:hover, &.active {
 			&::before {
-				background: var(--accentLighten);
+				background: var(--MI_THEME-accentLighten);
 			}
 		}
 	}
 
 	.postIcon {
 		position: relative;
-		color: var(--fgOnAccent);
+		color: var(--MI_THEME-fgOnAccent);
 	}
 
 	.postText {
@@ -472,7 +471,7 @@ function more(ev: MouseEvent) {
 			outline: none;
 
 			> .avatar {
-				box-shadow: 0 0 0 4px var(--focus);
+				box-shadow: 0 0 0 4px var(--MI_THEME-focus);
 			}
 		}
 	}
@@ -494,7 +493,7 @@ function more(ev: MouseEvent) {
 	.divider {
 		margin: 8px auto;
 		width: calc(100% - 32px);
-		border-top: solid 0.5px var(--divider);
+		border-top: solid 0.5px var(--MI_THEME-divider);
 	}
 
 	.item {
@@ -508,14 +507,14 @@ function more(ev: MouseEvent) {
 			outline: none;
 
 			&::before {
-				outline: 2px solid var(--focus);
+				outline: 2px solid var(--MI_THEME-focus);
 				outline-offset: -2px;
 			}
 		}
 
 		&:hover, &.active, &:focus {
 			text-decoration: none;
-			color: var(--accent);
+			color: var(--MI_THEME-accent);
 
 			&::before {
 				content: "";
@@ -529,7 +528,7 @@ function more(ev: MouseEvent) {
 				right: 0;
 				bottom: 0;
 				border-radius: 999px;
-				background: var(--accentedBg);
+				background: var(--MI_THEME-accentedBg);
 			}
 
 			> .icon,
@@ -553,9 +552,8 @@ function more(ev: MouseEvent) {
 		position: absolute;
 		top: 6px;
 		left: 24px;
-		color: var(--navIndicator);
+		color: var(--MI_THEME-navIndicator);
 		font-size: 8px;
-		animation: global-blink 1s infinite;
 
 		&:has(.itemIndicateValueIcon) {
 			animation: none;
diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue
index 550fc39b001b..da8fa8bb21f1 100644
--- a/packages/frontend/src/ui/_common_/statusbar-rss.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue
@@ -48,7 +48,7 @@ const fetching = ref(true);
 const key = ref(0);
 
 const tick = () => {
-	window.fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => {
+	window.fetch(`/api/fetch-rss?url=${encodeURIComponent(props.url)}`, {}).then(res => {
 		res.json().then((feed: Misskey.entities.FetchRssResponse) => {
 			if (props.shuffle) {
 				shuffle(feed.items);
diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue
index 690366307b1c..5f9a9380172a 100644
--- a/packages/frontend/src/ui/_common_/statusbars.vue
+++ b/packages/frontend/src/ui/_common_/statusbars.vue
@@ -32,7 +32,7 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue')
 <style lang="scss" module>
 .root {
 	font-size: 15px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 }
 
 .item {
@@ -81,7 +81,7 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue')
 .name {
 	padding: 0 var(--nameMargin);
 	font-weight: bold;
-	color: var(--accent);
+	color: var(--MI_THEME-accent);
 
 	&:empty {
 		display: none;
diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue
index ad93b7e61c55..cc62a28b14bf 100644
--- a/packages/frontend/src/ui/_common_/stream-indicator.vue
+++ b/packages/frontend/src/ui/_common_/stream-indicator.vue
@@ -48,8 +48,8 @@ onUnmounted(() => {
 .root {
 	position: fixed;
 	z-index: v-bind(zIndex);
-	bottom: calc(var(--minBottomSpacing) + var(--margin));
-	right: var(--margin);
+	bottom: calc(var(--MI-minBottomSpacing) + var(--MI-margin));
+	right: var(--MI-margin);
 	margin: 0;
 	padding: 12px;
 	font-size: 0.9em;
diff --git a/packages/frontend/src/ui/_common_/upload.vue b/packages/frontend/src/ui/_common_/upload.vue
index 6db7f9cae73a..c7d1387eae97 100644
--- a/packages/frontend/src/ui/_common_/upload.vue
+++ b/packages/frontend/src/ui/_common_/upload.vue
@@ -125,10 +125,10 @@ const zIndex = os.claimZIndex('high');
 	height: 8px;
 }
 .mk-uploader > ol > li > progress::-webkit-progress-value {
-  background: var(--accent);
+  background: var(--MI_THEME-accent);
 }
 .mk-uploader > ol > li > progress::-webkit-progress-bar {
-  //background: var(--accentAlpha01);
+  //background: var(--MI_THEME-accentAlpha01);
 	background: transparent;
 }
 </style>
diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue
index c03afd6cd62a..f4633314ae85 100644
--- a/packages/frontend/src/ui/classic.header.vue
+++ b/packages/frontend/src/ui/classic.header.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div v-if="item === '-'" class="divider"></div>
 				<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
 					<i class="ti-fw" :class="navbarItemDef[item].icon"></i>
-					<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="_indicatorCircle"></i></span>
+					<span v-if="navbarItemDef[item].indicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
 				</component>
 			</template>
 			<div class="divider"></div>
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 			<button v-click-anime class="item _button" @click="more">
 				<i class="ti ti-dots ti-fw"></i>
-				<span v-if="otherNavItemIndicated" class="indicator"><i class="_indicatorCircle"></i></span>
+				<span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
 			</button>
 		</div>
 		<div class="right">
@@ -104,7 +104,7 @@ onMounted(() => {
 	z-index: 1000;
 	width: 100%;
 	height: $height;
-	background-color: var(--bg);
+	background-color: var(--MI_THEME-bg);
 
 	> .body {
 		max-width: 1380px;
@@ -140,18 +140,17 @@ onMounted(() => {
 					position: absolute;
 					top: 0;
 					left: 0;
-					color: var(--navIndicator);
+					color: var(--MI_THEME-navIndicator);
 					font-size: 8px;
-					animation: global-blink 1s infinite;
 				}
 
 				&:hover {
 					text-decoration: none;
-					color: var(--navHoverFg);
+					color: var(--MI_THEME-navHoverFg);
 				}
 
 				&.active {
-					color: var(--navActive);
+					color: var(--MI_THEME-navActive);
 				}
 			}
 
@@ -159,7 +158,7 @@ onMounted(() => {
 				display: inline-block;
 				height: 16px;
 				margin: 0 10px;
-				border-right: solid 0.5px var(--divider);
+				border-right: solid 0.5px var(--MI_THEME-divider);
 			}
 
 			> .instance {
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index 87b4515d46e6..5acef0bef816 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div v-if="item === '-'" class="divider"></div>
 		<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
 			<i class="ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span>
-			<span v-if="navbarItemDef[item].indicated" class="indicator">
+			<span v-if="navbarItemDef[item].indicated" class="indicator _blink">
 				<span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span>
 				<i v-else class="_indicatorCircle"></i>
 			</span>
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</MkA>
 	<button v-click-anime class="item _button" @click="more">
 		<i class="ti ti-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span>
-		<span v-if="otherNavItemIndicated" class="indicator"><i class="_indicatorCircle"></i></span>
+		<span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
 	</button>
 	<MkA v-click-anime class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
 		<i class="ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
@@ -157,7 +157,7 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 
 	> .divider {
 		margin: 10px 0;
-		border-top: solid 0.5px var(--divider);
+		border-top: solid 0.5px var(--MI_THEME-divider);
 	}
 
 	> .post {
@@ -165,7 +165,7 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 		top: 0;
 		z-index: 1;
 		padding: 16px 0;
-		background: var(--bg);
+		background: var(--MI_THEME-bg);
 
 		> .button {
 			min-width: 0;
@@ -220,9 +220,8 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 			position: absolute;
 			top: 0;
 			left: 0;
-			color: var(--navIndicator);
+			color: var(--MI_THEME-navIndicator);
 			font-size: 8px;
-			animation: global-blink 1s infinite;
 
 			&:has(.itemIndicateValueIcon) {
 				animation: none;
@@ -233,11 +232,11 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 
 		&:hover {
 			text-decoration: none;
-			color: var(--navHoverFg);
+			color: var(--MI_THEME-navHoverFg);
 		}
 
 		&.active {
-			color: var(--navActive);
+			color: var(--MI_THEME-navActive);
 		}
 	}
 }
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index fa04409d2dbe..5ea9bf70680d 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<XSidebar/>
 		</div>
 		<div v-else-if="!pageMetadata?.needWideArea" ref="widgetsLeft" class="widgets left">
-			<XWidgets place="left" :marginTop="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/>
+			<XWidgets place="left" :marginTop="'var(--MI-margin)'" @mounted="attachSticky(widgetsLeft)"/>
 		</div>
 
 		<main class="main" @contextmenu.stop="onContextmenu">
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</main>
 
 		<div v-if="isDesktop && !pageMetadata?.needWideArea" ref="widgetsRight" class="widgets right">
-			<XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/>
+			<XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--MI-margin)'" @mounted="attachSticky(widgetsRight)"/>
 		</div>
 	</div>
 
@@ -216,8 +216,8 @@ onMounted(() => {
 	box-sizing: border-box;
 
 	&.wallpaper {
-		background: var(--wallpaperOverlay);
-		//backdrop-filter: var(--blur, blur(4px));
+		background: var(--MI_THEME-wallpaperOverlay);
+		//backdrop-filter: var(--MI-blur, blur(4px));
 	}
 
 	> .columns {
@@ -249,17 +249,17 @@ onMounted(() => {
 			min-width: 0;
 			width: 750px;
 			margin: 0 16px 0 0;
-			border-left: solid 1px var(--divider);
-			border-right: solid 1px var(--divider);
+			border-left: solid 1px var(--MI_THEME-divider);
+			border-right: solid 1px var(--MI_THEME-divider);
 			border-radius: 0;
 			overflow: clip;
-			--margin: 12px;
+			--MI-margin: 12px;
 		}
 
 		> .widgets {
-			//--panelBorder: none;
+			//--MI_THEME-panelBorder: none;
 			width: 300px;
-			padding-bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
+			padding-bottom: calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px));
 
 			@media (max-width: $widgets-hide-threshold) {
 				display: none;
@@ -277,13 +277,13 @@ onMounted(() => {
 		&.withGlobalHeader {
 			> .main {
 				margin-top: 0;
-				border: solid 1px var(--divider);
-				border-radius: var(--radius);
-				--stickyTop: var(--globalHeaderHeight);
+				border: solid 1px var(--MI_THEME-divider);
+				border-radius: var(--MI-radius);
+				--MI-stickyTop: var(--globalHeaderHeight);
 			}
 
 			> .widgets {
-				--stickyTop: var(--globalHeaderHeight);
+				--MI-stickyTop: var(--globalHeaderHeight);
 				margin-top: 0;
 			}
 		}
@@ -292,7 +292,7 @@ onMounted(() => {
 			margin: 0;
 
 			> .sidebar {
-				border-right: solid 0.5px var(--divider);
+				border-right: solid 0.5px var(--MI_THEME-divider);
 			}
 
 			> .main {
@@ -314,10 +314,10 @@ onMounted(() => {
 		right: 0;
 		z-index: 1001;
 		height: 100dvh;
-		padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
+		padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px));
 		box-sizing: border-box;
 		overflow: auto;
-		background: var(--bg);
+		background: var(--MI_THEME-bg);
 	}
 
 	> .ivnzpscs {
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 9c3addc482b3..a1a76a7e7da8 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -50,11 +50,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 
 	<div v-if="isMobile" :class="$style.nav">
-		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
+		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button>
 		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
 		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')">
 			<i :class="$style.navButtonIcon" class="ti ti-bell"></i>
-			<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator">
+			<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink">
 				<span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
 			</span>
 		</button>
@@ -97,6 +97,7 @@ import { v4 as uuid } from 'uuid';
 import XCommon from './_common_/common.vue';
 import { deckStore, columnTypes, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store.js';
 import type { ColumnType } from './deck/deck-store.js';
+import type { MenuItem } from '@/types/menu.js';
 import XSidebar from '@/ui/_common_/navbar.vue';
 import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -118,7 +119,6 @@ import XMentionsColumn from '@/ui/deck/mentions-column.vue';
 import XDirectColumn from '@/ui/deck/direct-column.vue';
 import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
 import { mainRouter } from '@/router/main.js';
-import { MenuItem } from '@/types/menu.js';
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
 const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
 
@@ -305,7 +305,7 @@ body {
 .root {
 	$nav-hide-threshold: 650px; // TODO: どこかに集約したい
 
-	--margin: var(--marginHalf);
+	--MI-margin: var(--MI-marginHalf);
 
 	--columnGap: 6px;
 
@@ -332,7 +332,7 @@ body {
 	overflow-x: auto;
 	overflow-y: clip;
 	overscroll-behavior: contain;
-	background: var(--deckBg);
+	background: var(--MI_THEME-deckBg);
 
 	&.center {
 		> .section:first-of-type {
@@ -414,7 +414,7 @@ body {
 	contain: strict;
 	overflow: auto;
 	overscroll-behavior: contain;
-	background: var(--navBg);
+	background: var(--MI_THEME-navBg);
 }
 
 .nav {
@@ -428,10 +428,10 @@ body {
 	grid-gap: 8px;
 	width: 100%;
 	box-sizing: border-box;
-	-webkit-backdrop-filter: var(--blur, blur(32px));
-	backdrop-filter: var(--blur, blur(32px));
-	background-color: var(--header);
-	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(32px));
+	backdrop-filter: var(--MI-blur, blur(32px));
+	background-color: var(--MI_THEME-header);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .navButton {
@@ -442,29 +442,29 @@ body {
 	max-width: 60px;
 	margin: auto;
 	border-radius: 100%;
-	background: var(--panel);
-	color: var(--fg);
+	background: var(--MI_THEME-panel);
+	color: var(--MI_THEME-fg);
 
 	&:hover {
-		background: var(--panelHighlight);
+		background: var(--MI_THEME-panelHighlight);
 	}
 
 	&:active {
-		background: hsl(from var(--panel) h s calc(l - 2));
+		background: hsl(from var(--MI_THEME-panel) h s calc(l - 2));
 	}
 }
 
 .postButton {
 	composes: navButton;
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
-	color: var(--fgOnAccent);
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
+	color: var(--MI_THEME-fgOnAccent);
 
 	&:hover {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 
 	&:active {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 }
 
@@ -477,9 +477,8 @@ body {
 	position: absolute;
 	top: 0;
 	left: 0;
-	color: var(--indicator);
+	color: var(--MI_THEME-indicator);
 	font-size: 16px;
-	animation: global-blink 1s infinite;
 
 	&:has(.itemIndicateValueIcon) {
 		animation: none;
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 987bd4db557e..a41639e71c46 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { antennasCache } from '@/cache.js';
 import { SoundStore } from '@/store.js';
 import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 42c07056e7c1..5479b53d9058 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -29,7 +29,7 @@ import * as os from '@/os.js';
 import { favoritedChannelsCache } from '@/cache.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { SoundStore } from '@/store.js';
 import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
 import * as sound from '@/scripts/sound.js';
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 893301122e0a..da0bf24a5625 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	>
 		<svg viewBox="0 0 256 128" :class="$style.tabShape">
 			<g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)">
-				<path d="M149.512,4.707L108.507,4.707C116.252,4.719 118.758,14.958 118.758,14.958C118.758,14.958 121.381,25.283 129.009,25.209L149.512,25.209L149.512,4.707Z" style="fill:var(--deckBg);"/>
+				<path d="M149.512,4.707L108.507,4.707C116.252,4.719 118.758,14.958 118.758,14.958C118.758,14.958 121.381,25.283 129.009,25.209L149.512,25.209L149.512,4.707Z" style="fill:var(--MI_THEME-deckBg);"/>
 			</g>
 		</svg>
 		<div :class="$style.color"></div>
@@ -46,7 +46,7 @@ import { onBeforeUnmount, onMounted, provide, watch, shallowRef, ref, computed }
 import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 
 provide('shouldHeaderThin', true);
 provide('shouldOmitHeaderTitle', true);
@@ -104,7 +104,27 @@ function toggleActive() {
 }
 
 function getMenu() {
-	let items: MenuItem[] = [{
+	const menuItems: MenuItem[] = [];
+
+	if (props.menu) {
+		menuItems.push(...props.menu, {
+			type: 'divider',
+		});
+	}
+
+	if (props.refresher) {
+		menuItems.push({
+			icon: 'ti ti-refresh',
+			text: i18n.ts.reload,
+			action: () => {
+				if (props.refresher) {
+					props.refresher();
+				}
+			},
+		});
+	}
+
+	menuItems.push({
 		icon: 'ti ti-settings',
 		text: i18n.ts._deck.configureColumn,
 		action: async () => {
@@ -129,74 +149,73 @@ function getMenu() {
 			if (canceled) return;
 			updateColumn(props.column.id, result);
 		},
+	});
+
+	const moveToMenuItems: MenuItem[] = [];
+
+	moveToMenuItems.push({
+		icon: 'ti ti-arrow-left',
+		text: i18n.ts._deck.swapLeft,
+		action: () => {
+			swapLeftColumn(props.column.id);
+		},
 	}, {
-		type: 'parent',
-		text: i18n.ts.move + '...',
-		icon: 'ti ti-arrows-move',
-		children: [{
-			icon: 'ti ti-arrow-left',
-			text: i18n.ts._deck.swapLeft,
-			action: () => {
-				swapLeftColumn(props.column.id);
-			},
-		}, {
-			icon: 'ti ti-arrow-right',
-			text: i18n.ts._deck.swapRight,
-			action: () => {
-				swapRightColumn(props.column.id);
-			},
-		}, props.isStacked ? {
+		icon: 'ti ti-arrow-right',
+		text: i18n.ts._deck.swapRight,
+		action: () => {
+			swapRightColumn(props.column.id);
+		},
+	});
+
+	if (props.isStacked) {
+		moveToMenuItems.push({
 			icon: 'ti ti-arrow-up',
 			text: i18n.ts._deck.swapUp,
 			action: () => {
 				swapUpColumn(props.column.id);
 			},
-		} : undefined, props.isStacked ? {
+		}, {
 			icon: 'ti ti-arrow-down',
 			text: i18n.ts._deck.swapDown,
 			action: () => {
 				swapDownColumn(props.column.id);
 			},
-		} : undefined],
+		});
+	}
+
+	menuItems.push({
+		type: 'parent',
+		text: i18n.ts.move + '...',
+		icon: 'ti ti-arrows-move',
+		children: moveToMenuItems,
 	}, {
 		icon: 'ti ti-stack-2',
 		text: i18n.ts._deck.stackLeft,
 		action: () => {
 			stackLeftColumn(props.column.id);
 		},
-	}, props.isStacked ? {
-		icon: 'ti ti-window-maximize',
-		text: i18n.ts._deck.popRight,
-		action: () => {
-			popRightColumn(props.column.id);
-		},
-	} : undefined, { type: 'divider' }, {
+	});
+
+	if (props.isStacked) {
+		menuItems.push({
+			icon: 'ti ti-window-maximize',
+			text: i18n.ts._deck.popRight,
+			action: () => {
+				popRightColumn(props.column.id);
+			},
+		});
+	}
+
+	menuItems.push({ type: 'divider' }, {
 		icon: 'ti ti-trash',
 		text: i18n.ts.remove,
 		danger: true,
 		action: () => {
 			removeColumn(props.column.id);
 		},
-	}];
-
-	if (props.menu) {
-		items.unshift({ type: 'divider' });
-		items = props.menu.concat(items);
-	}
-
-	if (props.refresher) {
-		items = [{
-			icon: 'ti ti-refresh',
-			text: i18n.ts.reload,
-			action: () => {
-				if (props.refresher) {
-					props.refresher();
-				}
-			},
-		}, ...items];
-	}
+	});
 
-	return items;
+	return menuItems;
 }
 
 function showSettingsMenu(ev: MouseEvent) {
@@ -280,7 +299,7 @@ function onDrop(ev) {
 			left: 0;
 			width: 100%;
 			height: 100%;
-			background: var(--focus);
+			background: var(--MI_THEME-focus);
 		}
 	}
 
@@ -294,7 +313,7 @@ function onDrop(ev) {
 			left: 0;
 			width: 100%;
 			height: 100%;
-			background: var(--focus);
+			background: var(--MI_THEME-focus);
 			opacity: 0.5;
 		}
 	}
@@ -312,19 +331,19 @@ function onDrop(ev) {
 	}
 
 	&.naked {
-		background: var(--acrylicBg) !important;
-		-webkit-backdrop-filter: var(--blur, blur(10px));
-		backdrop-filter: var(--blur, blur(10px));
+		background: var(--MI_THEME-acrylicBg) !important;
+		-webkit-backdrop-filter: var(--MI-blur, blur(10px));
+		backdrop-filter: var(--MI-blur, blur(10px));
 
 		> .header {
 			background: transparent;
 			box-shadow: none;
-			color: var(--fg);
+			color: var(--MI_THEME-fg);
 		}
 
 		> .body {
 			background: transparent !important;
-			scrollbar-color: var(--scrollbarHandle) transparent;
+			scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
 
 			&::-webkit-scrollbar-track {
 				background: transparent;
@@ -333,12 +352,12 @@ function onDrop(ev) {
 	}
 
 	&.paged {
-		background: var(--bg) !important;
+		background: var(--MI_THEME-bg) !important;
 
 		> .body {
-			background: var(--bg) !important;
+			background: var(--MI_THEME-bg) !important;
 			overflow-y: scroll !important;
-			scrollbar-color: var(--scrollbarHandle) transparent;
+			scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
 
 			&::-webkit-scrollbar-track {
 				background: inherit;
@@ -355,9 +374,9 @@ function onDrop(ev) {
 	height: var(--deckColumnHeaderHeight);
 	padding: 0 16px 0 30px;
 	font-size: 0.9em;
-	color: var(--panelHeaderFg);
-	background: var(--panelHeaderBg);
-	box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
+	color: var(--MI_THEME-panelHeaderFg);
+	background: var(--MI_THEME-panelHeaderBg);
+	box-shadow: 0 1px 0 0 var(--MI_THEME-panelHeaderDivider);
 	cursor: pointer;
 	user-select: none;
 }
@@ -368,7 +387,7 @@ function onDrop(ev) {
 	left: 12px;
 	width: 3px;
 	height: calc(100% - 24px);
-	background: var(--accent);
+	background: var(--MI_THEME-accent);
 	border-radius: 999px;
 }
 
@@ -422,11 +441,11 @@ function onDrop(ev) {
 	overscroll-behavior-y: contain;
 	box-sizing: border-box;
 	container-type: size;
-	background-color: var(--bg);
-	scrollbar-color: var(--scrollbarHandle) var(--panel);
+	background-color: var(--MI_THEME-bg);
+	scrollbar-color: var(--MI_THEME-scrollbarHandle) var(--MI_THEME-panel);
 
 	&::-webkit-scrollbar-track {
-		background: var(--panel);
+		background: var(--MI_THEME-panel);
 	}
 }
 </style>
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index eb587554b97c..318698234905 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -49,6 +49,7 @@ export type Column = {
 	tl?: BasicTimelineType;
 	withRenotes?: boolean;
 	withReplies?: boolean;
+	withSensitive?: boolean;
 	onlyFiles?: boolean;
 	soundSetting: SoundStore;
 };
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 9aa8f06476d6..8bb8fe7225cf 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -22,7 +22,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { SoundStore } from '@/store.js';
 import { userListsCache } from '@/cache.js';
 import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index a375e9c574f8..beb4237978c3 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -21,7 +21,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { SoundStore } from '@/store.js';
 import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js';
 import * as sound from '@/scripts/sound.js';
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index e210ee7b7a2c..74c4fb504bb5 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:src="column.tl"
 		:withRenotes="withRenotes"
 		:withReplies="withReplies"
+		:withSensitive="withSensitive"
 		:onlyFiles="onlyFiles"
 		@note="onNote"
 	/>
@@ -54,6 +55,7 @@ const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
 const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
 const withRenotes = ref(props.column.withRenotes ?? true);
 const withReplies = ref(props.column.withReplies ?? false);
+const withSensitive = ref(props.column.withSensitive ?? true);
 const onlyFiles = ref(props.column.onlyFiles ?? false);
 
 watch(withRenotes, v => {
@@ -68,6 +70,12 @@ watch(withReplies, v => {
 	});
 });
 
+watch(withSensitive, v => {
+	updateColumn(props.column.id, {
+		withSensitive: v,
+	});
+});
+
 watch(onlyFiles, v => {
 	updateColumn(props.column.id, {
 		onlyFiles: v,
@@ -113,29 +121,45 @@ function onNote() {
 	sound.playMisskeySfxFile(soundSetting.value);
 }
 
-const menu = computed<MenuItem[]>(() => [{
-	icon: 'ti ti-pencil',
-	text: i18n.ts.timeline,
-	action: setType,
-}, {
-	icon: 'ti ti-bell',
-	text: i18n.ts._deck.newNoteNotificationSettings,
-	action: () => soundSettingsButton(soundSetting),
-}, {
-	type: 'switch',
-	text: i18n.ts.showRenotes,
-	ref: withRenotes,
-}, hasWithReplies(props.column.tl) ? {
-	type: 'switch',
-	text: i18n.ts.showRepliesToOthersInTimeline,
-	ref: withReplies,
-	disabled: onlyFiles,
-} : undefined, {
-	type: 'switch',
-	text: i18n.ts.fileAttachedOnly,
-	ref: onlyFiles,
-	disabled: hasWithReplies(props.column.tl) ? withReplies : false,
-}]);
+const menu = computed<MenuItem[]>(() => {
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
+		icon: 'ti ti-pencil',
+		text: i18n.ts.timeline,
+		action: setType,
+	}, {
+		icon: 'ti ti-bell',
+		text: i18n.ts._deck.newNoteNotificationSettings,
+		action: () => soundSettingsButton(soundSetting),
+	}, {
+		type: 'switch',
+		text: i18n.ts.showRenotes,
+		ref: withRenotes,
+	});
+
+	if (hasWithReplies(props.column.tl)) {
+		menuItems.push({
+			type: 'switch',
+			text: i18n.ts.showRepliesToOthersInTimeline,
+			ref: withReplies,
+			disabled: onlyFiles,
+		});
+	}
+
+	menuItems.push({
+		type: 'switch',
+		text: i18n.ts.fileAttachedOnly,
+		ref: onlyFiles,
+		disabled: hasWithReplies(props.column.tl) ? withReplies : false,
+	}, {
+		type: 'switch',
+		text: i18n.ts.withSensitive,
+		ref: withSensitive,
+	});
+
+	return menuItems;
+});
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue
index 9995996771d9..a0e62c8264b6 100644
--- a/packages/frontend/src/ui/deck/widgets-column.vue
+++ b/packages/frontend/src/ui/deck/widgets-column.vue
@@ -57,10 +57,10 @@ const menu = [{
 
 <style lang="scss" module>
 .root {
-	--margin: 8px;
-	--panelBorder: none;
+	--MI-margin: 8px;
+	--MI_THEME-panelBorder: none;
 
-	padding: 0 var(--margin);
+	padding: 0 var(--MI-margin);
 }
 
 .intro {
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index a2a79c74a155..d739c2e1cddc 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<button v-if="(!isDesktop || pageMetadata?.needWideArea) && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
 
 	<div v-if="isMobile" ref="navFooter" :class="$style.nav">
-		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
+		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button>
 		<button :class="$style.navButton" class="_button" @click="isRoot ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
 		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')">
 			<i :class="$style.navButtonIcon" class="ti ti-bell"></i>
-			<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator">
+			<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink">
 				<span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
 			</span>
 		</button>
@@ -96,9 +96,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue';
+import { instanceName } from '@@/js/config.js';
+import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js';
+import { isLink } from '@@/js/is-link.js';
 import XCommon from './_common_/common.vue';
 import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
-import { instanceName } from '@@/js/config.js';
 import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -108,10 +110,8 @@ import { $i } from '@/account.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { miLocalStorage } from '@/local-storage.js';
-import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { mainRouter } from '@/router/main.js';
-import { isLink } from '@@/js/is-link.js';
 
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
@@ -225,12 +225,12 @@ provide<Ref<number>>(CURRENT_STICKY_BOTTOM, navFooterHeight);
 watch(navFooter, () => {
 	if (navFooter.value) {
 		navFooterHeight.value = navFooter.value.offsetHeight;
-		document.body.style.setProperty('--stickyBottom', `${navFooterHeight.value}px`);
-		document.body.style.setProperty('--minBottomSpacing', 'var(--minBottomSpacingMobile)');
+		document.body.style.setProperty('--MI-stickyBottom', `${navFooterHeight.value}px`);
+		document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)');
 	} else {
 		navFooterHeight.value = 0;
-		document.body.style.setProperty('--stickyBottom', '0px');
-		document.body.style.setProperty('--minBottomSpacing', '0px');
+		document.body.style.setProperty('--MI-stickyBottom', '0px');
+		document.body.style.setProperty('--MI-minBottomSpacing', '0px');
 	}
 }, {
 	immediate: true,
@@ -318,7 +318,7 @@ $widgets-hide-threshold: 1090px;
 }
 
 .sidebar {
-	border-right: solid 0.5px var(--divider);
+	border-right: solid 0.5px var(--MI_THEME-divider);
 }
 
 .contents {
@@ -328,7 +328,7 @@ $widgets-hide-threshold: 1090px;
 	overflow: auto;
 	overflow-y: scroll;
 	overscroll-behavior: contain;
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 }
 
 .widgets {
@@ -336,9 +336,9 @@ $widgets-hide-threshold: 1090px;
 	height: 100%;
 	box-sizing: border-box;
 	overflow: auto;
-	padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
-	border-left: solid 0.5px var(--divider);
-	background: var(--bg);
+	padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px));
+	border-left: solid 0.5px var(--MI_THEME-divider);
+	background: var(--MI_THEME-bg);
 
 	@media (max-width: $widgets-hide-threshold) {
 		display: none;
@@ -356,7 +356,7 @@ $widgets-hide-threshold: 1090px;
 	border-radius: 100%;
 	box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
 	font-size: 22px;
-	background: var(--panel);
+	background: var(--MI_THEME-panel);
 }
 
 .widgetsDrawerBg {
@@ -370,11 +370,11 @@ $widgets-hide-threshold: 1090px;
 	z-index: 1001;
 	width: 310px;
 	height: 100dvh;
-	padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important;
+	padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)) !important;
 	box-sizing: border-box;
 	overflow: auto;
 	overscroll-behavior: contain;
-	background: var(--bg);
+	background: var(--MI_THEME-bg);
 }
 
 .widgetsCloseButton {
@@ -400,10 +400,10 @@ $widgets-hide-threshold: 1090px;
 	grid-gap: 8px;
 	width: 100%;
 	box-sizing: border-box;
-	-webkit-backdrop-filter: var(--blur, blur(24px));
-	backdrop-filter: var(--blur, blur(24px));
-	background-color: var(--header);
-	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--MI-blur, blur(24px));
+	backdrop-filter: var(--MI-blur, blur(24px));
+	background-color: var(--MI_THEME-header);
+	border-top: solid 0.5px var(--MI_THEME-divider);
 }
 
 .navButton {
@@ -414,29 +414,29 @@ $widgets-hide-threshold: 1090px;
 	max-width: 60px;
 	margin: auto;
 	border-radius: 100%;
-	background: var(--panel);
-	color: var(--fg);
+	background: var(--MI_THEME-panel);
+	color: var(--MI_THEME-fg);
 
 	&:hover {
-		background: var(--panelHighlight);
+		background: var(--MI_THEME-panelHighlight);
 	}
 
 	&:active {
-		background: hsl(from var(--panel) h s calc(l - 2));
+		background: hsl(from var(--MI_THEME-panel) h s calc(l - 2));
 	}
 }
 
 .postButton {
 	composes: navButton;
-	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
-	color: var(--fgOnAccent);
+	background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB));
+	color: var(--MI_THEME-fgOnAccent);
 
 	&:hover {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 
 	&:active {
-		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
+		background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5)));
 	}
 }
 
@@ -449,9 +449,8 @@ $widgets-hide-threshold: 1090px;
 	position: absolute;
 	top: 0;
 	left: 0;
-	color: var(--indicator);
+	color: var(--MI_THEME-indicator);
 	font-size: 16px;
-	animation: global-blink 1s infinite;
 
 	&:has(.itemIndicateValueIcon) {
 		animation: none;
@@ -474,7 +473,7 @@ $widgets-hide-threshold: 1090px;
 	contain: strict;
 	overflow: auto;
 	overscroll-behavior: contain;
-	background: var(--navBg);
+	background: var(--MI_THEME-navBg);
 }
 
 .statusbars {
@@ -484,6 +483,6 @@ $widgets-hide-threshold: 1090px;
 }
 
 .spacer {
-	height: calc(var(--minBottomSpacing));
+	height: calc(var(--MI-minBottomSpacing));
 }
 </style>
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 01d073712393..7d8677e3be78 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div class="mk-app">
-	<a v-if="isRoot" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
+	<a v-if="isRoot" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--MI_THEME-panel); color:var(--MI_THEME-fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
 
 	<div v-if="!narrow && !isRoot" class="side">
 		<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
@@ -191,7 +191,7 @@ defineExpose({
 		left: 0;
 		width: 500px;
 		height: 100vh;
-		background: var(--accent);
+		background: var(--MI_THEME-accent);
 
 		> .banner {
 			position: absolute;
@@ -219,7 +219,7 @@ defineExpose({
 		min-width: 0;
 
 		> .header {
-			background: var(--panel);
+			background: var(--MI_THEME-panel);
 
 			> .wide {
 				line-height: 50px;
@@ -254,7 +254,7 @@ defineExpose({
 		left: 0;
 		width: 240px;
 		height: 100vh;
-		background: var(--panel);
+		background: var(--MI_THEME-panel);
 
 		> .link {
 			display: block;
@@ -268,7 +268,7 @@ defineExpose({
 		> .divider {
 			margin: 8px auto;
 			width: calc(100% - 32px);
-			border-top: solid 0.5px var(--divider);
+			border-top: solid 0.5px var(--MI_THEME-divider);
 		}
 
 		> .action {
@@ -283,7 +283,7 @@ defineExpose({
 				border-radius: 999px;
 
 				&._button {
-					background: var(--panel);
+					background: var(--MI_THEME-panel);
 				}
 
 				&:first-child {
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index f22bf41fd708..1f73b5fcafb8 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -63,12 +63,12 @@ document.documentElement.style.overflowY = 'scroll';
 }
 
 .rootWithBottom {
-	min-height: calc(100dvh - (60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px)));
+	min-height: calc(100dvh - (60px + (var(--MI-margin) * 2) + env(safe-area-inset-bottom, 0px)));
 	box-sizing: border-box;
 }
 
 .bottom {
-	height: calc(60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px));
+	height: calc(60px + (var(--MI-margin) * 2) + env(safe-area-inset-bottom, 0px));
 	width: 100%;
 	margin-top: auto;
 }
@@ -81,9 +81,9 @@ document.documentElement.style.overflowY = 'scroll';
 	max-width: 60px;
 	margin: auto;
 	border-radius: 100%;
-	background: var(--panel);
-	color: var(--fg);
-	right: var(--margin);
-	bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
+	background: var(--MI_THEME-panel);
+	color: var(--MI_THEME-fg);
+	right: var(--MI-margin);
+	bottom: calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px));
 }
 </style>
diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue
index a74483e85eb9..cf7e9c5a3e62 100644
--- a/packages/frontend/src/widgets/WidgetAiscript.vue
+++ b/packages/frontend/src/widgets/WidgetAiscript.vue
@@ -126,10 +126,10 @@ defineExpose<WidgetComponentExpose>({
 		max-width: 100%;
 		min-width: 100%;
 		padding: 16px;
-		color: var(--fg);
+		color: var(--MI_THEME-fg);
 		background: transparent;
 		border: none;
-		border-bottom: solid 0.5px var(--divider);
+		border-bottom: solid 0.5px var(--MI_THEME-divider);
 		border-radius: 0;
 		box-sizing: border-box;
 		font: inherit;
@@ -154,7 +154,7 @@ defineExpose<WidgetComponentExpose>({
 	}
 
 	> .logs {
-		border-top: solid 0.5px var(--divider);
+		border-top: solid 0.5px var(--MI_THEME-divider);
 		text-align: left;
 		padding: 16px;
 
diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
index bcfaaf00ab4f..c2bda85ac787 100644
--- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
+++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
@@ -115,7 +115,7 @@ defineExpose<WidgetComponentExpose>({
 <style lang="scss" module>
 .bdayFRoot {
 	overflow: hidden;
-	min-height: calc(calc(calc(50px * 3) - 8px) + calc(var(--margin) * 2));
+	min-height: calc(calc(calc(50px * 3) - 8px) + calc(var(--MI-margin) * 2));
 }
 .bdayFGrid {
 	display: grid;
@@ -123,7 +123,7 @@ defineExpose<WidgetComponentExpose>({
 	grid-template-rows: repeat(3, 42px);
 	place-content: center;
 	gap: 8px;
-	margin: var(--margin) auto;
+	margin: var(--MI-margin) auto;
 }
 
 .bdayFFallback {
@@ -139,6 +139,6 @@ defineExpose<WidgetComponentExpose>({
 	width: auto;
 	max-width: 90%;
 	margin-bottom: 8px;
-	border-radius: var(--radius);
+	border-radius: var(--MI-radius);
 }
 </style>
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index 412d5278192d..2443e407896a 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -207,7 +207,7 @@ defineExpose<WidgetComponentExpose>({
 .meter {
 	width: 100%;
 	overflow: hidden;
-	background: var(--X11);
+	background: var(--MI_THEME-X11);
 	border-radius: 8px;
 }
 
diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue
index c10416e4fbf0..4a10a612e2f8 100644
--- a/packages/frontend/src/widgets/WidgetFederation.vue
+++ b/packages/frontend/src/widgets/WidgetFederation.vue
@@ -105,7 +105,7 @@ defineExpose<WidgetComponentExpose>({
 			display: flex;
 			align-items: center;
 			padding: 14px 16px;
-			border-bottom: solid 0.5px var(--divider);
+			border-bottom: solid 0.5px var(--MI_THEME-divider);
 
 			> img {
 				display: block;
@@ -120,7 +120,7 @@ defineExpose<WidgetComponentExpose>({
 				flex: 1;
 				overflow: hidden;
 				font-size: 0.9em;
-				color: var(--fg);
+				color: var(--MI_THEME-fg);
 				padding-right: 8px;
 
 				> .a {
diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue
index edf6622a137d..0ee6b863dcd2 100644
--- a/packages/frontend/src/widgets/WidgetJobQueue.vue
+++ b/packages/frontend/src/widgets/WidgetJobQueue.vue
@@ -173,14 +173,14 @@ defineExpose<WidgetComponentExpose>({
 		padding: 16px;
 
 		&:not(:first-child) {
-			border-top: solid 0.5px var(--divider);
+			border-top: solid 0.5px var(--MI_THEME-divider);
 		}
 
 		> .label {
 			display: flex;
 
 			> .icon {
-				color: var(--warn);
+				color: var(--MI_THEME-warn);
 				margin-left: auto;
 				animation: warnBlink 1s infinite;
 			}
@@ -198,11 +198,11 @@ defineExpose<WidgetComponentExpose>({
 
 				> div:last-child {
 					&.inc {
-						color: var(--warn);
+						color: var(--MI_THEME-warn);
 					}
 
 					&.dec {
-						color: var(--success);
+						color: var(--MI_THEME-success);
 					}
 				}
 			}
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index 7ee83157c6af..7b179ce70352 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -84,10 +84,10 @@ defineExpose<WidgetComponentExpose>({
 	max-width: 100%;
 	min-width: 100%;
 	padding: 16px;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	background: transparent;
 	border: none;
-	border-bottom: solid 0.5px var(--divider);
+	border-bottom: solid 0.5px var(--MI_THEME-divider);
 	border-radius: 0;
 	box-sizing: border-box;
 	font: inherit;
diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
index d56ee96ac175..d8c4e259c8b9 100644
--- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue
+++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
@@ -72,6 +72,6 @@ defineExpose<WidgetComponentExpose>({
 }
 
 .text {
-	color: var(--fgTransparentWeak);
+	color: var(--MI_THEME-fgTransparentWeak);
 }
 </style>
diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue
index 34be8c5e572a..40e2d8fbc7a3 100644
--- a/packages/frontend/src/widgets/WidgetPhotos.vue
+++ b/packages/frontend/src/widgets/WidgetPhotos.vue
@@ -68,10 +68,10 @@ const onDriveFileCreated = (file) => {
 	}
 };
 
-const thumbnail = (image: any): string => {
+const thumbnail = (image: Misskey.entities.DriveFile): string => {
 	return defaultStore.state.disableShowingAnimatedImages
 		? getStaticImageUrl(image.url)
-		: image.thumbnailUrl;
+		: image.thumbnailUrl ?? image.url;
 };
 
 misskeyApi('drive/stream', {
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index 511777a5709c..92dc6d148e29 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -70,7 +70,7 @@ const items = computed(() => rawItems.value.slice(0, widgetProps.maxEntries));
 const fetching = ref(true);
 const fetchEndpoint = computed(() => {
 	const url = new URL('/api/fetch-rss', base);
-	url.searchParams.set('url', widgetProps.url);
+	url.searchParams.set('url', encodeURIComponent(widgetProps.url));
 	return url;
 });
 const intervalClear = ref<(() => void) | undefined>();
@@ -113,7 +113,7 @@ defineExpose<WidgetComponentExpose>({
 .item {
 	display: block;
 	padding: 8px 16px;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 	white-space: nowrap;
 	text-overflow: ellipsis;
 	overflow: hidden;
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index b393ecd74b9d..6957878572cd 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -99,7 +99,7 @@ const items = computed(() => {
 const fetching = ref(true);
 const fetchEndpoint = computed(() => {
 	const url = new URL('/api/fetch-rss', base);
-	url.searchParams.set('url', widgetProps.url);
+	url.searchParams.set('url', encodeURIComponent(widgetProps.url));
 	return url;
 });
 const intervalClear = ref<(() => void) | undefined>();
@@ -171,7 +171,7 @@ defineExpose<WidgetComponentExpose>({
 	display: inline-flex;
 	align-items: center;
 	vertical-align: bottom;
-	color: var(--fg);
+	color: var(--MI_THEME-fg);
 }
 
 .divider {
@@ -179,6 +179,6 @@ defineExpose<WidgetComponentExpose>({
 	width: 0.5px;
 	height: 16px;
 	margin: 0 1em;
-	background: var(--divider);
+	background: var(--MI_THEME-divider);
 }
 </style>
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index d02f9b8e224a..a4685fd1fc6c 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -40,6 +40,7 @@ import MkContainer from '@/components/MkContainer.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import { i18n } from '@/i18n.js';
 import { availableBasicTimelines, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
+import type { MenuItem } from '@/types/menu.js';
 
 const name = 'timeline';
 
@@ -109,11 +110,26 @@ const choose = async (ev) => {
 			setSrc('list');
 		},
 	}));
-	os.popupMenu([...availableBasicTimelines().map(tl => ({
+
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push(...availableBasicTimelines().map(tl => ({
 		text: i18n.ts._timelines[tl],
 		icon: basicTimelineIconClass(tl),
 		action: () => { setSrc(tl); },
-	})), antennaItems.length > 0 ? { type: 'divider' } : undefined, ...antennaItems, listItems.length > 0 ? { type: 'divider' } : undefined, ...listItems], ev.currentTarget ?? ev.target).then(() => {
+	})));
+
+	if (antennaItems.length > 0) {
+		menuItems.push({ type: 'divider' });
+		menuItems.push(...antennaItems);
+	}
+
+	if (listItems.length > 0) {
+		menuItems.push({ type: 'divider' });
+		menuItems.push(...listItems);
+	}
+
+	os.popupMenu(menuItems, ev.currentTarget ?? ev.target).then(() => {
 		menuOpened.value = false;
 	});
 };
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index a41db513e8d1..47a4efc106ba 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -91,13 +91,13 @@ defineExpose<WidgetComponentExpose>({
 			display: flex;
 			align-items: center;
 			padding: 14px 16px;
-			border-bottom: solid 0.5px var(--divider);
+			border-bottom: solid 0.5px var(--MI_THEME-divider);
 
 			> .tag {
 				flex: 1;
 				overflow: hidden;
 				font-size: 0.9em;
-				color: var(--fg);
+				color: var(--MI_THEME-fg);
 
 				> .a {
 					display: block;
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index e982df8ffd7e..504562a91e9f 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -109,6 +109,11 @@ export function getConfig(): UserConfig {
 					}
 				},
 			},
+			preprocessorOptions: {
+				scss: {
+					api: 'modern-compiler',
+				},
+			},
 		},
 
 		define: {
diff --git a/packages/misskey-bubble-game/build.js b/packages/misskey-bubble-game/build.js
index e626c97a5967..a80b71646f43 100644
--- a/packages/misskey-bubble-game/build.js
+++ b/packages/misskey-bubble-game/build.js
@@ -1,32 +1,32 @@
-import * as esbuild from "esbuild";
-import { build } from "esbuild";
-import { globSync } from "glob";
-import { execa } from "execa";
-import fs from "node:fs";
-import { fileURLToPath } from "node:url";
-import { dirname } from "node:path";
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
+import * as esbuild from 'esbuild';
+import { build } from 'esbuild';
+import { globSync } from 'glob';
+import { execa } from 'execa';
 
 const _filename = fileURLToPath(import.meta.url);
 const _dirname = dirname(_filename);
 const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
 
-const entryPoints = globSync("./src/**/**.{ts,tsx}");
+const entryPoints = globSync('./src/**/**.{ts,tsx}');
 
 /** @type {import('esbuild').BuildOptions} */
 const options = {
 	entryPoints,
 	minify: process.env.NODE_ENV === 'production',
-	outdir: "./built",
-	target: "es2022",
-	platform: "browser",
-	format: "esm",
+	outdir: './built',
+	target: 'es2022',
+	platform: 'browser',
+	format: 'esm',
 	sourcemap: 'linked',
 };
 
 // built配下をすべて削除する
 fs.rmSync('./built', { recursive: true, force: true });
 
-if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) {
+if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
 	await watchSrc();
 } else {
 	await buildSrc();
@@ -36,7 +36,7 @@ async function buildSrc() {
 	console.log(`[${_package.name}] start building...`);
 
 	await build(options)
-		.then(it => {
+		.then(() => {
 			console.log(`[${_package.name}] build succeeded.`);
 		})
 		.catch((err) => {
@@ -65,7 +65,7 @@ function buildDts() {
 		{
 			stdout: process.stdout,
 			stderr: process.stderr,
-		}
+		},
 	);
 }
 
@@ -86,7 +86,7 @@ async function watchSrc() {
 		},
 	}];
 
-	console.log(`[${_package.name}] start watching...`)
+	console.log(`[${_package.name}] start watching...`);
 
 	const context = await esbuild.context({ ...options, plugins });
 	await context.watch();
diff --git a/packages/misskey-bubble-game/eslint.config.js b/packages/misskey-bubble-game/eslint.config.js
index 86c21a22a3a7..bce383b1a6d5 100644
--- a/packages/misskey-bubble-game/eslint.config.js
+++ b/packages/misskey-bubble-game/eslint.config.js
@@ -1,6 +1,7 @@
 import tsParser from '@typescript-eslint/parser';
 import sharedConfig from '../shared/eslint.config.js';
 
+// eslint-disable-next-line import/no-default-export
 export default [
 	...sharedConfig,
 	{
diff --git a/packages/misskey-bubble-game/src/game.ts b/packages/misskey-bubble-game/src/game.ts
index 3bce4b1dcfe8..7f230e39cb8f 100644
--- a/packages/misskey-bubble-game/src/game.ts
+++ b/packages/misskey-bubble-game/src/game.ts
@@ -199,13 +199,12 @@ export class DropAndFusionGame extends EventEmitter<{
 		};
 		if (mono.shape === 'circle') {
 			return Matter.Bodies.circle(x, y, mono.sizeX / 2, options);
-		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 		} else if (mono.shape === 'rectangle') {
 			return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options);
-		} else if (mono.shape === 'custom') {
-			return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({
-				x: (j.x / mono.verticesSize!) * mono.sizeX,
-				y: (j.y / mono.verticesSize!) * mono.sizeY,
+		} else if (mono.shape === 'custom' && mono.vertices != null && mono.verticesSize != null) { //eslint-disable-line @typescript-eslint/no-unnecessary-condition
+			return Matter.Bodies.fromVertices(x, y, mono.vertices.map(i => i.map(j => ({
+				x: (j.x / mono.verticesSize!) * mono.sizeX, //eslint-disable-line @typescript-eslint/no-non-null-assertion
+				y: (j.y / mono.verticesSize!) * mono.sizeY, //eslint-disable-line @typescript-eslint/no-non-null-assertion
 			}))), options);
 		} else {
 			throw new Error('unrecognized shape');
@@ -227,7 +226,12 @@ export class DropAndFusionGame extends EventEmitter<{
 		this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id);
 		Matter.Composite.remove(this.engine.world, [bodyA, bodyB]);
 
-		const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!;
+		const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label);
+
+		if (currentMono == null) {
+			throw new Error('Current Mono Not Found');
+		}
+
 		const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1) ?? null;
 
 		if (nextMono) {
@@ -362,14 +366,18 @@ export class DropAndFusionGame extends EventEmitter<{
 	}
 
 	public getActiveMonos() {
-		return this.engine.world.bodies.map(x => this.monoDefinitions.find((mono) => mono.id === x.label)!).filter(x => x !== undefined);
+		return this.engine.world.bodies
+			.map(x => this.monoDefinitions.find((mono) => mono.id === x.label))
+			.filter(x => x !== undefined);
 	}
 
 	public drop(_x: number) {
 		if (this.isGameOver) return;
 		if (this.frame - this.latestDroppedAt < this.DROP_COOLTIME) return;
 
-		const head = this.stock.shift()!;
+		const head = this.stock.shift();
+		if (!head) return;
+
 		this.stock.push({
 			id: this.rng().toString(),
 			mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
@@ -411,13 +419,15 @@ export class DropAndFusionGame extends EventEmitter<{
 		});
 
 		if (this.holding) {
-			const head = this.stock.shift()!;
+			const head = this.stock.shift();
+			if (!head) return;
 			this.stock.unshift(this.holding);
 			this.holding = head;
 			this.emit('changeHolding', this.holding);
 			this.emit('changeStock', this.stock);
 		} else {
-			const head = this.stock.shift()!;
+			const head = this.stock.shift();
+			if (!head) return;
 			this.holding = head;
 			this.stock.push({
 				id: this.rng().toString(),
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 1ec7f0ec7f39..061b533b7244 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -4,7 +4,10 @@
 
 ```ts
 
+import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
 import { EventEmitter } from 'eventemitter3';
+import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
+import _ReconnectingWebsocket from 'reconnecting-websocket';
 
 // Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts
 //
@@ -118,6 +121,9 @@ type AdminAnnouncementsUpdateRequest = operations['admin___announcements___updat
 // @public (undocumented)
 type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
 
@@ -211,6 +217,9 @@ type AdminFederationRemoveAllFollowingRequest = operations['admin___federation__
 // @public (undocumented)
 type AdminFederationUpdateInstanceRequest = operations['admin___federation___update-instance']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminGetIndexStatsResponse = operations['admin___get-index-stats']['responses']['200']['content']['application/json'];
 
@@ -358,6 +367,9 @@ type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show']
 // @public (undocumented)
 type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
 
@@ -373,6 +385,9 @@ type AdminUnsetUserBannerRequest = operations['admin___unset-user-banner']['requ
 // @public (undocumented)
 type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
 
@@ -668,7 +683,7 @@ export type Channels = {
     };
     hashtag: {
         params: {
-            q?: string;
+            q: string[][];
         };
         events: {
             note: (payload: Note) => void;
@@ -1153,9 +1168,25 @@ export type Endpoints = Overwrite<Endpoints_2, {
         req: SignupPendingRequest;
         res: SignupPendingResponse;
     };
-    'signin': {
-        req: SigninRequest;
-        res: SigninResponse;
+    'signin-flow': {
+        req: SigninFlowRequest;
+        res: SigninFlowResponse;
+    };
+    'signin-with-passkey': {
+        req: SigninWithPasskeyRequest;
+        res: {
+            $switch: {
+                $cases: [
+                [
+                    {
+                    context: string;
+                },
+                SigninWithPasskeyResponse
+                ]
+                ];
+                $default: SigninWithPasskeyInitResponse;
+            };
+        };
     };
     'admin/roles/create': {
         req: Overwrite<AdminRolesCreateRequest, {
@@ -1187,8 +1218,11 @@ declare namespace entities {
         SignupResponse,
         SignupPendingRequest,
         SignupPendingResponse,
-        SigninRequest,
-        SigninResponse,
+        SigninFlowRequest,
+        SigninFlowResponse,
+        SigninWithPasskeyRequest,
+        SigninWithPasskeyInitResponse,
+        SigninWithPasskeyResponse,
         PartialRolePolicyOverride,
         EmptyRequest,
         EmptyResponse,
@@ -1222,6 +1256,7 @@ declare namespace entities {
         AdminAnnouncementsListResponse,
         AdminAnnouncementsUpdateRequest,
         AdminAvatarDecorationsCreateRequest,
+        AdminAvatarDecorationsCreateResponse,
         AdminAvatarDecorationsDeleteRequest,
         AdminAvatarDecorationsListRequest,
         AdminAvatarDecorationsListResponse,
@@ -1274,6 +1309,8 @@ declare namespace entities {
         AdminResetPasswordRequest,
         AdminResetPasswordResponse,
         AdminResolveAbuseUserReportRequest,
+        AdminForwardAbuseUserReportRequest,
+        AdminUpdateAbuseUserReportRequest,
         AdminSendEmailRequest,
         AdminServerInfoResponse,
         AdminShowModerationLogsRequest,
@@ -1308,6 +1345,7 @@ declare namespace entities {
         AdminSystemWebhookShowResponse,
         AdminSystemWebhookUpdateRequest,
         AdminSystemWebhookUpdateResponse,
+        AdminSystemWebhookTestRequest,
         AnnouncementsRequest,
         AnnouncementsResponse,
         AnnouncementsShowRequest,
@@ -1567,6 +1605,7 @@ declare namespace entities {
         IWebhooksShowResponse,
         IWebhooksUpdateRequest,
         IWebhooksDeleteRequest,
+        IWebhooksTestRequest,
         InviteCreateResponse,
         InviteDeleteRequest,
         InviteListRequest,
@@ -1654,6 +1693,7 @@ declare namespace entities {
         FlashCreateRequest,
         FlashCreateResponse,
         FlashDeleteRequest,
+        FlashFeaturedRequest,
         FlashFeaturedResponse,
         FlashLikeRequest,
         FlashShowRequest,
@@ -1903,6 +1943,9 @@ type FlashCreateResponse = operations['flash___create']['responses']['200']['con
 // @public (undocumented)
 type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json'];
 
@@ -2369,6 +2412,9 @@ type IWebhooksShowRequest = operations['i___webhooks___show']['requestBody']['co
 // @public (undocumented)
 type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
 
@@ -2513,6 +2559,12 @@ type ModerationLog = {
 } | {
     type: 'resolveAbuseReport';
     info: ModerationLogPayloads['resolveAbuseReport'];
+} | {
+    type: 'forwardAbuseReport';
+    info: ModerationLogPayloads['forwardAbuseReport'];
+} | {
+    type: 'updateAbuseReportNote';
+    info: ModerationLogPayloads['updateAbuseReportNote'];
 } | {
     type: 'unsetUserAvatar';
     info: ModerationLogPayloads['unsetUserAvatar'];
@@ -2552,7 +2604,7 @@ type ModerationLog = {
 });
 
 // @public (undocumented)
-export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"];
+export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "forwardAbuseReport", "updateAbuseReportNote", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"];
 
 // @public (undocumented)
 type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
@@ -3009,16 +3061,48 @@ type ServerStatsLog = ServerStats[];
 type Signin = components['schemas']['Signin'];
 
 // @public (undocumented)
-type SigninRequest = {
+type SigninFlowRequest = {
     username: string;
-    password: string;
+    password?: string;
     token?: string;
+    credential?: AuthenticationResponseJSON;
+    'hcaptcha-response'?: string | null;
+    'g-recaptcha-response'?: string | null;
+    'turnstile-response'?: string | null;
+    'm-captcha-response'?: string | null;
 };
 
 // @public (undocumented)
-type SigninResponse = {
+type SigninFlowResponse = {
+    finished: true;
     id: User['id'];
     i: string;
+} | {
+    finished: false;
+    next: 'captcha' | 'password' | 'totp';
+} | {
+    finished: false;
+    next: 'passkey';
+    authRequest: PublicKeyCredentialRequestOptionsJSON;
+};
+
+// @public (undocumented)
+type SigninWithPasskeyInitResponse = {
+    option: PublicKeyCredentialRequestOptionsJSON;
+    context: string;
+};
+
+// @public (undocumented)
+type SigninWithPasskeyRequest = {
+    credential?: AuthenticationResponseJSON;
+    context?: string;
+};
+
+// @public (undocumented)
+type SigninWithPasskeyResponse = {
+    signinResponse: SigninFlowResponse & {
+        finished: true;
+    };
 };
 
 // @public (undocumented)
@@ -3042,6 +3126,7 @@ type SignupRequest = {
     'hcaptcha-response'?: string | null;
     'g-recaptcha-response'?: string | null;
     'turnstile-response'?: string | null;
+    'm-captcha-response'?: string | null;
 };
 
 // @public (undocumented)
@@ -3057,7 +3142,7 @@ export class Stream extends EventEmitter<StreamEvents> implements IStream {
     constructor(origin: string, user: {
         token: string;
     } | null, options?: {
-        WebSocket?: WebSocket;
+        WebSocket?: _ReconnectingWebsocket.Options['WebSocket'];
     });
     // (undocumented)
     close(): void;
@@ -3319,7 +3404,7 @@ type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody']['
 
 // Warnings were encountered during analysis:
 //
-// src/entities.ts:49:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
+// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
 // src/streaming.types.ts:220:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts
 // src/streaming.types.ts:230:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts
 
diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json
index 4a02bcd8ff7c..212c92fba529 100644
--- a/packages/misskey-js/generator/package.json
+++ b/packages/misskey-js/generator/package.json
@@ -7,15 +7,15 @@
 		"generate": "tsx src/generator.ts && eslint ./built/**/*.ts --fix"
 	},
 	"devDependencies": {
-		"@readme/openapi-parser": "2.5.0",
+		"@readme/openapi-parser": "2.6.0",
 		"@types/node": "20.9.1",
-		"@typescript-eslint/eslint-plugin": "6.11.0",
-		"@typescript-eslint/parser": "6.11.0",
+		"@typescript-eslint/eslint-plugin": "7.17.0",
+		"@typescript-eslint/parser": "7.17.0",
 		"openapi-types": "12.1.3",
 		"openapi-typescript": "6.7.3",
-		"ts-case-convert": "2.0.2",
+		"ts-case-convert": "2.0.7",
 		"tsx": "4.4.0",
-		"typescript": "5.3.3"
+		"typescript": "5.6.2"
 	},
 	"files": [
 		"built"
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 39e687d4af34..19615cfee5e4 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,7 +1,7 @@
 {
 	"type": "module",
 	"name": "misskey-js",
-	"version": "2024.8.0",
+	"version": "2024.11.0-alpha.0",
 	"description": "Misskey SDK for JavaScript",
 	"license": "MIT",
 	"main": "./built/index.js",
@@ -35,9 +35,9 @@
 		"directory": "packages/misskey-js"
 	},
 	"devDependencies": {
-		"@microsoft/api-extractor": "7.47.4",
+		"@microsoft/api-extractor": "7.47.9",
 		"@swc/jest": "0.2.36",
-		"@types/jest": "29.5.12",
+		"@types/jest": "29.5.13",
 		"@types/node": "20.14.12",
 		"@typescript-eslint/eslint-plugin": "7.17.0",
 		"@typescript-eslint/parser": "7.17.0",
@@ -46,17 +46,18 @@
 		"jest-websocket-mock": "2.5.0",
 		"mock-socket": "9.3.1",
 		"ncp": "2.0.0",
-		"nodemon": "3.1.4",
-		"execa": "9.3.0",
-		"tsd": "0.31.1",
-		"typescript": "5.5.4",
-		"esbuild": "0.23.0",
+		"nodemon": "3.1.7",
+		"execa": "9.4.0",
+		"tsd": "0.31.2",
+		"typescript": "5.6.2",
+		"esbuild": "0.23.1",
 		"glob": "11.0.0"
 	},
 	"files": [
 		"built"
 	],
 	"dependencies": {
+		"@simplewebauthn/types": "10.0.0",
 		"eventemitter3": "5.0.1",
 		"reconnecting-websocket": "4.4.0"
 	}
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index 5ee4194db213..838949f8e1bb 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -3,8 +3,11 @@ import { UserDetailed } from './autogen/models.js';
 import { AdminRolesCreateRequest, AdminRolesCreateResponse, UsersShowRequest } from './autogen/entities.js';
 import {
 	PartialRolePolicyOverride,
-	SigninRequest,
-	SigninResponse,
+	SigninFlowRequest,
+	SigninFlowResponse,
+	SigninWithPasskeyInitResponse,
+	SigninWithPasskeyRequest,
+	SigninWithPasskeyResponse,
 	SignupPendingRequest,
 	SignupPendingResponse,
 	SignupRequest,
@@ -78,9 +81,25 @@ export type Endpoints = Overwrite<
 			res: SignupPendingResponse;
 		},
 		// api.jsonには載せないものなのでここで定義
-		'signin': {
-			req: SigninRequest;
-			res: SigninResponse;
+		'signin-flow': {
+			req: SigninFlowRequest;
+			res: SigninFlowResponse;
+		},
+		'signin-with-passkey': {
+			req: SigninWithPasskeyRequest;
+			res: {
+				$switch: {
+					$cases: [
+						[
+							{
+								context: string;
+							},
+							SigninWithPasskeyResponse,
+						],
+					];
+					$default: SigninWithPasskeyInitResponse;
+				},
+			},
 		},
 		'admin/roles/create': {
 			req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>;
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index e799d4a0c5e0..e2c7cbba5274 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -691,6 +691,28 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
+     */
+    request<E extends 'admin/forward-abuse-user-report', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
+     */
+    request<E extends 'admin/update-abuse-user-report', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -960,6 +982,18 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook*
+     */
+    request<E extends 'admin/system-webhook/test', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2819,6 +2853,18 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    request<E extends 'i/webhooks/test', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 8fbdbbb629ad..5e6bc0a99c27 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -31,6 +31,7 @@ import type {
 	AdminAnnouncementsListResponse,
 	AdminAnnouncementsUpdateRequest,
 	AdminAvatarDecorationsCreateRequest,
+	AdminAvatarDecorationsCreateResponse,
 	AdminAvatarDecorationsDeleteRequest,
 	AdminAvatarDecorationsListRequest,
 	AdminAvatarDecorationsListResponse,
@@ -83,6 +84,8 @@ import type {
 	AdminResetPasswordRequest,
 	AdminResetPasswordResponse,
 	AdminResolveAbuseUserReportRequest,
+	AdminForwardAbuseUserReportRequest,
+	AdminUpdateAbuseUserReportRequest,
 	AdminSendEmailRequest,
 	AdminServerInfoResponse,
 	AdminShowModerationLogsRequest,
@@ -117,6 +120,7 @@ import type {
 	AdminSystemWebhookShowResponse,
 	AdminSystemWebhookUpdateRequest,
 	AdminSystemWebhookUpdateResponse,
+	AdminSystemWebhookTestRequest,
 	AnnouncementsRequest,
 	AnnouncementsResponse,
 	AnnouncementsShowRequest,
@@ -376,6 +380,7 @@ import type {
 	IWebhooksShowResponse,
 	IWebhooksUpdateRequest,
 	IWebhooksDeleteRequest,
+	IWebhooksTestRequest,
 	InviteCreateResponse,
 	InviteDeleteRequest,
 	InviteListRequest,
@@ -463,6 +468,7 @@ import type {
 	FlashCreateRequest,
 	FlashCreateResponse,
 	FlashDeleteRequest,
+	FlashFeaturedRequest,
 	FlashFeaturedResponse,
 	FlashLikeRequest,
 	FlashShowRequest,
@@ -592,7 +598,7 @@ export type Endpoints = {
 	'admin/announcements/delete': { req: AdminAnnouncementsDeleteRequest; res: EmptyResponse };
 	'admin/announcements/list': { req: AdminAnnouncementsListRequest; res: AdminAnnouncementsListResponse };
 	'admin/announcements/update': { req: AdminAnnouncementsUpdateRequest; res: EmptyResponse };
-	'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: EmptyResponse };
+	'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: AdminAvatarDecorationsCreateResponse };
 	'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };
 	'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse };
 	'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
@@ -636,6 +642,8 @@ export type Endpoints = {
 	'admin/relays/remove': { req: AdminRelaysRemoveRequest; res: EmptyResponse };
 	'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse };
 	'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse };
+	'admin/forward-abuse-user-report': { req: AdminForwardAbuseUserReportRequest; res: EmptyResponse };
+	'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse };
 	'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse };
 	'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse };
 	'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse };
@@ -660,6 +668,7 @@ export type Endpoints = {
 	'admin/system-webhook/list': { req: AdminSystemWebhookListRequest; res: AdminSystemWebhookListResponse };
 	'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse };
 	'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse };
+	'admin/system-webhook/test': { req: AdminSystemWebhookTestRequest; res: EmptyResponse };
 	'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse };
 	'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse };
 	'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse };
@@ -826,6 +835,7 @@ export type Endpoints = {
 	'i/webhooks/show': { req: IWebhooksShowRequest; res: IWebhooksShowResponse };
 	'i/webhooks/update': { req: IWebhooksUpdateRequest; res: EmptyResponse };
 	'i/webhooks/delete': { req: IWebhooksDeleteRequest; res: EmptyResponse };
+	'i/webhooks/test': { req: IWebhooksTestRequest; res: EmptyResponse };
 	'invite/create': { req: EmptyRequest; res: InviteCreateResponse };
 	'invite/delete': { req: InviteDeleteRequest; res: EmptyResponse };
 	'invite/list': { req: InviteListRequest; res: InviteListResponse };
@@ -885,7 +895,7 @@ export type Endpoints = {
 	'pages/update': { req: PagesUpdateRequest; res: EmptyResponse };
 	'flash/create': { req: FlashCreateRequest; res: FlashCreateResponse };
 	'flash/delete': { req: FlashDeleteRequest; res: EmptyResponse };
-	'flash/featured': { req: EmptyRequest; res: FlashFeaturedResponse };
+	'flash/featured': { req: FlashFeaturedRequest; res: FlashFeaturedResponse };
 	'flash/like': { req: FlashLikeRequest; res: EmptyResponse };
 	'flash/show': { req: FlashShowRequest; res: FlashShowResponse };
 	'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse };
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 357b5e9eaf68..f3ddf6448106 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -34,6 +34,7 @@ export type AdminAnnouncementsListRequest = operations['admin___announcements___
 export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
 export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
 export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
+export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];
 export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json'];
 export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json'];
 export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json'];
@@ -86,6 +87,8 @@ export type AdminRelaysRemoveRequest = operations['admin___relays___remove']['re
 export type AdminResetPasswordRequest = operations['admin___reset-password']['requestBody']['content']['application/json'];
 export type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json'];
 export type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json'];
+export type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json'];
+export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json'];
 export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json'];
 export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json'];
 export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json'];
@@ -120,6 +123,7 @@ export type AdminSystemWebhookShowRequest = operations['admin___system-webhook__
 export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json'];
 export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json'];
 export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json'];
+export type AdminSystemWebhookTestRequest = operations['admin___system-webhook___test']['requestBody']['content']['application/json'];
 export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json'];
 export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json'];
 export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json'];
@@ -379,6 +383,7 @@ export type IWebhooksShowRequest = operations['i___webhooks___show']['requestBod
 export type IWebhooksShowResponse = operations['i___webhooks___show']['responses']['200']['content']['application/json'];
 export type IWebhooksUpdateRequest = operations['i___webhooks___update']['requestBody']['content']['application/json'];
 export type IWebhooksDeleteRequest = operations['i___webhooks___delete']['requestBody']['content']['application/json'];
+export type IWebhooksTestRequest = operations['i___webhooks___test']['requestBody']['content']['application/json'];
 export type InviteCreateResponse = operations['invite___create']['responses']['200']['content']['application/json'];
 export type InviteDeleteRequest = operations['invite___delete']['requestBody']['content']['application/json'];
 export type InviteListRequest = operations['invite___list']['requestBody']['content']['application/json'];
@@ -466,6 +471,7 @@ export type PagesUpdateRequest = operations['pages___update']['requestBody']['co
 export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json'];
 export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json'];
 export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json'];
+export type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json'];
 export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json'];
 export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json'];
 export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index b99a5373bbe3..a5333d4f93d9 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -576,6 +576,24 @@ export type paths = {
      */
     post: operations['admin___resolve-abuse-user-report'];
   };
+  '/admin/forward-abuse-user-report': {
+    /**
+     * admin/forward-abuse-user-report
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
+     */
+    post: operations['admin___forward-abuse-user-report'];
+  };
+  '/admin/update-abuse-user-report': {
+    /**
+     * admin/update-abuse-user-report
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
+     */
+    post: operations['admin___update-abuse-user-report'];
+  };
   '/admin/send-email': {
     /**
      * admin/send-email
@@ -797,6 +815,16 @@ export type paths = {
      */
     post: operations['admin___system-webhook___update'];
   };
+  '/admin/system-webhook/test': {
+    /**
+     * admin/system-webhook/test
+     * @description No description provided.
+     *
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook*
+     */
+    post: operations['admin___system-webhook___test'];
+  };
   '/announcements': {
     /**
      * announcements
@@ -2436,6 +2464,16 @@ export type paths = {
      */
     post: operations['i___webhooks___delete'];
   };
+  '/i/webhooks/test': {
+    /**
+     * i/webhooks/test
+     * @description No description provided.
+     *
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    post: operations['i___webhooks___test'];
+  };
   '/invite/create': {
     /**
      * invite/create
@@ -3698,6 +3736,9 @@ export type components = {
         }[];
       isBot?: boolean;
       isCat?: boolean;
+      requireSigninToViewContents?: boolean;
+      makeNotesFollowersOnlyBefore?: number | null;
+      makeNotesHiddenBefore?: number | null;
       instance?: {
         name: string | null;
         softwareName: string | null;
@@ -3762,15 +3803,13 @@ export type components = {
       followingVisibility: 'public' | 'followers' | 'private';
       /** @enum {string} */
       followersVisibility: 'public' | 'followers' | 'private';
-      /** @default false */
-      twoFactorEnabled: boolean;
-      /** @default false */
-      usePasswordLessLogin: boolean;
-      /** @default false */
-      securityKeys: boolean;
       roles: components['schemas']['RoleLite'][];
+      followedMessage?: string | null;
       memo: string | null;
       moderationNote?: string;
+      twoFactorEnabled?: boolean;
+      usePasswordLessLogin?: boolean;
+      securityKeys?: boolean;
       isFollowing?: boolean;
       isFollowed?: boolean;
       hasPendingFollowRequestFromYou?: boolean;
@@ -3788,6 +3827,7 @@ export type components = {
       avatarId: string | null;
       /** Format: id */
       bannerId: string | null;
+      followedMessage: string | null;
       isModerator: boolean | null;
       isAdmin: boolean | null;
       injectFeaturedNote: boolean;
@@ -3950,6 +3990,12 @@ export type components = {
         }[];
       loggedInDays: number;
       policies: components['schemas']['RolePolicies'];
+      /** @default false */
+      twoFactorEnabled: boolean;
+      /** @default false */
+      usePasswordLessLogin: boolean;
+      /** @default false */
+      securityKeys: boolean;
       email?: string | null;
       emailVerified?: boolean | null;
       securityKeysList?: {
@@ -4227,7 +4273,7 @@ export type components = {
       user: components['schemas']['UserLite'];
       /** Format: id */
       userId: string;
-    } | {
+    } | ({
       /** Format: id */
       id: string;
       /** Format: date-time */
@@ -4237,7 +4283,8 @@ export type components = {
       user: components['schemas']['UserLite'];
       /** Format: id */
       userId: string;
-    } | {
+      message: string | null;
+    }) | {
       /** Format: id */
       id: string;
       /** Format: date-time */
@@ -4255,6 +4302,24 @@ export type components = {
       /** @enum {string} */
       achievement: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead';
     }) | ({
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'exportCompleted';
+      /** @enum {string} */
+      exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList';
+      /** Format: id */
+      fileId: string;
+    }) | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'login';
+    } | ({
       /** Format: id */
       id: string;
       /** Format: date-time */
@@ -4802,6 +4867,11 @@ export type components = {
       userEachUserListsLimit: number;
       rateLimitFactor: number;
       avatarDecorationLimit: number;
+      canImportAntennas: boolean;
+      canImportBlocking: boolean;
+      canImportFollowing: boolean;
+      canImportMuting: boolean;
+      canImportUserLists: boolean;
     };
     ReversiGameLite: {
       /** Format: id */
@@ -4905,6 +4975,7 @@ export type components = {
       recaptchaSiteKey: string | null;
       enableTurnstile: boolean;
       turnstileSiteKey: string | null;
+      enableTestcaptcha: boolean;
       swPublickey: string | null;
       /** @default /assets/ai.png */
       mascotImageUrl: string;
@@ -4980,7 +5051,7 @@ export type components = {
       latestSentAt: string | null;
       latestStatus: number | null;
       name: string;
-      on: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[];
+      on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[];
       url: string;
       secret: string;
     };
@@ -5035,6 +5106,7 @@ export type operations = {
             recaptchaSiteKey: string | null;
             enableTurnstile: boolean;
             turnstileSiteKey: string | null;
+            enableTestcaptcha: boolean;
             swPublickey: string | null;
             /** @default /assets/ai.png */
             mascotImageUrl: string | null;
@@ -5055,6 +5127,7 @@ export type operations = {
             blockedHosts: string[];
             sensitiveWords: string[];
             prohibitedWords: string[];
+            prohibitedWordsForNameOfUser: string[];
             bannedEmailDomains?: string[];
             preservedUsernames: string[];
             hcaptchaSecretKey: string | null;
@@ -5095,6 +5168,7 @@ export type operations = {
             truemailAuthKey: string | null;
             enableChartsForRemoteUser: boolean;
             enableChartsForFederatedInstances: boolean;
+            enableStatsForFederatedInstances: boolean;
             enableServerMachineStats: boolean;
             enableIdenticonGeneration: boolean;
             manifestJsonOverride: string;
@@ -5105,6 +5179,7 @@ export type operations = {
             perRemoteUserUserTimelineCacheMax: number;
             perUserHomeTimelineCacheMax: number;
             perUserListTimelineCacheMax: number;
+            enableReactionsBuffering: boolean;
             notesPerOneAd: number;
             backgroundImageUrl: string | null;
             deeplAuthKey: string | null;
@@ -5137,6 +5212,8 @@ export type operations = {
             urlPreviewRequireContentLength: boolean;
             urlPreviewUserAgent: string | null;
             urlPreviewSummaryProxyUrl: string | null;
+            federation: string;
+            federationHosts: string[];
           };
         };
       };
@@ -5200,8 +5277,6 @@ export type operations = {
            * @enum {string}
            */
           targetUserOrigin?: 'combined' | 'local' | 'remote';
-          /** @default false */
-          forwarded?: boolean;
         };
       };
     };
@@ -5228,7 +5303,11 @@ export type operations = {
               assigneeId: string | null;
               reporter: components['schemas']['UserDetailedNotMe'];
               targetUser: components['schemas']['UserDetailedNotMe'];
-              assignee?: components['schemas']['UserDetailedNotMe'] | null;
+              assignee: components['schemas']['UserDetailedNotMe'] | null;
+              forwarded: boolean;
+              /** @enum {string|null} */
+              resolvedAs: 'accept' | 'reject' | null;
+              moderationNote: string;
             })[];
         };
       };
@@ -5562,6 +5641,7 @@ export type operations = {
         'application/json': {
           username: string;
           password: string;
+          setupPassword?: string | null;
         };
       };
     };
@@ -6244,9 +6324,22 @@ export type operations = {
       };
     };
     responses: {
-      /** @description OK (without any results) */
-      204: {
-        content: never;
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': {
+            /** Format: id */
+            id: string;
+            /** Format: date-time */
+            createdAt: string;
+            /** Format: date-time */
+            updatedAt: string | null;
+            name: string;
+            description: string;
+            url: string;
+            roleIdsThatCanBeUsedThisDecoration: string[];
+          };
+        };
       };
       /** @description Client error */
       400: {
@@ -8640,8 +8733,113 @@ export type operations = {
         'application/json': {
           /** Format: misskey:id */
           reportId: string;
-          /** @default false */
-          forward?: boolean;
+          /** @enum {string|null} */
+          resolvedAs?: 'accept' | 'reject' | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * admin/forward-abuse-user-report
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
+   */
+  'admin___forward-abuse-user-report': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          reportId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * admin/update-abuse-user-report
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report*
+   */
+  'admin___update-abuse-user-report': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          reportId: string;
+          moderationNote?: string;
         };
       };
     };
@@ -8898,6 +9096,7 @@ export type operations = {
           'application/json': {
             email: string | null;
             emailVerified: boolean;
+            followedMessage: string | null;
             autoAcceptFollowed: boolean;
             noCrawle: boolean;
             preventAiLearning: boolean;
@@ -9280,6 +9479,7 @@ export type operations = {
           blockedHosts?: string[] | null;
           sensitiveWords?: string[] | null;
           prohibitedWords?: string[] | null;
+          prohibitedWordsForNameOfUser?: string[] | null;
           themeColor?: string | null;
           mascotImageUrl?: string | null;
           bannerUrl?: string | null;
@@ -9312,6 +9512,7 @@ export type operations = {
           enableTurnstile?: boolean;
           turnstileSiteKey?: string | null;
           turnstileSecretKey?: string | null;
+          enableTestcaptcha?: boolean;
           /** @enum {string} */
           sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote';
           /** @enum {string} */
@@ -9363,6 +9564,7 @@ export type operations = {
           truemailAuthKey?: string | null;
           enableChartsForRemoteUser?: boolean;
           enableChartsForFederatedInstances?: boolean;
+          enableStatsForFederatedInstances?: boolean;
           enableServerMachineStats?: boolean;
           enableIdenticonGeneration?: boolean;
           serverRules?: string[];
@@ -9375,6 +9577,7 @@ export type operations = {
           perRemoteUserUserTimelineCacheMax?: number;
           perUserHomeTimelineCacheMax?: number;
           perUserListTimelineCacheMax?: number;
+          enableReactionsBuffering?: boolean;
           notesPerOneAd?: number;
           silencedHosts?: string[] | null;
           mediaSilencedHosts?: string[] | null;
@@ -9386,6 +9589,9 @@ export type operations = {
           urlPreviewRequireContentLength?: boolean;
           urlPreviewUserAgent?: string | null;
           urlPreviewSummaryProxyUrl?: string | null;
+          /** @enum {string} */
+          federation?: 'all' | 'none' | 'specified';
+          federationHosts?: string[];
         };
       };
     };
@@ -10059,7 +10265,7 @@ export type operations = {
         'application/json': {
           isActive: boolean;
           name: string;
-          on: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[];
+          on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[];
           url: string;
           secret: string;
         };
@@ -10169,7 +10375,7 @@ export type operations = {
       content: {
         'application/json': {
           isActive?: boolean;
-          on?: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[];
+          on?: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[];
         };
       };
     };
@@ -10282,7 +10488,7 @@ export type operations = {
           id: string;
           isActive: boolean;
           name: string;
-          on: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[];
+          on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[];
           url: string;
           secret: string;
         };
@@ -10327,6 +10533,71 @@ export type operations = {
       };
     };
   };
+  /**
+   * admin/system-webhook/test
+   * @description No description provided.
+   *
+   * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+   * **Credential required**: *Yes* / **Permission**: *read:admin:system-webhook*
+   */
+  'admin___system-webhook___test': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          webhookId: string;
+          /** @enum {string} */
+          type: 'abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged';
+          override?: {
+            url?: string;
+            secret?: string;
+          };
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * announcements
    * @description No description provided.
@@ -18438,8 +18709,8 @@ export type operations = {
           untilId?: string;
           /** @default true */
           markAsRead?: boolean;
-          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
-          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
         };
       };
     };
@@ -18506,8 +18777,8 @@ export type operations = {
           untilId?: string;
           /** @default true */
           markAsRead?: boolean;
-          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
-          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
         };
       };
     };
@@ -19560,6 +19831,7 @@ export type operations = {
         'application/json': {
           name?: string | null;
           description?: string | null;
+          followedMessage?: string | null;
           location?: string | null;
           birthday?: string | null;
           /** @enum {string|null} */
@@ -19588,6 +19860,9 @@ export type operations = {
           autoAcceptFollowed?: boolean;
           noCrawle?: boolean;
           preventAiLearning?: boolean;
+          requireSigninToViewContents?: boolean;
+          makeNotesFollowersOnlyBefore?: number | null;
+          makeNotesHiddenBefore?: number | null;
           isBot?: boolean;
           isCat?: boolean;
           injectFeaturedNote?: boolean;
@@ -20146,6 +20421,71 @@ export type operations = {
       };
     };
   };
+  /**
+   * i/webhooks/test
+   * @description No description provided.
+   *
+   * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+   * **Credential required**: *Yes* / **Permission**: *read:account*
+   */
+  i___webhooks___test: {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          webhookId: string;
+          /** @enum {string} */
+          type: 'mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction';
+          override?: {
+            url?: string;
+            secret?: string;
+          };
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * invite/create
    * @description No description provided.
@@ -23610,6 +23950,16 @@ export type operations = {
    * **Credential required**: *No*
    */
   flash___featured: {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** @default 0 */
+          offset?: number;
+          /** @default 10 */
+          limit?: number;
+        };
+      };
+    };
     responses: {
       /** @description OK (with results) */
       200: {
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index b4fbcffa9774..c5911a70eb78 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -142,6 +142,8 @@ export const moderationLogTypes = [
 	'markSensitiveDriveFile',
 	'unmarkSensitiveDriveFile',
 	'resolveAbuseReport',
+	'forwardAbuseReport',
+	'updateAbuseReportNote',
 	'createInvitation',
 	'createAd',
 	'updateAd',
@@ -330,7 +332,18 @@ export type ModerationLogPayloads = {
 	resolveAbuseReport: {
 		reportId: string;
 		report: ReceivedAbuseReport;
-		forwarded: boolean;
+		forwarded?: boolean;
+		resolvedAs?: string | null;
+	};
+	forwardAbuseReport: {
+		reportId: string;
+		report: ReceivedAbuseReport;
+	};
+	updateAbuseReportNote: {
+		reportId: string;
+		report: ReceivedAbuseReport;
+		before: string;
+		after: string;
 	};
 	createInvitation: {
 		invitations: InviteCode[];
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 08d3dc5c6d37..dd88791ed037 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -10,6 +10,7 @@ import {
 	User,
 	UserDetailedNotMe,
 } from './autogen/models.js';
+import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types';
 
 export * from './autogen/entities.js';
 export * from './autogen/models.js';
@@ -152,6 +153,12 @@ export type ModerationLog = {
 } | {
 	type: 'resolveAbuseReport';
 	info: ModerationLogPayloads['resolveAbuseReport'];
+} | {
+	type: 'forwardAbuseReport';
+	info: ModerationLogPayloads['forwardAbuseReport'];
+} | {
+	type: 'updateAbuseReportNote';
+	info: ModerationLogPayloads['updateAbuseReportNote'];
 } | {
 	type: 'unsetUserAvatar';
 	info: ModerationLogPayloads['unsetUserAvatar'];
@@ -250,6 +257,7 @@ export type SignupRequest = {
 	'hcaptcha-response'?: string | null;
 	'g-recaptcha-response'?: string | null;
 	'turnstile-response'?: string | null;
+	'm-captcha-response'?: string | null;
 }
 
 export type SignupResponse = MeDetailed & {
@@ -265,15 +273,42 @@ export type SignupPendingResponse = {
 	i: string,
 };
 
-export type SigninRequest = {
+export type SigninFlowRequest = {
 	username: string;
-	password: string;
+	password?: string;
 	token?: string;
+	credential?: AuthenticationResponseJSON;
+	'hcaptcha-response'?: string | null;
+	'g-recaptcha-response'?: string | null;
+	'turnstile-response'?: string | null;
+	'm-captcha-response'?: string | null;
 };
 
-export type SigninResponse = {
-	id: User['id'],
-	i: string,
+export type SigninFlowResponse = {
+	finished: true;
+	id: User['id'];
+	i: string;
+} | {
+	finished: false;
+	next: 'captcha' | 'password' | 'totp';
+} | {
+	finished: false;
+	next: 'passkey';
+	authRequest: PublicKeyCredentialRequestOptionsJSON;
+};
+
+export type SigninWithPasskeyRequest = {
+	credential?: AuthenticationResponseJSON;
+	context?: string;
+};
+
+export type SigninWithPasskeyInitResponse = {
+	option: PublicKeyCredentialRequestOptionsJSON;
+	context: string;
+};
+
+export type SigninWithPasskeyResponse = {
+	signinResponse: SigninFlowResponse & { finished: true };
 };
 
 type Values<T extends Record<PropertyKey, unknown>> = T[keyof T];
diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts
index ffb46c77f6c1..6e34ec150817 100644
--- a/packages/misskey-js/src/streaming.ts
+++ b/packages/misskey-js/src/streaming.ts
@@ -51,7 +51,7 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea
 	private idCounter = 0;
 
 	constructor(origin: string, user: { token: string; } | null, options?: {
-		WebSocket?: WebSocket;
+		WebSocket?: _ReconnectingWebsocket.Options['WebSocket'];
 	}) {
 		super();
 
diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts
index 4447a2e8fc14..26a50f9fa41a 100644
--- a/packages/misskey-js/src/streaming.types.ts
+++ b/packages/misskey-js/src/streaming.types.ts
@@ -124,7 +124,7 @@ export type Channels = {
 	};
 	hashtag: {
 		params: {
-			q?: string;
+			q: string[][];
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -233,7 +233,7 @@ export type Channels = {
 	}
 };
 
-export type NoteUpdatedEvent = {
+export type NoteUpdatedEvent = { id: Note['id'] } & ({
 	type: 'reacted';
 	body: {
 		reaction: string;
@@ -257,7 +257,7 @@ export type NoteUpdatedEvent = {
 		choice: number;
 		userId: User['id'];
 	};
-};
+});
 
 export type BroadcastEvents = {
 	noteUpdated: (payload: NoteUpdatedEvent) => void;
diff --git a/packages/misskey-reversi/build.js b/packages/misskey-reversi/build.js
index e626c97a5967..a80b71646f43 100644
--- a/packages/misskey-reversi/build.js
+++ b/packages/misskey-reversi/build.js
@@ -1,32 +1,32 @@
-import * as esbuild from "esbuild";
-import { build } from "esbuild";
-import { globSync } from "glob";
-import { execa } from "execa";
-import fs from "node:fs";
-import { fileURLToPath } from "node:url";
-import { dirname } from "node:path";
+import fs from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname } from 'node:path';
+import * as esbuild from 'esbuild';
+import { build } from 'esbuild';
+import { globSync } from 'glob';
+import { execa } from 'execa';
 
 const _filename = fileURLToPath(import.meta.url);
 const _dirname = dirname(_filename);
 const _package = JSON.parse(fs.readFileSync(_dirname + '/package.json', 'utf-8'));
 
-const entryPoints = globSync("./src/**/**.{ts,tsx}");
+const entryPoints = globSync('./src/**/**.{ts,tsx}');
 
 /** @type {import('esbuild').BuildOptions} */
 const options = {
 	entryPoints,
 	minify: process.env.NODE_ENV === 'production',
-	outdir: "./built",
-	target: "es2022",
-	platform: "browser",
-	format: "esm",
+	outdir: './built',
+	target: 'es2022',
+	platform: 'browser',
+	format: 'esm',
 	sourcemap: 'linked',
 };
 
 // built配下をすべて削除する
 fs.rmSync('./built', { recursive: true, force: true });
 
-if (process.argv.map(arg => arg.toLowerCase()).includes("--watch")) {
+if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
 	await watchSrc();
 } else {
 	await buildSrc();
@@ -36,7 +36,7 @@ async function buildSrc() {
 	console.log(`[${_package.name}] start building...`);
 
 	await build(options)
-		.then(it => {
+		.then(() => {
 			console.log(`[${_package.name}] build succeeded.`);
 		})
 		.catch((err) => {
@@ -65,7 +65,7 @@ function buildDts() {
 		{
 			stdout: process.stdout,
 			stderr: process.stderr,
-		}
+		},
 	);
 }
 
@@ -86,7 +86,7 @@ async function watchSrc() {
 		},
 	}];
 
-	console.log(`[${_package.name}] start watching...`)
+	console.log(`[${_package.name}] start watching...`);
 
 	const context = await esbuild.context({ ...options, plugins });
 	await context.watch();
diff --git a/packages/misskey-reversi/eslint.config.js b/packages/misskey-reversi/eslint.config.js
index 3f81df714508..8453d9b8e707 100644
--- a/packages/misskey-reversi/eslint.config.js
+++ b/packages/misskey-reversi/eslint.config.js
@@ -1,6 +1,7 @@
 import tsParser from '@typescript-eslint/parser';
 import sharedConfig from '../shared/eslint.config.js';
 
+// eslint-disable-next-line import/no-default-export
 export default [
 	...sharedConfig,
 	{
diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts
index 4afca9898cf0..35cc44feb4d9 100644
--- a/packages/misskey-reversi/src/game.ts
+++ b/packages/misskey-reversi/src/game.ts
@@ -53,9 +53,13 @@ export class Game {
 
 		//#region Options
 		this.opts = opts;
+
+		/* eslint-disable @typescript-eslint/no-unnecessary-condition */
 		if (this.opts.isLlotheo == null) this.opts.isLlotheo = false;
 		if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false;
 		if (this.opts.loopedBoard == null) this.opts.loopedBoard = false;
+		/* eslint-enable */
+
 		//#endregion
 
 		//#region Parse map data
@@ -123,12 +127,13 @@ export class Game {
 		// ターン計算
 		this.turn =
 			this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
-			this.canPutSomewhere(this.prevColor!) ? this.prevColor :
+			this.canPutSomewhere(this.prevColor!) ? this.prevColor : //eslint-disable-line @typescript-eslint/no-non-null-assertion
 			null;
 	}
 
 	public undo() {
-		const undo = this.logs.pop()!;
+		const undo = this.logs.pop();
+		if (undo == null) return;
 		this.prevColor = undo.color;
 		this.prevPos = undo.pos;
 		this.board[undo.pos] = null;
@@ -183,7 +188,7 @@ export class Game {
 
 			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
 			let [x, y] = this.posToXy(initPos);
-			while (true) {
+			while (true) { // eslint-disable-line @typescript-eslint/no-unnecessary-condition
 				[x, y] = nextPos(x, y);
 
 				// 座標が指し示す位置がボード外に出たとき
diff --git a/packages/shared/eslint.config.js b/packages/shared/eslint.config.js
index e9d27c4a7288..0368d008c080 100644
--- a/packages/shared/eslint.config.js
+++ b/packages/shared/eslint.config.js
@@ -6,6 +6,7 @@ export default [
 	{
 		files: ['**/*.cjs'],
 		languageOptions: {
+			sourceType: 'commonjs',
 			parserOptions: {
 				sourceType: 'commonjs',
 			},
@@ -25,4 +26,10 @@ export default [
 			globals: globals.node,
 		},
 	},
+	{
+		files: ['**/*.js', '**/*.cjs'],
+		rules: {
+			'@typescript-eslint/no-var-requires': 'off',
+		},
+	},
 ];
diff --git a/packages/sw/package.json b/packages/sw/package.json
index 9174f50ae3b3..09e604ff4c0a 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -9,16 +9,16 @@
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
 	"dependencies": {
-		"esbuild": "0.23.0",
+		"esbuild": "0.23.1",
 		"idb-keyval": "6.2.1",
 		"misskey-js": "workspace:*"
 	},
 	"devDependencies": {
 		"@typescript-eslint/parser": "7.17.0",
 		"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
-		"eslint-plugin-import": "2.29.1",
-		"nodemon": "3.1.4",
-		"typescript": "5.5.4"
+		"eslint-plugin-import": "2.30.0",
+		"nodemon": "3.1.7",
+		"typescript": "5.6.2"
 	},
 	"type": "module"
 }
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 3c3765795895..364328d4b070 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -210,6 +210,31 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 						tag: `achievement:${data.body.achievement}`,
 					}];
 
+				case 'login':
+					return [i18n.ts._notification.login, {
+						badge: iconUrl('login-2'),
+						data,
+					}];
+
+				case 'exportCompleted': {
+					const entityName = {
+						antenna: i18n.ts.antennas,
+						blocking: i18n.ts.blockedUsers,
+						clip: i18n.ts.clips,
+						customEmoji: i18n.ts.customEmojis,
+						favorite: i18n.ts.favorites,
+						following: i18n.ts.following,
+						muting: i18n.ts.mutedUsers,
+						note: i18n.ts.notes,
+						userList: i18n.ts.lists,
+					} as const satisfies Record<typeof data.body.exportedEntity, string>;
+
+					return [i18n.tsx._notification.exportOfXCompleted({ x: entityName[data.body.exportedEntity] }), {
+						badge: iconUrl('circle-check'),
+						data,
+					}];
+				}
+
 				case 'pollEnded':
 					return [i18n.ts._notification.pollEnded, {
 						body: data.body.note.text ?? '',
diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts
index fac3e707d811..4f827798083d 100644
--- a/packages/sw/src/types.ts
+++ b/packages/sw/src/types.ts
@@ -50,4 +50,5 @@ export type BadgeNames =
 	| 'quote'
 	| 'repeat'
 	| 'user-plus'
-	| 'users';
+	| 'users'
+	| 'login-2';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 60842367fbfa..1312e8c88642 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,10 +14,10 @@ importers:
     dependencies:
       cssnano:
         specifier: 6.1.2
-        version: 6.1.2(postcss@8.4.40)
+        version: 6.1.2(postcss@8.4.47)
       esbuild:
-        specifier: 0.23.0
-        version: 0.23.0
+        specifier: 0.23.1
+        version: 0.23.1
       execa:
         specifier: 8.0.1
         version: 8.0.1
@@ -34,17 +34,17 @@ importers:
         specifier: 4.1.0
         version: 4.1.0
       postcss:
-        specifier: 8.4.40
-        version: 8.4.40
+        specifier: 8.4.47
+        version: 8.4.47
       tar:
         specifier: 6.2.1
         version: 6.2.1
       terser:
-        specifier: 5.31.3
-        version: 5.31.3
+        specifier: 5.33.0
+        version: 5.33.0
       typescript:
-        specifier: 5.5.4
-        version: 5.5.4
+        specifier: 5.6.2
+        version: 5.6.2
     optionalDependencies:
       '@tensorflow/tfjs-core':
         specifier: 4.4.0
@@ -52,34 +52,34 @@ importers:
     devDependencies:
       '@misskey-dev/eslint-plugin':
         specifier: 2.0.3
-        version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0)
+        version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0)
       '@types/node':
         specifier: 20.14.12
         version: 20.14.12
       '@typescript-eslint/eslint-plugin':
         specifier: 7.17.0
-        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.8.0)(typescript@5.6.2)
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 13.13.1
-        version: 13.13.1
+        specifier: 13.14.2
+        version: 13.14.2
       eslint:
         specifier: 9.8.0
         version: 9.8.0
       globals:
-        specifier: 15.8.0
-        version: 15.8.0
+        specifier: 15.9.0
+        version: 15.9.0
       ncp:
         specifier: 2.0.0
         version: 2.0.0
       start-server-and-test:
-        specifier: 2.0.4
-        version: 2.0.4
+        specifier: 2.0.8
+        version: 2.0.8
 
   packages/backend:
     dependencies:
@@ -90,41 +90,41 @@ importers:
         specifier: 3.620.0
         version: 3.620.0(@aws-sdk/client-s3@3.620.0)
       '@bull-board/api':
-        specifier: 5.21.1
-        version: 5.21.1(@bull-board/ui@5.21.1)
+        specifier: 6.0.0
+        version: 6.0.0(@bull-board/ui@6.0.0)
       '@bull-board/fastify':
-        specifier: 5.21.1
-        version: 5.21.1
+        specifier: 6.0.0
+        version: 6.0.0
       '@bull-board/ui':
-        specifier: 5.21.1
-        version: 5.21.1
+        specifier: 6.0.0
+        version: 6.0.0
       '@discordapp/twemoji':
-        specifier: 15.0.3
-        version: 15.0.3
+        specifier: 15.1.0
+        version: 15.1.0
       '@fastify/accepts':
-        specifier: 4.3.0
-        version: 4.3.0
+        specifier: 5.0.1
+        version: 5.0.1
       '@fastify/cookie':
-        specifier: 9.3.1
-        version: 9.3.1
+        specifier: 10.0.1
+        version: 10.0.1
       '@fastify/cors':
-        specifier: 9.0.1
-        version: 9.0.1
+        specifier: 10.0.1
+        version: 10.0.1
       '@fastify/express':
-        specifier: 3.0.0
-        version: 3.0.0
+        specifier: 4.0.1
+        version: 4.0.1
       '@fastify/http-proxy':
-        specifier: 9.5.0
-        version: 9.5.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+        specifier: 10.0.0
+        version: 10.0.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       '@fastify/multipart':
-        specifier: 8.3.0
-        version: 8.3.0
+        specifier: 9.0.1
+        version: 9.0.1
       '@fastify/static':
-        specifier: 7.0.4
-        version: 7.0.4
+        specifier: 8.0.1
+        version: 8.0.1
       '@fastify/view':
-        specifier: 9.1.0
-        version: 9.1.0
+        specifier: 10.0.1
+        version: 10.0.1
       '@misskey-dev/sharp-read-bmp':
         specifier: 1.2.0
         version: 1.2.0
@@ -132,17 +132,17 @@ importers:
         specifier: 5.1.0
         version: 5.1.0
       '@napi-rs/canvas':
-        specifier: ^0.1.53
-        version: 0.1.53
+        specifier: 0.1.56
+        version: 0.1.56
       '@nestjs/common':
-        specifier: 10.3.10
-        version: 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1)
+        specifier: 10.4.4
+        version: 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1)
       '@nestjs/core':
-        specifier: 10.3.10
-        version: 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
+        specifier: 10.4.4
+        version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
       '@nestjs/testing':
-        specifier: 10.3.10
-        version: 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10))
+        specifier: 10.4.4
+        version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4))
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
@@ -189,11 +189,11 @@ importers:
         specifier: 2.0.5
         version: 2.0.5
       body-parser:
-        specifier: 1.20.2
-        version: 1.20.2
+        specifier: 1.20.3
+        version: 1.20.3
       bullmq:
-        specifier: 5.10.4
-        version: 5.10.4
+        specifier: 5.15.0
+        version: 5.15.0
       cacheable-lookup:
         specifier: 7.0.0
         version: 7.0.0
@@ -225,17 +225,17 @@ importers:
         specifier: 0.1.21
         version: 0.1.21
       fastify:
-        specifier: 4.28.1
-        version: 4.28.1
+        specifier: 5.0.0
+        version: 5.0.0
       fastify-raw-body:
-        specifier: 4.3.0
-        version: 4.3.0
+        specifier: 5.0.0
+        version: 5.0.0
       feed:
         specifier: 4.2.2
         version: 4.2.2
       file-type:
-        specifier: 19.3.0
-        version: 19.3.0
+        specifier: 19.5.0
+        version: 19.5.0
       fluent-ffmpeg:
         specifier: 2.1.3
         version: 2.1.3
@@ -246,8 +246,8 @@ importers:
         specifier: 14.4.2
         version: 14.4.2
       happy-dom:
-        specifier: 10.0.3
-        version: 10.0.3
+        specifier: 15.7.4
+        version: 15.7.4
       hpagent:
         specifier: 1.2.0
         version: 1.2.0
@@ -261,14 +261,14 @@ importers:
         specifier: 5.4.1
         version: 5.4.1
       ip-cidr:
-        specifier: 4.0.1
-        version: 4.0.1
+        specifier: 4.0.2
+        version: 4.0.2
       ipaddr.js:
         specifier: 2.2.0
         version: 2.2.0
       is-svg:
-        specifier: 5.0.1
-        version: 5.0.1
+        specifier: 5.1.0
+        version: 5.1.0
       js-yaml:
         specifier: 4.1.0
         version: 4.1.0
@@ -284,9 +284,12 @@ importers:
       jsrsasign:
         specifier: 11.1.0
         version: 11.1.0
+      juice:
+        specifier: 11.0.0
+        version: 11.0.0
       meilisearch:
-        specifier: 0.41.0
-        version: 0.41.0(encoding@0.1.13)
+        specifier: 0.42.0
+        version: 0.42.0(encoding@0.1.13)
       mfm-js:
         specifier: 0.24.0
         version: 0.24.0
@@ -315,8 +318,8 @@ importers:
         specifier: 3.3.2
         version: 3.3.2
       nodemailer:
-        specifier: 6.9.14
-        version: 6.9.14
+        specifier: 6.9.15
+        version: 6.9.15
       nsfwjs:
         specifier: 2.4.2
         version: 2.4.2(@tensorflow/tfjs@4.4.0(encoding@0.1.13)(seedrandom@3.0.5))
@@ -333,14 +336,14 @@ importers:
         specifier: 0.0.14
         version: 0.0.14
       otpauth:
-        specifier: 9.3.1
-        version: 9.3.1
+        specifier: 9.3.4
+        version: 9.3.4
       parse5:
         specifier: 7.1.2
         version: 7.1.2
       pg:
-        specifier: 8.12.0
-        version: 8.12.0
+        specifier: 8.13.0
+        version: 8.13.0
       pkce-challenge:
         specifier: 4.1.0
         version: 4.1.0
@@ -357,8 +360,8 @@ importers:
         specifier: 2.3.1
         version: 2.3.1
       qrcode:
-        specifier: 1.5.3
-        version: 1.5.3
+        specifier: 1.5.4
+        version: 1.5.4
       random-seed:
         specifier: 0.3.0
         version: 0.3.0
@@ -366,8 +369,8 @@ importers:
         specifier: 3.4.1
         version: 3.4.1
       re2:
-        specifier: 1.21.3
-        version: 1.21.3
+        specifier: 1.21.4
+        version: 1.21.4
       redis-lock:
         specifier: 0.1.4
         version: 0.1.4
@@ -384,14 +387,14 @@ importers:
         specifier: 7.8.1
         version: 7.8.1
       sanitize-html:
-        specifier: 2.13.0
-        version: 2.13.0
+        specifier: 2.13.1
+        version: 2.13.1
       secure-json-parse:
         specifier: 2.7.0
         version: 2.7.0
       sharp:
-        specifier: 0.33.4
-        version: 0.33.4
+        specifier: 0.33.5
+        version: 0.33.5
       slacc:
         specifier: 0.0.10
         version: 0.0.10
@@ -402,8 +405,8 @@ importers:
         specifier: 2.1.0
         version: 2.1.0
       systeminformation:
-        specifier: 5.22.11
-        version: 5.22.11
+        specifier: 5.23.5
+        version: 5.23.5
       tinycolor2:
         specifier: 1.6.0
         version: 1.6.0
@@ -418,10 +421,10 @@ importers:
         version: 4.2.0
       typeorm:
         specifier: 0.3.20
-        version: 0.3.20(ioredis@5.4.1)(pg@8.12.0)
+        version: 0.3.20(ioredis@5.4.1)(pg@8.13.0)
       typescript:
-        specifier: 5.5.4
-        version: 5.5.4
+        specifier: 5.6.2
+        version: 5.6.2
       ulid:
         specifier: 2.3.0
         version: 2.3.0
@@ -530,8 +533,8 @@ importers:
         specifier: 29.7.0
         version: 29.7.0
       '@nestjs/platform-express':
-        specifier: 10.3.10
-        version: 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)
+        specifier: 10.4.4
+        version: 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4)
       '@simplewebauthn/types':
         specifier: 10.0.0
         version: 10.0.0
@@ -551,14 +554,14 @@ importers:
         specifier: 1.19.5
         version: 1.19.5
       '@types/color-convert':
-        specifier: 2.0.3
-        version: 2.0.3
+        specifier: 2.0.4
+        version: 2.0.4
       '@types/content-disposition':
         specifier: 0.5.8
         version: 0.5.8
       '@types/fluent-ffmpeg':
-        specifier: 2.1.24
-        version: 2.1.24
+        specifier: 2.1.26
+        version: 2.1.26
       '@types/htmlescape':
         specifier: 1.1.3
         version: 1.1.3
@@ -566,8 +569,8 @@ importers:
         specifier: 1.0.7
         version: 1.0.7
       '@types/jest':
-        specifier: 29.5.12
-        version: 29.5.12
+        specifier: 29.5.13
+        version: 29.5.13
       '@types/js-yaml':
         specifier: 4.0.9
         version: 4.0.9
@@ -590,8 +593,8 @@ importers:
         specifier: 20.14.12
         version: 20.14.12
       '@types/nodemailer':
-        specifier: 6.4.15
-        version: 6.4.15
+        specifier: 6.4.16
+        version: 6.4.16
       '@types/oauth':
         specifier: 0.9.5
         version: 0.9.5
@@ -602,8 +605,8 @@ importers:
         specifier: 0.1.2
         version: 0.1.2
       '@types/pg':
-        specifier: 8.11.6
-        version: 8.11.6
+        specifier: 8.11.10
+        version: 8.11.10
       '@types/pug':
         specifier: 2.0.10
         version: 2.0.10
@@ -623,8 +626,8 @@ importers:
         specifier: 1.0.7
         version: 1.0.7
       '@types/sanitize-html':
-        specifier: 2.11.0
-        version: 2.11.0
+        specifier: 2.13.0
+        version: 2.13.0
       '@types/semver':
         specifier: 7.5.8
         version: 7.5.8
@@ -647,14 +650,14 @@ importers:
         specifier: 3.6.3
         version: 3.6.3
       '@types/ws':
-        specifier: 8.5.11
-        version: 8.5.11
+        specifier: 8.5.12
+        version: 8.5.12
       '@typescript-eslint/eslint-plugin':
         specifier: 7.17.0
-        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       aws-sdk-client-mock:
         specifier: 4.0.1
         version: 4.0.1
@@ -662,11 +665,11 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       eslint-plugin-import:
-        specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)
+        specifier: 2.30.0
+        version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)
       execa:
-        specifier: 9.3.0
-        version: 9.3.0
+        specifier: 9.4.0
+        version: 9.4.0
       fkill:
         specifier: 9.0.0
         version: 9.0.0
@@ -677,8 +680,8 @@ importers:
         specifier: 29.7.0
         version: 29.7.0
       nodemon:
-        specifier: 3.1.4
-        version: 3.1.4
+        specifier: 3.1.7
+        version: 3.1.7
       pid-port:
         specifier: 1.0.0
         version: 1.0.0
@@ -689,8 +692,8 @@ importers:
   packages/frontend:
     dependencies:
       '@discordapp/twemoji':
-        specifier: 15.0.3
-        version: 15.0.3
+        specifier: 15.1.0
+        version: 15.1.0
       '@github/webauthn-json':
         specifier: 2.1.1
         version: 2.1.1
@@ -702,13 +705,13 @@ importers:
         version: 2024.1.0
       '@rollup/plugin-json':
         specifier: 6.1.0
-        version: 6.1.0(rollup@4.19.1)
+        version: 6.1.0(rollup@4.22.5)
       '@rollup/plugin-replace':
         specifier: 5.0.7
-        version: 5.0.7(rollup@4.19.1)
+        version: 5.0.7(rollup@4.22.5)
       '@rollup/pluginutils':
-        specifier: 5.1.0
-        version: 5.1.0(rollup@4.19.1)
+        specifier: 5.1.2
+        version: 5.1.2(rollup@4.22.5)
       '@syuilo/aiscript':
         specifier: 0.19.0
         version: 0.19.0
@@ -719,17 +722,17 @@ importers:
         specifier: 15.1.1
         version: 15.1.1
       '@vitejs/plugin-vue':
-        specifier: 5.1.0
-        version: 5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
+        specifier: 5.1.4
+        version: 5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.11(typescript@5.6.2))
       '@vue/compiler-sfc':
-        specifier: 3.4.37
-        version: 3.4.37
+        specifier: 3.5.11
+        version: 3.5.11
       aiscript-vscode:
         specifier: github:aiscript-dev/aiscript-vscode#v0.1.11
         version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9
       astring:
-        specifier: 1.8.6
-        version: 1.8.6
+        specifier: 1.9.0
+        version: 1.9.0
       broadcast-channel:
         specifier: 7.0.0
         version: 7.0.0
@@ -740,35 +743,32 @@ importers:
         specifier: 1.9.3
         version: 1.9.3
       chart.js:
-        specifier: 4.4.3
-        version: 4.4.3
+        specifier: 4.4.4
+        version: 4.4.4
       chartjs-adapter-date-fns:
         specifier: 3.0.0
-        version: 3.0.0(chart.js@4.4.3)(date-fns@2.30.0)
+        version: 3.0.0(chart.js@4.4.4)(date-fns@2.30.0)
       chartjs-chart-matrix:
         specifier: 2.0.1
-        version: 2.0.1(chart.js@4.4.3)
+        version: 2.0.1(chart.js@4.4.4)
       chartjs-plugin-gradient:
         specifier: 0.6.1
-        version: 0.6.1(chart.js@4.4.3)
+        version: 0.6.1(chart.js@4.4.4)
       chartjs-plugin-zoom:
         specifier: 2.0.1
-        version: 2.0.1(chart.js@4.4.3)
+        version: 2.0.1(chart.js@4.4.4)
       chromatic:
-        specifier: 11.5.6
-        version: 11.5.6
+        specifier: 11.11.0
+        version: 11.11.0
       compare-versions:
         specifier: 6.1.1
         version: 6.1.1
       cropperjs:
-        specifier: 2.0.0-rc.1
-        version: 2.0.0-rc.1
+        specifier: 2.0.0-rc.2
+        version: 2.0.0-rc.2
       date-fns:
         specifier: 2.30.0
         version: 2.30.0
-      escape-regexp:
-        specifier: 0.0.1
-        version: 0.0.1
       estree-walker:
         specifier: 3.0.3
         version: 3.0.3
@@ -812,17 +812,17 @@ importers:
         specifier: 2.3.1
         version: 2.3.1
       rollup:
-        specifier: 4.19.1
-        version: 4.19.1
+        specifier: 4.22.5
+        version: 4.22.5
       sanitize-html:
-        specifier: 2.13.0
-        version: 2.13.0
+        specifier: 2.13.1
+        version: 2.13.1
       sass:
-        specifier: 1.77.8
-        version: 1.77.8
+        specifier: 1.79.3
+        version: 1.79.3
       shiki:
-        specifier: 1.12.0
-        version: 1.12.0
+        specifier: 1.21.0
+        version: 1.21.0
       strict-event-emitter-types:
         specifier: 2.0.0
         version: 2.0.0
@@ -830,8 +830,8 @@ importers:
         specifier: 3.1.0
         version: 3.1.0
       three:
-        specifier: 0.167.0
-        version: 0.167.0
+        specifier: 0.169.0
+        version: 0.169.0
       throttle-debounce:
         specifier: 5.0.2
         version: 5.0.2
@@ -845,90 +845,90 @@ importers:
         specifier: 4.2.0
         version: 4.2.0
       typescript:
-        specifier: 5.5.4
-        version: 5.5.4
+        specifier: 5.6.2
+        version: 5.6.2
       uuid:
         specifier: 10.0.0
         version: 10.0.0
       v-code-diff:
-        specifier: 1.12.0
-        version: 1.12.0(vue@3.4.37(typescript@5.5.4))
+        specifier: 1.13.1
+        version: 1.13.1(vue@3.5.11(typescript@5.6.2))
       vite:
-        specifier: 5.3.5
-        version: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+        specifier: 5.4.8
+        version: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
       vue:
-        specifier: 3.4.37
-        version: 3.4.37(typescript@5.5.4)
+        specifier: 3.5.11
+        version: 3.5.11(typescript@5.6.2)
       vuedraggable:
         specifier: next
-        version: 4.1.0(vue@3.4.37(typescript@5.5.4))
+        version: 4.1.0(vue@3.5.11(typescript@5.6.2))
     devDependencies:
       '@misskey-dev/summaly':
         specifier: 5.1.0
         version: 5.1.0
       '@storybook/addon-actions':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/addon-essentials':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/addon-interactions':
-        specifier: 8.2.6
-        version: 8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/addon-links':
-        specifier: 8.2.6
-        version: 8.2.6(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/addon-mdx-gfm':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/addon-storysource':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/blocks':
-        specifier: 8.2.6
-        version: 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/components':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/core-events':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/manager-api':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/preview-api':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/react':
-        specifier: 8.2.6
-        version: 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)
+        specifier: 8.3.4
+        version: 8.3.4(@storybook/test@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)
       '@storybook/react-vite':
-        specifier: 8.2.6
-        version: 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.19.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
+        specifier: 8.3.4
+        version: 8.3.4(@storybook/test@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.5)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))
       '@storybook/test':
-        specifier: 8.2.6
-        version: 8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/theming':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/types':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/vue3':
-        specifier: 8.2.6
-        version: 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.37(typescript@5.5.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.11(typescript@5.6.2))
       '@storybook/vue3-vite':
-        specifier: 8.1.11
-        version: 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
+        specifier: 8.3.4
+        version: 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.11(typescript@5.6.2))
       '@testing-library/vue':
         specifier: 8.1.0
-        version: 8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))
-      '@types/escape-regexp':
-        specifier: 0.0.3
-        version: 0.0.3
+        version: 8.1.0(@vue/compiler-sfc@3.5.11)(@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2))
+      '@types/canvas-confetti':
+        specifier: ^1.6.4
+        version: 1.6.4
       '@types/estree':
-        specifier: 1.0.5
-        version: 1.0.5
+        specifier: 1.0.6
+        version: 1.0.6
       '@types/matter-js':
         specifier: 0.19.7
         version: 0.19.7
@@ -942,8 +942,8 @@ importers:
         specifier: 2.1.4
         version: 2.1.4
       '@types/sanitize-html':
-        specifier: 2.11.0
-        version: 2.11.0
+        specifier: 2.13.0
+        version: 2.13.0
       '@types/seedrandom':
         specifier: 3.0.8
         version: 3.0.8
@@ -957,20 +957,20 @@ importers:
         specifier: 10.0.0
         version: 10.0.0
       '@types/ws':
-        specifier: 8.5.11
-        version: 8.5.11
+        specifier: 8.5.12
+        version: 8.5.12
       '@typescript-eslint/eslint-plugin':
         specifier: 7.17.0
-        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       '@vitest/coverage-v8':
         specifier: 1.6.0
-        version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
+        version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0))
       '@vue/runtime-core':
-        specifier: 3.4.37
-        version: 3.4.37
+        specifier: 3.5.11
+        version: 3.5.11
       acorn:
         specifier: 8.12.1
         version: 8.12.1
@@ -978,14 +978,14 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 13.13.1
-        version: 13.13.1
+        specifier: 13.15.0
+        version: 13.15.0
       eslint-plugin-import:
-        specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)
+        specifier: 2.31.0
+        version: 2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)
       eslint-plugin-vue:
-        specifier: 9.27.0
-        version: 9.27.0(eslint@9.8.0)
+        specifier: 9.28.0
+        version: 9.28.0(eslint@9.11.0)
       fast-glob:
         specifier: 3.3.2
         version: 3.3.2
@@ -996,17 +996,17 @@ importers:
         specifier: 0.12.2
         version: 0.12.2
       micromatch:
-        specifier: 4.0.7
-        version: 4.0.7
+        specifier: 4.0.8
+        version: 4.0.8
       msw:
-        specifier: 2.3.4
-        version: 2.3.4(typescript@5.5.4)
+        specifier: 2.4.9
+        version: 2.4.9(typescript@5.6.2)
       msw-storybook-addon:
         specifier: 2.0.3
-        version: 2.0.3(msw@2.3.4(typescript@5.5.4))
+        version: 2.0.3(msw@2.4.9(typescript@5.6.2))
       nodemon:
-        specifier: 3.1.4
-        version: 3.1.4
+        specifier: 3.1.7
+        version: 3.1.7
       prettier:
         specifier: 3.3.3
         version: 3.3.3
@@ -1020,50 +1020,47 @@ importers:
         specifier: 3.0.5
         version: 3.0.5
       start-server-and-test:
-        specifier: 2.0.4
-        version: 2.0.4
+        specifier: 2.0.8
+        version: 2.0.8
       storybook:
-        specifier: 8.2.6
-        version: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+        specifier: 8.3.4
+        version: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       storybook-addon-misskey-theme:
         specifier: github:misskey-dev/storybook-addon-misskey-theme
-        version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(3rvqj7p7l43ansgshs3zbslm7u)
+        version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       vite-plugin-turbosnap:
         specifier: 1.0.3
         version: 1.0.3
       vitest:
         specifier: 1.6.0
-        version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)
+        version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)
       vitest-fetch-mock:
         specifier: 0.2.2
-        version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
+        version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0))
       vue-component-type-helpers:
-        specifier: 2.0.29
-        version: 2.0.29
+        specifier: 2.1.6
+        version: 2.1.6
       vue-eslint-parser:
         specifier: 9.4.3
-        version: 9.4.3(eslint@9.8.0)
+        version: 9.4.3(eslint@9.11.0)
       vue-tsc:
-        specifier: 2.0.29
-        version: 2.0.29(typescript@5.5.4)
+        specifier: 2.1.6
+        version: 2.1.6(typescript@5.6.2)
 
   packages/frontend-embed:
     dependencies:
       '@discordapp/twemoji':
-        specifier: 15.0.3
-        version: 15.0.3
-      '@github/webauthn-json':
-        specifier: 2.1.1
-        version: 2.1.1
+        specifier: 15.1.0
+        version: 15.1.0
       '@rollup/plugin-json':
         specifier: 6.1.0
-        version: 6.1.0(rollup@4.19.1)
+        version: 6.1.0(rollup@4.22.5)
       '@rollup/plugin-replace':
         specifier: 5.0.7
-        version: 5.0.7(rollup@4.19.1)
+        version: 5.0.7(rollup@4.22.5)
       '@rollup/pluginutils':
-        specifier: 5.1.0
-        version: 5.1.0(rollup@4.19.1)
+        specifier: 5.1.2
+        version: 5.1.2(rollup@4.22.5)
       '@tabler/icons-webfont':
         specifier: 3.3.0
         version: 3.3.0
@@ -1071,41 +1068,23 @@ importers:
         specifier: 15.1.1
         version: 15.1.1
       '@vitejs/plugin-vue':
-        specifier: 5.1.0
-        version: 5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))
+        specifier: 5.1.4
+        version: 5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0))(vue@3.5.11(typescript@5.6.2))
       '@vue/compiler-sfc':
-        specifier: 3.4.37
-        version: 3.4.37
+        specifier: 3.5.11
+        version: 3.5.11
       astring:
-        specifier: 1.8.6
-        version: 1.8.6
+        specifier: 1.9.0
+        version: 1.9.0
       buraha:
         specifier: 0.0.1
         version: 0.0.1
-      compare-versions:
-        specifier: 6.1.1
-        version: 6.1.1
-      date-fns:
-        specifier: 2.30.0
-        version: 2.30.0
-      escape-regexp:
-        specifier: 0.0.1
-        version: 0.0.1
       estree-walker:
         specifier: 3.0.3
         version: 3.0.3
-      eventemitter3:
-        specifier: 5.0.1
-        version: 5.0.1
       frontend-shared:
         specifier: workspace:*
         version: link:../frontend-shared
-      idb-keyval:
-        specifier: 6.2.1
-        version: 6.2.1
-      is-file-animated:
-        specifier: 1.0.2
-        version: 1.0.2
       json5:
         specifier: 2.2.3
         version: 2.2.3
@@ -1119,23 +1098,14 @@ importers:
         specifier: 2.3.1
         version: 2.3.1
       rollup:
-        specifier: 4.19.1
-        version: 4.19.1
-      sanitize-html:
-        specifier: 2.13.0
-        version: 2.13.0
+        specifier: 4.22.5
+        version: 4.22.5
       sass:
-        specifier: 1.77.8
-        version: 1.77.8
+        specifier: 1.79.4
+        version: 1.79.4
       shiki:
-        specifier: 1.12.0
-        version: 1.12.0
-      strict-event-emitter-types:
-        specifier: 2.0.0
-        version: 2.0.0
-      throttle-debounce:
-        specifier: 5.0.2
-        version: 5.0.2
+        specifier: 1.21.0
+        version: 1.21.0
       tinycolor2:
         specifier: 1.6.0
         version: 1.6.0
@@ -1146,30 +1116,27 @@ importers:
         specifier: 4.2.0
         version: 4.2.0
       typescript:
-        specifier: 5.5.4
-        version: 5.5.4
+        specifier: 5.6.2
+        version: 5.6.2
       uuid:
         specifier: 10.0.0
         version: 10.0.0
       vite:
-        specifier: 5.3.5
-        version: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+        specifier: 5.4.8
+        version: 5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0)
       vue:
-        specifier: 3.4.37
-        version: 3.4.37(typescript@5.5.4)
+        specifier: 3.5.11
+        version: 3.5.11(typescript@5.6.2)
     devDependencies:
       '@misskey-dev/summaly':
         specifier: 5.1.0
         version: 5.1.0
       '@testing-library/vue':
         specifier: 8.1.0
-        version: 8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))
-      '@types/escape-regexp':
-        specifier: 0.0.3
-        version: 0.0.3
+        version: 8.1.0(@vue/compiler-sfc@3.5.11)(@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2))
       '@types/estree':
-        specifier: 1.0.5
-        version: 1.0.5
+        specifier: 1.0.6
+        version: 1.0.6
       '@types/micromatch':
         specifier: 4.0.9
         version: 4.0.9
@@ -1179,12 +1146,6 @@ importers:
       '@types/punycode':
         specifier: 2.1.4
         version: 2.1.4
-      '@types/sanitize-html':
-        specifier: 2.11.0
-        version: 2.11.0
-      '@types/throttle-debounce':
-        specifier: 5.0.2
-        version: 5.0.2
       '@types/tinycolor2':
         specifier: 1.4.6
         version: 1.4.6
@@ -1192,20 +1153,20 @@ importers:
         specifier: 10.0.0
         version: 10.0.0
       '@types/ws':
-        specifier: 8.5.11
-        version: 8.5.11
+        specifier: 8.5.12
+        version: 8.5.12
       '@typescript-eslint/eslint-plugin':
         specifier: 7.17.0
-        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       '@vitest/coverage-v8':
         specifier: 1.6.0
-        version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3))
+        version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.33.0))
       '@vue/runtime-core':
-        specifier: 3.4.37
-        version: 3.4.37
+        specifier: 3.5.11
+        version: 3.5.11
       acorn:
         specifier: 8.12.1
         version: 8.12.1
@@ -1213,11 +1174,11 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       eslint-plugin-import:
-        specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)
+        specifier: 2.31.0
+        version: 2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)
       eslint-plugin-vue:
-        specifier: 9.27.0
-        version: 9.27.0(eslint@9.8.0)
+        specifier: 9.28.0
+        version: 9.28.0(eslint@9.11.0)
       fast-glob:
         specifier: 3.3.2
         version: 3.3.2
@@ -1228,32 +1189,32 @@ importers:
         specifier: 0.12.2
         version: 0.12.2
       micromatch:
-        specifier: 4.0.7
-        version: 4.0.7
+        specifier: 4.0.8
+        version: 4.0.8
       msw:
         specifier: 2.3.4
-        version: 2.3.4(typescript@5.5.4)
+        version: 2.3.4(typescript@5.6.2)
       nodemon:
-        specifier: 3.1.4
-        version: 3.1.4
+        specifier: 3.1.7
+        version: 3.1.7
       prettier:
         specifier: 3.3.3
         version: 3.3.3
       start-server-and-test:
-        specifier: 2.0.4
-        version: 2.0.4
+        specifier: 2.0.8
+        version: 2.0.8
       vite-plugin-turbosnap:
         specifier: 1.0.3
         version: 1.0.3
       vue-component-type-helpers:
-        specifier: 2.0.29
-        version: 2.0.29
+        specifier: 2.1.6
+        version: 2.1.6
       vue-eslint-parser:
         specifier: 9.4.3
-        version: 9.4.3(eslint@9.8.0)
+        version: 9.4.3(eslint@9.11.0)
       vue-tsc:
-        specifier: 2.0.29
-        version: 2.0.29(typescript@5.5.4)
+        specifier: 2.1.6
+        version: 2.1.6(typescript@5.6.2)
 
   packages/frontend-shared:
     dependencies:
@@ -1269,22 +1230,22 @@ importers:
         version: 20.14.12
       '@typescript-eslint/eslint-plugin':
         specifier: 7.17.0
-        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.5.4))(eslint@9.11.0)(typescript@5.5.4)
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.11.0)(typescript@5.5.4)
       esbuild:
         specifier: 0.23.0
         version: 0.23.0
       eslint-plugin-vue:
         specifier: 9.27.0
-        version: 9.27.0(eslint@9.8.0)
+        version: 9.27.0(eslint@9.11.0)
       typescript:
         specifier: 5.5.4
         version: 5.5.4
       vue-eslint-parser:
         specifier: 9.4.3
-        version: 9.4.3(eslint@9.8.0)
+        version: 9.4.3(eslint@9.11.0)
 
   packages/misskey-bubble-game:
     dependencies:
@@ -1309,10 +1270,10 @@ importers:
         version: 3.0.8
       '@typescript-eslint/eslint-plugin':
         specifier: 7.1.0
-        version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)
+        version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3))(eslint@9.11.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
         specifier: 7.1.0
-        version: 7.1.0(eslint@9.8.0)(typescript@5.3.3)
+        version: 7.1.0(eslint@9.11.0)(typescript@5.3.3)
       esbuild:
         specifier: 0.19.11
         version: 0.19.11
@@ -1331,6 +1292,9 @@ importers:
 
   packages/misskey-js:
     dependencies:
+      '@simplewebauthn/types':
+        specifier: 10.0.0
+        version: 10.0.0
       eventemitter3:
         specifier: 5.0.1
         version: 5.0.1
@@ -1339,29 +1303,29 @@ importers:
         version: 4.4.0
     devDependencies:
       '@microsoft/api-extractor':
-        specifier: 7.47.4
-        version: 7.47.4(@types/node@20.14.12)
+        specifier: 7.47.9
+        version: 7.47.9(@types/node@20.14.12)
       '@swc/jest':
         specifier: 0.2.36
         version: 0.2.36(@swc/core@1.6.13)
       '@types/jest':
-        specifier: 29.5.12
-        version: 29.5.12
+        specifier: 29.5.13
+        version: 29.5.13
       '@types/node':
         specifier: 20.14.12
         version: 20.14.12
       '@typescript-eslint/eslint-plugin':
         specifier: 7.17.0
-        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       esbuild:
-        specifier: 0.23.0
-        version: 0.23.0
+        specifier: 0.23.1
+        version: 0.23.1
       execa:
-        specifier: 9.3.0
-        version: 9.3.0
+        specifier: 9.4.0
+        version: 9.4.0
       glob:
         specifier: 11.0.0
         version: 11.0.0
@@ -1381,29 +1345,29 @@ importers:
         specifier: 2.0.0
         version: 2.0.0
       nodemon:
-        specifier: 3.1.4
-        version: 3.1.4
+        specifier: 3.1.7
+        version: 3.1.7
       tsd:
-        specifier: 0.31.1
-        version: 0.31.1
+        specifier: 0.31.2
+        version: 0.31.2
       typescript:
-        specifier: 5.5.4
-        version: 5.5.4
+        specifier: 5.6.2
+        version: 5.6.2
 
   packages/misskey-js/generator:
     devDependencies:
       '@readme/openapi-parser':
-        specifier: 2.5.0
-        version: 2.5.0(openapi-types@12.1.3)
+        specifier: 2.6.0
+        version: 2.6.0(openapi-types@12.1.3)
       '@types/node':
         specifier: 20.9.1
         version: 20.9.1
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.11.0
-        version: 6.11.0(@typescript-eslint/parser@6.11.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)
+        specifier: 7.17.0
+        version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/parser':
-        specifier: 6.11.0
-        version: 6.11.0(eslint@9.8.0)(typescript@5.3.3)
+        specifier: 7.17.0
+        version: 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       openapi-types:
         specifier: 12.1.3
         version: 12.1.3
@@ -1411,14 +1375,14 @@ importers:
         specifier: 6.7.3
         version: 6.7.3
       ts-case-convert:
-        specifier: 2.0.2
-        version: 2.0.2
+        specifier: 2.0.7
+        version: 2.0.7
       tsx:
         specifier: 4.4.0
         version: 4.4.0
       typescript:
-        specifier: 5.3.3
-        version: 5.3.3
+        specifier: 5.6.2
+        version: 5.6.2
 
   packages/misskey-reversi:
     dependencies:
@@ -1431,10 +1395,10 @@ importers:
         version: 20.11.5
       '@typescript-eslint/eslint-plugin':
         specifier: 7.1.0
-        version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)
+        version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3))(eslint@9.11.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
         specifier: 7.1.0
-        version: 7.1.0(eslint@9.8.0)(typescript@5.3.3)
+        version: 7.1.0(eslint@9.11.0)(typescript@5.3.3)
       esbuild:
         specifier: 0.19.11
         version: 0.19.11
@@ -1454,8 +1418,8 @@ importers:
   packages/sw:
     dependencies:
       esbuild:
-        specifier: 0.23.0
-        version: 0.23.0
+        specifier: 0.23.1
+        version: 0.23.1
       idb-keyval:
         specifier: 6.2.1
         version: 6.2.1
@@ -1465,24 +1429,24 @@ importers:
     devDependencies:
       '@typescript-eslint/parser':
         specifier: 7.17.0
-        version: 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+        version: 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       '@typescript/lib-webworker':
         specifier: npm:@types/serviceworker@0.0.67
         version: '@types/serviceworker@0.0.67'
       eslint-plugin-import:
-        specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)
+        specifier: 2.30.0
+        version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)
       nodemon:
-        specifier: 3.1.4
-        version: 3.1.4
+        specifier: 3.1.7
+        version: 3.1.7
       typescript:
-        specifier: 5.5.4
-        version: 5.5.4
+        specifier: 5.6.2
+        version: 5.6.2
 
 packages:
 
-  '@adobe/css-tools@4.3.3':
-    resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==}
+  '@adobe/css-tools@4.4.0':
+    resolution: {integrity: sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ==}
 
   '@aiscript-dev/aiscript-languageserver@https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz':
     resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz}
@@ -1493,17 +1457,9 @@ packages:
     resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
     engines: {node: '>=6.0.0'}
 
-  '@apidevtools/openapi-schemas@2.1.0':
-    resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
-    engines: {node: '>=10'}
-
   '@apidevtools/swagger-methods@3.0.2':
     resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
 
-  '@aw-web-design/x-default-browser@1.4.126':
-    resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
-    hasBin: true
-
   '@aws-crypto/crc32@5.2.0':
     resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
     engines: {node: '>=16.0.0'}
@@ -1709,14 +1665,6 @@ packages:
     resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-annotate-as-pure@7.24.7':
-    resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7':
-    resolution: {integrity: sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helper-compilation-targets@7.22.15':
     resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==}
     engines: {node: '>=6.9.0'}
@@ -1725,23 +1673,6 @@ packages:
     resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-create-class-features-plugin@7.24.7':
-    resolution: {integrity: sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/helper-create-regexp-features-plugin@7.24.7':
-    resolution: {integrity: sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/helper-define-polyfill-provider@0.6.2':
-    resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==}
-    peerDependencies:
-      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
-
   '@babel/helper-environment-visitor@7.22.20':
     resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
     engines: {node: '>=6.9.0'}
@@ -1766,10 +1697,6 @@ packages:
     resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-member-expression-to-functions@7.24.7':
-    resolution: {integrity: sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helper-module-imports@7.22.15':
     resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
     engines: {node: '>=6.9.0'}
@@ -1790,30 +1717,10 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0
 
-  '@babel/helper-optimise-call-expression@7.24.7':
-    resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helper-plugin-utils@7.22.5':
     resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-plugin-utils@7.24.7':
-    resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-remap-async-to-generator@7.24.7':
-    resolution: {integrity: sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/helper-replace-supers@7.24.7':
-    resolution: {integrity: sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
   '@babel/helper-simple-access@7.22.5':
     resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
     engines: {node: '>=6.9.0'}
@@ -1822,10 +1729,6 @@ packages:
     resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-skip-transparent-expression-wrappers@7.24.7':
-    resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helper-split-export-declaration@7.22.6':
     resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
     engines: {node: '>=6.9.0'}
@@ -1838,6 +1741,10 @@ packages:
     resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/helper-string-parser@7.24.8':
+    resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/helper-validator-identifier@7.24.7':
     resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
     engines: {node: '>=6.9.0'}
@@ -1850,10 +1757,6 @@ packages:
     resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/helper-wrap-function@7.24.7':
-    resolution: {integrity: sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/helpers@7.23.5':
     resolution: {integrity: sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==}
     engines: {node: '>=6.9.0'}
@@ -1875,35 +1778,10 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7':
-    resolution: {integrity: sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7':
-    resolution: {integrity: sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7':
-    resolution: {integrity: sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.13.0
-
-  '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7':
-    resolution: {integrity: sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
-    resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@babel/parser@7.25.6':
+    resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
 
   '@babel/plugin-syntax-async-generators@7.8.4':
     resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
@@ -1920,40 +1798,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-syntax-class-static-block@7.14.5':
-    resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-syntax-dynamic-import@7.8.3':
-    resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-syntax-export-namespace-from@7.8.3':
-    resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-syntax-flow@7.23.3':
-    resolution: {integrity: sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-syntax-import-assertions@7.24.7':
-    resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-syntax-import-attributes@7.24.7':
-    resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/plugin-syntax-import-meta@7.10.4':
     resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
     peerDependencies:
@@ -2000,12 +1844,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-syntax-private-property-in-object@7.14.5':
-    resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/plugin-syntax-top-level-await@7.14.5':
     resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
     engines: {node: '>=6.9.0'}
@@ -2018,370 +1856,36 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-syntax-unicode-sets-regex@7.18.6':
-    resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/plugin-transform-arrow-functions@7.24.7':
-    resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==}
+  '@babel/runtime@7.23.4':
+    resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-async-generator-functions@7.24.7':
-    resolution: {integrity: sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==}
+  '@babel/template@7.22.15':
+    resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-async-to-generator@7.24.7':
-    resolution: {integrity: sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==}
+  '@babel/template@7.24.0':
+    resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-block-scoped-functions@7.24.7':
-    resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==}
+  '@babel/template@7.24.7':
+    resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-block-scoping@7.24.7':
-    resolution: {integrity: sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==}
+  '@babel/traverse@7.23.5':
+    resolution: {integrity: sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-class-properties@7.24.7':
-    resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==}
+  '@babel/traverse@7.24.7':
+    resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-transform-class-static-block@7.24.7':
-    resolution: {integrity: sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==}
+  '@babel/types@7.24.7':
+    resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
     engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.12.0
 
-  '@babel/plugin-transform-classes@7.24.7':
-    resolution: {integrity: sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-computed-properties@7.24.7':
-    resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-destructuring@7.24.7':
-    resolution: {integrity: sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-dotall-regex@7.24.7':
-    resolution: {integrity: sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-duplicate-keys@7.24.7':
-    resolution: {integrity: sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-dynamic-import@7.24.7':
-    resolution: {integrity: sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-exponentiation-operator@7.24.7':
-    resolution: {integrity: sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-export-namespace-from@7.24.7':
-    resolution: {integrity: sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-flow-strip-types@7.23.3':
-    resolution: {integrity: sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-for-of@7.24.7':
-    resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-function-name@7.24.7':
-    resolution: {integrity: sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-json-strings@7.24.7':
-    resolution: {integrity: sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-literals@7.24.7':
-    resolution: {integrity: sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-logical-assignment-operators@7.24.7':
-    resolution: {integrity: sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-member-expression-literals@7.24.7':
-    resolution: {integrity: sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-modules-amd@7.24.7':
-    resolution: {integrity: sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-modules-commonjs@7.24.7':
-    resolution: {integrity: sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-modules-systemjs@7.24.7':
-    resolution: {integrity: sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-modules-umd@7.24.7':
-    resolution: {integrity: sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-named-capturing-groups-regex@7.24.7':
-    resolution: {integrity: sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/plugin-transform-new-target@7.24.7':
-    resolution: {integrity: sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-nullish-coalescing-operator@7.24.7':
-    resolution: {integrity: sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-numeric-separator@7.24.7':
-    resolution: {integrity: sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-object-rest-spread@7.24.7':
-    resolution: {integrity: sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-object-super@7.24.7':
-    resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-optional-catch-binding@7.24.7':
-    resolution: {integrity: sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-optional-chaining@7.24.7':
-    resolution: {integrity: sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-parameters@7.24.7':
-    resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-private-methods@7.24.7':
-    resolution: {integrity: sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-private-property-in-object@7.24.7':
-    resolution: {integrity: sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-property-literals@7.24.7':
-    resolution: {integrity: sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-regenerator@7.24.7':
-    resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-reserved-words@7.24.7':
-    resolution: {integrity: sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-shorthand-properties@7.24.7':
-    resolution: {integrity: sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-spread@7.24.7':
-    resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-sticky-regex@7.24.7':
-    resolution: {integrity: sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-template-literals@7.24.7':
-    resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-typeof-symbol@7.24.7':
-    resolution: {integrity: sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-typescript@7.23.5':
-    resolution: {integrity: sha512-2fMkXEJkrmwgu2Bsv1Saxgj30IXZdJ+84lQcKKI7sm719oXs0BBw2ZENKdJdR1PjWndgLCEBNXJOri0fk7RYQA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-unicode-escapes@7.24.7':
-    resolution: {integrity: sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-unicode-property-regex@7.24.7':
-    resolution: {integrity: sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-unicode-regex@7.24.7':
-    resolution: {integrity: sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-transform-unicode-sets-regex@7.24.7':
-    resolution: {integrity: sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-
-  '@babel/preset-env@7.24.7':
-    resolution: {integrity: sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/preset-flow@7.23.3':
-    resolution: {integrity: sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/preset-modules@0.1.6-no-external-plugins':
-    resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
-
-  '@babel/preset-typescript@7.23.3':
-    resolution: {integrity: sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/register@7.22.15':
-    resolution: {integrity: sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/regjsgen@0.8.0':
-    resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==}
-
-  '@babel/runtime@7.23.4':
-    resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/template@7.22.15':
-    resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/template@7.24.0':
-    resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/template@7.24.7':
-    resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/traverse@7.23.5':
-    resolution: {integrity: sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/traverse@7.24.7':
-    resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/types@7.24.7':
-    resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
+  '@babel/types@7.25.6':
+    resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==}
     engines: {node: '>=6.9.0'}
 
   '@base2/pretty-print-object@1.0.1':
@@ -2390,16 +1894,16 @@ packages:
   '@bcoe/v8-coverage@0.2.3':
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
 
-  '@bull-board/api@5.21.1':
-    resolution: {integrity: sha512-anzTfhOJ93eraT/GYeyxWpxRMarHwuevn6pPBfdOj0LC2eg98OPnkfdMSjcrpL3qrqsxON0RslS7kuPfCEnX6A==}
+  '@bull-board/api@6.0.0':
+    resolution: {integrity: sha512-O0IsIwAOU47bPTJnqRO7RtKFQToMvwRebbuPi6M+SG1gXyiqixLg9pycnfXgSeroaT9E7QQ2PsCPW1HO8VORvw==}
     peerDependencies:
-      '@bull-board/ui': 5.21.1
+      '@bull-board/ui': 6.0.0
 
-  '@bull-board/fastify@5.21.1':
-    resolution: {integrity: sha512-We33yolc70SALjDdF3cjEaLn1L/vrw85eFCsrviESaW3dFVIdB+xn0fdqMFK6NnaC0JjBa3Ypfev4Co+eaZ+1A==}
+  '@bull-board/fastify@6.0.0':
+    resolution: {integrity: sha512-VrKa5BdxYmXh5fJvlSPSm71b+QA9VVXHyGk6xmI/qAefUQbwd2cWJo+ppqaWSaweXa9ymJc+V4l/un0K4oomVA==}
 
-  '@bull-board/ui@5.21.1':
-    resolution: {integrity: sha512-JBDeCqG7j/c3WE0uGMN9snPkRJz9/D6MpTZzyVj7KOxIJwNKPOICNFZbCrCNi7bcJYHDJ2xGTN9OO1mw7i43BQ==}
+  '@bull-board/ui@6.0.0':
+    resolution: {integrity: sha512-wAFTlBTJbq5DSWxCzTV+FOyZDVwrXP+G1CQ2BpLG9o9+dpwYxUESx/VxNEDHnyPcy13gm29kB4fSRY+nkelkcQ==}
 
   '@bundled-es-modules/cookie@2.0.0':
     resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==}
@@ -2417,41 +1921,41 @@ packages:
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
     engines: {node: '>=0.1.90'}
 
-  '@cropper/element-canvas@2.0.0-rc.1':
-    resolution: {integrity: sha512-jRt9OM7cls+zch8U2m7pA9wp8dNOz0EtedGKkqH+DInUYw1+UtonEJirrxyl1YHRgOme5M5DTsDmkUSrUiZN6g==}
+  '@cropper/element-canvas@2.0.0-rc.2':
+    resolution: {integrity: sha512-0aqbJ3ycQM6/yn4T03vw8K/OeTB8C6+Z/jimuavy4UM2CENH9ucSLM4hAG0yYCgghIyv9Zd0unaBmtgW+I5+SQ==}
 
-  '@cropper/element-crosshair@2.0.0-rc.1':
-    resolution: {integrity: sha512-xfLelqM8EnRZUf7xEE88RWQQx5erUv7jrzni52bAw3/Ua8HXIz3uAMnkrGKOTBj8K4Rv/mNJY8k1DVAEfHY6Lg==}
+  '@cropper/element-crosshair@2.0.0-rc.2':
+    resolution: {integrity: sha512-yopINLvaZhL3E2GNienju1zeQ1Cifkn5f/0R7ZabXcAgUI0s2sLzNqL8+2XV2J3DzEzYEIYc+49KmMle04nVWQ==}
 
-  '@cropper/element-grid@2.0.0-rc.1':
-    resolution: {integrity: sha512-U/BYPl76upd9sXT+pZTFoQzUqWyNxdGs4YR2UtaVCfTMHLDTrssPAedmqEEnHgbqVcr325sIEfVmwWVA+v+8Dg==}
+  '@cropper/element-grid@2.0.0-rc.2':
+    resolution: {integrity: sha512-PzAfEya6CmIc/o/lcA/NZ1rohszz42wjq2z3E2zq2jMfNDxY/EIoFnGI6+hJrxCAaoKD8UlKOEHQdRQbtnjcMg==}
 
-  '@cropper/element-handle@2.0.0-rc.1':
-    resolution: {integrity: sha512-GuOHbjkg5CP1+oFzWQeD7VZffUE86dp4gKv5egLxkBEwnQp1VQxjO7L1Wkgj+KsQymoDczsl+x4bF12KDyDg2g==}
+  '@cropper/element-handle@2.0.0-rc.2':
+    resolution: {integrity: sha512-wOWX4xpryxKcrhnJC2mHebqQQ622UN2oyQoDZcaMzvlwt7nnX3bInF+SFrIj9/aCxtCUYY0oD2gaJkfd6aNJ0g==}
 
-  '@cropper/element-image@2.0.0-rc.1':
-    resolution: {integrity: sha512-ttzawKbUkR2A9U3bc2AN/jbNdszBP/yb83PIc5jekjOs+Z7kUBVdOo1SLIewpQ0DjUzhfCRXWUowP1McVQUXZw==}
+  '@cropper/element-image@2.0.0-rc.2':
+    resolution: {integrity: sha512-RTKnuJrqn1K8FscS11auit2W57AG04mxRNOxBldYs3lKTkwZjzJdQFkZ/Nxu+cwVXT+c6IeEiayNKvu4B7CAQg==}
 
-  '@cropper/element-selection@2.0.0-rc.1':
-    resolution: {integrity: sha512-AcRHRbsyt9xRfBD1QRyNDTS+vaYg6uAeuqhk/Ra58pqxlhtoimAV3oQ7uc/edwOlK60f/DxtKCc8rSOYFQ85bQ==}
+  '@cropper/element-selection@2.0.0-rc.2':
+    resolution: {integrity: sha512-UIgIHKHz4qNKlm5YRnC/Pu9+VrInm5TSOzkmU8kPt2swUk0WHNRv3ZcOjCQZ2ccTQnAH3FVM3FYDZ8HjRwLcBg==}
 
-  '@cropper/element-shade@2.0.0-rc.1':
-    resolution: {integrity: sha512-nHv2WujETENoIfxWQn7TYiOnXm5YUnZsoG4r6njK5cxj0gIUfPudUSbjWCQSuB2oxxpeEK8oyTdfOZtP9cxK4g==}
+  '@cropper/element-shade@2.0.0-rc.2':
+    resolution: {integrity: sha512-vHAGFxlqgflGZWkRYNWNHUY0zsV72YZGmCgtUu4sMrnWLZL/jMGhxmm8zZCe/aB94F829XcQ6uf3BoiApB+7Ng==}
 
-  '@cropper/element-viewer@2.0.0-rc.1':
-    resolution: {integrity: sha512-xTj0BObCygbVWXc7t7FYZ9k2eFyWN360it5uGeAkImXcwINRQGTFcLLOjs6i3SwedI7F1a1yNcTBfoT1B/sNAg==}
+  '@cropper/element-viewer@2.0.0-rc.2':
+    resolution: {integrity: sha512-2z9mIA7ic3enNS4xvq9Gq6hnRZ1tPr0h+lCrOHP55NL4he63lE9oTVJfDx19rL95wUS4VxL2ANvr2BVLNiBM7A==}
 
-  '@cropper/element@2.0.0-rc.1':
-    resolution: {integrity: sha512-OPKgjUgYC2Xmv77vEqtAR6bdfKOW+v9FrSjr4re3u95rcVj6NJ0JidIta41Ipp8KydHTXSmLetq4XDrA+vuIJQ==}
+  '@cropper/element@2.0.0-rc.2':
+    resolution: {integrity: sha512-4G6lTJblndwzpsb43YKeHiKcocOkDIWystGzbHNbqRysE0U0lYHuRyvV7FW6a9S63wtMFSYuwFxcdUdUcmkF8w==}
 
-  '@cropper/elements@2.0.0-rc.1':
-    resolution: {integrity: sha512-6qbtCq3iL3dETVav2XA03a8iLkHXWMIqHFxViMjlLr9CSuDjjaS5wp0JDuGtPv5FHxjsjyQ8Yayt8Ak5p09Zxg==}
+  '@cropper/elements@2.0.0-rc.2':
+    resolution: {integrity: sha512-NG5kdqpv7/tGvUfNjJiIHr2Ip431v5t/P5cIXTcYAgt8PRyFJmjx3fatC7NLnP/FUlv+bbzd8PMRI4LY4Gaw3Q==}
 
-  '@cropper/utils@2.0.0-rc.1':
-    resolution: {integrity: sha512-kreB3wdrAhmTEscfB8/j7ksGBgYSKN+28t37CAI0Vb5DvX/aUDPDH+3e2kyD7YE+DIZgdnuY2FsMYJAQ9sTThg==}
+  '@cropper/utils@2.0.0-rc.2':
+    resolution: {integrity: sha512-EEivNsyV6BtL496m4Q/IeAC6FGlyKjKIT1qMtwaxtkR+2ZlKnf9O7AdcGpClemIBA+TbwWAzp0UyIvYFtKUZ1Q==}
 
-  '@cypress/request@3.0.0':
-    resolution: {integrity: sha512-GKFCqwZwMYmL3IBoNeR2MM1SnxRIGERsQOTWeQKoYBt2JLqcqiy7JXqO894FLrpjZYqGxW92MNwRH2BN56obdQ==}
+  '@cypress/request@3.0.5':
+    resolution: {integrity: sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==}
     engines: {node: '>= 6'}
 
   '@cypress/xvfb@1.2.4':
@@ -2461,20 +1965,11 @@ packages:
     resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==}
     engines: {node: '>=14.0'}
 
-  '@discordapp/twemoji@15.0.3':
-    resolution: {integrity: sha512-5t0LLrNaSqViG0cSaomWwfR0+3fWqok+xLq40M8hJHxNX7s8gIoyNZYybQJo+s5/rGMjgdldpt8Ox8MapGvBUA==}
-
-  '@discoveryjs/json-ext@0.5.7':
-    resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
-    engines: {node: '>=10.0.0'}
-
-  '@emnapi/runtime@1.1.1':
-    resolution: {integrity: sha512-3bfqkzuR1KLx57nZfjr2NLnFOobvyS0aTszaEGCGqmYMVDRaGvgIZbjGSV/MHSSmLgQ/b9JFHQ5xm5WRZYd+XQ==}
+  '@discordapp/twemoji@15.1.0':
+    resolution: {integrity: sha512-QdpV4ifTONAXvDjRrMohausZeGrQ1ac/Ox6togUh6Xl3XKJ/KAaMMuAEi0qsb0wDwoVTSZBll5Y6+N3hB2ktBw==}
 
-  '@emotion/use-insertion-effect-with-fallbacks@1.0.1':
-    resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
-    peerDependencies:
-      react: '>=16.8.0'
+  '@emnapi/runtime@1.2.0':
+    resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==}
 
   '@esbuild/aix-ppc64@0.19.11':
     resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==}
@@ -2494,6 +1989,12 @@ packages:
     cpu: [ppc64]
     os: [aix]
 
+  '@esbuild/aix-ppc64@0.23.1':
+    resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
+
   '@esbuild/android-arm64@0.18.20':
     resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
     engines: {node: '>=12'}
@@ -2518,6 +2019,12 @@ packages:
     cpu: [arm64]
     os: [android]
 
+  '@esbuild/android-arm64@0.23.1':
+    resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [android]
+
   '@esbuild/android-arm@0.18.20':
     resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
     engines: {node: '>=12'}
@@ -2542,6 +2049,12 @@ packages:
     cpu: [arm]
     os: [android]
 
+  '@esbuild/android-arm@0.23.1':
+    resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [android]
+
   '@esbuild/android-x64@0.18.20':
     resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
     engines: {node: '>=12'}
@@ -2566,6 +2079,12 @@ packages:
     cpu: [x64]
     os: [android]
 
+  '@esbuild/android-x64@0.23.1':
+    resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [android]
+
   '@esbuild/darwin-arm64@0.18.20':
     resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
     engines: {node: '>=12'}
@@ -2590,6 +2109,12 @@ packages:
     cpu: [arm64]
     os: [darwin]
 
+  '@esbuild/darwin-arm64@0.23.1':
+    resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [darwin]
+
   '@esbuild/darwin-x64@0.18.20':
     resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
     engines: {node: '>=12'}
@@ -2614,6 +2139,12 @@ packages:
     cpu: [x64]
     os: [darwin]
 
+  '@esbuild/darwin-x64@0.23.1':
+    resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [darwin]
+
   '@esbuild/freebsd-arm64@0.18.20':
     resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
     engines: {node: '>=12'}
@@ -2638,6 +2169,12 @@ packages:
     cpu: [arm64]
     os: [freebsd]
 
+  '@esbuild/freebsd-arm64@0.23.1':
+    resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [freebsd]
+
   '@esbuild/freebsd-x64@0.18.20':
     resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
     engines: {node: '>=12'}
@@ -2662,6 +2199,12 @@ packages:
     cpu: [x64]
     os: [freebsd]
 
+  '@esbuild/freebsd-x64@0.23.1':
+    resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [freebsd]
+
   '@esbuild/linux-arm64@0.18.20':
     resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
     engines: {node: '>=12'}
@@ -2686,6 +2229,12 @@ packages:
     cpu: [arm64]
     os: [linux]
 
+  '@esbuild/linux-arm64@0.23.1':
+    resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [linux]
+
   '@esbuild/linux-arm@0.18.20':
     resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
     engines: {node: '>=12'}
@@ -2710,6 +2259,12 @@ packages:
     cpu: [arm]
     os: [linux]
 
+  '@esbuild/linux-arm@0.23.1':
+    resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==}
+    engines: {node: '>=18'}
+    cpu: [arm]
+    os: [linux]
+
   '@esbuild/linux-ia32@0.18.20':
     resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
     engines: {node: '>=12'}
@@ -2734,6 +2289,12 @@ packages:
     cpu: [ia32]
     os: [linux]
 
+  '@esbuild/linux-ia32@0.23.1':
+    resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [linux]
+
   '@esbuild/linux-loong64@0.18.20':
     resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
     engines: {node: '>=12'}
@@ -2758,6 +2319,12 @@ packages:
     cpu: [loong64]
     os: [linux]
 
+  '@esbuild/linux-loong64@0.23.1':
+    resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==}
+    engines: {node: '>=18'}
+    cpu: [loong64]
+    os: [linux]
+
   '@esbuild/linux-mips64el@0.18.20':
     resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
     engines: {node: '>=12'}
@@ -2782,6 +2349,12 @@ packages:
     cpu: [mips64el]
     os: [linux]
 
+  '@esbuild/linux-mips64el@0.23.1':
+    resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==}
+    engines: {node: '>=18'}
+    cpu: [mips64el]
+    os: [linux]
+
   '@esbuild/linux-ppc64@0.18.20':
     resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
     engines: {node: '>=12'}
@@ -2806,6 +2379,12 @@ packages:
     cpu: [ppc64]
     os: [linux]
 
+  '@esbuild/linux-ppc64@0.23.1':
+    resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [linux]
+
   '@esbuild/linux-riscv64@0.18.20':
     resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
     engines: {node: '>=12'}
@@ -2830,6 +2409,12 @@ packages:
     cpu: [riscv64]
     os: [linux]
 
+  '@esbuild/linux-riscv64@0.23.1':
+    resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==}
+    engines: {node: '>=18'}
+    cpu: [riscv64]
+    os: [linux]
+
   '@esbuild/linux-s390x@0.18.20':
     resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
     engines: {node: '>=12'}
@@ -2854,6 +2439,12 @@ packages:
     cpu: [s390x]
     os: [linux]
 
+  '@esbuild/linux-s390x@0.23.1':
+    resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==}
+    engines: {node: '>=18'}
+    cpu: [s390x]
+    os: [linux]
+
   '@esbuild/linux-x64@0.18.20':
     resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
     engines: {node: '>=12'}
@@ -2878,6 +2469,12 @@ packages:
     cpu: [x64]
     os: [linux]
 
+  '@esbuild/linux-x64@0.23.1':
+    resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [linux]
+
   '@esbuild/netbsd-x64@0.18.20':
     resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
     engines: {node: '>=12'}
@@ -2902,12 +2499,24 @@ packages:
     cpu: [x64]
     os: [netbsd]
 
+  '@esbuild/netbsd-x64@0.23.1':
+    resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [netbsd]
+
   '@esbuild/openbsd-arm64@0.23.0':
     resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==}
     engines: {node: '>=18'}
     cpu: [arm64]
     os: [openbsd]
 
+  '@esbuild/openbsd-arm64@0.23.1':
+    resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [openbsd]
+
   '@esbuild/openbsd-x64@0.18.20':
     resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
     engines: {node: '>=12'}
@@ -2932,6 +2541,12 @@ packages:
     cpu: [x64]
     os: [openbsd]
 
+  '@esbuild/openbsd-x64@0.23.1':
+    resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [openbsd]
+
   '@esbuild/sunos-x64@0.18.20':
     resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
     engines: {node: '>=12'}
@@ -2956,6 +2571,12 @@ packages:
     cpu: [x64]
     os: [sunos]
 
+  '@esbuild/sunos-x64@0.23.1':
+    resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [sunos]
+
   '@esbuild/win32-arm64@0.18.20':
     resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
     engines: {node: '>=12'}
@@ -2980,6 +2601,12 @@ packages:
     cpu: [arm64]
     os: [win32]
 
+  '@esbuild/win32-arm64@0.23.1':
+    resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==}
+    engines: {node: '>=18'}
+    cpu: [arm64]
+    os: [win32]
+
   '@esbuild/win32-ia32@0.18.20':
     resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
     engines: {node: '>=12'}
@@ -3004,6 +2631,12 @@ packages:
     cpu: [ia32]
     os: [win32]
 
+  '@esbuild/win32-ia32@0.23.1':
+    resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==}
+    engines: {node: '>=18'}
+    cpu: [ia32]
+    os: [win32]
+
   '@esbuild/win32-x64@0.18.20':
     resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
     engines: {node: '>=12'}
@@ -3028,6 +2661,12 @@ packages:
     cpu: [x64]
     os: [win32]
 
+  '@esbuild/win32-x64@0.23.1':
+    resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==}
+    engines: {node: '>=18'}
+    cpu: [x64]
+    os: [win32]
+
   '@eslint-community/eslint-utils@4.4.0':
     resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3050,10 +2689,18 @@ packages:
     resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@eslint/config-array@0.18.0':
+    resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@eslint/eslintrc@3.1.0':
     resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  '@eslint/js@9.11.0':
+    resolution: {integrity: sha512-LPkkenkDqyzTFauZLLAPhIb48fj6drrfMvRGSL9tS3AcZBSVTllemLSNyCvHNNL2t797S/6DJNSIwRwXgMO/eQ==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
   '@eslint/js@9.8.0':
     resolution: {integrity: sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -3062,64 +2709,64 @@ packages:
     resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@fal-works/esbuild-plugin-global-externals@2.1.2':
-    resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==}
+  '@eslint/plugin-kit@0.2.0':
+    resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
-  '@fastify/accept-negotiator@1.0.0':
-    resolution: {integrity: sha512-4R/N2KfYeld7A5LGkai+iUFMahXcxxYbDp+XS2B1yuL3cdmZLJ9TlCnNzT3q5xFTqsYm0GPpinLUwfSwjcVjyA==}
-    engines: {node: '>=14'}
+  '@fastify/accept-negotiator@2.0.0':
+    resolution: {integrity: sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==}
 
-  '@fastify/accepts@4.3.0':
-    resolution: {integrity: sha512-QK4FoqXdwwPmaPOLL6NrxsyaXVvdviYVoS6ltHyOLdFlUyREIaMykHQIp+x0aJz9hB3B3n/Ht6QRdvBeGkptGQ==}
+  '@fastify/accepts@5.0.1':
+    resolution: {integrity: sha512-8ji2MGTbceSnAXKYx/U9iWt6Fmf0zJovh0meO5rpwYS/vy0Z3QIR2J/hKmbcTpYfMu5NUliNpsAtMavmzBQhmA==}
 
-  '@fastify/ajv-compiler@3.5.0':
-    resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==}
+  '@fastify/ajv-compiler@4.0.0':
+    resolution: {integrity: sha512-dt0jyLAlay14LpIn4Fg1SY7V5NJ9KH0YFDpYVQY5cgIVBvdI8908AMx5zQ0bBYPGT6Wh+bM3f2caMmOXLP3QsQ==}
 
   '@fastify/busboy@2.1.0':
     resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==}
     engines: {node: '>=14'}
 
-  '@fastify/cookie@9.3.1':
-    resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==}
+  '@fastify/busboy@3.0.0':
+    resolution: {integrity: sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==}
 
-  '@fastify/cors@9.0.1':
-    resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==}
+  '@fastify/cookie@10.0.1':
+    resolution: {integrity: sha512-NV/wbCUv4ETJ5KM1KMu0fLx0nSCm9idIxwg66NZnNbfPQH3rdbx6k0qRs5uy0y+MhBgvDudYRA30KlK659chyw==}
 
-  '@fastify/deepmerge@1.3.0':
-    resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==}
+  '@fastify/cors@10.0.1':
+    resolution: {integrity: sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA==}
 
-  '@fastify/error@3.4.0':
-    resolution: {integrity: sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==}
+  '@fastify/deepmerge@2.0.0':
+    resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==}
 
-  '@fastify/express@3.0.0':
-    resolution: {integrity: sha512-Ug6aulXCUiHgMyrHVYQqnQbGdsAV0aTad6nZxbOr6w3QjKn1mdQS3Kyzvc+I0xMjZ9yIyMUWHSooHgZ0l7nOng==}
+  '@fastify/error@4.0.0':
+    resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==}
 
-  '@fastify/fast-json-stringify-compiler@4.3.0':
-    resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==}
+  '@fastify/express@4.0.1':
+    resolution: {integrity: sha512-mEQ6pawaENeZ3swqVtkxdLi8NQC5eKBkclE+7ma1qQMuB+yI6WxDyEp55pdbqPIqBQTN/cGgHv84qxVS7NKC2Q==}
 
-  '@fastify/http-proxy@9.5.0':
-    resolution: {integrity: sha512-1iqIdV10d5k9YtfHq9ylX5zt1NiM50fG+rIX40qt00R694sqWso3ukyTFZVk33SDoSiBW8roB7n11RUVUoN+Ag==}
+  '@fastify/fast-json-stringify-compiler@5.0.0':
+    resolution: {integrity: sha512-tywfuZfXsyxLC5kEqrMubbFa9vpAxNtuPE7j9w5si1r+6p5b981pDfZ5Y8HBqmjDQl+PABT7cV5jZgXI2j+I5g==}
 
-  '@fastify/multipart@8.3.0':
-    resolution: {integrity: sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==}
+  '@fastify/http-proxy@10.0.0':
+    resolution: {integrity: sha512-n5/EPspNKtzpCUavuDflYtvtB+aEkablb2sZM83gDKbxM9GF+93maJYQrGozJ2HNRqpt7wfzsDeUuGVFFkYzMQ==}
 
-  '@fastify/reply-from@9.0.1':
-    resolution: {integrity: sha512-q9vFNUiXZTY1x8omDPe59os2MYq+3y7KgO/kZoXpZlnud+45Nd8Ot/svEvrUATzjkizIggfS4K8LR9zXDyZZKg==}
+  '@fastify/merge-json-schemas@0.1.1':
+    resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==}
 
-  '@fastify/send@2.0.1':
-    resolution: {integrity: sha512-8jdouu0o5d0FMq1+zCKeKXc1tmOQ5tTGYdQP3MpyF9+WWrZT1KCBdh6hvoEYxOm3oJG/akdE9BpehLiJgYRvGw==}
+  '@fastify/multipart@9.0.1':
+    resolution: {integrity: sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==}
 
-  '@fastify/static@6.12.0':
-    resolution: {integrity: sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==}
+  '@fastify/reply-from@11.0.0':
+    resolution: {integrity: sha512-dv3o8hyy4sxhg1RN9l6ueM+PMMaIPKLjtL2T99H5M7h1Xt8d1RX3r+xC+sL5AqJqLvReX4N+7mTq9QDeB8i6Lg==}
 
-  '@fastify/static@7.0.4':
-    resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==}
+  '@fastify/send@3.1.1':
+    resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==}
 
-  '@fastify/view@8.2.0':
-    resolution: {integrity: sha512-hBSiBofCnJNlPHEMZWpO1SL84eqOaqujJ1hR3jntFyZZCkweH5jMs12DKYyGesjVll7SJFRRxPUBB8kmUmneRQ==}
+  '@fastify/static@8.0.1':
+    resolution: {integrity: sha512-7idyhbcgf14v4bjWzUeHEFvnVxvNJ1n5cyGPgFtwTZjnjUQ1wgC7a2FQai7OGKqCKywDEjzbPhAZRW+uEK1LMg==}
 
-  '@fastify/view@9.1.0':
-    resolution: {integrity: sha512-jRTGDljs/uB2p8bf6c1x4stGjP7H84VQkhbtDgCx55Mxf9Fplud5UZIHubvL4BTTX8jNYEzP1FpNAOBi7vibxg==}
+  '@fastify/view@10.0.1':
+    resolution: {integrity: sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==}
 
   '@github/webauthn-json@2.1.1':
     resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==}
@@ -3158,116 +2805,108 @@ packages:
     resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
     engines: {node: '>=18.18'}
 
-  '@img/sharp-darwin-arm64@0.33.4':
-    resolution: {integrity: sha512-p0suNqXufJs9t3RqLBO6vvrgr5OhgbWp76s5gTRvdmxmuv9E1rcaqGUsl3l4mKVmXPkTkTErXediAui4x+8PSA==}
-    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-darwin-arm64@0.33.5':
+    resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [arm64]
     os: [darwin]
 
-  '@img/sharp-darwin-x64@0.33.4':
-    resolution: {integrity: sha512-0l7yRObwtTi82Z6ebVI2PnHT8EB2NxBgpK2MiKJZJ7cz32R4lxd001ecMhzzsZig3Yv9oclvqqdV93jo9hy+Dw==}
-    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-darwin-x64@0.33.5':
+    resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [x64]
     os: [darwin]
 
-  '@img/sharp-libvips-darwin-arm64@1.0.2':
-    resolution: {integrity: sha512-tcK/41Rq8IKlSaKRCCAuuY3lDJjQnYIW1UXU1kxcEKrfL8WR7N6+rzNoOxoQRJWTAECuKwgAHnPvqXGN8XfkHA==}
-    engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-darwin-arm64@1.0.4':
+    resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
     cpu: [arm64]
     os: [darwin]
 
-  '@img/sharp-libvips-darwin-x64@1.0.2':
-    resolution: {integrity: sha512-Ofw+7oaWa0HiiMiKWqqaZbaYV3/UGL2wAPeLuJTx+9cXpCRdvQhCLG0IH8YGwM0yGWGLpsF4Su9vM1o6aer+Fw==}
-    engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-darwin-x64@1.0.4':
+    resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
     cpu: [x64]
     os: [darwin]
 
-  '@img/sharp-libvips-linux-arm64@1.0.2':
-    resolution: {integrity: sha512-x7kCt3N00ofFmmkkdshwj3vGPCnmiDh7Gwnd4nUwZln2YjqPxV1NlTyZOvoDWdKQVDL911487HOueBvrpflagw==}
-    engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-linux-arm64@1.0.4':
+    resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
     cpu: [arm64]
     os: [linux]
 
-  '@img/sharp-libvips-linux-arm@1.0.2':
-    resolution: {integrity: sha512-iLWCvrKgeFoglQxdEwzu1eQV04o8YeYGFXtfWU26Zr2wWT3q3MTzC+QTCO3ZQfWd3doKHT4Pm2kRmLbupT+sZw==}
-    engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-linux-arm@1.0.5':
+    resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
     cpu: [arm]
     os: [linux]
 
-  '@img/sharp-libvips-linux-s390x@1.0.2':
-    resolution: {integrity: sha512-cmhQ1J4qVhfmS6szYW7RT+gLJq9dH2i4maq+qyXayUSn9/3iY2ZeWpbAgSpSVbV2E1JUL2Gg7pwnYQ1h8rQIog==}
-    engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-linux-s390x@1.0.4':
+    resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
     cpu: [s390x]
     os: [linux]
 
-  '@img/sharp-libvips-linux-x64@1.0.2':
-    resolution: {integrity: sha512-E441q4Qdb+7yuyiADVi5J+44x8ctlrqn8XgkDTwr4qPJzWkaHwD489iZ4nGDgcuya4iMN3ULV6NwbhRZJ9Z7SQ==}
-    engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-linux-x64@1.0.4':
+    resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
     cpu: [x64]
     os: [linux]
 
-  '@img/sharp-libvips-linuxmusl-arm64@1.0.2':
-    resolution: {integrity: sha512-3CAkndNpYUrlDqkCM5qhksfE+qSIREVpyoeHIU6jd48SJZViAmznoQQLAv4hVXF7xyUB9zf+G++e2v1ABjCbEQ==}
-    engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
+    resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
     cpu: [arm64]
     os: [linux]
 
-  '@img/sharp-libvips-linuxmusl-x64@1.0.2':
-    resolution: {integrity: sha512-VI94Q6khIHqHWNOh6LLdm9s2Ry4zdjWJwH56WoiJU7NTeDwyApdZZ8c+SADC8OH98KWNQXnE01UdJ9CSfZvwZw==}
-    engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-libvips-linuxmusl-x64@1.0.4':
+    resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
     cpu: [x64]
     os: [linux]
 
-  '@img/sharp-linux-arm64@0.33.4':
-    resolution: {integrity: sha512-2800clwVg1ZQtxwSoTlHvtm9ObgAax7V6MTAB/hDT945Tfyy3hVkmiHpeLPCKYqYR1Gcmv1uDZ3a4OFwkdBL7Q==}
-    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-linux-arm64@0.33.5':
+    resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [arm64]
     os: [linux]
 
-  '@img/sharp-linux-arm@0.33.4':
-    resolution: {integrity: sha512-RUgBD1c0+gCYZGCCe6mMdTiOFS0Zc/XrN0fYd6hISIKcDUbAW5NtSQW9g/powkrXYm6Vzwd6y+fqmExDuCdHNQ==}
-    engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-linux-arm@0.33.5':
+    resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [arm]
     os: [linux]
 
-  '@img/sharp-linux-s390x@0.33.4':
-    resolution: {integrity: sha512-h3RAL3siQoyzSoH36tUeS0PDmb5wINKGYzcLB5C6DIiAn2F3udeFAum+gj8IbA/82+8RGCTn7XW8WTFnqag4tQ==}
-    engines: {glibc: '>=2.31', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-linux-s390x@0.33.5':
+    resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [s390x]
     os: [linux]
 
-  '@img/sharp-linux-x64@0.33.4':
-    resolution: {integrity: sha512-GoR++s0XW9DGVi8SUGQ/U4AeIzLdNjHka6jidVwapQ/JebGVQIpi52OdyxCNVRE++n1FCLzjDovJNozif7w/Aw==}
-    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-linux-x64@0.33.5':
+    resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [x64]
     os: [linux]
 
-  '@img/sharp-linuxmusl-arm64@0.33.4':
-    resolution: {integrity: sha512-nhr1yC3BlVrKDTl6cO12gTpXMl4ITBUZieehFvMntlCXFzH2bvKG76tBL2Y/OqhupZt81pR7R+Q5YhJxW0rGgQ==}
-    engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-linuxmusl-arm64@0.33.5':
+    resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [arm64]
     os: [linux]
 
-  '@img/sharp-linuxmusl-x64@0.33.4':
-    resolution: {integrity: sha512-uCPTku0zwqDmZEOi4ILyGdmW76tH7dm8kKlOIV1XC5cLyJ71ENAAqarOHQh0RLfpIpbV5KOpXzdU6XkJtS0daw==}
-    engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-linuxmusl-x64@0.33.5':
+    resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [x64]
     os: [linux]
 
-  '@img/sharp-wasm32@0.33.4':
-    resolution: {integrity: sha512-Bmmauh4sXUsUqkleQahpdNXKvo+wa1V9KhT2pDA4VJGKwnKMJXiSTGphn0gnJrlooda0QxCtXc6RX1XAU6hMnQ==}
-    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-wasm32@0.33.5':
+    resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [wasm32]
 
-  '@img/sharp-win32-ia32@0.33.4':
-    resolution: {integrity: sha512-99SJ91XzUhYHbx7uhK3+9Lf7+LjwMGQZMDlO/E/YVJ7Nc3lyDFZPGhjwiYdctoH2BOzW9+TnfqcaMKt0jHLdqw==}
-    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-win32-ia32@0.33.5':
+    resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [ia32]
     os: [win32]
 
-  '@img/sharp-win32-x64@0.33.4':
-    resolution: {integrity: sha512-3QLocdTRVIrFNye5YocZl+KKpYKP+fksi1QhmOArgx7GyhIbQp/WrJRu176jm8IxromS7RIkzMiMINVdBtC8Aw==}
-    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+  '@img/sharp-win32-x64@0.33.5':
+    resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     cpu: [x64]
     os: [win32]
 
@@ -3287,18 +2926,6 @@ packages:
     resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==}
     engines: {node: '>=18'}
 
-  '@intlify/core-base@9.13.1':
-    resolution: {integrity: sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==}
-    engines: {node: '>= 16'}
-
-  '@intlify/message-compiler@9.13.1':
-    resolution: {integrity: sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==}
-    engines: {node: '>= 16'}
-
-  '@intlify/shared@9.13.1':
-    resolution: {integrity: sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==}
-    engines: {node: '>= 16'}
-
   '@ioredis/commands@1.2.0':
     resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
 
@@ -3384,8 +3011,8 @@ packages:
     resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
 
-  '@joshwooding/vite-plugin-react-docgen-typescript@0.3.1':
-    resolution: {integrity: sha512-pdoMZ9QaPnVlSM+SdU/wgg0nyD/8wQ7y90ttO2CMCyrrm7RxveYIJ5eNfjPaoMFqW41LZra7QO9j+xV4Y18Glw==}
+  '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0':
+    resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==}
     peerDependencies:
       typescript: '>= 4.3.x'
       vite: ^3.0.0 || ^4.0.0 || ^5.0.0
@@ -3422,6 +3049,9 @@ packages:
   '@jridgewell/sourcemap-codec@1.4.15':
     resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
 
+  '@jridgewell/sourcemap-codec@1.5.0':
+    resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
+
   '@jridgewell/trace-mapping@0.3.18':
     resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
 
@@ -3441,8 +3071,8 @@ packages:
     resolution: {integrity: sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==}
     engines: {node: '>=8'}
 
-  '@lukeed/ms@2.0.1':
-    resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==}
+  '@lukeed/ms@2.0.2':
+    resolution: {integrity: sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==}
     engines: {node: '>=8'}
 
   '@mapbox/node-pre-gyp@1.0.9':
@@ -3461,11 +3091,11 @@ packages:
       '@types/react': '>=16'
       react: '>=16'
 
-  '@microsoft/api-extractor-model@7.29.4':
-    resolution: {integrity: sha512-LHOMxmT8/tU1IiiiHOdHFF83Qsi+V8d0kLfscG4EvQE9cafiR8blOYr8SfkQKWB1wgEilQgXJX3MIA4vetDLZw==}
+  '@microsoft/api-extractor-model@7.29.8':
+    resolution: {integrity: sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==}
 
-  '@microsoft/api-extractor@7.47.4':
-    resolution: {integrity: sha512-HKm+P4VNzWwvq1Ey+Jfhhj/3MjsD+ka2hbt8L5AcRM95lu1MFOYnz3XlU7Gr79Q/ZhOb7W/imAKeYrOI0bFydg==}
+  '@microsoft/api-extractor@7.47.9':
+    resolution: {integrity: sha512-TTq30M1rikVsO5wZVToQT/dGyJY7UXJmjiRtkHPLb74Prx3Etw8+bX7Bv7iLuby6ysb7fuu1NFWqma+csym8Jw==}
     hasBin: true
 
   '@microsoft/tsdoc-config@0.17.0':
@@ -3531,66 +3161,70 @@ packages:
     resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==}
     engines: {node: '>=18'}
 
-  '@napi-rs/canvas-android-arm64@0.1.53':
-    resolution: {integrity: sha512-2YhxfVsZguATlRWE0fZdTx35SE9+r5D7HV5GPNDataZOKmHf+zZ5//dspuuBSbOriQdoicaFrgXKCUqI0pK3WQ==}
+  '@mswjs/interceptors@0.35.8':
+    resolution: {integrity: sha512-PFfqpHplKa7KMdoQdj5td03uG05VK2Ng1dG0sP4pT9h0dGSX2v9txYt/AnrzPb/vAmfyBBC0NQV7VaBEX+efgQ==}
+    engines: {node: '>=18'}
+
+  '@napi-rs/canvas-android-arm64@0.1.56':
+    resolution: {integrity: sha512-xBGqW2RZMAupkzar9t3gpbok9r524f3Wlk4PG2qnQdxbsiEND06OB8VxVtTcql6R02uJpXJGnyIhN02Te+GMVQ==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [android]
 
-  '@napi-rs/canvas-darwin-arm64@0.1.53':
-    resolution: {integrity: sha512-ls+CWLMusf4RAGo5BvIIzA6dNcc0elwVp6LKjHfQECHA8KKmvdB58YuE5BQcTlb2rzk0SEKtBC/Th3NI2oNdfg==}
+  '@napi-rs/canvas-darwin-arm64@0.1.56':
+    resolution: {integrity: sha512-Pvuz6Ib9YZTB5MlGL9WSu9a2asUC0DZ1zBHozDiBXr/6Zurs9l/ZH5NxFYTM829BpkdkO8kuI8b8Rz7ek30zzQ==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [darwin]
 
-  '@napi-rs/canvas-darwin-x64@0.1.53':
-    resolution: {integrity: sha512-ZAgcoCH5+5OKS2P8Lxx+jbkAPKkyLD2x6OvSrHg1U6ppdxmLA+CkJlRl8w45HCXwuyIiP7OeymECRtiNYTwznQ==}
+  '@napi-rs/canvas-darwin-x64@0.1.56':
+    resolution: {integrity: sha512-O393jWt7G6rg0X1ralbsbBeskSG0iwlkD7mEHhMLJxqRqe+eQn0/xnwhs9l6dUNFC+5dM8LOvfFca4o9Vs2Vww==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
 
-  '@napi-rs/canvas-linux-arm-gnueabihf@0.1.53':
-    resolution: {integrity: sha512-p9km/3C/loDxu3AvA8/vtpIS1BGMd/Ehkl2Iu/v/Gw8N/KUIt3HUvTS7AKApyVE28bxTfq96wJQjtcT8jzDncw==}
+  '@napi-rs/canvas-linux-arm-gnueabihf@0.1.56':
+    resolution: {integrity: sha512-30NFb5lrF3YEwAO5XuATxpWDSXaBAgaFVswPJ+hYcAUyE3IkPPIFRY4ijQEh4frcSBvrzFGGYdNSoC18oLLWaQ==}
     engines: {node: '>= 10'}
     cpu: [arm]
     os: [linux]
 
-  '@napi-rs/canvas-linux-arm64-gnu@0.1.53':
-    resolution: {integrity: sha512-QKK+sykEiYwjwd+ogyLcpcnH38DNZ8KViBlnfEpoGA2Wa+21/cWQKfMxnbgb/rbvm5tazJinZcihFvH577WQ5g==}
+  '@napi-rs/canvas-linux-arm64-gnu@0.1.56':
+    resolution: {integrity: sha512-ODbWH9TLvba+39UxFwPn2Hm1ImALmWOZ0pEv5do/pz0439326Oz49hlfGot4KmkSBeKK81knWxRj9EXMSPwXPg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
-  '@napi-rs/canvas-linux-arm64-musl@0.1.53':
-    resolution: {integrity: sha512-2N41U0X8RnrTKzpTtPv1ozlYkJtPsUdbfF3uP/KEd/BsULGd8Y8ghkGMS6CM+821au4ex0dPrWOOdT9wC1rSqQ==}
+  '@napi-rs/canvas-linux-arm64-musl@0.1.56':
+    resolution: {integrity: sha512-zqE4nz8CWiJJ0q5By7q9CDPicNkc0oyErgavK3ZV279zJL7Aapd3cIqayT6ynECArg7GgBl2WYSvr5AaRFmYgg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
 
-  '@napi-rs/canvas-linux-x64-gnu@0.1.53':
-    resolution: {integrity: sha512-7XjuTvDKCODtf/vMwF43VGDrjfgwYKgS91ggdcX3UrJaBYWyWu/+eqNvNj+zdXSe/0x+YOjf5jG4m8xIXdBMQA==}
+  '@napi-rs/canvas-linux-x64-gnu@0.1.56':
+    resolution: {integrity: sha512-JTnGAtJBQMhfSpN8/rbMnf5oxuO/juUNa0n4LA0LlW0JS9UBpmsS2BwFNCakFqOeAPaqIM6sFFsK3M4hve+Esw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
-  '@napi-rs/canvas-linux-x64-musl@0.1.53':
-    resolution: {integrity: sha512-970WEvB8vmj+uxvgdBZ+AGFV7uq9GJhXrqG5PGQ5lWciHX0P0d/OhS2F7TITgFR0LsKDQZ7XQgzMxsYOfwZ0FQ==}
+  '@napi-rs/canvas-linux-x64-musl@0.1.56':
+    resolution: {integrity: sha512-mpws7DhVDIj8ZKa/qcnUVLAm0fxD9RK5ojfNNSI9TOzn2E0f+GUXx8sGsCxDpMVMtN+mtyrMwRqH3F3rTUMWXw==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
 
-  '@napi-rs/canvas-win32-x64-msvc@0.1.53':
-    resolution: {integrity: sha512-rLFQCSJaWg/sv54Aap9nAhaodi4Vyb4un50EgW+PNkk8icMziU6KLRKirGBdQr9ZdxnshAPeQXD1g2ArStujKA==}
+  '@napi-rs/canvas-win32-x64-msvc@0.1.56':
+    resolution: {integrity: sha512-VKAAkgXF+lbFvRFawPOtkfV/P7ogAgWTu5FMCIiBn0Gc3vnkKFG2cLo/IHIJ7FuriToKEidkJGT88iAh7W7GDA==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
 
-  '@napi-rs/canvas@0.1.53':
-    resolution: {integrity: sha512-XsEZi97+kKykmAiPpY+IpZoHxJY1srqFZp8jDt1/RySzC0kB0iZYt/VMIFqQKpLCARZjD7SOAz2AULtwYlesCA==}
+  '@napi-rs/canvas@0.1.56':
+    resolution: {integrity: sha512-SujSchzG6lLc/wT+Mwxam/w30Kk2sFTiU6bLFcidecKSmlhenAhGMQhZh2iGFfKoh2+8iit0jrt99n6TqReICQ==}
     engines: {node: '>= 10'}
 
-  '@nestjs/common@10.3.10':
-    resolution: {integrity: sha512-H8k0jZtxk1IdtErGDmxFRy0PfcOAUg41Prrqpx76DQusGGJjsaovs1zjXVD1rZWaVYchfT1uczJ6L4Kio10VNg==}
+  '@nestjs/common@10.4.4':
+    resolution: {integrity: sha512-0j2/zqRw9nvHV1GKTktER8B/hIC/Z8CYFjN/ZqUuvwayCH+jZZBhCR2oRyuvLTXdnlSmtCAg2xvQ0ULqQvzqhA==}
     peerDependencies:
       class-transformer: '*'
       class-validator: '*'
@@ -3602,8 +3236,8 @@ packages:
       class-validator:
         optional: true
 
-  '@nestjs/core@10.3.10':
-    resolution: {integrity: sha512-ZbQ4jovQyzHtCGCrzK5NdtW1SYO2fHSsgSY1+/9WdruYCUra+JDkWEXgZ4M3Hv480Dl3OXehAmY1wCOojeMyMQ==}
+  '@nestjs/core@10.4.4':
+    resolution: {integrity: sha512-y9tjmAzU6LTh1cC/lWrRsCcOd80khSR0qAHAqwY2svbW+AhsR/XCzgpZrAAKJrm/dDfjLCZKyxJSayeirGcW5Q==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/microservices': ^10.0.0
@@ -3619,14 +3253,14 @@ packages:
       '@nestjs/websockets':
         optional: true
 
-  '@nestjs/platform-express@10.3.10':
-    resolution: {integrity: sha512-wK2ow3CZI2KFqWeEpPmoR300OB6BcBLxARV1EiClJLCj4S1mZsoCmS0YWgpk3j1j6mo0SI8vNLi/cC2iZPEPQA==}
+  '@nestjs/platform-express@10.4.4':
+    resolution: {integrity: sha512-y52q1MxhbHaT3vAgWd08RgiYon0lJgtTa8U6g6gV0KI0IygwZhDQFJVxnrRDUdxQGIP5CKHmfQu3sk9gTNFoEA==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/core': ^10.0.0
 
-  '@nestjs/testing@10.3.10':
-    resolution: {integrity: sha512-i3HAtVQJijxNxJq1k39aelyJlyEIBRONys7IipH/4r8W0J+M1V+y5EKDOyi4j1SdNSb/vmNyWpZ2/ewZjl3kRA==}
+  '@nestjs/testing@10.4.4':
+    resolution: {integrity: sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/core': ^10.0.0
@@ -3638,9 +3272,9 @@ packages:
       '@nestjs/platform-express':
         optional: true
 
-  '@noble/hashes@1.4.0':
-    resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==}
-    engines: {node: '>= 16'}
+  '@noble/hashes@1.5.0':
+    resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
+    engines: {node: ^14.21.3 || >=16}
 
   '@nodelib/fs.scandir@2.1.5':
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -3890,12 +3524,16 @@ packages:
   '@readme/json-schema-ref-parser@1.2.0':
     resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==}
 
-  '@readme/openapi-parser@2.5.0':
-    resolution: {integrity: sha512-IbymbOqRuUzoIgxfAAR7XJt2FWl6n2yqN09fF5adacGm7W03siA3bj1Emql0X9D2T+RpBYz3x9zDsMhuoMP62A==}
-    engines: {node: '>=14'}
+  '@readme/openapi-parser@2.6.0':
+    resolution: {integrity: sha512-pyFJXezWj9WI1O+gdp95CoxfY+i+Uq3kKk4zXIFuRAZi9YnHpHOpjumWWr67wkmRTw19Hskh9spyY0Iyikf3fA==}
+    engines: {node: '>=18'}
     peerDependencies:
       openapi-types: '>=7'
 
+  '@readme/openapi-schemas@3.1.0':
+    resolution: {integrity: sha512-9FC/6ho8uFa8fV50+FPy/ngWN53jaUu4GRXlAjcxIRrzhltJnpKkBG2Tp0IDraFJeWrOpk84RJ9EMEEYzaI1Bw==}
+    engines: {node: '>=18'}
+
   '@rollup/plugin-json@6.1.0':
     resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
     engines: {node: '>=14.0.0'}
@@ -3914,8 +3552,8 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/pluginutils@5.1.0':
-    resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
+  '@rollup/pluginutils@5.1.2':
+    resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -3923,88 +3561,91 @@ packages:
       rollup:
         optional: true
 
-  '@rollup/rollup-android-arm-eabi@4.19.1':
-    resolution: {integrity: sha512-XzqSg714++M+FXhHfXpS1tDnNZNpgxxuGZWlRG/jSj+VEPmZ0yg6jV4E0AL3uyBKxO8mO3xtOsP5mQ+XLfrlww==}
+  '@rollup/rollup-android-arm-eabi@4.22.5':
+    resolution: {integrity: sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==}
     cpu: [arm]
     os: [android]
 
-  '@rollup/rollup-android-arm64@4.19.1':
-    resolution: {integrity: sha512-thFUbkHteM20BGShD6P08aungq4irbIZKUNbG70LN8RkO7YztcGPiKTTGZS7Kw+x5h8hOXs0i4OaHwFxlpQN6A==}
+  '@rollup/rollup-android-arm64@4.22.5':
+    resolution: {integrity: sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==}
     cpu: [arm64]
     os: [android]
 
-  '@rollup/rollup-darwin-arm64@4.19.1':
-    resolution: {integrity: sha512-8o6eqeFZzVLia2hKPUZk4jdE3zW7LCcZr+MD18tXkgBBid3lssGVAYuox8x6YHoEPDdDa9ixTaStcmx88lio5Q==}
+  '@rollup/rollup-darwin-arm64@4.22.5':
+    resolution: {integrity: sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==}
     cpu: [arm64]
     os: [darwin]
 
-  '@rollup/rollup-darwin-x64@4.19.1':
-    resolution: {integrity: sha512-4T42heKsnbjkn7ovYiAdDVRRWZLU9Kmhdt6HafZxFcUdpjlBlxj4wDrt1yFWLk7G4+E+8p2C9tcmSu0KA6auGA==}
+  '@rollup/rollup-darwin-x64@4.22.5':
+    resolution: {integrity: sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==}
     cpu: [x64]
     os: [darwin]
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.19.1':
-    resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
+  '@rollup/rollup-linux-arm-gnueabihf@4.22.5':
+    resolution: {integrity: sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm-musleabihf@4.19.1':
-    resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
+  '@rollup/rollup-linux-arm-musleabihf@4.22.5':
+    resolution: {integrity: sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==}
     cpu: [arm]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-gnu@4.19.1':
-    resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
+  '@rollup/rollup-linux-arm64-gnu@4.22.5':
+    resolution: {integrity: sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-arm64-musl@4.19.1':
-    resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
+  '@rollup/rollup-linux-arm64-musl@4.22.5':
+    resolution: {integrity: sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==}
     cpu: [arm64]
     os: [linux]
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
-    resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
+  '@rollup/rollup-linux-powerpc64le-gnu@4.22.5':
+    resolution: {integrity: sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==}
     cpu: [ppc64]
     os: [linux]
 
-  '@rollup/rollup-linux-riscv64-gnu@4.19.1':
-    resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
+  '@rollup/rollup-linux-riscv64-gnu@4.22.5':
+    resolution: {integrity: sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==}
     cpu: [riscv64]
     os: [linux]
 
-  '@rollup/rollup-linux-s390x-gnu@4.19.1':
-    resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
+  '@rollup/rollup-linux-s390x-gnu@4.22.5':
+    resolution: {integrity: sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==}
     cpu: [s390x]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-gnu@4.19.1':
-    resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
+  '@rollup/rollup-linux-x64-gnu@4.22.5':
+    resolution: {integrity: sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-linux-x64-musl@4.19.1':
-    resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
+  '@rollup/rollup-linux-x64-musl@4.22.5':
+    resolution: {integrity: sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==}
     cpu: [x64]
     os: [linux]
 
-  '@rollup/rollup-win32-arm64-msvc@4.19.1':
-    resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==}
+  '@rollup/rollup-win32-arm64-msvc@4.22.5':
+    resolution: {integrity: sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==}
     cpu: [arm64]
     os: [win32]
 
-  '@rollup/rollup-win32-ia32-msvc@4.19.1':
-    resolution: {integrity: sha512-LdxxcqRVSXi6k6JUrTah1rHuaupoeuiv38du8Mt4r4IPer3kwlTo+RuvfE8KzZ/tL6BhaPlzJ3835i6CxrFIRQ==}
+  '@rollup/rollup-win32-ia32-msvc@4.22.5':
+    resolution: {integrity: sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==}
     cpu: [ia32]
     os: [win32]
 
-  '@rollup/rollup-win32-x64-msvc@4.19.1':
-    resolution: {integrity: sha512-2bIrL28PcK3YCqD9anGxDxamxdiJAxA+l7fWIwM5o8UqNy1t3d1NdAweO2XhA0KTDJ5aH1FsuiT5+7VhtHliXg==}
+  '@rollup/rollup-win32-x64-msvc@4.22.5':
+    resolution: {integrity: sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==}
     cpu: [x64]
     os: [win32]
 
-  '@rushstack/node-core-library@5.5.1':
-    resolution: {integrity: sha512-ZutW56qIzH8xIOlfyaLQJFx+8IBqdbVCZdnj+XT1MorQ1JqqxHse8vbCpEM+2MjsrqcbxcgDIbfggB1ZSQ2A3g==}
+  '@rtsao/scc@1.1.0':
+    resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
+
+  '@rushstack/node-core-library@5.9.0':
+    resolution: {integrity: sha512-MMsshEWkTbXqxqFxD4gcIUWQOCeBChlGczdZbHfqmNZQFLHB3yWxDFSMHFUdu2/OB9NUk7Awn5qRL+rws4HQNg==}
     peerDependencies:
       '@types/node': '*'
     peerDependenciesMeta:
@@ -4014,16 +3655,16 @@ packages:
   '@rushstack/rig-package@0.5.3':
     resolution: {integrity: sha512-olzSSjYrvCNxUFZowevC3uz8gvKr3WTpHQ7BkpjtRpA3wK+T0ybep/SRUMfr195gBzJm5gaXw0ZMgjIyHqJUow==}
 
-  '@rushstack/terminal@0.13.3':
-    resolution: {integrity: sha512-fc3zjXOw8E0pXS5t9vTiIPx9gHA0fIdTXsu9mT4WbH+P3mYvnrX0iAQ5a6NvyK1+CqYWBTw/wVNx7SDJkI+WYQ==}
+  '@rushstack/terminal@0.14.2':
+    resolution: {integrity: sha512-2fC1wqu1VCExKC0/L+0noVcFQEXEnoBOtCIex1TOjBzEDWcw8KzJjjj7aTP6mLxepG0XIyn9OufeFb6SFsa+sg==}
     peerDependencies:
       '@types/node': '*'
     peerDependenciesMeta:
       '@types/node':
         optional: true
 
-  '@rushstack/ts-command-line@4.22.3':
-    resolution: {integrity: sha512-edMpWB3QhFFZ4KtSzS8WNjBgR4PXPPOVrOHMbb7kNpmQ1UFS9HdVtjCXg1H5fG+xYAbeE+TMPcVPUyX2p84STA==}
+  '@rushstack/ts-command-line@4.22.8':
+    resolution: {integrity: sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==}
 
   '@sec-ant/readable-stream@0.4.1':
     resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@@ -4059,12 +3700,27 @@ packages:
     resolution: {integrity: sha512-+1I5H8dojURiEUGPliDwheQk8dhjp8uV1sMccR/W/zjFrt4wZyPs+Ttp/V7gzm9LDJoNek9tmELert/jQqWTgg==}
     engines: {node: '>=14.18'}
 
-  '@shikijs/core@1.12.0':
-    resolution: {integrity: sha512-mc1cLbm6UQ8RxLc0dZES7v5rkH+99LxQp/ZvTqV3NLyYsO/fD6JhEflP1H5b2SDq9gI0+0G36AVZWxvounfR9w==}
+  '@shikijs/core@1.21.0':
+    resolution: {integrity: sha512-zAPMJdiGuqXpZQ+pWNezQAk5xhzRXBNiECFPcJLtUdsFM3f//G95Z15EHTnHchYycU8kIIysqGgxp8OVSj1SPQ==}
+
+  '@shikijs/engine-javascript@1.21.0':
+    resolution: {integrity: sha512-jxQHNtVP17edFW4/0vICqAVLDAxmyV31MQJL4U/Kg+heQALeKYVOWo0sMmEZ18FqBt+9UCdyqGKYE7bLRtk9mg==}
+
+  '@shikijs/engine-oniguruma@1.21.0':
+    resolution: {integrity: sha512-AIZ76XocENCrtYzVU7S4GY/HL+tgHGbVU+qhiDyNw1qgCA5OSi4B4+HY4BtAoJSMGuD/L5hfTzoRVbzEm2WTvg==}
+
+  '@shikijs/types@1.21.0':
+    resolution: {integrity: sha512-tzndANDhi5DUndBtpojEq/42+dpUF2wS7wdCDQaFtIXm3Rd1QkrcVgSSRLOvEwexekihOXfbYJINW37g96tJRw==}
+
+  '@shikijs/vscode-textmate@9.2.2':
+    resolution: {integrity: sha512-TMp15K+GGYrWlZM8+Lnj9EaHEFmOen0WJBrfa17hF7taDOYthuPPV0GWzfd/9iMij0akS/8Yw2ikquH7uVi/fg==}
 
   '@sideway/address@4.1.4':
     resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
 
+  '@sideway/address@4.1.5':
+    resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==}
+
   '@sideway/formula@3.0.1':
     resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==}
 
@@ -4093,10 +3749,6 @@ packages:
     resolution: {integrity: sha512-WDTlVTyvFivSOuyvMeedzg2hdoBLZ3f1uNVuEida2Rl9BrfjrIRjWA/VZIrMRLvSwJYCAlCRA3usDt1THytxWQ==}
     engines: {node: '>=18'}
 
-  '@sindresorhus/merge-streams@2.3.0':
-    resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
-    engines: {node: '>=18'}
-
   '@sindresorhus/merge-streams@4.0.0':
     resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
     engines: {node: '>=18'}
@@ -4349,115 +4001,97 @@ packages:
   '@sqltools/formatter@1.2.5':
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
 
-  '@storybook/addon-actions@8.2.6':
-    resolution: {integrity: sha512-iCsf3V28/jJ95w2zd8aSvR4denoA2UYV3fpNCTGOURqICyKOG3cyVxvqKp8Hhcwn7trNOsK+HlL6q5gpv56ViA==}
+  '@storybook/addon-actions@8.3.4':
+    resolution: {integrity: sha512-1y0yD3upKcyzNwwA6loAGW2cRDqExwl4oAT7GJQA4tmabI+fNwmANSgU/ezLvvSUf4Qo0eJHg2Zcn8y+Apq2eA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-backgrounds@8.2.6':
-    resolution: {integrity: sha512-61NFowA6EmCw+Eyzp0U4fat9MlPDdnT7aoDyzqSImLwWLITY9IvmWuTeo7XKJZN3fe22z1r7cZseKdYrtaHcKw==}
+  '@storybook/addon-backgrounds@8.3.4':
+    resolution: {integrity: sha512-o3nl7cN3x8erJNxLEv8YptanEQAnbqnaseOAsvSC6/nnSAcRYBSs3BvekKvo4CcpS2mxn7F5NJTBFYnCXzy8EA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-controls@8.2.6':
-    resolution: {integrity: sha512-EHUwHy+oZZv3pXzN7fuXWrS/meHFjqcELY3RBvOyEkGf21agl6co6R1tnf6d5N5QoYAGfIbDO7dkauSL2RfNAw==}
+  '@storybook/addon-controls@8.3.4':
+    resolution: {integrity: sha512-qQcaK6dczsb6wXkzGZKOjUYNA7FfKBewRv6NvoVKYY6LfhllGOkmUAtYpdtQG8adsZWTSoZaAOJS2vP2uM67lw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-docs@8.2.6':
-    resolution: {integrity: sha512-qe7hxntaezqjKdU9QS+Q9NFL6i/uNdBxdvOnCKgPhBAY/zY6yhk5t3sOvonynPK5nkaNAowfSNPIzNxAXlJ1sA==}
+  '@storybook/addon-docs@8.3.4':
+    resolution: {integrity: sha512-TWauhqF/gJgfwPuWeM6KM3LwC+ErCOM+K2z16w3vgao9s67sij8lnrdAoQ0hjA+kw2/KAdCakFS6FyciG81qog==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-essentials@8.2.6':
-    resolution: {integrity: sha512-diGjGZcZNov+RCAVQBTm8JKP2kUtMRuJIQFBeXdPWpu6hYBk6lw1FlAf2GywWGCvdny1pJT90hfoD33qUMNuDg==}
+  '@storybook/addon-essentials@8.3.4':
+    resolution: {integrity: sha512-C3+3hpmSn/8zdx5sXEP0eE6zMzxgRosHVZYfe9nBcMiEDp6UKVUyHVetWxEULOEgN46ysjcpllZ0bUkRYxi2IQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-highlight@8.2.6':
-    resolution: {integrity: sha512-03cV9USsfP3bS4wYV06DYcIaGPfoheQe53Q0Jr1B2yJUVyIPKvmO2nGjLBsqzeL3Wl7vSfLQn0/dUdxCcbqLsw==}
+  '@storybook/addon-highlight@8.3.4':
+    resolution: {integrity: sha512-rxZTeuZyZ7RnU+xmRhS01COFLbGnVEmlUNxBw8ArsrTEZKW5PbKpIxNLTj9F0zdH8H0MfryJGP+Aadcm0oHWlw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-interactions@8.2.6':
-    resolution: {integrity: sha512-YXpHf8jWPz9HJV+Fw4GaunaCWeE6uqF24aLXdAd8xuhN1UfWJeNV6AwAvFQ0hTLqvmz0yMhX/5JXDKeKESoYDA==}
+  '@storybook/addon-interactions@8.3.4':
+    resolution: {integrity: sha512-ORxqe35wUmF7EDHo45mdDHiju3Ryk2pZ1vO9PyvW6ZItNlHt/IxAr7T/TysGejZ/eTBg6tMZR3ExGky3lTg/CQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-links@8.2.6':
-    resolution: {integrity: sha512-CUuU3nk8wyZ3bljCmOG/OCKazan+bPuNbCph8N763zyzdEx5M/CbBxV9d3pi3zjYpix7txlqrl2/YdMCejfyFw==}
+  '@storybook/addon-links@8.3.4':
+    resolution: {integrity: sha512-R1DjARmxRIKJDGIG6uxmQ1yFNyoQbb+QIPUFjgWCak8+AdLJbC7W+Esvo9F5hQfh6czyy0piiM3qj5hpQJVh3A==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      storybook: ^8.2.6
+      storybook: ^8.3.4
     peerDependenciesMeta:
       react:
         optional: true
 
-  '@storybook/addon-mdx-gfm@8.2.6':
-    resolution: {integrity: sha512-PFVfJeuydxlV1VmxEuKNQ7z2vCDvQtHa2GB0ANM11ahxDSUz8QxsO0Y/L3LOn2JjJGYiVFrsHAaC+8NW43iArQ==}
+  '@storybook/addon-mdx-gfm@8.3.4':
+    resolution: {integrity: sha512-O0sMP7VFo1fKsdViY+W6OMNYEXvB5FzEEsqgsydMcsJ0qOKR1li2l3cLCMLXdUKVZ+2uRbEhnm2RnB9RWF5O7g==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-measure@8.2.6':
-    resolution: {integrity: sha512-neI8YeSOAtOmzasLxo6O8ZLr2ebMaD7XVF+kYatl5+SpyuwwvUGcP9NkKe5S+mB8V2zxFUIsXS74XrhmQhRoaQ==}
+  '@storybook/addon-measure@8.3.4':
+    resolution: {integrity: sha512-IJ6WKEbqmG+r7sukFjo+bVmPB2Zry04sylGx/OGyOh7zIhhqAqpwOwMHP0uQrc3tLNnUM6qB/o83UyYX79ql+A==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-outline@8.2.6':
-    resolution: {integrity: sha512-uAlPtqDWlq7MQQ4zJT80qdjbSdLF/zsvtPhidX6h9cjLKNPWAv79xJQ14AJHaMv+Hzy5xKnM4wdEhgPbzKabQg==}
+  '@storybook/addon-outline@8.3.4':
+    resolution: {integrity: sha512-kRRJTTLKM8gMfeh/e83djN5XLlc0hFtr9zKWxuZxaXt9Hmr+9tH/PRFtVK/S4SgqnBDoXk49Wgv6raiwj5/e3A==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-storysource@8.2.6':
-    resolution: {integrity: sha512-8H2kvRIM12oXN4kO/oowABu88IOY9Je7PphCUUs/nIfTIB+Ck1GrLxx5fCNNCSwUrTBEZY8bDOfxmkf9d4qngw==}
+  '@storybook/addon-storysource@8.3.4':
+    resolution: {integrity: sha512-uHTUiK7dzWRZAKpPafBH3U5PWAP7+J97lg66HDKAHpmmQdy7v3HfXaYNX1FoI+PeC5piUxFETXM0z+BNvJCknA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-toolbars@8.2.6':
-    resolution: {integrity: sha512-0JmRirMpxHS6VZzBk0kY871xWTpkk3TN4S1sxoFf5fcnCfVTHDjEJ5Ws/QWru1RJlIZHuJKRdQIA6Vuq5X+KfQ==}
+  '@storybook/addon-toolbars@8.3.4':
+    resolution: {integrity: sha512-Km1YciVIxqluDbd1xmHjANNFyMonEOtnA6e4MrnBnC9XkPXSigeFlj0JvxyI/zjBsLBoFRmQiwq55W6l3hQ9sA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/addon-viewport@8.2.6':
-    resolution: {integrity: sha512-IAxH9H8tVFzSmZhKf5E+EALiAdkp19RzGqP/rWluD8LH7oW5HumQE/4oN0ZhVMy1RxYsCKFYjWyAp7AuxeMRSw==}
+  '@storybook/addon-viewport@8.3.4':
+    resolution: {integrity: sha512-fU4LdXSSqIOLbCEh2leq/tZUYlFliXZBWr/+igQHdUoU7HY8RIImXqVUaR9wlCaTb48WezAWT60vJtwNijyIiQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/blocks@8.2.6':
-    resolution: {integrity: sha512-nMlZJjVTyfOJ6xwORptsNuS1AZZlDbJUVXc2R8uukGd5GIXxxCdrPk4NvUsjfQslMT9LhYuFld3z62FATsM2rw==}
+  '@storybook/blocks@8.3.4':
+    resolution: {integrity: sha512-1g4aCrd5CcN+pVhF2ATu9ZRVvAIgBMb2yF9KkCuTpdvqKDuDNK3sGb0CxjS7jp3LOvyjJr9laTOQsz8v8MQc5A==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      storybook: ^8.2.6
+      storybook: ^8.3.4
     peerDependenciesMeta:
       react:
         optional: true
       react-dom:
         optional: true
 
-  '@storybook/builder-manager@8.1.11':
-    resolution: {integrity: sha512-U7bmed4Ayg+OlJ8HPmLeGxLTHzDY7rxmxM4aAs4YL01fufYfBcjkIP9kFhJm+GJOvGm+YJEUAPe5mbM1P/bn0Q==}
-
-  '@storybook/builder-vite@8.1.11':
-    resolution: {integrity: sha512-hG4eoNMCPgjZ2Ai+zSmk69zjsyEihe75XbJXtYfGRqjMWtz2+SAUFO54fLc2BD5svcUiTeN+ukWcTrwApyPsKg==}
-    peerDependencies:
-      '@preact/preset-vite': '*'
-      typescript: '>= 4.3.x'
-      vite: ^4.0.0 || ^5.0.0
-      vite-plugin-glimmerx: '*'
-    peerDependenciesMeta:
-      '@preact/preset-vite':
-        optional: true
-      typescript:
-        optional: true
-      vite-plugin-glimmerx:
-        optional: true
-
-  '@storybook/builder-vite@8.2.6':
-    resolution: {integrity: sha512-3PrsPZAedpQUbzRBEl23Fi1zG5bkQD76JsygVwmfiSm4Est4K8kW2AIB2ht9cIfKXh3mfQkyQlxXKHeQEHeQwQ==}
+  '@storybook/builder-vite@8.3.4':
+    resolution: {integrity: sha512-Sa6SZ7LeHpkrnuvua8P8MR8e8a+MPKbyMmr9TqCCy8Ud/t4AM4kHY3JpJGtrgeK9l43fBnBwfdZYoRl5J6oWeA==}
     peerDependencies:
       '@preact/preset-vite': '*'
-      storybook: ^8.2.6
+      storybook: ^8.3.4
       typescript: '>= 4.3.x'
       vite: ^4.0.0 || ^5.0.0
       vite-plugin-glimmerx: '*'
@@ -4469,190 +4103,115 @@ packages:
       vite-plugin-glimmerx:
         optional: true
 
-  '@storybook/channels@8.1.11':
-    resolution: {integrity: sha512-fu5FTqo6duOqtJFa6gFzKbiSLJoia+8Tibn3xFfB6BeifWrH81hc+AZq0lTmHo5qax2G5t8ZN8JooHjMw6k2RA==}
-
-  '@storybook/client-logger@8.1.11':
-    resolution: {integrity: sha512-DVMh2usz3yYmlqCLCiCKy5fT8/UR9aTh+gSqwyNFkGZrIM4otC5A8eMXajXifzotQLT5SaOEnM3WzHwmpvMIEA==}
-
-  '@storybook/codemod@8.2.6':
-    resolution: {integrity: sha512-+mFJ6R+JhJLpU7VPDlXU5Yn6nqIBq745GaEosnIiFOdNo3jaxJ58wq/sGhbQvoCHPUxMA+sDQvR7pS62YFoLRQ==}
-
-  '@storybook/components@8.2.6':
-    resolution: {integrity: sha512-H8ckH1AnLkHtMtvJ3J8LxnmDtHxkJ7NJacGctHMRrsBIvdKTVwlT4su5nAVVJlan/PrEou+jESfw+OjjBYE5PA==}
-    peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/core-common@8.1.11':
-    resolution: {integrity: sha512-Ix0nplD4I4DrV2t9B+62jaw1baKES9UbR/Jz9LVKFF9nsua3ON0aVe73dOjMxFWBngpzBYWe+zYBTZ7aQtDH4Q==}
+  '@storybook/components@8.3.4':
+    resolution: {integrity: sha512-iQzLJd87uGbFBbYNqlrN/ABrnx3dUrL0tjPCarzglzshZoPCNOsllJeJx5TJwB9kCxSZ8zB9TTOgr7NXl+oyVA==}
     peerDependencies:
-      prettier: ^2 || ^3
-    peerDependenciesMeta:
-      prettier:
-        optional: true
+      storybook: ^8.3.4
 
-  '@storybook/core-events@8.1.11':
-    resolution: {integrity: sha512-vXaNe2KEW9BGlLrg0lzmf5cJ0xt+suPjWmEODH5JqBbrdZ67X6ApA2nb6WcxDQhykesWCuFN5gp1l+JuDOBi7A==}
-
-  '@storybook/core-events@8.2.6':
-    resolution: {integrity: sha512-bmtm7sHBExKCSGiCIyhwfHKFIsdrRQqd8ZEb/iNWsR93AxHszcf/adYAVynencdWKipw1haIWBNaiDhnsOBVPA==}
+  '@storybook/core-events@8.3.4':
+    resolution: {integrity: sha512-3/5oJN2UnlmUILXCh7SXMTa2MYZOvrjeZCm3wFomoQASU2FFzS5AxBYYnwNdtrZmn4w32uw4T7qvA0+96Utwsg==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/core-server@8.1.11':
-    resolution: {integrity: sha512-L6dzQTmR0np/kagNONvvlm6lSvF1FNc9js3vxsEEPnEypLbhx8bDZaHmuhmBpYUzKyUMpRVQTE/WgjHLuBBuxA==}
+      storybook: ^8.3.4
 
-  '@storybook/core@8.2.6':
-    resolution: {integrity: sha512-XY71g3AcpD6IiER9k9Lt+vlUMYfPIYgWekd7e0Ggzz2gJkPuLunKEdQccLGDSHf5OFAobHhrTJc7ZsvWhmDMag==}
+  '@storybook/core@8.3.4':
+    resolution: {integrity: sha512-4PZB91JJpuKfcjeOR2LXj3ABaPLLSd2P/SfYOKNCygrDstsQa/yay3/yN5Z9yi1cIG84KRr6/sUW+0x8HsGLPg==}
 
-  '@storybook/csf-plugin@8.1.11':
-    resolution: {integrity: sha512-hkA8gjFtSN/tabG0cuvmEqanMXtxPr3qTkp4UNSt1R6jBEgFHRG2y/KYLl367kDwOSFTT987ZgRfJJruU66Fvw==}
-
-  '@storybook/csf-plugin@8.2.6':
-    resolution: {integrity: sha512-USn7E/bMQYVqvFBuW6d9rKoSuCImjk0BAmc/0wIOuMQ/yQNp2Xze0m8eVkNHUIUDokyx0TXDjRjwq10Xxk16ag==}
+  '@storybook/csf-plugin@8.3.4':
+    resolution: {integrity: sha512-ZMFWYxeTN4GxCn8dyIH4roECyLDy29yv/QKM+pHM3AC5Ny2HWI35SohWao4fGBAFxPQFbR5hPN8xa6ofHPSSTg==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/csf-tools@8.1.11':
-    resolution: {integrity: sha512-6qMWAg/dBwCVIHzANM9lSHoirwqSS+wWmv+NwAs0t9S94M75IttHYxD3IyzwaSYCC5llp0EQFvtXXAuSfFbibg==}
+      storybook: ^8.3.4
 
   '@storybook/csf@0.1.11':
     resolution: {integrity: sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==}
 
-  '@storybook/csf@0.1.9':
-    resolution: {integrity: sha512-JlZ6v/iFn+iKohKGpYXnMeNeTiiAMeFoDhYnPLIC8GnyyIWqEI9wJYrOK9i9rxlJ8NZAH/ojGC/u/xVC41qSgQ==}
-
-  '@storybook/docs-mdx@3.1.0-next.0':
-    resolution: {integrity: sha512-t4syFIeSyufieNovZbLruPt2DmRKpbwL4fERCZ1MifWDRIORCKLc4NCEHy+IqvIqd71/SJV2k4B51nF7vlJfmQ==}
-
-  '@storybook/docs-tools@8.1.11':
-    resolution: {integrity: sha512-mEXtR9rS7Y+OdKtT/QG6JBGYR1L41mcDhIqhnk7RmYl9qJstVAegrCKWR53sPKFdTVOHU7dmu6k+BD+TqHpyyw==}
-
   '@storybook/global@5.0.0':
     resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
 
-  '@storybook/icons@1.2.5':
-    resolution: {integrity: sha512-m3jnuE+zmkZy6K+cdUDzAoUuCJyl0fWCAXPCji7VZCH1TzFohyvnPqhc9JMkQpanej2TOW3wWXaplPzHghcBSg==}
+  '@storybook/icons@1.2.12':
+    resolution: {integrity: sha512-UxgyK5W3/UV4VrI3dl6ajGfHM4aOqMAkFLWe2KibeQudLf6NJpDrDMSHwZj+3iKC4jFU7dkKbbtH2h/al4sW3Q==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
 
-  '@storybook/instrumenter@8.2.6':
-    resolution: {integrity: sha512-RxtpcMTUSq8/wPM6cR6EXVrPEiNuRbC71cIFVFZagOFYvnnOKwSPV+GOLPK0wxMbGB4c5/+Xe8ADefmZTvxOsA==}
+  '@storybook/instrumenter@8.3.4':
+    resolution: {integrity: sha512-jVhfNOPekOyJmta0BTkQl9Z6rgRbFHlc0eV4z1oSrzaawSlc9TFzAeDCtCP57vg3FuBX8ydDYAvyZ7s4xPpLyg==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/manager-api@8.1.11':
-    resolution: {integrity: sha512-QSgwKfAw01K9YvvZj30iGBMgQ4YaCT3vojmttuqdH5ukyXkiO7pENLJj4Y+alwUeSi0g+SJeadCI3PXySBHOGg==}
+      storybook: ^8.3.4
 
-  '@storybook/manager-api@8.2.6':
-    resolution: {integrity: sha512-uv36h/b5RhlajWtEg4cVPBYV8gZs6juux0nIE+6G9i7vt8Ild6gM9tW1KNabgZcaHFiyWJYCNWxJZoKjgUmXDg==}
+  '@storybook/manager-api@8.3.4':
+    resolution: {integrity: sha512-tBx7MBfPUrKSlD666zmVjtIvoNArwCciZiW/UJ8IWmomrTJRfFBnVvPVM2gp1lkDIzRHYmz5x9BHbYaEDNcZWQ==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/manager@8.1.11':
-    resolution: {integrity: sha512-e02y9dmxowo7cTKYm9am7UO6NOHoHy6Xi7xZf/UA932qLwFZUtk5pnwIEFaZWI3OQsRUCGhP+FL5zizU7uVZeg==}
-
-  '@storybook/node-logger@8.1.11':
-    resolution: {integrity: sha512-wdzFo7B2naGhS52L3n1qBkt5BfvQjs8uax6B741yKRpiGgeAN8nz8+qelkD25MbSukxvbPgDot7WJvsMU/iCzg==}
+      storybook: ^8.3.4
 
-  '@storybook/preview-api@8.1.11':
-    resolution: {integrity: sha512-8ZChmFV56GKppCJ0hnBd/kNTfGn2gWVq1242kuet13pbJtBpvOhyq4W01e/Yo14tAPXvgz8dSnMvWLbJx4QfhQ==}
-
-  '@storybook/preview-api@8.2.6':
-    resolution: {integrity: sha512-5vTj2ndX5ng4nDntZYe+r8UwLjCIGFymhq5/r2adAvRKL+Bo4zQDWGO7bhvGJk16do2THb2JvPz49ComW9LLZw==}
+  '@storybook/preview-api@8.3.4':
+    resolution: {integrity: sha512-/YKQ3QDVSHmtFXXCShf5w0XMlg8wkfTpdYxdGv1CKFV8DU24f3N7KWulAgeWWCWQwBzZClDa9kzxmroKlQqx3A==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/preview@8.1.11':
-    resolution: {integrity: sha512-K/9NZmjnL0D1BROkTNWNoPqgL2UaocALRSqCARmkBLgU2Rn/FuZgEclHkWlYo6pUrmLNK+bZ+XzpNMu12iTbpg==}
+      storybook: ^8.3.4
 
-  '@storybook/react-dom-shim@8.2.6':
-    resolution: {integrity: sha512-B+x8UAEQPDp1yhN3tMh09NvSL38QNfJB7PAyLgKrfE7xIAzvewq+RLW2DfGkoZCy+Zr7QSHm1p7NOgud8+sQCg==}
+  '@storybook/react-dom-shim@8.3.4':
+    resolution: {integrity: sha512-L4llDvjaAzqPx6h4ddZMh36wPr75PrI2S8bXy+flLqAeVRYnRt4WNKGuxqH0t0U6MwId9+vlCZ13JBfFuY7eQQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/react-vite@8.2.6':
-    resolution: {integrity: sha512-BpbteaIzsJZL1QN3iR7uuslrPfdtbZYXPhcU9awpfl5pW5MOQThuvl7728mwT8V7KdANeikJPgsnlETOb/afDA==}
+  '@storybook/react-vite@8.3.4':
+    resolution: {integrity: sha512-0Xm8eTH+jQ7SV4moLkPN4G6U2IDrqXPXUqsZdXaccepIMcD4G75foQFm2LOrFJuY+IMySPspKeTqf8OLskPppw==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      storybook: ^8.2.6
+      storybook: ^8.3.4
       vite: ^4.0.0 || ^5.0.0
 
-  '@storybook/react@8.2.6':
-    resolution: {integrity: sha512-awJlzfiAMrf8l9AgiLhjXEJ+HvS3VKPxNNQaRwBELGq/vigjJe656tMrhvg4OIlJXtlS+6XPshd2knLwjIWNLw==}
+  '@storybook/react@8.3.4':
+    resolution: {integrity: sha512-PA7iQL4/9X2/iLrv+AUPNtlhTHJWhDao9gQIT1Hef39FtFk+TU9lZGbv+g29R1H9V3cHP5162nG2aTu395kmbA==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
+      '@storybook/test': 8.3.4
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      storybook: ^8.2.6
+      storybook: ^8.3.4
       typescript: '>= 4.2.x'
     peerDependenciesMeta:
+      '@storybook/test':
+        optional: true
       typescript:
         optional: true
 
-  '@storybook/router@8.1.11':
-    resolution: {integrity: sha512-nU5lsBvy0L8wBYOkjagh29ztZicDATpZNYrHuavlhQ2jznmmHdJvXKYk+VrMAbthjQ6ZBqfeeMNPR1UlnqR5Rw==}
-
-  '@storybook/source-loader@8.2.6':
-    resolution: {integrity: sha512-mOVf+TJhlQywCymFMs7l604CxEZRKZRKVQojrrgU6CH6EhhLx/q6BT8tf1CakY9JO3Ey+PhUMBBCerYiDaHLcQ==}
-    peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/telemetry@8.1.11':
-    resolution: {integrity: sha512-Jqvm7HcZismKzPuebhyLECO6KjGiSk4ycbca1WUM/TUvifxCXqgoUPlHHQEEfaRdHS63/MSqtMNjLsQRLC/vNQ==}
-
-  '@storybook/test@8.2.6':
-    resolution: {integrity: sha512-nTzNxReBcMRlX1+8PNU/MuA9ArFbeQhfZXMBIwJJoHOhnNe1knYpyn1++xINxAHKOh0BBhQ0NIMoKdcGmW3V6w==}
+  '@storybook/source-loader@8.3.4':
+    resolution: {integrity: sha512-wH//LuWfa2iOmjykSqsub8M8e0EdhEUZoHUFhwBeizfYQQHaMaSEBhhAQCaWWKmdGB9lnCe1cioQ32c2IWtBIw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/theming@8.1.11':
-    resolution: {integrity: sha512-Chn/opjO6Rl1isNobutYqAH2PjKNkj09YBw/8noomk6gElSa3JbUTyaG/+JCHA6OG/9kUsqoKDb5cZmAKNq/jA==}
+  '@storybook/test@8.3.4':
+    resolution: {integrity: sha512-HRiUenitln8QPHu6DEWUg9s9cEoiGN79lMykzXzw9shaUvdEIhWCsh82YKtmB3GJPj6qcc6dZL/Aio8srxyGAg==}
     peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-    peerDependenciesMeta:
-      react:
-        optional: true
-      react-dom:
-        optional: true
+      storybook: ^8.3.4
 
-  '@storybook/theming@8.2.6':
-    resolution: {integrity: sha512-ICnYuLIVsYifVCMQljdHgrp+5vAquNybHxDGWiPeOxBicotwHF8rLhTckD2CdVQbMp0jk6r6jetvjXbFJ2MbvQ==}
+  '@storybook/theming@8.3.4':
+    resolution: {integrity: sha512-D4XVsQgTtpHEHLhwkx59aGy1GBwOedVr/mNns7hFrH8FjEpxrrWCuZQASq1ZpCl8LXlh7uvmT5sM2rOdQbGuGg==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/types@8.1.11':
-    resolution: {integrity: sha512-k9N5iRuY2+t7lVRL6xeu6diNsxO3YI3lS4Juv3RZ2K4QsE/b3yG5ElfJB8DjHDSHwRH4ORyrU71KkOCUVfvtnw==}
+      storybook: ^8.3.4
 
-  '@storybook/types@8.2.6':
-    resolution: {integrity: sha512-9Kb5+nui8M7TP/EDGwiuOAHYQPg9U6iQl0OWwgbDIYGBpldwlCwVKAoQWzXz/LlhQijULXIpe1cLvEvJN2Uwhg==}
+  '@storybook/types@8.3.4':
+    resolution: {integrity: sha512-kIyb0g8C6EizI0Mv+l6L6yjCJe9/vW3UvgsZL5BXqs8THTAfs3/+A9Q9jDEMovSIVI3EgesO79+OCEazDUHmOA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
 
-  '@storybook/vue3-vite@8.1.11':
-    resolution: {integrity: sha512-q0bqh8XEEunaTmp4YiDqM2+YZLwEIevTb5PnNe7G7f2qOiSCE1ncBDnBK717UlCd+iYr34NTztgV2/jIhz1i5w==}
+  '@storybook/vue3-vite@8.3.4':
+    resolution: {integrity: sha512-0H1tLbRd8i6L3EW8QC9bDlgPIUM5i6b7onvyyQhyIxODWRfigHi6UP9sjHfrljdvnlOtYlZT2A5QbpkugzwLjg==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
+      storybook: ^8.3.4
       vite: ^4.0.0 || ^5.0.0
 
-  '@storybook/vue3@8.1.11':
-    resolution: {integrity: sha512-xJtvfLiCOY3UqwDMd0hZdsadPm1q8dwjfM1UN2Q2ssRWNfXzww1oi+Msj902wz9zFZMYVZypfTfgrdRgWmfEjA==}
+  '@storybook/vue3@8.3.4':
+    resolution: {integrity: sha512-NNQXwidr+QjLndORWtPjXv/obsNNfJhP5Xj6vUZslrDpdIyTL3NEM+ktLK2EMw/a3zUbJMnMkyMgoWvioCNHxQ==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
-      vue: ^3.0.0
-
-  '@storybook/vue3@8.2.6':
-    resolution: {integrity: sha512-j4gMuWc1ZDzqWSdf79YswcZmcbhmbByq/6upqxwqXtjv1mHAiBnEs8bbnnylDrzg4GOvBC8w+FjArkzlFA7uXg==}
-    engines: {node: '>=18.0.0'}
-    peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.4
       vue: ^3.0.0
 
   '@swc/cli@0.3.12':
@@ -4948,34 +4507,17 @@ packages:
     resolution: {integrity: sha512-EmCsnzdvawyk4b+4JKaLLuicHcJQRZtL1zSy9AWJLiiHTbDDseYgLxfaCEfLk8v2bUe7SBXwl3n3B7OjgvH11Q==}
     hasBin: true
 
-  '@testing-library/dom@10.1.0':
-    resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==}
+  '@testing-library/dom@10.4.0':
+    resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
     engines: {node: '>=18'}
 
   '@testing-library/dom@9.3.4':
     resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==}
     engines: {node: '>=14'}
 
-  '@testing-library/jest-dom@6.4.5':
-    resolution: {integrity: sha512-AguB9yvTXmCnySBP1lWjfNNUwpbElsaQ567lt2VdGqAdHtpieLgjmcVyv1q7PMIvLbgpDdkWV5Ydv3FEejyp2A==}
+  '@testing-library/jest-dom@6.5.0':
+    resolution: {integrity: sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA==}
     engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
-    peerDependencies:
-      '@jest/globals': '>= 28'
-      '@types/bun': latest
-      '@types/jest': '>= 28'
-      jest: '>= 28'
-      vitest: '>= 0.32'
-    peerDependenciesMeta:
-      '@jest/globals':
-        optional: true
-      '@types/bun':
-        optional: true
-      '@types/jest':
-        optional: true
-      jest:
-        optional: true
-      vitest:
-        optional: true
 
   '@testing-library/user-event@14.5.2':
     resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==}
@@ -5007,6 +4549,9 @@ packages:
   '@twemoji/parser@15.0.0':
     resolution: {integrity: sha512-lh9515BNsvKSNvyUqbj5yFu83iIDQ77SwVcsN/SnEGawczhsKU6qWuogewN1GweTi5Imo5ToQ9s+nNTf97IXvg==}
 
+  '@twemoji/parser@15.1.0':
+    resolution: {integrity: sha512-3HTiSxPvkWUJ4kZeCvwyKlIwkpTUfBOk6igpBBRQni58ceQMv5YK4smkc8vX/eqOlMMNER/9qobv+Q6Q8LVrqA==}
+
   '@twemoji/parser@15.1.1':
     resolution: {integrity: sha512-CChRzIu6ngkCJOmURBlYEdX5DZSu+bBTtqR60XjBkFrmvplKW7OQsea+i8XwF4bLVlUXBO7ZmHhRPDzfQyLwwg==}
 
@@ -5046,8 +4591,11 @@ packages:
   '@types/cacheable-request@6.0.3':
     resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
 
-  '@types/color-convert@2.0.3':
-    resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==}
+  '@types/canvas-confetti@1.6.4':
+    resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==}
+
+  '@types/color-convert@2.0.4':
+    resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==}
 
   '@types/color-name@1.1.1':
     resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==}
@@ -5064,36 +4612,15 @@ packages:
   '@types/cookie@0.6.0':
     resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
 
-  '@types/cross-spawn@6.0.2':
-    resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==}
-
   '@types/debug@4.1.12':
     resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
 
-  '@types/detect-port@1.3.2':
-    resolution: {integrity: sha512-xxgAGA2SAU4111QefXPSp5eGbDm/hW6zhvYl9IeEPZEry9F4d66QAHm5qpUXjb6IsevZV/7emAEx5MhP6O192g==}
-
-  '@types/diff@5.2.1':
-    resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==}
-
   '@types/disposable-email-domains@1.0.2':
     resolution: {integrity: sha512-SDKwyYTjk3y5aZBxxc38yRecpJPjsqn57STz1bNxYYlv4k11bBe7QB8w4llXDTmQXKT1mFvgGmJv+8Zdu3YmJw==}
 
-  '@types/doctrine@0.0.3':
-    resolution: {integrity: sha512-w5jZ0ee+HaPOaX25X2/2oGR/7rgAQSYII7X7pp0m9KgBfMP7uKfMfTvcpl5Dj+eDBbpxKGiqE+flqDr6XTd2RA==}
-
   '@types/doctrine@0.0.9':
     resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==}
 
-  '@types/ejs@3.1.2':
-    resolution: {integrity: sha512-ZmiaE3wglXVWBM9fyVC17aGPkLo/UgaOjEiI2FXQfyczrCefORPxIe+2dVmnmk3zkVIbizjrlQzmPGhSYGXG5g==}
-
-  '@types/emscripten@1.39.7':
-    resolution: {integrity: sha512-tLqYV94vuqDrXh515F/FOGtBcRMTPGvVV1LzLbtYDcQmmhtpf/gLYf+hikBbQk8MzOHNz37wpFfJbYAuSn8HqA==}
-
-  '@types/escape-regexp@0.0.3':
-    resolution: {integrity: sha512-FQMYUxaf1dVeWLUzJFSvfdDugfOpDyM13p67QfyMdagxSkBa689opkr/q9uR/VWyrWrl0jAyQaSPKxX9MpAXFw==}
-
   '@types/escodegen@0.0.6':
     resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==}
 
@@ -5103,8 +4630,8 @@ packages:
   '@types/estree@0.0.51':
     resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
 
-  '@types/estree@1.0.5':
-    resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
+  '@types/estree@1.0.6':
+    resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
 
   '@types/express-serve-static-core@4.17.33':
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
@@ -5118,8 +4645,8 @@ packages:
   '@types/find-cache-dir@3.2.1':
     resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==}
 
-  '@types/fluent-ffmpeg@2.1.24':
-    resolution: {integrity: sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==}
+  '@types/fluent-ffmpeg@2.1.26':
+    resolution: {integrity: sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==}
 
   '@types/glob@7.2.0':
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
@@ -5148,8 +4675,8 @@ packages:
   '@types/istanbul-reports@3.0.1':
     resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==}
 
-  '@types/jest@29.5.12':
-    resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==}
+  '@types/jest@29.5.13':
+    resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==}
 
   '@types/js-yaml@4.0.9':
     resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==}
@@ -5220,9 +4747,6 @@ packages:
   '@types/node-fetch@2.6.11':
     resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==}
 
-  '@types/node@18.17.15':
-    resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==}
-
   '@types/node@20.11.5':
     resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==}
 
@@ -5232,8 +4756,11 @@ packages:
   '@types/node@20.9.1':
     resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==}
 
-  '@types/nodemailer@6.4.15':
-    resolution: {integrity: sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==}
+  '@types/node@22.5.5':
+    resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==}
+
+  '@types/nodemailer@6.4.16':
+    resolution: {integrity: sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==}
 
   '@types/normalize-package-data@2.4.1':
     resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -5256,15 +4783,12 @@ packages:
   '@types/pg-pool@2.0.4':
     resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==}
 
-  '@types/pg@8.11.6':
-    resolution: {integrity: sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==}
+  '@types/pg@8.11.10':
+    resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==}
 
   '@types/pg@8.6.1':
     resolution: {integrity: sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==}
 
-  '@types/pretty-hrtime@1.0.1':
-    resolution: {integrity: sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ==}
-
   '@types/prop-types@15.7.5':
     resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
 
@@ -5304,8 +4828,8 @@ packages:
   '@types/responselike@1.0.0':
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
 
-  '@types/sanitize-html@2.11.0':
-    resolution: {integrity: sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==}
+  '@types/sanitize-html@2.13.0':
+    resolution: {integrity: sha512-X31WxbvW9TjIhZZNyNBZ/p5ax4ti7qsNDBDEnH4zAgmEh35YnFD1UiS6z9Cd34kKm0LslFW0KPmTQzu/oGtsqQ==}
 
   '@types/scheduler@0.16.2':
     resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
@@ -5385,8 +4909,8 @@ packages:
   '@types/wrap-ansi@3.0.0':
     resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==}
 
-  '@types/ws@8.5.11':
-    resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==}
+  '@types/ws@8.5.12':
+    resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==}
 
   '@types/yargs-parser@21.0.0':
     resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
@@ -5397,17 +4921,6 @@ packages:
   '@types/yauzl@2.10.0':
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
 
-  '@typescript-eslint/eslint-plugin@6.11.0':
-    resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-    peerDependencies:
-      '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
-      eslint: ^7.0.0 || ^8.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-
   '@typescript-eslint/eslint-plugin@7.1.0':
     resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5430,16 +4943,6 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/parser@6.11.0':
-    resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-    peerDependencies:
-      eslint: ^7.0.0 || ^8.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-
   '@typescript-eslint/parser@7.1.0':
     resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5460,10 +4963,6 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/scope-manager@6.11.0':
-    resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-
   '@typescript-eslint/scope-manager@7.1.0':
     resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5472,16 +4971,6 @@ packages:
     resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==}
     engines: {node: ^18.18.0 || >=20.0.0}
 
-  '@typescript-eslint/type-utils@6.11.0':
-    resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-    peerDependencies:
-      eslint: ^7.0.0 || ^8.0.0
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-
   '@typescript-eslint/type-utils@7.1.0':
     resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5502,10 +4991,6 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/types@6.11.0':
-    resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-
   '@typescript-eslint/types@7.1.0':
     resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5514,15 +4999,6 @@ packages:
     resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==}
     engines: {node: ^18.18.0 || >=20.0.0}
 
-  '@typescript-eslint/typescript-estree@6.11.0':
-    resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-    peerDependencies:
-      typescript: '*'
-    peerDependenciesMeta:
-      typescript:
-        optional: true
-
   '@typescript-eslint/typescript-estree@7.1.0':
     resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5541,12 +5017,6 @@ packages:
       typescript:
         optional: true
 
-  '@typescript-eslint/utils@6.11.0':
-    resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-    peerDependencies:
-      eslint: ^7.0.0 || ^8.0.0
-
   '@typescript-eslint/utils@7.1.0':
     resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5559,10 +5029,6 @@ packages:
     peerDependencies:
       eslint: ^8.56.0
 
-  '@typescript-eslint/visitor-keys@6.11.0':
-    resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==}
-    engines: {node: ^16.0.0 || >=18.0.0}
-
   '@typescript-eslint/visitor-keys@7.1.0':
     resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5574,8 +5040,8 @@ packages:
   '@ungap/structured-clone@1.2.0':
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
 
-  '@vitejs/plugin-vue@5.1.0':
-    resolution: {integrity: sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==}
+  '@vitejs/plugin-vue@5.1.4':
+    resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==}
     engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
       vite: ^5.0.0
@@ -5589,6 +5055,15 @@ packages:
   '@vitest/expect@1.6.0':
     resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
 
+  '@vitest/expect@2.0.5':
+    resolution: {integrity: sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA==}
+
+  '@vitest/pretty-format@2.0.5':
+    resolution: {integrity: sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==}
+
+  '@vitest/pretty-format@2.1.1':
+    resolution: {integrity: sha512-SjxPFOtuINDUW8/UkElJYQSFtnWX7tMksSGW0vfjxMneFqxVr8YJ979QpMbDW7g+BIiq88RAGDjf7en6rvLPPQ==}
+
   '@vitest/runner@1.6.0':
     resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
 
@@ -5598,48 +5073,69 @@ packages:
   '@vitest/spy@1.6.0':
     resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==}
 
+  '@vitest/spy@2.0.5':
+    resolution: {integrity: sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA==}
+
   '@vitest/utils@1.6.0':
     resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==}
 
+  '@vitest/utils@2.0.5':
+    resolution: {integrity: sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ==}
+
+  '@vitest/utils@2.1.1':
+    resolution: {integrity: sha512-Y6Q9TsI+qJ2CC0ZKj6VBb+T8UPz593N113nnUykqwANqhgf3QkZeHFlusgKLTqrnVHbj/XDKZcDHol+dxVT+rQ==}
+
   '@volar/language-core@2.2.0':
     resolution: {integrity: sha512-a8WG9+4OdeNDW4ywABZIM6S6UN7em8uIlM/BZ2pWQUYrVmX+m8sj/X+QadvO+Li/t/LjAqbWJQtVgxdpEWLALQ==}
 
-  '@volar/language-core@2.4.0-alpha.18':
-    resolution: {integrity: sha512-JAYeJvYQQROmVRtSBIczaPjP3DX4QW1fOqW1Ebs0d3Y3EwSNRglz03dSv0Dm61dzd0Yx3WgTW3hndDnTQqgmyg==}
+  '@volar/language-core@2.4.5':
+    resolution: {integrity: sha512-F4tA0DCO5Q1F5mScHmca0umsi2ufKULAnMOVBfMsZdT4myhVl4WdKRwCaKcfOkIEuyrAVvtq1ESBdZ+rSyLVww==}
 
   '@volar/source-map@2.2.0':
     resolution: {integrity: sha512-HQlPRlHOVqCCHK8wI76ZldHkEwKsjp7E6idUc36Ekni+KJDNrqgSqPvyHQixybXPHNU7CI9Uxd9/IkxO7LuNBw==}
 
-  '@volar/source-map@2.4.0-alpha.18':
-    resolution: {integrity: sha512-MTeCV9MUwwsH0sNFiZwKtFrrVZUK6p8ioZs3xFzHc2cvDXHWlYN3bChdQtwKX+FY2HG6H3CfAu1pKijolzIQ8g==}
+  '@volar/source-map@2.4.5':
+    resolution: {integrity: sha512-varwD7RaKE2J/Z+Zu6j3mNNJbNT394qIxXwdvz/4ao/vxOfyClZpSDtLKkwWmecinkOVos5+PWkWraelfMLfpw==}
 
   '@volar/typescript@2.2.0':
     resolution: {integrity: sha512-wC6l4zLiiCLxF+FGaHCbWlQYf4vMsnRxYhcI6WgvaNppOD6r1g+Ef1RKRJUApALWU46Yy/JDU/TbdV6w/X6Liw==}
 
-  '@volar/typescript@2.4.0-alpha.18':
-    resolution: {integrity: sha512-sXh5Y8sqGUkgxpMWUGvRXggxYHAVxg0Pa1C42lQZuPDrW6vHJPR0VCK8Sr7WJsAW530HuNQT/ZIskmXtxjybMQ==}
-
-  '@vue/compiler-core@3.4.31':
-    resolution: {integrity: sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==}
+  '@volar/typescript@2.4.5':
+    resolution: {integrity: sha512-mcT1mHvLljAEtHviVcBuOyAwwMKz1ibXTi5uYtP/pf4XxoAzpdkQ+Br2IC0NPCvLCbjPZmbf3I0udndkfB1CDg==}
 
   '@vue/compiler-core@3.4.37':
     resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==}
 
+  '@vue/compiler-core@3.5.10':
+    resolution: {integrity: sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==}
+
+  '@vue/compiler-core@3.5.11':
+    resolution: {integrity: sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==}
+
   '@vue/compiler-dom@3.4.37':
     resolution: {integrity: sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==}
 
+  '@vue/compiler-dom@3.5.10':
+    resolution: {integrity: sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==}
+
+  '@vue/compiler-dom@3.5.11':
+    resolution: {integrity: sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==}
+
   '@vue/compiler-sfc@3.4.37':
     resolution: {integrity: sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==}
 
+  '@vue/compiler-sfc@3.5.11':
+    resolution: {integrity: sha512-gsbBtT4N9ANXXepprle+X9YLg2htQk1sqH/qGJ/EApl+dgpUBdTv3yP7YlR535uHZY3n6XaR0/bKo0BgwwDniw==}
+
   '@vue/compiler-ssr@3.4.37':
     resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==}
 
+  '@vue/compiler-ssr@3.5.11':
+    resolution: {integrity: sha512-P4+GPjOuC2aFTk1Z4WANvEhyOykcvEd5bIj2KVNGKGfM745LaXGr++5njpdBTzVz5pZifdlR1kpYSJJpIlSePA==}
+
   '@vue/compiler-vue2@2.7.16':
     resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
 
-  '@vue/devtools-api@6.6.1':
-    resolution: {integrity: sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA==}
-
   '@vue/language-core@2.0.16':
     resolution: {integrity: sha512-Bc2sexRH99pznOph8mLw2BlRZ9edm7tW51kcBXgx8adAoOcZUWJj3UNSsdQ6H9Y8meGz7BoazVrVo/jUukIsPw==}
     peerDependencies:
@@ -5648,8 +5144,8 @@ packages:
       typescript:
         optional: true
 
-  '@vue/language-core@2.0.29':
-    resolution: {integrity: sha512-o2qz9JPjhdoVj8D2+9bDXbaI4q2uZTHQA/dbyZT4Bj1FR9viZxDJnLcKVHfxdn6wsOzRgpqIzJEEmSSvgMvDTQ==}
+  '@vue/language-core@2.1.6':
+    resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
@@ -5659,23 +5155,40 @@ packages:
   '@vue/reactivity@3.4.37':
     resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==}
 
+  '@vue/reactivity@3.5.11':
+    resolution: {integrity: sha512-Nqo5VZEn8MJWlCce8XoyVqHZbd5P2NH+yuAaFzuNSR96I+y1cnuUiq7xfSG+kyvLSiWmaHTKP1r3OZY4mMD50w==}
+
   '@vue/runtime-core@3.4.37':
     resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==}
 
+  '@vue/runtime-core@3.5.11':
+    resolution: {integrity: sha512-7PsxFGqwfDhfhh0OcDWBG1DaIQIVOLgkwA5q6MtkPiDFjp5gohVnJEahSktwSFLq7R5PtxDKy6WKURVN1UDbzA==}
+
   '@vue/runtime-dom@3.4.37':
     resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==}
 
+  '@vue/runtime-dom@3.5.11':
+    resolution: {integrity: sha512-GNghjecT6IrGf0UhuYmpgaOlN7kxzQBhxWEn08c/SQDxv1yy4IXI1bn81JgEpQ4IXjRxWtPyI8x0/7TF5rPfYQ==}
+
   '@vue/server-renderer@3.4.37':
     resolution: {integrity: sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==}
     peerDependencies:
       vue: 3.4.37
 
-  '@vue/shared@3.4.31':
-    resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==}
+  '@vue/server-renderer@3.5.11':
+    resolution: {integrity: sha512-cVOwYBxR7Wb1B1FoxYvtjJD8X/9E5nlH4VSkJy2uMA1MzYNdzAAB//l8nrmN9py/4aP+3NjWukf9PZ3TeWULaA==}
+    peerDependencies:
+      vue: 3.5.11
 
   '@vue/shared@3.4.37':
     resolution: {integrity: sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==}
 
+  '@vue/shared@3.5.10':
+    resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==}
+
+  '@vue/shared@3.5.11':
+    resolution: {integrity: sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==}
+
   '@vue/test-utils@2.4.1':
     resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
     peerDependencies:
@@ -5688,20 +5201,6 @@ packages:
   '@webgpu/types@0.1.30':
     resolution: {integrity: sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==}
 
-  '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15':
-    resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
-    engines: {node: '>=14.15.0'}
-    peerDependencies:
-      esbuild: '>=0.10.0'
-
-  '@yarnpkg/fslib@2.10.3':
-    resolution: {integrity: sha512-41H+Ga78xT9sHvWLlFOZLIhtU6mTGZ20pZ29EiZa97vnxdohJD2AF42rCoAoWfqUz486xY6fhjMH+DYEM9r14A==}
-    engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'}
-
-  '@yarnpkg/libzip@2.3.0':
-    resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==}
-    engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'}
-
   abbrev@1.1.1:
     resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
 
@@ -5753,10 +5252,6 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
-  address@1.2.2:
-    resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==}
-    engines: {node: '>= 10.0.0'}
-
   adm-zip@0.5.10:
     resolution: {integrity: sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==}
     engines: {node: '>=6.0'}
@@ -5794,14 +5289,6 @@ packages:
       ajv:
         optional: true
 
-  ajv-formats@2.1.1:
-    resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
-    peerDependencies:
-      ajv: ^8.0.0
-    peerDependenciesMeta:
-      ajv:
-        optional: true
-
   ajv-formats@3.0.1:
     resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
     peerDependencies:
@@ -5861,9 +5348,6 @@ packages:
     resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
     engines: {node: '>= 8'}
 
-  app-root-dir@1.0.2:
-    resolution: {integrity: sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g==}
-
   app-root-path@3.1.0:
     resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==}
     engines: {node: '>= 6.0.0'}
@@ -5885,9 +5369,6 @@ packages:
     resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
     engines: {node: '>= 14'}
 
-  archy@1.0.0:
-    resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
-
   are-we-there-yet@2.0.0:
     resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
     engines: {node: '>=10'}
@@ -5911,19 +5392,23 @@ packages:
   array-buffer-byte-length@1.0.0:
     resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==}
 
+  array-buffer-byte-length@1.0.1:
+    resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==}
+    engines: {node: '>= 0.4'}
+
   array-flatten@1.1.1:
     resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
 
-  array-includes@3.1.7:
-    resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==}
+  array-includes@3.1.8:
+    resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
     engines: {node: '>= 0.4'}
 
   array-union@2.1.0:
     resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
     engines: {node: '>=8'}
 
-  array.prototype.findlastindex@1.2.3:
-    resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==}
+  array.prototype.findlastindex@1.2.5:
+    resolution: {integrity: sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==}
     engines: {node: '>= 0.4'}
 
   array.prototype.flat@1.3.2:
@@ -5938,6 +5423,10 @@ packages:
     resolution: {integrity: sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==}
     engines: {node: '>= 0.4'}
 
+  arraybuffer.prototype.slice@1.0.3:
+    resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==}
+    engines: {node: '>= 0.4'}
+
   arrify@1.0.1:
     resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==}
     engines: {node: '>=0.10.0'}
@@ -5962,12 +5451,13 @@ packages:
     resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
     engines: {node: '>=0.8'}
 
-  assert@2.1.0:
-    resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
-
   assertion-error@1.1.0:
     resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
 
+  assertion-error@2.0.1:
+    resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+    engines: {node: '>=12'}
+
   ast-types@0.16.1:
     resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
     engines: {node: '>=4'}
@@ -5976,8 +5466,8 @@ packages:
     resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
     engines: {node: '>=8'}
 
-  astring@1.8.6:
-    resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
+  astring@1.9.0:
+    resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
     hasBin: true
 
   async-mutex@0.5.0:
@@ -6004,8 +5494,12 @@ packages:
     resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
     engines: {node: '>= 0.4'}
 
-  avvio@8.3.0:
-    resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==}
+  available-typed-arrays@1.0.7:
+    resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+    engines: {node: '>= 0.4'}
+
+  avvio@9.0.0:
+    resolution: {integrity: sha512-UbYrOXgE/I+knFG+3kJr9AgC7uNo8DG+FGGODpH9Bj1O1kL/QDjBXnTem9leD3VdQKtaHjV3O85DQ7hHh4IIHw==}
 
   aws-sdk-client-mock@4.0.1:
     resolution: {integrity: sha512-yD2mmgy73Xce097G5hIpr1k7j50qzvJ49/+6osGZiCyk4m6cwhb+2x7kKFY1gEMwTzaS8+m8fXv9SB29SkRYyQ==}
@@ -6019,17 +5513,12 @@ packages:
   axios@0.24.0:
     resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
 
-  axios@1.6.2:
-    resolution: {integrity: sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==}
+  axios@1.7.7:
+    resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==}
 
   b4a@1.6.4:
     resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
 
-  babel-core@7.0.0-bridge.0:
-    resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   babel-jest@29.7.0:
     resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -6044,21 +5533,6 @@ packages:
     resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
 
-  babel-plugin-polyfill-corejs2@0.4.11:
-    resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==}
-    peerDependencies:
-      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
-
-  babel-plugin-polyfill-corejs3@0.10.4:
-    resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==}
-    peerDependencies:
-      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
-
-  babel-plugin-polyfill-regenerator@0.6.2:
-    resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==}
-    peerDependencies:
-      '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
-
   babel-preset-current-node-syntax@1.0.1:
     resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==}
     peerDependencies:
@@ -6093,10 +5567,6 @@ packages:
     resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==}
     engines: {node: '>=12.0.0'}
 
-  big-integer@1.6.51:
-    resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
-    engines: {node: '>=0.6'}
-
   bin-check@4.1.0:
     resolution: {integrity: sha512-b6weQyEUKsDGFlACWSIOfveEnImkJyK/FGW6FAG42loyoquvjdtOIqO6yBFzHyqyVVhNgNkQxxx09SFLK28YnA==}
     engines: {node: '>=4'}
@@ -6113,9 +5583,6 @@ packages:
     resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
     engines: {node: '>=8'}
 
-  bl@4.1.0:
-    resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
-
   blob-util@2.0.2:
     resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==}
 
@@ -6128,8 +5595,8 @@ packages:
   bn.js@4.12.0:
     resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==}
 
-  body-parser@1.20.2:
-    resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
+  body-parser@1.20.3:
+    resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
 
   boolbase@1.0.0:
@@ -6138,10 +5605,6 @@ packages:
   bowser@2.11.0:
     resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
 
-  bplist-parser@0.2.0:
-    resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==}
-    engines: {node: '>= 5.10.0'}
-
   brace-expansion@1.1.11:
     resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
 
@@ -6205,8 +5668,8 @@ packages:
     resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
     engines: {node: '>=6.14.2'}
 
-  bullmq@5.10.4:
-    resolution: {integrity: sha512-YEssEbWBbPXvSW2YMjIBKZdkIPZsOaTGWo1y2wpCFv/wUY+tRLKiSVuHgv09x0QEieybx844f9//UWuarG1JHg==}
+  bullmq@5.15.0:
+    resolution: {integrity: sha512-h53shVjx8s6wxYGtUfzAfENpSP7N5T0D4PMTvbZncozLjb8yUKhopfpa7PmcpQfq7SSO9dm/OZ9XQuGOCSGNug==}
 
   buraha@0.0.1:
     resolution: {integrity: sha512-G563A0mTbzknm2jDaNxfZuNKIdeArs8T+XQN6t+KbmgnOoevXSXhKDkyf8Md/36Jrx99ikwbCag37VGe3myExQ==}
@@ -6215,10 +5678,6 @@ packages:
     resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
     engines: {node: '>=10.16.0'}
 
-  bytes@3.0.0:
-    resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
-    engines: {node: '>= 0.8'}
-
   bytes@3.1.2:
     resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
     engines: {node: '>= 0.8'}
@@ -6258,6 +5717,10 @@ packages:
   call-bind@1.0.2:
     resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
 
+  call-bind@1.0.7:
+    resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
+    engines: {node: '>= 0.4'}
+
   call-me-maybe@1.0.2:
     resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==}
 
@@ -6306,6 +5769,10 @@ packages:
     resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==}
     engines: {node: '>=4'}
 
+  chai@5.1.1:
+    resolution: {integrity: sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==}
+    engines: {node: '>=12'}
+
   chalk-template@1.1.0:
     resolution: {integrity: sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==}
     engines: {node: '>=14.16'}
@@ -6330,14 +5797,20 @@ packages:
     resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
     engines: {node: '>=10'}
 
+  character-entities-html4@2.1.0:
+    resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
+
+  character-entities-legacy@3.0.0:
+    resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==}
+
   character-entities@2.0.2:
     resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
 
   character-parser@2.2.0:
     resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
 
-  chart.js@4.4.3:
-    resolution: {integrity: sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==}
+  chart.js@4.4.4:
+    resolution: {integrity: sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==}
     engines: {pnpm: '>=8'}
 
   chartjs-adapter-date-fns@3.0.0:
@@ -6364,6 +5837,10 @@ packages:
   check-error@1.0.3:
     resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
 
+  check-error@2.1.1:
+    resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==}
+    engines: {node: '>= 16'}
+
   check-more-types@2.24.0:
     resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==}
     engines: {node: '>= 0.8.0'}
@@ -6371,6 +5848,10 @@ packages:
   cheerio-select@2.1.0:
     resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
 
+  cheerio@1.0.0:
+    resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==}
+    engines: {node: '>=18.17'}
+
   cheerio@1.0.0-rc.12:
     resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==}
     engines: {node: '>= 6'}
@@ -6386,8 +5867,8 @@ packages:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
     engines: {node: '>=10'}
 
-  chromatic@11.5.6:
-    resolution: {integrity: sha512-ycX/hlZLs69BltwwBNsEXr+As6x5/0rlwp6W/CiHMZ3tpm7dmkd+hQCsb8JGHb1h49W3qPOKQ/Lh9evqcJ1yeQ==}
+  chromatic@11.11.0:
+    resolution: {integrity: sha512-mwmYsNMsZlRLtlfFUEtac5zhoVRhc+O/lsuMdOpwkiDQiKX6WdSNIhic+dkLenfuzao2r18s50nphcOgFoatBg==}
     hasBin: true
     peerDependencies:
       '@chromatic-com/cypress': ^0.*.* || ^1.0.0
@@ -6448,17 +5929,9 @@ packages:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
 
-  clone-deep@4.0.1:
-    resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==}
-    engines: {node: '>=6'}
-
   clone-response@1.0.3:
     resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
 
-  clone@1.0.4:
-    resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
-    engines: {node: '>=0.8'}
-
   cluster-key-slot@1.1.2:
     resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
     engines: {node: '>=0.10.0'}
@@ -6508,10 +5981,17 @@ packages:
     resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
     engines: {node: '>= 0.8'}
 
+  comma-separated-tokens@2.0.3:
+    resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
+
   commander@10.0.1:
     resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
     engines: {node: '>=14'}
 
+  commander@12.1.0:
+    resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
+    engines: {node: '>=18'}
+
   commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
 
@@ -6545,14 +6025,6 @@ packages:
     resolution: {integrity: sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==}
     engines: {node: '>= 14'}
 
-  compressible@2.0.18:
-    resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==}
-    engines: {node: '>= 0.6'}
-
-  compression@1.7.4:
-    resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==}
-    engines: {node: '>= 0.8.0'}
-
   computeds@0.0.1:
     resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
 
@@ -6601,9 +6073,6 @@ packages:
     resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
     engines: {node: '>= 0.6'}
 
-  core-js-compat@3.37.1:
-    resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==}
-
   core-js@3.29.1:
     resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==}
 
@@ -6635,8 +6104,8 @@ packages:
     resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==}
     engines: {node: '>=12.0.0'}
 
-  cropperjs@2.0.0-rc.1:
-    resolution: {integrity: sha512-Y9ciurIuK6G1vy0ErHC8Gt6wHWvsHWJ5fgE60GL6vsuF2WzHwDpH7F1yof40XAEheeSN4v3rD09D1VZ7kiiSOA==}
+  cropperjs@2.0.0-rc.2:
+    resolution: {integrity: sha512-BTuz+UeZphGOEnBCuQiNT4rk1uFfKJaKmTgoH9XU7Q8IMkLdodW7YPWINmXJXwWMt1nXiKze5qKADVbz9xtVFg==}
 
   cross-env@7.0.3:
     resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
@@ -6656,10 +6125,6 @@ packages:
     resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
     engines: {node: '>= 8'}
 
-  crypto-random-string@4.0.0:
-    resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==}
-    engines: {node: '>=12'}
-
   css-declaration-sorter@7.2.0:
     resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==}
     engines: {node: ^14 || ^16 || >=18}
@@ -6721,8 +6186,13 @@ packages:
   cwise-compiler@1.1.3:
     resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==}
 
-  cypress@13.13.1:
-    resolution: {integrity: sha512-8F9UjL5MDUdgC/S5hr8CGLHbS5gGht5UOV184qc2pFny43fnkoaKxlzH/U6//zmGu/xRTaKimNfjknLT8+UDFg==}
+  cypress@13.14.2:
+    resolution: {integrity: sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==}
+    engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
+    hasBin: true
+
+  cypress@13.15.0:
+    resolution: {integrity: sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==}
     engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
     hasBin: true
 
@@ -6741,6 +6211,18 @@ packages:
     resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
     engines: {node: '>=18'}
 
+  data-view-buffer@1.0.1:
+    resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==}
+    engines: {node: '>= 0.4'}
+
+  data-view-byte-length@1.0.1:
+    resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==}
+    engines: {node: '>= 0.4'}
+
+  data-view-byte-offset@1.0.0:
+    resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==}
+    engines: {node: '>= 0.4'}
+
   date-fns@2.30.0:
     resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
     engines: {node: '>=0.11'}
@@ -6785,6 +6267,15 @@ packages:
       supports-color:
         optional: true
 
+  debug@4.3.7:
+    resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
   decamelize-keys@1.1.1:
     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
     engines: {node: '>=0.10.0'}
@@ -6826,6 +6317,10 @@ packages:
     resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
     engines: {node: '>=6'}
 
+  deep-eql@5.0.2:
+    resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+    engines: {node: '>=6'}
+
   deep-equal@2.2.0:
     resolution: {integrity: sha512-RdpzE0Hv4lhowpIUKKMJfeH6C1pXdtT1/it80ubgWqwI3qpuxUBpC1S4hnHg+zjnuOoDkzUtUCEEkG+XG5l3Mw==}
 
@@ -6836,17 +6331,14 @@ packages:
     resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
     engines: {node: '>=0.10.0'}
 
-  default-browser-id@3.0.0:
-    resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
-    engines: {node: '>=12'}
-
-  defaults@1.0.4:
-    resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
-
   defer-to-connect@2.0.1:
     resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==}
     engines: {node: '>=10'}
 
+  define-data-property@1.1.4:
+    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+    engines: {node: '>= 0.4'}
+
   define-lazy-prop@2.0.0:
     resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
     engines: {node: '>=8'}
@@ -6855,8 +6347,9 @@ packages:
     resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
     engines: {node: '>= 0.4'}
 
-  defu@6.1.4:
-    resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+  define-properties@1.2.1:
+    resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+    engines: {node: '>= 0.4'}
 
   delayed-stream@1.0.0:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
@@ -6881,10 +6374,6 @@ packages:
     resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
 
-  detect-indent@6.1.0:
-    resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
-    engines: {node: '>=8'}
-
   detect-libc@2.0.3:
     resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
     engines: {node: '>=8'}
@@ -6893,14 +6382,6 @@ packages:
     resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
     engines: {node: '>=8'}
 
-  detect-package-manager@2.0.1:
-    resolution: {integrity: sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==}
-    engines: {node: '>=12'}
-
-  detect-port@1.5.1:
-    resolution: {integrity: sha512-aBzdj76lueB6uUst5iAs7+0H/oOjqI5D16XUWxlWMIMROhcM0rfsNVk93zTngq1dDNpoXRr++Sus7ETAExppAQ==}
-    hasBin: true
-
   devlop@1.1.0:
     resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
 
@@ -6946,22 +6427,35 @@ packages:
   dom-accessibility-api@0.6.3:
     resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
 
+  dom-serializer@1.4.1:
+    resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==}
+
   dom-serializer@2.0.0:
     resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
 
   domelementtype@2.3.0:
     resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
 
+  domhandler@3.3.0:
+    resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==}
+    engines: {node: '>= 4'}
+
+  domhandler@4.3.1:
+    resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==}
+    engines: {node: '>= 4'}
+
   domhandler@5.0.3:
     resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
     engines: {node: '>= 4'}
 
+  domutils@2.8.0:
+    resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
+
   domutils@3.0.1:
     resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==}
 
-  dotenv-expand@10.0.0:
-    resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
-    engines: {node: '>=12'}
+  domutils@3.1.0:
+    resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==}
 
   dotenv@16.0.3:
     resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
@@ -7008,13 +6502,17 @@ packages:
   emoji-regex@9.2.2:
     resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
 
-  encode-utf8@1.0.3:
-    resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
-
   encodeurl@1.0.2:
     resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
     engines: {node: '>= 0.8'}
 
+  encodeurl@2.0.0:
+    resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+    engines: {node: '>= 0.8'}
+
+  encoding-sniffer@0.2.0:
+    resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==}
+
   encoding@0.1.13:
     resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
 
@@ -7040,11 +6538,6 @@ packages:
     resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
     engines: {node: '>=6'}
 
-  envinfo@7.8.1:
-    resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==}
-    engines: {node: '>=4'}
-    hasBin: true
-
   err-code@2.0.3:
     resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
 
@@ -7055,19 +6548,42 @@ packages:
     resolution: {integrity: sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==}
     engines: {node: '>= 0.4'}
 
+  es-abstract@1.23.3:
+    resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==}
+    engines: {node: '>= 0.4'}
+
+  es-define-property@1.0.0:
+    resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
+    engines: {node: '>= 0.4'}
+
+  es-errors@1.3.0:
+    resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+    engines: {node: '>= 0.4'}
+
   es-get-iterator@1.1.3:
     resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==}
 
   es-module-lexer@1.5.4:
     resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
 
+  es-object-atoms@1.0.0:
+    resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
+    engines: {node: '>= 0.4'}
+
   es-set-tostringtag@2.0.1:
     resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==}
     engines: {node: '>= 0.4'}
 
+  es-set-tostringtag@2.0.3:
+    resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==}
+    engines: {node: '>= 0.4'}
+
   es-shim-unscopables@1.0.0:
     resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==}
 
+  es-shim-unscopables@1.0.2:
+    resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==}
+
   es-to-primitive@1.2.1:
     resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==}
     engines: {node: '>= 0.4'}
@@ -7078,9 +6594,6 @@ packages:
   es6-promisify@5.0.0:
     resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==}
 
-  esbuild-plugin-alias@0.2.1:
-    resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==}
-
   esbuild-register@3.5.0:
     resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==}
     peerDependencies:
@@ -7106,10 +6619,19 @@ packages:
     engines: {node: '>=18'}
     hasBin: true
 
+  esbuild@0.23.1:
+    resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==}
+    engines: {node: '>=18'}
+    hasBin: true
+
   escalade@3.1.1:
     resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
     engines: {node: '>=6'}
 
+  escape-goat@3.0.0:
+    resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==}
+    engines: {node: '>=10'}
+
   escape-html@1.0.3:
     resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
 
@@ -7144,8 +6666,29 @@ packages:
   eslint-import-resolver-node@0.3.9:
     resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
 
-  eslint-module-utils@2.8.0:
-    resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+  eslint-module-utils@2.11.0:
+    resolution: {integrity: sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: '*'
+      eslint-import-resolver-node: '*'
+      eslint-import-resolver-typescript: '*'
+      eslint-import-resolver-webpack: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+      eslint:
+        optional: true
+      eslint-import-resolver-node:
+        optional: true
+      eslint-import-resolver-typescript:
+        optional: true
+      eslint-import-resolver-webpack:
+        optional: true
+
+  eslint-module-utils@2.12.0:
+    resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==}
     engines: {node: '>=4'}
     peerDependencies:
       '@typescript-eslint/parser': '*'
@@ -7165,8 +6708,8 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
 
-  eslint-plugin-import@2.29.1:
-    resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+  eslint-plugin-import@2.30.0:
+    resolution: {integrity: sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==}
     engines: {node: '>=4'}
     peerDependencies:
       '@typescript-eslint/parser': '*'
@@ -7175,12 +6718,28 @@ packages:
       '@typescript-eslint/parser':
         optional: true
 
+  eslint-plugin-import@2.31.0:
+    resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+
   eslint-plugin-vue@9.27.0:
     resolution: {integrity: sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
 
+  eslint-plugin-vue@9.28.0:
+    resolution: {integrity: sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==}
+    engines: {node: ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
+
   eslint-rule-docs@1.1.235:
     resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==}
 
@@ -7200,6 +6759,16 @@ packages:
     resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
 
+  eslint@9.11.0:
+    resolution: {integrity: sha512-yVS6XODx+tMFMDFcG4+Hlh+qG7RM6cCJXtQhCKLSsr3XkLvWggHjCqjfh0XsPPnt1c56oaT6PMgW9XWQQjdHXA==}
+    engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+    hasBin: true
+    peerDependencies:
+      jiti: '*'
+    peerDependenciesMeta:
+      jiti:
+        optional: true
+
   eslint@9.8.0:
     resolution: {integrity: sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==}
     engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7284,8 +6853,8 @@ packages:
     resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
     engines: {node: '>=16.17'}
 
-  execa@9.3.0:
-    resolution: {integrity: sha512-l6JFbqnHEadBoVAVpN5dl2yCyfX28WoBAGaoQcNmLLSedOxTxcn2Qa83s8I/PA5i56vWru2OHOtrwF7Om2vqlg==}
+  execa@9.4.0:
+    resolution: {integrity: sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==}
     engines: {node: ^18.19.0 || >=20.5.0}
 
   executable@4.1.1:
@@ -7303,8 +6872,8 @@ packages:
   exponential-backoff@3.1.1:
     resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==}
 
-  express@4.19.2:
-    resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
+  express@4.21.0:
+    resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==}
     engines: {node: '>= 0.10.0'}
 
   ext-list@2.2.2:
@@ -7327,8 +6896,8 @@ packages:
     resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==}
     engines: {'0': node >=0.6.0}
 
-  fast-content-type-parse@1.1.0:
-    resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==}
+  fast-content-type-parse@2.0.0:
+    resolution: {integrity: sha512-fCqg/6Sps8tqk8p+kqyKqYfOF0VjPNYrqpLiqNl0RBKmD80B080AJWVV6EkSkscjToNExcXg1+Mfzftrx6+iSA==}
 
   fast-decode-uri-component@1.0.1:
     resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==}
@@ -7346,8 +6915,8 @@ packages:
   fast-json-stable-stringify@2.1.0:
     resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
 
-  fast-json-stringify@5.8.0:
-    resolution: {integrity: sha512-VVwK8CFMSALIvt14U8AvrSzQAwN/0vaVRiFFUVlpnXSnDGrSkOAO5MtzyN8oQNjLd5AqTW5OZRgyjoNuAuR3jQ==}
+  fast-json-stringify@6.0.0:
+    resolution: {integrity: sha512-FGMKZwniMTgZh7zQp9b6XnBVxUmKVahQLQeRQHqwYmPDqDhcEKZ3BaQsxelFFI5PY7nN71OEeiL47/zUWcYe1A==}
 
   fast-levenshtein@2.0.6:
     resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
@@ -7362,8 +6931,8 @@ packages:
   fast-safe-stringify@2.1.1:
     resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
 
-  fast-uri@2.2.0:
-    resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==}
+  fast-uri@2.4.0:
+    resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==}
 
   fast-uri@3.0.1:
     resolution: {integrity: sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==}
@@ -7372,15 +6941,22 @@ packages:
     resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==}
     hasBin: true
 
-  fastify-plugin@4.5.0:
-    resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==}
+  fast-xml-parser@4.5.0:
+    resolution: {integrity: sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==}
+    hasBin: true
+
+  fastify-plugin@4.5.1:
+    resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
 
-  fastify-raw-body@4.3.0:
-    resolution: {integrity: sha512-F4o8ZIMVx4YoxGfwrZys6wyjl40gF3Yv6AWWRy62ozFAyZBSS831/uyyCAqKYw3tR73g180ryG98yih6To1PUQ==}
+  fastify-plugin@5.0.0:
+    resolution: {integrity: sha512-0725fmH/yYi8ugsjszLci+lLnGBK6cG+WSxM7edY2OXJEU7gr2JiGBoieL2h9mhTych1vFsEfXsAsGGDJ/Rd5w==}
+
+  fastify-raw-body@5.0.0:
+    resolution: {integrity: sha512-2qfoaQ3BQDhZ1gtbkKZd6n0kKxJISJGM6u/skD9ljdWItAscjXrtZ1lnjr7PavmXX9j4EyCPmBDiIsLn07d5vA==}
     engines: {node: '>= 10'}
 
-  fastify@4.28.1:
-    resolution: {integrity: sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==}
+  fastify@5.0.0:
+    resolution: {integrity: sha512-Qe4dU+zGOzg7vXjw4EvcuyIbNnMwTmcuOhlOrOJsgwzvjEZmsM/IeHulgJk+r46STjdJS/ZJbxO8N70ODXDMEQ==}
 
   fastq@1.17.1:
     resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
@@ -7388,9 +6964,6 @@ packages:
   fb-watchman@2.0.2:
     resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
 
-  fd-package-json@1.2.0:
-    resolution: {integrity: sha512-45LSPmWf+gC5tdCQMNH4s9Sr00bIkiD9aN7dc5hqkrEw1geRYyDQS1v1oMHAW3ysfxfndqGsrDREHHjNNbKUfA==}
-
   fd-slicer@1.1.0:
     resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
 
@@ -7402,9 +6975,6 @@ packages:
     resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
     engines: {node: ^12.20 || >= 14.13}
 
-  fetch-retry@5.0.4:
-    resolution: {integrity: sha512-LXcdgpdcVedccGg0AZqg+S8lX/FCdwXD92WNZ5k5qsb0irRhSFsBOpcJt7oevyqT2/C2nEE0zSFNdBEpj3YOSw==}
-
   figures@3.2.0:
     resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
     engines: {node: '>=8'}
@@ -7417,15 +6987,12 @@ packages:
     resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
     engines: {node: '>=16.0.0'}
 
-  file-system-cache@2.3.0:
-    resolution: {integrity: sha512-l4DMNdsIPsVnKrgEXbJwDJsA5mB8rGwHYERMgqQx/xAUtChPJMre1bXBzDEqqVbWv9AIbFezXMxeEkZDSrXUOQ==}
-
   file-type@17.1.6:
     resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
-  file-type@19.3.0:
-    resolution: {integrity: sha512-mROwiKLZf/Kwa/2Rol+OOZQn1eyTkPB3ZTwC0ExY6OLFCbgxHYZvBm7xI77NvfZFMKBsmuXfmLJnD4eEftEhrA==}
+  file-type@19.5.0:
+    resolution: {integrity: sha512-dMuq6WWnP6BpQY0zYJNpTtQWgeCImSMG0BTIzUBXvxbwc1HWP/E7AE4UWU9XSCOPGJuOHda0HpDnwM2FW+d90A==}
     engines: {node: '>=18'}
 
   filelist@1.0.4:
@@ -7447,29 +7014,21 @@ packages:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
 
-  finalhandler@1.2.0:
-    resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+  finalhandler@1.3.1:
+    resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
     engines: {node: '>= 0.8'}
 
-  find-cache-dir@2.1.0:
-    resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==}
-    engines: {node: '>=6'}
-
   find-cache-dir@3.3.2:
     resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
     engines: {node: '>=8'}
 
-  find-my-way@8.2.0:
-    resolution: {integrity: sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==}
+  find-my-way@9.0.1:
+    resolution: {integrity: sha512-/5NN/R0pFWuff16TMajeKt2JyiW+/OE8nOO8vo1DwZTxLaIURb7lcBYPIgRPh61yCNh9l8voeKwcrkUzmB00vw==}
     engines: {node: '>=14'}
 
   find-package-json@1.2.0:
     resolution: {integrity: sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==}
 
-  find-up@3.0.0:
-    resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
-    engines: {node: '>=6'}
-
   find-up@4.1.0:
     resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
     engines: {node: '>=8'}
@@ -7493,10 +7052,6 @@ packages:
   flatted@3.3.1:
     resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
 
-  flow-parser@0.202.0:
-    resolution: {integrity: sha512-ZiXxSIXK3zPmY3zrzCofFonM2T+/3Jz5QZKJyPVtUERQEJUnYkXBQ+0H3FzyqiyJs+VXqb/UNU6/K6sziVYdxw==}
-    engines: {node: '>=0.4.0'}
-
   fluent-ffmpeg@2.1.3:
     resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==}
     engines: {node: '>=18'}
@@ -7510,6 +7065,15 @@ packages:
       debug:
         optional: true
 
+  follow-redirects@1.15.9:
+    resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
+    engines: {node: '>=4.0'}
+    peerDependencies:
+      debug: '*'
+    peerDependenciesMeta:
+      debug:
+        optional: true
+
   for-each@0.3.3:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
 
@@ -7593,6 +7157,10 @@ packages:
     resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==}
     engines: {node: '>= 0.4'}
 
+  function.prototype.name@1.1.6:
+    resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==}
+    engines: {node: '>= 0.4'}
+
   functions-have-names@1.2.3:
     resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
 
@@ -7615,6 +7183,10 @@ packages:
   get-intrinsic@1.2.1:
     resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
 
+  get-intrinsic@1.2.4:
+    resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
+    engines: {node: '>= 0.4'}
+
   get-package-type@0.1.0:
     resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
     engines: {node: '>=8.0.0'}
@@ -7646,6 +7218,10 @@ packages:
     resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
     engines: {node: '>= 0.4'}
 
+  get-symbol-description@1.0.2:
+    resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
+    engines: {node: '>= 0.4'}
+
   get-tsconfig@4.7.2:
     resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==}
 
@@ -7659,10 +7235,6 @@ packages:
     resolution: {integrity: sha512-++rNGpDBgWQ9eXj9JfTBLHMUEd7lDOdzIvFyHQM9yL8ffxkcg4G6jWmsgu/r59Uq6nHc3wcVwtgy3geLnIWunQ==}
     engines: {node: '>= 0.8.0'}
 
-  giget@1.1.2:
-    resolution: {integrity: sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==}
-    hasBin: true
-
   github-slugger@2.0.0:
     resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
 
@@ -7680,19 +7252,11 @@ packages:
     peerDependencies:
       glob: ^7.1.6
 
-  glob-to-regexp@0.4.1:
-    resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
-
   glob@10.3.10:
     resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==}
     engines: {node: '>=16 || 14 >=14.17'}
     hasBin: true
 
-  glob@10.4.2:
-    resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==}
-    engines: {node: '>=16 || 14 >=14.18'}
-    hasBin: true
-
   glob@11.0.0:
     resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==}
     engines: {node: 20 || >=22}
@@ -7723,8 +7287,8 @@ packages:
     resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
     engines: {node: '>=18'}
 
-  globals@15.8.0:
-    resolution: {integrity: sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==}
+  globals@15.9.0:
+    resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==}
     engines: {node: '>=18'}
 
   globalthis@1.0.3:
@@ -7735,10 +7299,6 @@ packages:
     resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
     engines: {node: '>=10'}
 
-  globby@14.0.1:
-    resolution: {integrity: sha512-jOMLD2Z7MAhyG8aJpNOpmziMOP4rPLcc95oQPKXBazW82z+CEgPFBQvEpRUa1KeIMUJo4Wsm+q6uzO/Q/4BksQ==}
-    engines: {node: '>=18'}
-
   google-protobuf@3.21.2:
     resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==}
 
@@ -7774,14 +7334,13 @@ packages:
     resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
     engines: {node: '>=0.8.0'}
 
-  handlebars@4.7.7:
-    resolution: {integrity: sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==}
-    engines: {node: '>=0.4.7'}
-    hasBin: true
-
   happy-dom@10.0.3:
     resolution: {integrity: sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==}
 
+  happy-dom@15.7.4:
+    resolution: {integrity: sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==}
+    engines: {node: '>=18.0.0'}
+
   har-schema@2.0.0:
     resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
     engines: {node: '>=4'}
@@ -7809,10 +7368,17 @@ packages:
   has-property-descriptors@1.0.0:
     resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==}
 
+  has-property-descriptors@1.0.2:
+    resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
   has-proto@1.0.1:
     resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==}
     engines: {node: '>= 0.4'}
 
+  has-proto@1.0.3:
+    resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
+    engines: {node: '>= 0.4'}
+
   has-symbols@1.0.3:
     resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
     engines: {node: '>= 0.4'}
@@ -7821,6 +7387,10 @@ packages:
     resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==}
     engines: {node: '>= 0.4'}
 
+  has-tostringtag@1.0.2:
+    resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+    engines: {node: '>= 0.4'}
+
   has-unicode@2.0.1:
     resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
 
@@ -7831,22 +7401,29 @@ packages:
   hash-sum@2.0.0:
     resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==}
 
-  hashlru@2.3.0:
-    resolution: {integrity: sha512-0cMsjjIC8I+D3M44pOQdsy0OHXGLVz6Z0beRuufhKa0KfaD2wGwAev6jILzXsd3/vpnNQJmWyZtIILqM1N+n5A==}
-
   hasown@2.0.0:
     resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
     engines: {node: '>= 0.4'}
 
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
   hast-util-heading-rank@3.0.0:
     resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
 
   hast-util-is-element@3.0.0:
     resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
 
+  hast-util-to-html@9.0.3:
+    resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==}
+
   hast-util-to-string@3.0.0:
     resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==}
 
+  hast-util-whitespace@3.0.0:
+    resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
+
   he@1.2.0:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
@@ -7857,8 +7434,8 @@ packages:
   highlight.js@10.7.3:
     resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
 
-  highlight.js@11.9.0:
-    resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==}
+  highlight.js@11.10.0:
+    resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==}
     engines: {node: '>=12.0.0'}
 
   hosted-git-info@2.8.9:
@@ -7886,13 +7463,22 @@ packages:
     resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==}
     engines: {node: '>=8'}
 
+  html-void-elements@3.0.0:
+    resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+
   htmlescape@1.1.1:
     resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==}
     engines: {node: '>=0.10'}
 
+  htmlparser2@5.0.1:
+    resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==}
+
   htmlparser2@8.0.1:
     resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
 
+  htmlparser2@9.1.0:
+    resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==}
+
   http-cache-semantics@4.1.1:
     resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
 
@@ -7912,8 +7498,8 @@ packages:
     resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
     engines: {node: '>=0.8', npm: '>=1.3.7'}
 
-  http-signature@1.3.6:
-    resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==}
+  http-signature@1.4.0:
+    resolution: {integrity: sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==}
     engines: {node: '>=0.10'}
 
   http2-wrapper@1.0.3:
@@ -7940,10 +7526,6 @@ packages:
     resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==}
     engines: {node: '>= 14'}
 
-  https-proxy-agent@7.0.4:
-    resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==}
-    engines: {node: '>= 14'}
-
   https-proxy-agent@7.0.5:
     resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==}
     engines: {node: '>= 14'}
@@ -7964,8 +7546,8 @@ packages:
     resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
     engines: {node: '>=16.17.0'}
 
-  human-signals@7.0.0:
-    resolution: {integrity: sha512-74kytxOUSvNbjrT9KisAbaTZ/eJwD/LrbM/kh5j0IhPuJzwuA19dWvniFGwBzN9rVjg+O/e+F310PjObDXS+9Q==}
+  human-signals@8.0.0:
+    resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==}
     engines: {node: '>=18.18.0'}
 
   iconv-lite@0.4.24:
@@ -8056,6 +7638,10 @@ packages:
     resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==}
     engines: {node: '>= 0.4'}
 
+  internal-slot@1.0.7:
+    resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==}
+    engines: {node: '>= 0.4'}
+
   intersection-observer@0.12.2:
     resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
 
@@ -8070,8 +7656,8 @@ packages:
     resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
     engines: {node: '>= 12'}
 
-  ip-cidr@4.0.1:
-    resolution: {integrity: sha512-V5Nce94SVJ7NtyT/UKUeTM7sY3V7TEk48hURhtBgTiGduOa5t6p9Hd+zBOGvr4Gu7iWPxFVYNl017p0akQA84w==}
+  ip-cidr@4.0.2:
+    resolution: {integrity: sha512-KifhLKBjdS/hB3TD4UUOalVp1BpzPFvRpgJvXcP0Ya98tuSQTUQ71iI7EW7CKddkBJTYB3GfTWl5eJwpLOXj2A==}
     engines: {node: '>=16.14.0'}
 
   ip-regex@4.3.0:
@@ -8104,6 +7690,10 @@ packages:
   is-array-buffer@3.0.2:
     resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
 
+  is-array-buffer@3.0.4:
+    resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==}
+    engines: {node: '>= 0.4'}
+
   is-arrayish@0.2.1:
     resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
 
@@ -8135,6 +7725,14 @@ packages:
   is-core-module@2.13.1:
     resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
 
+  is-core-module@2.15.1:
+    resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==}
+    engines: {node: '>= 0.4'}
+
+  is-data-view@1.0.1:
+    resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==}
+    engines: {node: '>= 0.4'}
+
   is-date-object@1.0.5:
     resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==}
     engines: {node: '>= 0.4'}
@@ -8174,10 +7772,6 @@ packages:
     resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==}
     engines: {node: '>=10'}
 
-  is-interactive@1.0.0:
-    resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
-    engines: {node: '>=8'}
-
   is-ip@3.1.0:
     resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==}
     engines: {node: '>=8'}
@@ -8188,14 +7782,14 @@ packages:
   is-map@2.0.2:
     resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
 
-  is-nan@1.3.2:
-    resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
-    engines: {node: '>= 0.4'}
-
   is-negative-zero@2.0.2:
     resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==}
     engines: {node: '>= 0.4'}
 
+  is-negative-zero@2.0.3:
+    resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+    engines: {node: '>= 0.4'}
+
   is-node-process@1.2.0:
     resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==}
 
@@ -8219,10 +7813,6 @@ packages:
     resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
     engines: {node: '>=12'}
 
-  is-plain-object@2.0.4:
-    resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
-    engines: {node: '>=0.10.0'}
-
   is-plain-object@5.0.0:
     resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
     engines: {node: '>=0.10.0'}
@@ -8243,6 +7833,10 @@ packages:
   is-shared-array-buffer@1.0.2:
     resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
 
+  is-shared-array-buffer@1.0.3:
+    resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==}
+    engines: {node: '>= 0.4'}
+
   is-stream@1.1.0:
     resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==}
     engines: {node: '>=0.10.0'}
@@ -8263,8 +7857,8 @@ packages:
     resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==}
     engines: {node: '>= 0.4'}
 
-  is-svg@5.0.1:
-    resolution: {integrity: sha512-mLYxDsfisQWdS4+gSblAwhATDoNMS/tx8G7BKA+aBIf7F0m1iUwMvuKAo6mW4WMleQAEE50I1Zqef9yMMfHk3w==}
+  is-svg@5.1.0:
+    resolution: {integrity: sha512-uVg5yifaTxHoefNf5Jcx+i9RZe2OBYd/UStp1umx+EERa4xGRa3LLGXjoEph43qUORC0qkafUgrXZ6zzK89yGA==}
     engines: {node: '>=14.16'}
 
   is-symbol@1.0.4:
@@ -8275,6 +7869,10 @@ packages:
     resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==}
     engines: {node: '>= 0.4'}
 
+  is-typed-array@1.1.13:
+    resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
+    engines: {node: '>= 0.4'}
+
   is-typedarray@1.0.0:
     resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
 
@@ -8315,10 +7913,6 @@ packages:
     resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==}
     engines: {node: '>=16'}
 
-  isobject@3.0.1:
-    resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
-    engines: {node: '>=0.10.0'}
-
   isstream@0.1.2:
     resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
 
@@ -8358,10 +7952,6 @@ packages:
     resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
     engines: {node: '>=14'}
 
-  jackspeak@3.4.0:
-    resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==}
-    engines: {node: '>=14'}
-
   jackspeak@4.0.1:
     resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==}
     engines: {node: 20 || >=22}
@@ -8512,6 +8102,9 @@ packages:
   joi@17.11.0:
     resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==}
 
+  joi@17.13.3:
+    resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
+
   jpeg-js@0.3.7:
     resolution: {integrity: sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==}
 
@@ -8547,14 +8140,9 @@ packages:
     resolution: {integrity: sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==}
     engines: {node: '>=0.1.90'}
 
-  jscodeshift@0.15.1:
-    resolution: {integrity: sha512-hIJfxUy8Rt4HkJn/zZPU9ChKfKZM1342waJ1QC2e2YsPcWhM+3BJ4dcfQCzArTrk1jJeNLB341H+qOcEHRxJZg==}
-    hasBin: true
-    peerDependencies:
-      '@babel/preset-env': ^7.1.6
-    peerDependenciesMeta:
-      '@babel/preset-env':
-        optional: true
+  jsdoc-type-pratt-parser@4.1.0:
+    resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
+    engines: {node: '>=12.0.0'}
 
   jsdom@24.1.1:
     resolution: {integrity: sha512-5O1wWV99Jhq4DV7rCLIoZ/UIhyQeDR7wHVyZAHAshbrvZsLs+Xzz7gtwnlJTJDjleiTKh54F4dXrX70vJQTyJQ==}
@@ -8565,10 +8153,6 @@ packages:
       canvas:
         optional: true
 
-  jsesc@0.5.0:
-    resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==}
-    hasBin: true
-
   jsesc@2.5.2:
     resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
     engines: {node: '>=4'}
@@ -8580,6 +8164,9 @@ packages:
   json-parse-even-better-errors@2.3.1:
     resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
 
+  json-schema-ref-resolver@1.0.1:
+    resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==}
+
   json-schema-traverse@0.4.1:
     resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
 
@@ -8642,6 +8229,11 @@ packages:
   jstransformer@1.0.0:
     resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==}
 
+  juice@11.0.0:
+    resolution: {integrity: sha512-sGF8hPz9/Wg+YXbaNDqc1Iuoaw+J/P9lBHNQKXAGc9pPNjCd4fyPai0Zxj7MRtdjMr0lcgk5PjEIkP2b8R9F3w==}
+    engines: {node: '>=18.17'}
+    hasBin: true
+
   just-extend@4.2.1:
     resolution: {integrity: sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==}
 
@@ -8680,10 +8272,6 @@ packages:
     resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==}
     engines: {node: '> 0.8'}
 
-  lazy-universal-dotenv@4.0.0:
-    resolution: {integrity: sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==}
-    engines: {node: '>=14.0.0'}
-
   lazystream@1.0.1:
     resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
     engines: {node: '>= 0.6.3'}
@@ -8696,8 +8284,8 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
-  light-my-request@5.11.0:
-    resolution: {integrity: sha512-qkFCeloXCOMpmEdZ/MV91P8AT4fjwFXWaAFz3lUeStM8RcoM1ks4J/F8r1b3r6y/H4u3ACEJ1T+Gv5bopj7oDA==}
+  light-my-request@6.0.0:
+    resolution: {integrity: sha512-kFkFXrmKCL0EEeOmJybMH5amWFd+AFvlvMlvFTRxCUwbhfapZqDmeLMPoWihntnYY6JpoQDE9k+vOzObF1fDqg==}
 
   lilconfig@3.1.1:
     resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
@@ -8719,10 +8307,6 @@ packages:
     resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
     engines: {node: '>=14'}
 
-  locate-path@3.0.0:
-    resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
-    engines: {node: '>=6'}
-
   locate-path@5.0.0:
     resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
     engines: {node: '>=8'}
@@ -8731,9 +8315,6 @@ packages:
     resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
     engines: {node: '>=10'}
 
-  lodash.debounce@4.0.8:
-    resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
-
   lodash.defaults@4.2.0:
     resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
 
@@ -8779,6 +8360,9 @@ packages:
   loupe@2.3.7:
     resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
 
+  loupe@3.1.1:
+    resolution: {integrity: sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==}
+
   lowercase-keys@2.0.0:
     resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
     engines: {node: '>=8'}
@@ -8828,16 +8412,15 @@ packages:
   magic-string@0.30.10:
     resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
 
+  magic-string@0.30.11:
+    resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
+
   magicast@0.3.4:
     resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==}
 
   mailcheck@1.1.1:
     resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
 
-  make-dir@2.1.0:
-    resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
-    engines: {node: '>=6'}
-
   make-dir@3.1.0:
     resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
     engines: {node: '>=8'}
@@ -8906,6 +8489,9 @@ packages:
   mdast-util-phrasing@4.1.0:
     resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
 
+  mdast-util-to-hast@13.2.0:
+    resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==}
+
   mdast-util-to-markdown@2.1.0:
     resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==}
 
@@ -8922,18 +8508,21 @@ packages:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
 
-  meilisearch@0.41.0:
-    resolution: {integrity: sha512-5KcGLxEXD7E+uNO7R68rCbGSHgCqeM3Q3RFFLSsN7ZrIgr8HPDXVAIlP4LHggAZfk0FkSzo8VSXifHCwa2k80g==}
+  meilisearch@0.42.0:
+    resolution: {integrity: sha512-pXaOPx/uhVGYVpejNuOcXifQVJlRVSxtvpgrGKb7ygmYo4qSNXkQXPxq1p0Tv+4/RsPJug3W04pcNnYXiqungA==}
 
   memoizerific@1.11.3:
     resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==}
 
+  mensch@0.3.4:
+    resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==}
+
   meow@9.0.0:
     resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==}
     engines: {node: '>=10'}
 
-  merge-descriptors@1.0.1:
-    resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
+  merge-descriptors@1.0.3:
+    resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
 
   merge-stream@2.0.0:
     resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
@@ -9037,8 +8626,8 @@ packages:
   micromark@4.0.0:
     resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
 
-  micromatch@4.0.7:
-    resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==}
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
     engines: {node: '>=8.6'}
 
   mime-db@1.52.0:
@@ -9054,6 +8643,11 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
+  mime@2.6.0:
+    resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
+    engines: {node: '>=4.0.0'}
+    hasBin: true
+
   mime@3.0.0:
     resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
     engines: {node: '>=10.0.0'}
@@ -9182,8 +8776,8 @@ packages:
   mlly@1.5.0:
     resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==}
 
-  mnemonist@0.39.6:
-    resolution: {integrity: sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==}
+  mnemonist@0.39.8:
+    resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==}
 
   mock-socket@9.3.1:
     resolution: {integrity: sha512-qxBgB7Qa2sEQgHFjj0dSigq7fX4k6Saisd5Nelwp2q8mlbAFh5dHV9JTTlF8viYJLSSWgMCZFUom8PJcMNBoJw==}
@@ -9192,10 +8786,6 @@ packages:
   module-details-from-path@1.0.3:
     resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==}
 
-  mri@1.2.0:
-    resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
-    engines: {node: '>=4'}
-
   ms@2.0.0:
     resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
 
@@ -9231,6 +8821,16 @@ packages:
       typescript:
         optional: true
 
+  msw@2.4.9:
+    resolution: {integrity: sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==}
+    engines: {node: '>=18'}
+    hasBin: true
+    peerDependencies:
+      typescript: '>= 4.8.x'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
   muggle-string@0.4.1:
     resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
 
@@ -9293,9 +8893,6 @@ packages:
     resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
     engines: {node: '>= 0.6'}
 
-  neo-async@2.6.2:
-    resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
-
   nested-property@4.0.0:
     resolution: {integrity: sha512-yFehXNWRs4cM0+dz7QxCd06hTbWbSkV0ISsqBfkntU6TOY4Qm3Q88fRRLOddkGh2Qq6dZvnKVAahfhjcUvLnyA==}
 
@@ -9324,17 +8921,10 @@ packages:
     resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==}
     engines: {node: '>=v0.6.5'}
 
-  node-dir@0.1.17:
-    resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==}
-    engines: {node: '>= 0.10.5'}
-
   node-domexception@1.0.0:
     resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
     engines: {node: '>=10.5.0'}
 
-  node-fetch-native@1.0.2:
-    resolution: {integrity: sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ==}
-
   node-fetch@2.6.13:
     resolution: {integrity: sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==}
     engines: {node: 4.x || >=6.0.0}
@@ -9365,8 +8955,8 @@ packages:
     resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
     hasBin: true
 
-  node-gyp@10.1.0:
-    resolution: {integrity: sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==}
+  node-gyp@10.2.0:
+    resolution: {integrity: sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==}
     engines: {node: ^16.14.0 || >=18.0.0}
     hasBin: true
 
@@ -9376,8 +8966,8 @@ packages:
   node-releases@2.0.14:
     resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
 
-  nodemailer@6.9.14:
-    resolution: {integrity: sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==}
+  nodemailer@6.9.15:
+    resolution: {integrity: sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==}
     engines: {node: '>=6.0.0'}
 
   nodemon@3.0.2:
@@ -9385,8 +8975,8 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  nodemon@3.1.4:
-    resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==}
+  nodemon@3.1.7:
+    resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -9448,6 +9038,10 @@ packages:
     resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
 
+  npm-run-path@6.0.0:
+    resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
+    engines: {node: '>=18'}
+
   npmlog@5.0.1:
     resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
     deprecated: This package is no longer supported.
@@ -9483,6 +9077,10 @@ packages:
   object-inspect@1.12.3:
     resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
 
+  object-inspect@1.13.2:
+    resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
+    engines: {node: '>= 0.4'}
+
   object-is@1.1.5:
     resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==}
     engines: {node: '>= 0.4'}
@@ -9495,15 +9093,20 @@ packages:
     resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==}
     engines: {node: '>= 0.4'}
 
-  object.fromentries@2.0.7:
-    resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==}
+  object.assign@4.1.5:
+    resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==}
+    engines: {node: '>= 0.4'}
+
+  object.fromentries@2.0.8:
+    resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
     engines: {node: '>= 0.4'}
 
-  object.groupby@1.0.1:
-    resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==}
+  object.groupby@1.0.3:
+    resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
+    engines: {node: '>= 0.4'}
 
-  object.values@1.1.7:
-    resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==}
+  object.values@1.2.0:
+    resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==}
     engines: {node: '>= 0.4'}
 
   obliterator@2.0.4:
@@ -9526,10 +9129,6 @@ packages:
     resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
     engines: {node: '>= 0.8'}
 
-  on-headers@1.0.2:
-    resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
-    engines: {node: '>= 0.8'}
-
   once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
 
@@ -9541,6 +9140,9 @@ packages:
     resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==}
     engines: {node: '>=12'}
 
+  oniguruma-to-js@0.4.3:
+    resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==}
+
   open@8.4.2:
     resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
     engines: {node: '>=12'}
@@ -9562,10 +9164,6 @@ packages:
     resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
     engines: {node: '>= 0.8.0'}
 
-  ora@5.4.1:
-    resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
-    engines: {node: '>=10'}
-
   os-filter-obj@2.0.0:
     resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==}
     engines: {node: '>=4'}
@@ -9576,12 +9174,15 @@ packages:
   ospath@1.2.2:
     resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
 
-  otpauth@9.3.1:
-    resolution: {integrity: sha512-E6d2tMxPofHNk4sRFp+kqW7vQ+WJGO9VLI2N/W00DnI+ThskU12Qa10kyNSGklrzhN5c+wRUsN4GijVgCU2N9w==}
+  otpauth@9.3.4:
+    resolution: {integrity: sha512-qXv+lpsCUO9ewitLYfeDKbLYt7UUCivnU/fwGK2OqhgrCBsRkTUNKWsgKAhkXG3aistOY+jEeuL90JEBu6W3mQ==}
 
   outvariant@1.4.2:
     resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==}
 
+  outvariant@1.4.3:
+    resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==}
+
   p-cancelable@2.1.1:
     resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
     engines: {node: '>=8'}
@@ -9610,10 +9211,6 @@ packages:
     resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==}
     engines: {node: '>=18'}
 
-  p-locate@3.0.0:
-    resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
-    engines: {node: '>=6'}
-
   p-locate@4.1.0:
     resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
     engines: {node: '>=8'}
@@ -9665,6 +9262,9 @@ packages:
   parse5-htmlparser2-tree-adapter@7.0.0:
     resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==}
 
+  parse5-parser-stream@7.1.2:
+    resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
+
   parse5@5.1.1:
     resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==}
 
@@ -9681,10 +9281,6 @@ packages:
   path-browserify@1.0.1:
     resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
 
-  path-exists@3.0.0:
-    resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==}
-    engines: {node: '>=4'}
-
   path-exists@4.0.0:
     resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
     engines: {node: '>=8'}
@@ -9712,51 +9308,50 @@ packages:
     resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
     engines: {node: '>=16 || 14 >=14.17'}
 
-  path-scurry@1.11.1:
-    resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
-    engines: {node: '>=16 || 14 >=14.18'}
-
   path-scurry@2.0.0:
     resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
     engines: {node: 20 || >=22}
 
-  path-to-regexp@0.1.7:
-    resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
+  path-to-regexp@0.1.10:
+    resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
 
   path-to-regexp@1.8.0:
     resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==}
 
-  path-to-regexp@3.2.0:
-    resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==}
+  path-to-regexp@3.3.0:
+    resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==}
 
   path-to-regexp@6.2.1:
     resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
 
+  path-to-regexp@6.3.0:
+    resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==}
+
   path-type@4.0.0:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
 
-  path-type@5.0.0:
-    resolution: {integrity: sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==}
-    engines: {node: '>=12'}
-
   pathe@1.1.2:
     resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
 
   pathval@1.1.1:
     resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
 
+  pathval@2.0.0:
+    resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==}
+    engines: {node: '>= 14.16'}
+
   pause-stream@0.0.11:
     resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==}
 
-  peek-readable@5.0.0:
-    resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==}
-    engines: {node: '>=14.16'}
-
   peek-readable@5.1.3:
     resolution: {integrity: sha512-kCsc9HwH5RgVA3H3VqkWFyGQwsxUxLdiSX1d5nqAm7hnMFjNFX1VhBLmJoUY0hZNc8gmDNgBkLjfhiWPsziXWA==}
     engines: {node: '>=14.16'}
 
+  peek-readable@5.2.0:
+    resolution: {integrity: sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==}
+    engines: {node: '>=14.16'}
+
   pend@1.2.0:
     resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
 
@@ -9766,8 +9361,8 @@ packages:
   pg-cloudflare@1.1.1:
     resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==}
 
-  pg-connection-string@2.6.4:
-    resolution: {integrity: sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==}
+  pg-connection-string@2.7.0:
+    resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==}
 
   pg-int8@1.0.1:
     resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
@@ -9777,14 +9372,17 @@ packages:
     resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
     engines: {node: '>=4'}
 
-  pg-pool@3.6.2:
-    resolution: {integrity: sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==}
+  pg-pool@3.7.0:
+    resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==}
     peerDependencies:
       pg: '>=8.0'
 
   pg-protocol@1.6.1:
     resolution: {integrity: sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==}
 
+  pg-protocol@1.7.0:
+    resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==}
+
   pg-types@2.2.0:
     resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
     engines: {node: '>=4'}
@@ -9793,8 +9391,8 @@ packages:
     resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
     engines: {node: '>=10'}
 
-  pg@8.12.0:
-    resolution: {integrity: sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==}
+  pg@8.13.0:
+    resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==}
     engines: {node: '>= 8.0.0'}
     peerDependencies:
       pg-native: '>=3.0.1'
@@ -9815,6 +9413,9 @@ packages:
   picocolors@1.0.1:
     resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
 
+  picocolors@1.1.0:
+    resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
+
   picomatch@2.3.1:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
@@ -9827,10 +9428,6 @@ packages:
     resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
     engines: {node: '>=0.10.0'}
 
-  pify@4.0.1:
-    resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
-    engines: {node: '>=6'}
-
   pino-abstract-transport@1.2.0:
     resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==}
 
@@ -9852,18 +9449,10 @@ packages:
     resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==}
     engines: {node: '>=16.20.0'}
 
-  pkg-dir@3.0.0:
-    resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
-    engines: {node: '>=6'}
-
   pkg-dir@4.2.0:
     resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
     engines: {node: '>=8'}
 
-  pkg-dir@5.0.0:
-    resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==}
-    engines: {node: '>=10'}
-
   pkg-types@1.0.3:
     resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
 
@@ -9890,6 +9479,10 @@ packages:
     resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==}
     engines: {node: '>=10'}
 
+  possible-typed-array-names@1.0.0:
+    resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
+    engines: {node: '>= 0.4'}
+
   postcss-calc@9.0.1:
     resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==}
     engines: {node: ^14 || ^16 || >=18.0}
@@ -10059,8 +9652,8 @@ packages:
   postcss-value-parser@4.2.0:
     resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
 
-  postcss@8.4.40:
-    resolution: {integrity: sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==}
+  postcss@8.4.47:
+    resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==}
     engines: {node: ^10 || ^12 || >=14}
 
   postgres-array@2.0.0:
@@ -10119,10 +9712,6 @@ packages:
     resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
 
-  pretty-hrtime@1.0.3:
-    resolution: {integrity: sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==}
-    engines: {node: '>= 0.8'}
-
   pretty-ms@9.0.0:
     resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==}
     engines: {node: '>=18'}
@@ -10133,8 +9722,8 @@ packages:
   probe-image-size@7.2.3:
     resolution: {integrity: sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==}
 
-  proc-log@3.0.0:
-    resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==}
+  proc-log@4.2.0:
+    resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 
   process-exists@5.0.0:
@@ -10144,12 +9733,12 @@ packages:
   process-nextick-args@2.0.1:
     resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
 
-  process-warning@2.2.0:
-    resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==}
-
   process-warning@3.0.0:
     resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
 
+  process-warning@4.0.0:
+    resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==}
+
   process@0.11.10:
     resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
     engines: {node: '>= 0.6.0'}
@@ -10178,6 +9767,9 @@ packages:
   prop-types@15.8.1:
     resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
 
+  property-information@6.5.0:
+    resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
+
   proto-list@1.2.4:
     resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
 
@@ -10265,21 +9857,13 @@ packages:
     resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
     engines: {node: '>=6.0.0'}
 
-  qrcode@1.5.3:
-    resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
+  qrcode@1.5.4:
+    resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
     engines: {node: '>=10.13.0'}
     hasBin: true
 
-  qs@6.10.4:
-    resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==}
-    engines: {node: '>=0.6'}
-
-  qs@6.11.0:
-    resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
-    engines: {node: '>=0.6'}
-
-  qs@6.11.1:
-    resolution: {integrity: sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==}
+  qs@6.13.0:
+    resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
     engines: {node: '>=0.6'}
 
   qs@6.5.3:
@@ -10309,9 +9893,6 @@ packages:
     resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
     engines: {node: '>=10'}
 
-  ramda@0.29.0:
-    resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==}
-
   random-seed@0.3.0:
     resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==}
     engines: {node: '>= 0.6.0'}
@@ -10327,12 +9908,16 @@ packages:
     resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
     engines: {node: '>= 0.8'}
 
+  raw-body@3.0.0:
+    resolution: {integrity: sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==}
+    engines: {node: '>= 0.8'}
+
   rdf-canonize@3.4.0:
     resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==}
     engines: {node: '>=12'}
 
-  re2@1.21.3:
-    resolution: {integrity: sha512-GI+KoGkHT4kxTaX+9p0FgNB1XUnCndO9slG5qqeEoZ7kbf6Dk6ohQVpmwKVeSp7LPLn+g6Q3BaCopz4oHuBDuQ==}
+  re2@1.21.4:
+    resolution: {integrity: sha512-MVIfXWJmsP28mRsSt8HeL750ifb8H5+oF2UDIxGaiJCr8fkMqhLZ7kcX9ADRk2dC8qeGKedB7UVYRfBVpEiLfA==}
 
   react-colorful@5.6.1:
     resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
@@ -10442,33 +10027,22 @@ packages:
   reflect-metadata@0.2.2:
     resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
 
-  regenerate-unicode-properties@10.1.0:
-    resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
-    engines: {node: '>=4'}
-
-  regenerate@1.4.2:
-    resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==}
-
   regenerator-runtime@0.13.11:
     resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
 
   regenerator-runtime@0.14.0:
     resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
 
-  regenerator-transform@0.15.2:
-    resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==}
+  regex@4.3.3:
+    resolution: {integrity: sha512-r/AadFO7owAq1QJVeZ/nq9jNS1vyZt+6t1p/E59B56Rn2GCya+gr1KSyOzNL/er+r+B7phv5jG2xU2Nz1YkmJg==}
 
   regexp.prototype.flags@1.5.0:
     resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==}
     engines: {node: '>= 0.4'}
 
-  regexpu-core@5.3.2:
-    resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==}
-    engines: {node: '>=4'}
-
-  regjsparser@0.9.1:
-    resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==}
-    hasBin: true
+  regexp.prototype.flags@1.5.2:
+    resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
+    engines: {node: '>= 0.4'}
 
   rehype-external-links@3.0.0:
     resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==}
@@ -10551,8 +10125,8 @@ packages:
     resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
     engines: {node: '>=8'}
 
-  ret@0.4.3:
-    resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==}
+  ret@0.5.0:
+    resolution: {integrity: sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==}
     engines: {node: '>=10'}
 
   retry@0.12.0:
@@ -10566,10 +10140,8 @@ packages:
   rfdc@1.3.0:
     resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==}
 
-  rimraf@2.6.3:
-    resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==}
-    deprecated: Rimraf versions prior to v4 are no longer supported
-    hasBin: true
+  rfdc@1.4.1:
+    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
 
   rimraf@2.7.1:
     resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
@@ -10581,8 +10153,8 @@ packages:
     deprecated: Rimraf versions prior to v4 are no longer supported
     hasBin: true
 
-  rollup@4.19.1:
-    resolution: {integrity: sha512-K5vziVlg7hTpYfFBI+91zHBEMo6jafYXpkMlqZjg7/zhIG9iHqazBf4xz9AVdjS9BruRn280ROqLI7G3OFRIlw==}
+  rollup@4.22.5:
+    resolution: {integrity: sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
 
@@ -10605,6 +10177,10 @@ packages:
     resolution: {integrity: sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==}
     engines: {node: '>=0.4'}
 
+  safe-array-concat@1.1.2:
+    resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==}
+    engines: {node: '>=0.4'}
+
   safe-buffer@5.1.2:
     resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
 
@@ -10614,8 +10190,12 @@ packages:
   safe-regex-test@1.0.0:
     resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==}
 
-  safe-regex2@3.1.0:
-    resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==}
+  safe-regex-test@1.0.3:
+    resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
+    engines: {node: '>= 0.4'}
+
+  safe-regex2@4.0.0:
+    resolution: {integrity: sha512-Hvjfv25jPDVr3U+4LDzBuZPPOymELG3PYcSk5hcevooo1yxxamQL/bHs/GrEPGmMoMEwRrHVGiCA1pXi97B8Ew==}
 
   safe-stable-stringify@2.4.2:
     resolution: {integrity: sha512-gMxvPJYhP0O9n2pvcfYfIuYgbledAOJFcqRThtPRmjscaipiwcwPPKLytpVzMkG2HAN87Qmo2d4PtGiri1dSLA==}
@@ -10624,11 +10204,16 @@ packages:
   safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
 
-  sanitize-html@2.13.0:
-    resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==}
+  sanitize-html@2.13.1:
+    resolution: {integrity: sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==}
 
-  sass@1.77.8:
-    resolution: {integrity: sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==}
+  sass@1.79.3:
+    resolution: {integrity: sha512-m7dZxh0W9EZ3cw50Me5GOuYm/tVAJAn91SUnohLRo9cXBixGUOdvmryN+dXpwR831bhoY3Zv7rEFt85PUwTmzA==}
+    engines: {node: '>=14.0.0'}
+    hasBin: true
+
+  sass@1.79.4:
+    resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==}
     engines: {node: '>=14.0.0'}
     hasBin: true
 
@@ -10645,6 +10230,9 @@ packages:
   secure-json-parse@2.7.0:
     resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
 
+  secure-json-parse@3.0.0:
+    resolution: {integrity: sha512-YO+gVWyp97H+nCG/qdC8X819iKx5g+BpnO9nYT4uFq4uyI0rSxwtx5qD9rGfScg7FGLYu/YBf8uOtwQKv+gq8g==}
+
   seedrandom@3.0.5:
     resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==}
 
@@ -10674,12 +10262,17 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
-  send@0.18.0:
-    resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
+  semver@7.6.3:
+    resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  send@0.19.0:
+    resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
     engines: {node: '>= 0.8.0'}
 
-  serve-static@1.15.0:
-    resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
+  serve-static@1.16.2:
+    resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
     engines: {node: '>= 0.8.0'}
 
   set-blocking@2.0.0:
@@ -10688,6 +10281,14 @@ packages:
   set-cookie-parser@2.6.0:
     resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
 
+  set-function-length@1.2.2:
+    resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+    engines: {node: '>= 0.4'}
+
+  set-function-name@2.0.2:
+    resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+    engines: {node: '>= 0.4'}
+
   setimmediate@1.0.5:
     resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
 
@@ -10698,13 +10299,9 @@ packages:
     resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==}
     hasBin: true
 
-  shallow-clone@3.0.1:
-    resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==}
-    engines: {node: '>=8'}
-
-  sharp@0.33.4:
-    resolution: {integrity: sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==}
-    engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+  sharp@0.33.5:
+    resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
 
   shebang-command@1.2.0:
     resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
@@ -10722,8 +10319,8 @@ packages:
     resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
     engines: {node: '>=8'}
 
-  shiki@1.12.0:
-    resolution: {integrity: sha512-BuAxWOm5JhRcbSOl7XCei8wGjgJJonnV0oipUupPY58iULxUGyHhW5CF+9FRMuM1pcJ5cGEJGll1LusX6FwpPA==}
+  shiki@1.21.0:
+    resolution: {integrity: sha512-apCH5BoWTrmHDPGgg3RF8+HAAbEL/CdbYr8rMw7eIrdhCkZHdVGat5mMNlRtd1erNG01VPMIKHNQ0Pj2HMAiog==}
 
   shimmer@1.2.1:
     resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
@@ -10731,6 +10328,10 @@ packages:
   side-channel@1.0.4:
     resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
 
+  side-channel@1.0.6:
+    resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
+    engines: {node: '>= 0.4'}
+
   siginfo@2.0.0:
     resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
 
@@ -10842,10 +10443,6 @@ packages:
     resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
     engines: {node: '>=8'}
 
-  slash@5.1.0:
-    resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
-    engines: {node: '>=14.16'}
-
   slice-ansi@3.0.0:
     resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==}
     engines: {node: '>=8'}
@@ -10854,6 +10451,9 @@ packages:
     resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
     engines: {node: '>=10'}
 
+  slick@1.12.2:
+    resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==}
+
   smart-buffer@4.2.0:
     resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
     engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -10884,6 +10484,10 @@ packages:
     resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
     engines: {node: '>=0.10.0'}
 
+  source-map-js@1.2.1:
+    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+    engines: {node: '>=0.10.0'}
+
   source-map-support@0.5.13:
     resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
 
@@ -10931,6 +10535,11 @@ packages:
     engines: {node: '>=0.10.0'}
     hasBin: true
 
+  sshpk@1.18.0:
+    resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
+    engines: {node: '>=0.10.0'}
+    hasBin: true
+
   ssri@10.0.4:
     resolution: {integrity: sha512-12+IR2CB2C28MMAw0Ncqwj5QbTcs0nGIhgJzYWzDkb21vWmfNI83KS4f3Ci6GI98WreIfG7o9UXp3C0qbpA8nQ==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -10945,8 +10554,8 @@ packages:
   standard-as-callback@2.1.0:
     resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
 
-  start-server-and-test@2.0.4:
-    resolution: {integrity: sha512-CKNeBTcP0hVqIlNismHMudb9q3lLdAjcVPO13/7gfI66fcJpeIb/o4NzQd1JK/CD+lfWVqr10ZH9Y14+OwlJuw==}
+  start-server-and-test@2.0.8:
+    resolution: {integrity: sha512-v2fV6NV2F7tL1ocwfI4Wpait+IKjRbT5l3ZZ+ZikXdMLmxYsS8ynGAsCQAUVXkVyGyS+UibsRnvgHkMvJIvCsw==}
     engines: {node: '>=16'}
     hasBin: true
 
@@ -10961,9 +10570,6 @@ packages:
     resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==}
     engines: {node: '>= 0.4'}
 
-  store2@2.14.2:
-    resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==}
-
   storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640:
     resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640}
     version: 0.0.0
@@ -10983,8 +10589,8 @@ packages:
       react-dom:
         optional: true
 
-  storybook@8.2.6:
-    resolution: {integrity: sha512-8j30wDxQmkcqI0fWcSYFsUCjErsY1yTWbTW+yjbwM8DyW18Cud6CwbFRCxjFsH+2M0CjP6Pqs/m1PGI0vcQscQ==}
+  storybook@8.3.4:
+    resolution: {integrity: sha512-nzvuK5TsEgJwcWGLGgafabBOxKn37lfJVv7ZoUVPgJIjk2mNRyJDFwYRJzUZaD37eiR/c/lQ6MoaeqlGwiXoxw==}
     hasBin: true
 
   stream-browserify@3.0.0:
@@ -10996,10 +10602,6 @@ packages:
   stream-parser@0.3.1:
     resolution: {integrity: sha512-bJ/HgKq41nlKvlhccD5kaCr/P+Hu0wPNKPJOH7en+YrJu/9EgqUF+88w5Jb6KNcjOFMhfX4B2asfeAtIGuHObQ==}
 
-  stream-wormhole@1.1.0:
-    resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==}
-    engines: {node: '>=4.0.0'}
-
   streamsearch@1.1.0:
     resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
     engines: {node: '>=10.0.0'}
@@ -11033,12 +10635,23 @@ packages:
     resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==}
     engines: {node: '>= 0.4'}
 
+  string.prototype.trim@1.2.9:
+    resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==}
+    engines: {node: '>= 0.4'}
+
   string.prototype.trimend@1.0.6:
     resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==}
 
+  string.prototype.trimend@1.0.8:
+    resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==}
+
   string.prototype.trimstart@1.0.6:
     resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==}
 
+  string.prototype.trimstart@1.0.8:
+    resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+    engines: {node: '>= 0.4'}
+
   string_decoder@0.10.31:
     resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
 
@@ -11048,6 +10661,9 @@ packages:
   string_decoder@1.3.0:
     resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
 
+  stringify-entities@4.0.4:
+    resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
+
   stringz@2.1.0:
     resolution: {integrity: sha512-KlywLT+MZ+v0IRepfMxRtnSvDCMc3nR1qqCs3m/qIbSOWkNZYT8XHQA31rS3TnKp0c5xjZu3M4GY/2aRKSi/6A==}
 
@@ -11109,8 +10725,8 @@ packages:
     resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==}
     engines: {node: '>=14.16'}
 
-  strtok3@8.0.1:
-    resolution: {integrity: sha512-HNkTAnNWQj2YBzfTtoC5OQyu1QwPsMwiB7VyQmNvQKCrmEDSvFB857Vh97UY9InGLNRAB91sdS1ztifRo/3hdA==}
+  strtok3@8.1.0:
+    resolution: {integrity: sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw==}
     engines: {node: '>=16'}
 
   stylehacks@6.1.1:
@@ -11151,8 +10767,8 @@ packages:
   symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
 
-  systeminformation@5.22.11:
-    resolution: {integrity: sha512-aLws5yi4KCHTb0BVvbodQY5bY8eW4asMRDTxTW46hqw9lGjACX6TlLdJrkdoHYRB0qs+MekqEq1zG7WDnWE8Ug==}
+  systeminformation@5.23.5:
+    resolution: {integrity: sha512-PEpJwhRYxZgBCAlWZhWIgfMTjXLqfcaZ1pJsJn9snWNfBW/Z1YQg1mbIUSWrEV3ErAHF7l/OoVLQeaZDlPzkpA==}
     engines: {node: '>=8.0.0'}
     os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
     hasBin: true
@@ -11175,20 +10791,8 @@ packages:
   telejson@7.2.0:
     resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==}
 
-  temp-dir@3.0.0:
-    resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==}
-    engines: {node: '>=14.16'}
-
-  temp@0.8.4:
-    resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==}
-    engines: {node: '>=6.0.0'}
-
-  tempy@3.1.0:
-    resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==}
-    engines: {node: '>=14.16'}
-
-  terser@5.31.3:
-    resolution: {integrity: sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA==}
+  terser@5.33.0:
+    resolution: {integrity: sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==}
     engines: {node: '>=10'}
     hasBin: true
 
@@ -11212,8 +10816,8 @@ packages:
   thread-stream@3.1.0:
     resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
 
-  three@0.167.0:
-    resolution: {integrity: sha512-9Y1a66fpjqF3rhq7ivKTaKtjQLZ97Hj/lZ00DmZWaKHaQFH4uzYT1znwRDWQOcgMmCcOloQzo61gDmqO8l9xmA==}
+  three@0.169.0:
+    resolution: {integrity: sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==}
 
   throttle-debounce@5.0.2:
     resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
@@ -11231,10 +10835,6 @@ packages:
   tiny-invariant@1.3.3:
     resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
 
-  tiny-lru@10.0.1:
-    resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==}
-    engines: {node: '>=6'}
-
   tinybench@2.6.0:
     resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==}
 
@@ -11245,10 +10845,18 @@ packages:
     resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==}
     engines: {node: '>=14.0.0'}
 
+  tinyrainbow@1.2.0:
+    resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==}
+    engines: {node: '>=14.0.0'}
+
   tinyspy@2.2.0:
     resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==}
     engines: {node: '>=14.0.0'}
 
+  tinyspy@3.0.2:
+    resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==}
+    engines: {node: '>=14.0.0'}
+
   tmp@0.2.3:
     resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
     engines: {node: '>=14.14'}
@@ -11308,6 +10916,9 @@ packages:
   trace-redirect@1.0.6:
     resolution: {integrity: sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg==}
 
+  trim-lines@3.0.1:
+    resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+
   trim-newlines@3.0.1:
     resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
     engines: {node: '>=8'}
@@ -11331,8 +10942,8 @@ packages:
     peerDependencies:
       typescript: '>=4.2.0'
 
-  ts-case-convert@2.0.2:
-    resolution: {integrity: sha512-vdKfx1VAdpvEBOBv5OpVu5ZFqRg9HdTI4sYt6qqMeICBeNyXvitrarCnFWNDAki51IKwCyx+ZssY46Q9jH5otA==}
+  ts-case-convert@2.0.7:
+    resolution: {integrity: sha512-Kqj8wrkuduWsKUOUNRczrkdHCDt4ZNNd6HKjVw42EnMIGHQUABS4pqfy0acETVLwUTppc1fzo/yi11+uMTaqzw==}
 
   ts-dedent@2.2.0:
     resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
@@ -11352,20 +10963,20 @@ packages:
     resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
     engines: {node: '>=6'}
 
-  tsd@0.31.1:
-    resolution: {integrity: sha512-sSL84A0SFwx2xGMWrxlGaarKFSQszWjJS2vgNDDLwatytzg2aq6ShlwHsBYxRNmjzXISODwMva5ZOdAg/4AoOA==}
+  tsd@0.31.2:
+    resolution: {integrity: sha512-VplBAQwvYrHzVihtzXiUVXu5bGcr7uH1juQZ1lmKgkuGNGT+FechUCqmx9/zk7wibcqR2xaNEwCkDyKh+VVZnQ==}
     engines: {node: '>=14.16'}
     hasBin: true
 
-  tslib@1.14.1:
-    resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
-
   tslib@2.6.2:
     resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
 
   tslib@2.6.3:
     resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
 
+  tslib@2.7.0:
+    resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
+
   tsx@4.4.0:
     resolution: {integrity: sha512-4fwcEjRUxW20ciSaMB8zkpGwCPxuRGnadDuj/pBk5S9uT29zvWz15PK36GrKJo45mSJomDxVejZ73c6lr3811Q==}
     engines: {node: '>=18.0.0'}
@@ -11405,10 +11016,6 @@ packages:
     resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
     engines: {node: '>=8'}
 
-  type-fest@1.4.0:
-    resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
-    engines: {node: '>=10'}
-
   type-fest@2.19.0:
     resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
     engines: {node: '>=12.20'}
@@ -11425,17 +11032,33 @@ packages:
     resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==}
     engines: {node: '>= 0.4'}
 
+  typed-array-buffer@1.0.2:
+    resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==}
+    engines: {node: '>= 0.4'}
+
   typed-array-byte-length@1.0.0:
     resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==}
     engines: {node: '>= 0.4'}
 
+  typed-array-byte-length@1.0.1:
+    resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==}
+    engines: {node: '>= 0.4'}
+
   typed-array-byte-offset@1.0.0:
     resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==}
     engines: {node: '>= 0.4'}
 
+  typed-array-byte-offset@1.0.2:
+    resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==}
+    engines: {node: '>= 0.4'}
+
   typed-array-length@1.0.4:
     resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==}
 
+  typed-array-length@1.0.6:
+    resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==}
+    engines: {node: '>= 0.4'}
+
   typedarray@0.0.6:
     resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
 
@@ -11512,14 +11135,14 @@ packages:
     engines: {node: '>=14.17'}
     hasBin: true
 
+  typescript@5.6.2:
+    resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
+
   ufo@1.3.2:
     resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
 
-  uglify-js@3.17.4:
-    resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
-    engines: {node: '>=0.8.0'}
-    hasBin: true
-
   uid2@0.0.4:
     resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
 
@@ -11544,28 +11167,19 @@ packages:
   undici-types@5.26.5:
     resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
 
+  undici-types@6.19.8:
+    resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
+
   undici@5.28.2:
     resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
     engines: {node: '>=14.0'}
 
-  unicode-canonical-property-names-ecmascript@2.0.0:
-    resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
-    engines: {node: '>=4'}
-
-  unicode-match-property-ecmascript@2.0.0:
-    resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==}
-    engines: {node: '>=4'}
-
-  unicode-match-property-value-ecmascript@2.1.0:
-    resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==}
-    engines: {node: '>=4'}
-
-  unicode-property-aliases-ecmascript@2.1.0:
-    resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==}
-    engines: {node: '>=4'}
+  undici@6.19.8:
+    resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==}
+    engines: {node: '>=18.17'}
 
-  unicorn-magic@0.1.0:
-    resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
+  unicorn-magic@0.3.0:
+    resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
     engines: {node: '>=18'}
 
   unified@11.0.4:
@@ -11582,13 +11196,12 @@ packages:
     resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
 
-  unique-string@3.0.0:
-    resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==}
-    engines: {node: '>=12'}
-
   unist-util-is@6.0.0:
     resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
 
+  unist-util-position@5.0.0:
+    resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
+
   unist-util-stringify-position@4.0.0:
     resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
 
@@ -11671,8 +11284,8 @@ packages:
     resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
     hasBin: true
 
-  v-code-diff@1.12.0:
-    resolution: {integrity: sha512-vvdCBG02mIIiW6Gx6jF119hzxELt+6TlJIwchglR1JYzboHePNxIkVBjR/aoAOVlsGa+5Vtb77cd/N84nrXWPA==}
+  v-code-diff@1.13.1:
+    resolution: {integrity: sha512-9LTV1dZhC1oYTntyB94vfumGgsfIX5u0fEDSI2Txx4vCE5sI5LkgeLJRRy2SsTVZmDcV+R73sBr0GpPn0TJxMw==}
     peerDependencies:
       '@vue/composition-api': ^1.4.9
       vue: ^2.6.0 || >=3.0.0
@@ -11684,6 +11297,10 @@ packages:
     resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
     engines: {node: '>=10.12.0'}
 
+  valid-data-url@3.0.1:
+    resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==}
+    engines: {node: '>=10'}
+
   validate-npm-package-license@3.0.4:
     resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
 
@@ -11709,8 +11326,8 @@ packages:
   vite-plugin-turbosnap@1.0.3:
     resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
 
-  vite@5.3.5:
-    resolution: {integrity: sha512-MdjglKR6AQXQb9JGiS7Rc2wC6uMjcm7Go/NHNO63EwiJXfuk9PgqiP/n5IDJCziMkfw9n4Ubp7lttNwz+8ZVKA==}
+  vite@5.4.8:
+    resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -11718,6 +11335,7 @@ packages:
       less: '*'
       lightningcss: ^1.21.0
       sass: '*'
+      sass-embedded: '*'
       stylus: '*'
       sugarss: '*'
       terser: ^5.4.0
@@ -11730,6 +11348,8 @@ packages:
         optional: true
       sass:
         optional: true
+      sass-embedded:
+        optional: true
       stylus:
         optional: true
       sugarss:
@@ -11810,9 +11430,6 @@ packages:
   vue-component-type-helpers@2.0.16:
     resolution: {integrity: sha512-qisL/iAfdO++7w+SsfYQJVPj6QKvxp4i1MMxvsNO41z/8zu3KuAw9LkhKUfP/kcOWGDxESp+pQObWppXusejCA==}
 
-  vue-component-type-helpers@2.0.29:
-    resolution: {integrity: sha512-58i+ZhUAUpwQ+9h5Hck0D+jr1qbYl4voRt5KffBx8qzELViQ4XdT/Tuo+mzq8u63teAG8K0lLaOiL5ofqW38rg==}
-
   vue-component-type-helpers@2.1.6:
     resolution: {integrity: sha512-ng11B8B/ZADUMMOsRbqv0arc442q7lifSubD0v8oDXIFoMg/mXwAPUunrroIDkY+mcD0dHKccdaznSVp8EoX3w==}
 
@@ -11838,12 +11455,6 @@ packages:
     peerDependencies:
       eslint: '>=6.0.0'
 
-  vue-i18n@9.13.1:
-    resolution: {integrity: sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==}
-    engines: {node: '>= 16'}
-    peerDependencies:
-      vue: ^3.0.0
-
   vue-inbrowser-compiler-independent-utils@4.71.1:
     resolution: {integrity: sha512-K3wt3iVmNGaFEOUR4JIThQRWfqokxLfnPslD41FDZB2ajXp789+wCqJyGYlIFsvEQ2P61PInw6/ph5iiqg51gg==}
     peerDependencies:
@@ -11852,8 +11463,8 @@ packages:
   vue-template-compiler@2.7.14:
     resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==}
 
-  vue-tsc@2.0.29:
-    resolution: {integrity: sha512-MHhsfyxO3mYShZCGYNziSbc63x7cQ5g9kvijV7dRe1TTXBRLxXyL0FnXWpUF1xII2mJ86mwYpYsUmMwkmerq7Q==}
+  vue-tsc@2.1.6:
+    resolution: {integrity: sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==}
     hasBin: true
     peerDependencies:
       typescript: '>=5.0.0'
@@ -11866,6 +11477,14 @@ packages:
       typescript:
         optional: true
 
+  vue@3.5.11:
+    resolution: {integrity: sha512-/8Wurrd9J3lb72FTQS7gRMNQD4nztTtKPmuDuPuhqXmmpD6+skVjAeahNpVzsuky6Sy9gy7wn8UadqPtt9SQIg==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
   vuedraggable@4.1.0:
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
     peerDependencies:
@@ -11875,29 +11494,23 @@ packages:
     resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
     engines: {node: '>=18'}
 
-  wait-on@7.2.0:
-    resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==}
+  wait-on@8.0.1:
+    resolution: {integrity: sha512-1wWQOyR2LVVtaqrcIL2+OM+x7bkpmzVROa0Nf6FryXkS+er5Sa1kzFGjzZRqLnHa3n1rACFLeTwUqE1ETL9Mig==}
     engines: {node: '>=12.0.0'}
     hasBin: true
 
-  walk-up-path@3.0.1:
-    resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==}
-
   walker@1.0.8:
     resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
 
-  watchpack@2.4.0:
-    resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==}
-    engines: {node: '>=10.13.0'}
-
-  wcwidth@1.0.1:
-    resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
-
   web-push@3.6.7:
     resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==}
     engines: {node: '>= 16'}
     hasBin: true
 
+  web-resource-inliner@7.0.0:
+    resolution: {integrity: sha512-NlfnGF8MY9ZUwFjyq3vOUBx7KwF8bmE+ywR781SB0nWB6MoMxN4BA8gtgP1KGTZo/O/AyWJz7HZpR704eaj4mg==}
+    engines: {node: '>=10.0.0'}
+
   web-streams-polyfill@3.2.1:
     resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==}
     engines: {node: '>= 8'}
@@ -11956,6 +11569,10 @@ packages:
     resolution: {integrity: sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==}
     engines: {node: '>= 0.4'}
 
+  which-typed-array@1.1.15:
+    resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==}
+    engines: {node: '>= 0.4'}
+
   which@1.3.1:
     resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
     hasBin: true
@@ -11986,9 +11603,6 @@ packages:
     resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
     engines: {node: '>=0.10.0'}
 
-  wordwrap@1.0.0:
-    resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
-
   wrap-ansi@6.2.0:
     resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
     engines: {node: '>=8'}
@@ -12004,9 +11618,6 @@ packages:
   wrappy@1.0.2:
     resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
 
-  write-file-atomic@2.4.3:
-    resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==}
-
   write-file-atomic@4.0.2:
     resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -12117,7 +11728,7 @@ packages:
 
 snapshots:
 
-  '@adobe/css-tools@4.3.3': {}
+  '@adobe/css-tools@4.4.0': {}
 
   '@aiscript-dev/aiscript-languageserver@https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.6/aiscript-dev-aiscript-languageserver-0.1.6.tgz':
     dependencies:
@@ -12132,25 +11743,19 @@ snapshots:
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
 
-  '@apidevtools/openapi-schemas@2.1.0': {}
-
   '@apidevtools/swagger-methods@3.0.2': {}
 
-  '@aw-web-design/x-default-browser@1.4.126':
-    dependencies:
-      default-browser-id: 3.0.0
-
   '@aws-crypto/crc32@5.2.0':
     dependencies:
       '@aws-crypto/util': 5.2.0
       '@aws-sdk/types': 3.609.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-crypto/crc32c@5.2.0':
     dependencies:
       '@aws-crypto/util': 5.2.0
       '@aws-sdk/types': 3.609.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-crypto/sha1-browser@5.2.0':
     dependencies:
@@ -12159,7 +11764,7 @@ snapshots:
       '@aws-sdk/types': 3.609.0
       '@aws-sdk/util-locate-window': 3.208.0
       '@smithy/util-utf8': 2.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-crypto/sha256-browser@5.2.0':
     dependencies:
@@ -12169,23 +11774,23 @@ snapshots:
       '@aws-sdk/types': 3.609.0
       '@aws-sdk/util-locate-window': 3.208.0
       '@smithy/util-utf8': 2.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-crypto/sha256-js@5.2.0':
     dependencies:
       '@aws-crypto/util': 5.2.0
       '@aws-sdk/types': 3.609.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-crypto/supports-web-crypto@5.2.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-crypto/util@5.2.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/util-utf8': 2.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/client-s3@3.620.0':
     dependencies:
@@ -12291,7 +11896,7 @@ snapshots:
       '@smithy/util-middleware': 3.0.3
       '@smithy/util-retry': 3.0.3
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - aws-crt
 
@@ -12334,7 +11939,7 @@ snapshots:
       '@smithy/util-middleware': 3.0.3
       '@smithy/util-retry': 3.0.3
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - aws-crt
 
@@ -12379,7 +11984,7 @@ snapshots:
       '@smithy/util-middleware': 3.0.3
       '@smithy/util-retry': 3.0.3
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - aws-crt
 
@@ -12391,14 +11996,14 @@ snapshots:
       '@smithy/smithy-client': 3.1.11
       '@smithy/types': 3.3.0
       fast-xml-parser: 4.2.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/credential-provider-env@3.609.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/property-provider': 3.1.3
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/credential-provider-http@3.620.0':
     dependencies:
@@ -12410,7 +12015,7 @@ snapshots:
       '@smithy/smithy-client': 3.1.11
       '@smithy/types': 3.3.0
       '@smithy/util-stream': 3.1.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/credential-provider-ini@3.620.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))(@aws-sdk/client-sts@3.620.0)':
     dependencies:
@@ -12425,7 +12030,7 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/shared-ini-file-loader': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - '@aws-sdk/client-sso-oidc'
       - aws-crt
@@ -12443,7 +12048,7 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/shared-ini-file-loader': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - '@aws-sdk/client-sso-oidc'
       - '@aws-sdk/client-sts'
@@ -12455,7 +12060,7 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/shared-ini-file-loader': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/credential-provider-sso@3.620.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))':
     dependencies:
@@ -12465,7 +12070,7 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/shared-ini-file-loader': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - '@aws-sdk/client-sso-oidc'
       - aws-crt
@@ -12476,7 +12081,7 @@ snapshots:
       '@aws-sdk/types': 3.609.0
       '@smithy/property-provider': 3.1.3
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/lib-storage@3.620.0(@aws-sdk/client-s3@3.620.0)':
     dependencies:
@@ -12497,14 +12102,14 @@ snapshots:
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
       '@smithy/util-config-provider': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-expect-continue@3.620.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-flexible-checksums@3.620.0':
     dependencies:
@@ -12515,33 +12120,33 @@ snapshots:
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-host-header@3.620.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-location-constraint@3.609.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-logger@3.609.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-recursion-detection@3.620.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-sdk-s3@3.620.0':
     dependencies:
@@ -12555,7 +12160,7 @@ snapshots:
       '@smithy/util-config-provider': 3.0.0
       '@smithy/util-stream': 3.1.3
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-signing@3.620.0':
     dependencies:
@@ -12565,13 +12170,13 @@ snapshots:
       '@smithy/signature-v4': 4.1.0
       '@smithy/types': 3.3.0
       '@smithy/util-middleware': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-ssec@3.609.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/middleware-user-agent@3.620.0':
     dependencies:
@@ -12579,7 +12184,7 @@ snapshots:
       '@aws-sdk/util-endpoints': 3.614.0
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/region-config-resolver@3.614.0':
     dependencies:
@@ -12588,7 +12193,7 @@ snapshots:
       '@smithy/types': 3.3.0
       '@smithy/util-config-provider': 3.0.0
       '@smithy/util-middleware': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/signature-v4-multi-region@3.620.0':
     dependencies:
@@ -12597,7 +12202,7 @@ snapshots:
       '@smithy/protocol-http': 4.1.0
       '@smithy/signature-v4': 4.1.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))':
     dependencies:
@@ -12606,46 +12211,46 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/shared-ini-file-loader': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/types@3.609.0':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/util-arn-parser@3.568.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/util-endpoints@3.614.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/types': 3.3.0
       '@smithy/util-endpoints': 2.0.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/util-locate-window@3.208.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/util-user-agent-browser@3.609.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/types': 3.3.0
       bowser: 2.11.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/util-user-agent-node@3.614.0':
     dependencies:
       '@aws-sdk/types': 3.609.0
       '@smithy/node-config-provider': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@aws-sdk/xml-builder@3.609.0':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@babel/code-frame@7.23.5':
     dependencies:
@@ -12674,7 +12279,7 @@ snapshots:
       '@babel/traverse': 7.23.5
       '@babel/types': 7.24.7
       convert-source-map: 2.0.0
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -12694,7 +12299,7 @@ snapshots:
       '@babel/traverse': 7.24.7
       '@babel/types': 7.24.7
       convert-source-map: 2.0.0
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
       json5: 2.2.3
       semver: 6.3.1
@@ -12715,17 +12320,6 @@ snapshots:
       '@jridgewell/trace-mapping': 0.3.25
       jsesc: 2.5.2
 
-  '@babel/helper-annotate-as-pure@7.24.7':
-    dependencies:
-      '@babel/types': 7.24.7
-
-  '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7':
-    dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helper-compilation-targets@7.22.15':
     dependencies:
       '@babel/compat-data': 7.23.5
@@ -12742,39 +12336,6 @@ snapshots:
       lru-cache: 5.1.1
       semver: 6.3.1
 
-  '@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-annotate-as-pure': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-function-name': 7.24.7
-      '@babel/helper-member-expression-to-functions': 7.24.7
-      '@babel/helper-optimise-call-expression': 7.24.7
-      '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-      '@babel/helper-split-export-declaration': 7.24.7
-      semver: 6.3.1
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/helper-create-regexp-features-plugin@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-annotate-as-pure': 7.24.7
-      regexpu-core: 5.3.2
-      semver: 6.3.1
-
-  '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      debug: 4.3.5(supports-color@5.5.0)
-      lodash.debounce: 4.0.8
-      resolve: 1.22.8
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helper-environment-visitor@7.22.20': {}
 
   '@babel/helper-environment-visitor@7.24.7':
@@ -12799,13 +12360,6 @@ snapshots:
     dependencies:
       '@babel/types': 7.24.7
 
-  '@babel/helper-member-expression-to-functions@7.24.7':
-    dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helper-module-imports@7.22.15':
     dependencies:
       '@babel/types': 7.24.7
@@ -12837,32 +12391,8 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-optimise-call-expression@7.24.7':
-    dependencies:
-      '@babel/types': 7.24.7
-
   '@babel/helper-plugin-utils@7.22.5': {}
 
-  '@babel/helper-plugin-utils@7.24.7': {}
-
-  '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-annotate-as-pure': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-wrap-function': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/helper-replace-supers@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-member-expression-to-functions': 7.24.7
-      '@babel/helper-optimise-call-expression': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helper-simple-access@7.22.5':
     dependencies:
       '@babel/types': 7.24.7
@@ -12874,13 +12404,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/helper-skip-transparent-expression-wrappers@7.24.7':
-    dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helper-split-export-declaration@7.22.6':
     dependencies:
       '@babel/types': 7.24.7
@@ -12891,21 +12414,14 @@ snapshots:
 
   '@babel/helper-string-parser@7.24.7': {}
 
+  '@babel/helper-string-parser@7.24.8': {}
+
   '@babel/helper-validator-identifier@7.24.7': {}
 
   '@babel/helper-validator-option@7.23.5': {}
 
   '@babel/helper-validator-option@7.24.7': {}
 
-  '@babel/helper-wrap-function@7.24.7':
-    dependencies:
-      '@babel/helper-function-name': 7.24.7
-      '@babel/template': 7.24.7
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/helpers@7.23.5':
     dependencies:
       '@babel/template': 7.22.15
@@ -12936,47 +12452,16 @@ snapshots:
     dependencies:
       '@babel/types': 7.24.7
 
-  '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)':
+  '@babel/parser@7.25.6':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/types': 7.25.6
 
-  '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-      '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-
-  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)':
+  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)':
     dependencies:
       '@babel/core': 7.23.5
       '@babel/helper-plugin-utils': 7.22.5
@@ -12986,623 +12471,60 @@ snapshots:
       '@babel/core': 7.23.5
       '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-syntax-flow@7.23.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
   '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)':
     dependencies:
       '@babel/core': 7.23.5
       '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-module-imports': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-classes@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-annotate-as-pure': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-function-name': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-split-export-declaration': 7.24.7
-      globals: 11.12.0
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/template': 7.24.7
-
-  '@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-flow-strip-types@7.23.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-flow': 7.23.3(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-function-name': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-literals@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-simple-access': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-hoist-variables': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-validator-identifier': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7)
-
-  '@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-annotate-as-pure': 7.24.7
-      '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      regenerator-transform: 0.15.2
-
-  '@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-typeof-symbol@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@babel/plugin-transform-typescript@7.23.5(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-annotate-as-pure': 7.24.7
-      '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7)
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/preset-env@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/compat-data': 7.24.7
-      '@babel/core': 7.24.7
-      '@babel/helper-compilation-targets': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-validator-option': 7.24.7
-      '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7)
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7)
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7)
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7)
-      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7)
-      '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.7)
-      '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-async-generator-functions': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-systemjs': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-typeof-symbol': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.24.7)
-      '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.7)
-      babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.7)
-      babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.7)
-      babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.7)
-      core-js-compat: 3.37.1
-      semver: 6.3.1
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/preset-flow@7.23.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-validator-option': 7.24.7
-      '@babel/plugin-transform-flow-strip-types': 7.23.3(@babel/core@7.24.7)
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/types': 7.24.7
-      esutils: 2.0.3
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/preset-typescript@7.23.3(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      '@babel/helper-validator-option': 7.24.7
-      '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-typescript': 7.23.5(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/register@7.22.15(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.24.7
-      clone-deep: 4.0.1
-      find-cache-dir: 2.1.0
-      make-dir: 2.1.0
-      pirates: 4.0.5
-      source-map-support: 0.5.21
-
-  '@babel/regjsgen@0.8.0': {}
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
   '@babel/runtime@7.23.4':
     dependencies:
@@ -13611,7 +12533,7 @@ snapshots:
   '@babel/template@7.22.15':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/parser': 7.25.6
       '@babel/types': 7.24.7
 
   '@babel/template@7.24.0':
@@ -13623,7 +12545,7 @@ snapshots:
   '@babel/template@7.24.7':
     dependencies:
       '@babel/code-frame': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/parser': 7.25.6
       '@babel/types': 7.24.7
 
   '@babel/traverse@7.23.5':
@@ -13634,9 +12556,9 @@ snapshots:
       '@babel/helper-function-name': 7.23.0
       '@babel/helper-hoist-variables': 7.22.5
       '@babel/helper-split-export-declaration': 7.22.6
-      '@babel/parser': 7.24.7
+      '@babel/parser': 7.25.6
       '@babel/types': 7.24.7
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -13649,9 +12571,9 @@ snapshots:
       '@babel/helper-function-name': 7.24.7
       '@babel/helper-hoist-variables': 7.24.7
       '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/parser': 7.24.7
+      '@babel/parser': 7.25.6
       '@babel/types': 7.24.7
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
@@ -13662,26 +12584,32 @@ snapshots:
       '@babel/helper-validator-identifier': 7.24.7
       to-fast-properties: 2.0.0
 
+  '@babel/types@7.25.6':
+    dependencies:
+      '@babel/helper-string-parser': 7.24.8
+      '@babel/helper-validator-identifier': 7.24.7
+      to-fast-properties: 2.0.0
+
   '@base2/pretty-print-object@1.0.1': {}
 
   '@bcoe/v8-coverage@0.2.3': {}
 
-  '@bull-board/api@5.21.1(@bull-board/ui@5.21.1)':
+  '@bull-board/api@6.0.0(@bull-board/ui@6.0.0)':
     dependencies:
-      '@bull-board/ui': 5.21.1
+      '@bull-board/ui': 6.0.0
       redis-info: 3.1.0
 
-  '@bull-board/fastify@5.21.1':
+  '@bull-board/fastify@6.0.0':
     dependencies:
-      '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1)
-      '@bull-board/ui': 5.21.1
-      '@fastify/static': 6.12.0
-      '@fastify/view': 8.2.0
+      '@bull-board/api': 6.0.0(@bull-board/ui@6.0.0)
+      '@bull-board/ui': 6.0.0
+      '@fastify/static': 8.0.1
+      '@fastify/view': 10.0.1
       ejs: 3.1.10
 
-  '@bull-board/ui@5.21.1':
+  '@bull-board/ui@6.0.0':
     dependencies:
-      '@bull-board/api': 5.21.1(@bull-board/ui@5.21.1)
+      '@bull-board/api': 6.0.0(@bull-board/ui@6.0.0)
 
   '@bundled-es-modules/cookie@2.0.0':
     dependencies:
@@ -13701,73 +12629,73 @@ snapshots:
   '@colors/colors@1.5.0':
     optional: true
 
-  '@cropper/element-canvas@2.0.0-rc.1':
+  '@cropper/element-canvas@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-crosshair@2.0.0-rc.1':
+  '@cropper/element-crosshair@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-grid@2.0.0-rc.1':
+  '@cropper/element-grid@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-handle@2.0.0-rc.1':
+  '@cropper/element-handle@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-image@2.0.0-rc.1':
+  '@cropper/element-image@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/element-canvas': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/element-canvas': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-selection@2.0.0-rc.1':
+  '@cropper/element-selection@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/element-canvas': 2.0.0-rc.1
-      '@cropper/element-image': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/element-canvas': 2.0.0-rc.2
+      '@cropper/element-image': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-shade@2.0.0-rc.1':
+  '@cropper/element-shade@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/element-canvas': 2.0.0-rc.1
-      '@cropper/element-selection': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/element-canvas': 2.0.0-rc.2
+      '@cropper/element-selection': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element-viewer@2.0.0-rc.1':
+  '@cropper/element-viewer@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/element-canvas': 2.0.0-rc.1
-      '@cropper/element-image': 2.0.0-rc.1
-      '@cropper/element-selection': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/element-canvas': 2.0.0-rc.2
+      '@cropper/element-image': 2.0.0-rc.2
+      '@cropper/element-selection': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/element@2.0.0-rc.1':
+  '@cropper/element@2.0.0-rc.2':
     dependencies:
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/utils': 2.0.0-rc.2
 
-  '@cropper/elements@2.0.0-rc.1':
+  '@cropper/elements@2.0.0-rc.2':
     dependencies:
-      '@cropper/element': 2.0.0-rc.1
-      '@cropper/element-canvas': 2.0.0-rc.1
-      '@cropper/element-crosshair': 2.0.0-rc.1
-      '@cropper/element-grid': 2.0.0-rc.1
-      '@cropper/element-handle': 2.0.0-rc.1
-      '@cropper/element-image': 2.0.0-rc.1
-      '@cropper/element-selection': 2.0.0-rc.1
-      '@cropper/element-shade': 2.0.0-rc.1
-      '@cropper/element-viewer': 2.0.0-rc.1
+      '@cropper/element': 2.0.0-rc.2
+      '@cropper/element-canvas': 2.0.0-rc.2
+      '@cropper/element-crosshair': 2.0.0-rc.2
+      '@cropper/element-grid': 2.0.0-rc.2
+      '@cropper/element-handle': 2.0.0-rc.2
+      '@cropper/element-image': 2.0.0-rc.2
+      '@cropper/element-selection': 2.0.0-rc.2
+      '@cropper/element-shade': 2.0.0-rc.2
+      '@cropper/element-viewer': 2.0.0-rc.2
 
-  '@cropper/utils@2.0.0-rc.1': {}
+  '@cropper/utils@2.0.0-rc.2': {}
 
-  '@cypress/request@3.0.0':
+  '@cypress/request@3.0.5':
     dependencies:
       aws-sign2: 0.7.0
       aws4: 1.12.0
@@ -13775,14 +12703,14 @@ snapshots:
       combined-stream: 1.0.8
       extend: 3.0.2
       forever-agent: 0.6.1
-      form-data: 2.3.3
-      http-signature: 1.3.6
+      form-data: 4.0.0
+      http-signature: 1.4.0
       is-typedarray: 1.0.0
       isstream: 0.1.2
       json-stringify-safe: 5.0.1
       mime-types: 2.1.35
       performance-now: 2.1.0
-      qs: 6.10.4
+      qs: 6.13.0
       safe-buffer: 5.2.1
       tough-cookie: 4.1.4
       tunnel-agent: 0.6.0
@@ -13803,24 +12731,18 @@ snapshots:
     transitivePeerDependencies:
       - web-streams-polyfill
 
-  '@discordapp/twemoji@15.0.3':
+  '@discordapp/twemoji@15.1.0':
     dependencies:
-      '@twemoji/parser': 15.0.0
+      '@twemoji/parser': 15.1.0
       fs-extra: 8.1.0
       jsonfile: 5.0.0
       universalify: 0.1.2
 
-  '@discoveryjs/json-ext@0.5.7': {}
-
-  '@emnapi/runtime@1.1.1':
+  '@emnapi/runtime@1.2.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
     optional: true
 
-  '@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.3.1)':
-    dependencies:
-      react: 18.3.1
-
   '@esbuild/aix-ppc64@0.19.11':
     optional: true
 
@@ -13830,6 +12752,9 @@ snapshots:
   '@esbuild/aix-ppc64@0.23.0':
     optional: true
 
+  '@esbuild/aix-ppc64@0.23.1':
+    optional: true
+
   '@esbuild/android-arm64@0.18.20':
     optional: true
 
@@ -13842,6 +12767,9 @@ snapshots:
   '@esbuild/android-arm64@0.23.0':
     optional: true
 
+  '@esbuild/android-arm64@0.23.1':
+    optional: true
+
   '@esbuild/android-arm@0.18.20':
     optional: true
 
@@ -13854,6 +12782,9 @@ snapshots:
   '@esbuild/android-arm@0.23.0':
     optional: true
 
+  '@esbuild/android-arm@0.23.1':
+    optional: true
+
   '@esbuild/android-x64@0.18.20':
     optional: true
 
@@ -13866,6 +12797,9 @@ snapshots:
   '@esbuild/android-x64@0.23.0':
     optional: true
 
+  '@esbuild/android-x64@0.23.1':
+    optional: true
+
   '@esbuild/darwin-arm64@0.18.20':
     optional: true
 
@@ -13878,6 +12812,9 @@ snapshots:
   '@esbuild/darwin-arm64@0.23.0':
     optional: true
 
+  '@esbuild/darwin-arm64@0.23.1':
+    optional: true
+
   '@esbuild/darwin-x64@0.18.20':
     optional: true
 
@@ -13890,6 +12827,9 @@ snapshots:
   '@esbuild/darwin-x64@0.23.0':
     optional: true
 
+  '@esbuild/darwin-x64@0.23.1':
+    optional: true
+
   '@esbuild/freebsd-arm64@0.18.20':
     optional: true
 
@@ -13902,6 +12842,9 @@ snapshots:
   '@esbuild/freebsd-arm64@0.23.0':
     optional: true
 
+  '@esbuild/freebsd-arm64@0.23.1':
+    optional: true
+
   '@esbuild/freebsd-x64@0.18.20':
     optional: true
 
@@ -13914,6 +12857,9 @@ snapshots:
   '@esbuild/freebsd-x64@0.23.0':
     optional: true
 
+  '@esbuild/freebsd-x64@0.23.1':
+    optional: true
+
   '@esbuild/linux-arm64@0.18.20':
     optional: true
 
@@ -13926,6 +12872,9 @@ snapshots:
   '@esbuild/linux-arm64@0.23.0':
     optional: true
 
+  '@esbuild/linux-arm64@0.23.1':
+    optional: true
+
   '@esbuild/linux-arm@0.18.20':
     optional: true
 
@@ -13938,6 +12887,9 @@ snapshots:
   '@esbuild/linux-arm@0.23.0':
     optional: true
 
+  '@esbuild/linux-arm@0.23.1':
+    optional: true
+
   '@esbuild/linux-ia32@0.18.20':
     optional: true
 
@@ -13950,6 +12902,9 @@ snapshots:
   '@esbuild/linux-ia32@0.23.0':
     optional: true
 
+  '@esbuild/linux-ia32@0.23.1':
+    optional: true
+
   '@esbuild/linux-loong64@0.18.20':
     optional: true
 
@@ -13962,6 +12917,9 @@ snapshots:
   '@esbuild/linux-loong64@0.23.0':
     optional: true
 
+  '@esbuild/linux-loong64@0.23.1':
+    optional: true
+
   '@esbuild/linux-mips64el@0.18.20':
     optional: true
 
@@ -13974,6 +12932,9 @@ snapshots:
   '@esbuild/linux-mips64el@0.23.0':
     optional: true
 
+  '@esbuild/linux-mips64el@0.23.1':
+    optional: true
+
   '@esbuild/linux-ppc64@0.18.20':
     optional: true
 
@@ -13986,6 +12947,9 @@ snapshots:
   '@esbuild/linux-ppc64@0.23.0':
     optional: true
 
+  '@esbuild/linux-ppc64@0.23.1':
+    optional: true
+
   '@esbuild/linux-riscv64@0.18.20':
     optional: true
 
@@ -13998,6 +12962,9 @@ snapshots:
   '@esbuild/linux-riscv64@0.23.0':
     optional: true
 
+  '@esbuild/linux-riscv64@0.23.1':
+    optional: true
+
   '@esbuild/linux-s390x@0.18.20':
     optional: true
 
@@ -14010,6 +12977,9 @@ snapshots:
   '@esbuild/linux-s390x@0.23.0':
     optional: true
 
+  '@esbuild/linux-s390x@0.23.1':
+    optional: true
+
   '@esbuild/linux-x64@0.18.20':
     optional: true
 
@@ -14022,6 +12992,9 @@ snapshots:
   '@esbuild/linux-x64@0.23.0':
     optional: true
 
+  '@esbuild/linux-x64@0.23.1':
+    optional: true
+
   '@esbuild/netbsd-x64@0.18.20':
     optional: true
 
@@ -14034,9 +13007,15 @@ snapshots:
   '@esbuild/netbsd-x64@0.23.0':
     optional: true
 
+  '@esbuild/netbsd-x64@0.23.1':
+    optional: true
+
   '@esbuild/openbsd-arm64@0.23.0':
     optional: true
 
+  '@esbuild/openbsd-arm64@0.23.1':
+    optional: true
+
   '@esbuild/openbsd-x64@0.18.20':
     optional: true
 
@@ -14049,6 +13028,9 @@ snapshots:
   '@esbuild/openbsd-x64@0.23.0':
     optional: true
 
+  '@esbuild/openbsd-x64@0.23.1':
+    optional: true
+
   '@esbuild/sunos-x64@0.18.20':
     optional: true
 
@@ -14061,6 +13043,9 @@ snapshots:
   '@esbuild/sunos-x64@0.23.0':
     optional: true
 
+  '@esbuild/sunos-x64@0.23.1':
+    optional: true
+
   '@esbuild/win32-arm64@0.18.20':
     optional: true
 
@@ -14073,6 +13058,9 @@ snapshots:
   '@esbuild/win32-arm64@0.23.0':
     optional: true
 
+  '@esbuild/win32-arm64@0.23.1':
+    optional: true
+
   '@esbuild/win32-ia32@0.18.20':
     optional: true
 
@@ -14085,6 +13073,9 @@ snapshots:
   '@esbuild/win32-ia32@0.23.0':
     optional: true
 
+  '@esbuild/win32-ia32@0.23.1':
+    optional: true
+
   '@esbuild/win32-x64@0.18.20':
     optional: true
 
@@ -14097,6 +13088,14 @@ snapshots:
   '@esbuild/win32-x64@0.23.0':
     optional: true
 
+  '@esbuild/win32-x64@0.23.1':
+    optional: true
+
+  '@eslint-community/eslint-utils@4.4.0(eslint@9.11.0)':
+    dependencies:
+      eslint: 9.11.0
+      eslint-visitor-keys: 3.4.3
+
   '@eslint-community/eslint-utils@4.4.0(eslint@9.8.0)':
     dependencies:
       eslint: 9.8.0
@@ -14111,7 +13110,15 @@ snapshots:
   '@eslint/config-array@0.17.1':
     dependencies:
       '@eslint/object-schema': 2.1.4
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/config-array@0.18.0':
+    dependencies:
+      '@eslint/object-schema': 2.1.4
+      debug: 4.3.7(supports-color@8.1.1)
       minimatch: 3.1.2
     transitivePeerDependencies:
       - supports-color
@@ -14119,7 +13126,7 @@ snapshots:
   '@eslint/eslintrc@3.1.0':
     dependencies:
       ajv: 6.12.6
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       espree: 10.1.0
       globals: 14.0.0
       ignore: 5.3.1
@@ -14130,115 +13137,110 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  '@eslint/js@9.11.0': {}
+
   '@eslint/js@9.8.0': {}
 
   '@eslint/object-schema@2.1.4': {}
 
-  '@fal-works/esbuild-plugin-global-externals@2.1.2': {}
+  '@eslint/plugin-kit@0.2.0':
+    dependencies:
+      levn: 0.4.1
 
-  '@fastify/accept-negotiator@1.0.0': {}
+  '@fastify/accept-negotiator@2.0.0': {}
 
-  '@fastify/accepts@4.3.0':
+  '@fastify/accepts@5.0.1':
     dependencies:
       accepts: 1.3.8
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.0
 
-  '@fastify/ajv-compiler@3.5.0':
+  '@fastify/ajv-compiler@4.0.0':
     dependencies:
       ajv: 8.17.1
-      ajv-formats: 2.1.1(ajv@8.17.1)
-      fast-uri: 2.2.0
+      ajv-formats: 3.0.1(ajv@8.17.1)
+      fast-uri: 3.0.1
 
   '@fastify/busboy@2.1.0': {}
 
-  '@fastify/cookie@9.3.1':
+  '@fastify/busboy@3.0.0': {}
+
+  '@fastify/cookie@10.0.1':
     dependencies:
       cookie-signature: 1.2.1
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.0
 
-  '@fastify/cors@9.0.1':
+  '@fastify/cors@10.0.1':
     dependencies:
-      fastify-plugin: 4.5.0
-      mnemonist: 0.39.6
+      fastify-plugin: 5.0.0
+      mnemonist: 0.39.8
 
-  '@fastify/deepmerge@1.3.0': {}
+  '@fastify/deepmerge@2.0.0': {}
 
-  '@fastify/error@3.4.0': {}
+  '@fastify/error@4.0.0': {}
 
-  '@fastify/express@3.0.0':
+  '@fastify/express@4.0.1':
     dependencies:
-      express: 4.19.2
-      fastify-plugin: 4.5.0
+      express: 4.21.0
+      fastify-plugin: 5.0.0
     transitivePeerDependencies:
       - supports-color
 
-  '@fastify/fast-json-stringify-compiler@4.3.0':
+  '@fastify/fast-json-stringify-compiler@5.0.0':
     dependencies:
-      fast-json-stringify: 5.8.0
+      fast-json-stringify: 6.0.0
 
-  '@fastify/http-proxy@9.5.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)':
+  '@fastify/http-proxy@10.0.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)':
     dependencies:
-      '@fastify/reply-from': 9.0.1
+      '@fastify/reply-from': 11.0.0
       fast-querystring: 1.1.2
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.0
       ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
     transitivePeerDependencies:
       - bufferutil
       - utf-8-validate
 
-  '@fastify/multipart@8.3.0':
+  '@fastify/merge-json-schemas@0.1.1':
     dependencies:
-      '@fastify/busboy': 2.1.0
-      '@fastify/deepmerge': 1.3.0
-      '@fastify/error': 3.4.0
-      fastify-plugin: 4.5.0
-      secure-json-parse: 2.7.0
-      stream-wormhole: 1.1.0
+      fast-deep-equal: 3.1.3
+
+  '@fastify/multipart@9.0.1':
+    dependencies:
+      '@fastify/busboy': 3.0.0
+      '@fastify/deepmerge': 2.0.0
+      '@fastify/error': 4.0.0
+      fastify-plugin: 5.0.0
+      secure-json-parse: 3.0.0
 
-  '@fastify/reply-from@9.0.1':
+  '@fastify/reply-from@11.0.0':
     dependencies:
-      '@fastify/error': 3.4.0
+      '@fastify/error': 4.0.0
       end-of-stream: 1.4.4
+      fast-content-type-parse: 2.0.0
       fast-querystring: 1.1.2
-      fastify-plugin: 4.5.0
-      pump: 3.0.0
-      tiny-lru: 10.0.1
-      undici: 5.28.2
+      fastify-plugin: 4.5.1
+      toad-cache: 3.7.0
+      undici: 6.19.8
 
-  '@fastify/send@2.0.1':
+  '@fastify/send@3.1.1':
     dependencies:
-      '@lukeed/ms': 2.0.1
+      '@lukeed/ms': 2.0.2
       escape-html: 1.0.3
       fast-decode-uri-component: 1.0.1
       http-errors: 2.0.0
       mime: 3.0.0
 
-  '@fastify/static@6.12.0':
-    dependencies:
-      '@fastify/accept-negotiator': 1.0.0
-      '@fastify/send': 2.0.1
-      content-disposition: 0.5.4
-      fastify-plugin: 4.5.0
-      glob: 8.1.0
-      p-limit: 3.1.0
-
-  '@fastify/static@7.0.4':
+  '@fastify/static@8.0.1':
     dependencies:
-      '@fastify/accept-negotiator': 1.0.0
-      '@fastify/send': 2.0.1
+      '@fastify/accept-negotiator': 2.0.0
+      '@fastify/send': 3.1.1
       content-disposition: 0.5.4
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.0
       fastq: 1.17.1
-      glob: 10.4.2
+      glob: 11.0.0
 
-  '@fastify/view@8.2.0':
+  '@fastify/view@10.0.1':
     dependencies:
-      fastify-plugin: 4.5.0
-      hashlru: 2.3.0
-
-  '@fastify/view@9.1.0':
-    dependencies:
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.0
       toad-cache: 3.7.0
 
   '@github/webauthn-json@2.1.1': {}
@@ -14271,79 +13273,79 @@ snapshots:
 
   '@humanwhocodes/retry@0.3.0': {}
 
-  '@img/sharp-darwin-arm64@0.33.4':
+  '@img/sharp-darwin-arm64@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-darwin-arm64': 1.0.2
+      '@img/sharp-libvips-darwin-arm64': 1.0.4
     optional: true
 
-  '@img/sharp-darwin-x64@0.33.4':
+  '@img/sharp-darwin-x64@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-darwin-x64': 1.0.2
+      '@img/sharp-libvips-darwin-x64': 1.0.4
     optional: true
 
-  '@img/sharp-libvips-darwin-arm64@1.0.2':
+  '@img/sharp-libvips-darwin-arm64@1.0.4':
     optional: true
 
-  '@img/sharp-libvips-darwin-x64@1.0.2':
+  '@img/sharp-libvips-darwin-x64@1.0.4':
     optional: true
 
-  '@img/sharp-libvips-linux-arm64@1.0.2':
+  '@img/sharp-libvips-linux-arm64@1.0.4':
     optional: true
 
-  '@img/sharp-libvips-linux-arm@1.0.2':
+  '@img/sharp-libvips-linux-arm@1.0.5':
     optional: true
 
-  '@img/sharp-libvips-linux-s390x@1.0.2':
+  '@img/sharp-libvips-linux-s390x@1.0.4':
     optional: true
 
-  '@img/sharp-libvips-linux-x64@1.0.2':
+  '@img/sharp-libvips-linux-x64@1.0.4':
     optional: true
 
-  '@img/sharp-libvips-linuxmusl-arm64@1.0.2':
+  '@img/sharp-libvips-linuxmusl-arm64@1.0.4':
     optional: true
 
-  '@img/sharp-libvips-linuxmusl-x64@1.0.2':
+  '@img/sharp-libvips-linuxmusl-x64@1.0.4':
     optional: true
 
-  '@img/sharp-linux-arm64@0.33.4':
+  '@img/sharp-linux-arm64@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-linux-arm64': 1.0.2
+      '@img/sharp-libvips-linux-arm64': 1.0.4
     optional: true
 
-  '@img/sharp-linux-arm@0.33.4':
+  '@img/sharp-linux-arm@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-linux-arm': 1.0.2
+      '@img/sharp-libvips-linux-arm': 1.0.5
     optional: true
 
-  '@img/sharp-linux-s390x@0.33.4':
+  '@img/sharp-linux-s390x@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-linux-s390x': 1.0.2
+      '@img/sharp-libvips-linux-s390x': 1.0.4
     optional: true
 
-  '@img/sharp-linux-x64@0.33.4':
+  '@img/sharp-linux-x64@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-linux-x64': 1.0.2
+      '@img/sharp-libvips-linux-x64': 1.0.4
     optional: true
 
-  '@img/sharp-linuxmusl-arm64@0.33.4':
+  '@img/sharp-linuxmusl-arm64@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-linuxmusl-arm64': 1.0.2
+      '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
     optional: true
 
-  '@img/sharp-linuxmusl-x64@0.33.4':
+  '@img/sharp-linuxmusl-x64@0.33.5':
     optionalDependencies:
-      '@img/sharp-libvips-linuxmusl-x64': 1.0.2
+      '@img/sharp-libvips-linuxmusl-x64': 1.0.4
     optional: true
 
-  '@img/sharp-wasm32@0.33.4':
+  '@img/sharp-wasm32@0.33.5':
     dependencies:
-      '@emnapi/runtime': 1.1.1
+      '@emnapi/runtime': 1.2.0
     optional: true
 
-  '@img/sharp-win32-ia32@0.33.4':
+  '@img/sharp-win32-ia32@0.33.5':
     optional: true
 
-  '@img/sharp-win32-x64@0.33.4':
+  '@img/sharp-win32-x64@0.33.5':
     optional: true
 
   '@inquirer/confirm@3.1.6':
@@ -14371,18 +13373,6 @@ snapshots:
 
   '@inquirer/type@1.3.1': {}
 
-  '@intlify/core-base@9.13.1':
-    dependencies:
-      '@intlify/message-compiler': 9.13.1
-      '@intlify/shared': 9.13.1
-
-  '@intlify/message-compiler@9.13.1':
-    dependencies:
-      '@intlify/shared': 9.13.1
-      source-map-js: 1.2.0
-
-  '@intlify/shared@9.13.1': {}
-
   '@ioredis/commands@1.2.0': {}
 
   '@isaacs/cliui@8.0.2':
@@ -14439,7 +13429,7 @@ snapshots:
       jest-util: 29.7.0
       jest-validate: 29.7.0
       jest-watcher: 29.7.0
-      micromatch: 4.0.7
+      micromatch: 4.0.8
       pretty-format: 29.7.0
       slash: 3.0.0
       strip-ansi: 6.0.1
@@ -14554,7 +13544,7 @@ snapshots:
       jest-haste-map: 29.7.0
       jest-regex-util: 29.6.3
       jest-util: 29.7.0
-      micromatch: 4.0.7
+      micromatch: 4.0.8
       pirates: 4.0.5
       slash: 3.0.0
       write-file-atomic: 4.0.2
@@ -14570,15 +13560,15 @@ snapshots:
       '@types/yargs': 17.0.19
       chalk: 4.1.2
 
-  '@joshwooding/vite-plugin-react-docgen-typescript@0.3.1(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))':
+  '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))':
     dependencies:
       glob: 7.2.3
       glob-promise: 4.2.2(glob@7.2.3)
       magic-string: 0.27.0
-      react-docgen-typescript: 2.2.2(typescript@5.5.4)
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      react-docgen-typescript: 2.2.2(typescript@5.6.2)
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   '@jridgewell/gen-mapping@0.3.2':
     dependencies:
@@ -14607,6 +13597,8 @@ snapshots:
 
   '@jridgewell/sourcemap-codec@1.4.15': {}
 
+  '@jridgewell/sourcemap-codec@1.5.0': {}
+
   '@jridgewell/trace-mapping@0.3.18':
     dependencies:
       '@jridgewell/resolve-uri': 3.1.0
@@ -14625,7 +13617,7 @@ snapshots:
 
   '@lukeed/csprng@1.0.1': {}
 
-  '@lukeed/ms@2.0.1': {}
+  '@lukeed/ms@2.0.2': {}
 
   '@mapbox/node-pre-gyp@1.0.9(encoding@0.1.13)':
     dependencies:
@@ -14655,23 +13647,23 @@ snapshots:
       '@types/react': 18.0.28
       react: 18.3.1
 
-  '@microsoft/api-extractor-model@7.29.4(@types/node@20.14.12)':
+  '@microsoft/api-extractor-model@7.29.8(@types/node@20.14.12)':
     dependencies:
       '@microsoft/tsdoc': 0.15.0
       '@microsoft/tsdoc-config': 0.17.0
-      '@rushstack/node-core-library': 5.5.1(@types/node@20.14.12)
+      '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12)
     transitivePeerDependencies:
       - '@types/node'
 
-  '@microsoft/api-extractor@7.47.4(@types/node@20.14.12)':
+  '@microsoft/api-extractor@7.47.9(@types/node@20.14.12)':
     dependencies:
-      '@microsoft/api-extractor-model': 7.29.4(@types/node@20.14.12)
+      '@microsoft/api-extractor-model': 7.29.8(@types/node@20.14.12)
       '@microsoft/tsdoc': 0.15.0
       '@microsoft/tsdoc-config': 0.17.0
-      '@rushstack/node-core-library': 5.5.1(@types/node@20.14.12)
+      '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12)
       '@rushstack/rig-package': 0.5.3
-      '@rushstack/terminal': 0.13.3(@types/node@20.14.12)
-      '@rushstack/ts-command-line': 4.22.3(@types/node@20.14.12)
+      '@rushstack/terminal': 0.14.2(@types/node@20.14.12)
+      '@rushstack/ts-command-line': 4.22.8(@types/node@20.14.12)
       lodash: 4.17.21
       minimatch: 3.0.8
       resolve: 1.22.8
@@ -14692,20 +13684,20 @@ snapshots:
 
   '@misskey-dev/browser-image-resizer@2024.1.0': {}
 
-  '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0))(eslint@9.8.0)(globals@15.8.0)':
+  '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0)':
     dependencies:
       '@eslint/compat': 1.1.1
-      '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)
-      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+      '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
       eslint: 9.8.0
-      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)
-      globals: 15.8.0
+      eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)
+      globals: 15.9.0
 
   '@misskey-dev/sharp-read-bmp@1.2.0':
     dependencies:
       decode-bmp: 0.2.1
       decode-ico: 0.4.1
-      sharp: 0.33.4
+      sharp: 0.33.5
 
   '@misskey-dev/summaly@5.1.0':
     dependencies:
@@ -14756,90 +13748,99 @@ snapshots:
       outvariant: 1.4.2
       strict-event-emitter: 0.5.1
 
-  '@napi-rs/canvas-android-arm64@0.1.53':
+  '@mswjs/interceptors@0.35.8':
+    dependencies:
+      '@open-draft/deferred-promise': 2.2.0
+      '@open-draft/logger': 0.3.0
+      '@open-draft/until': 2.1.0
+      is-node-process: 1.2.0
+      outvariant: 1.4.3
+      strict-event-emitter: 0.5.1
+
+  '@napi-rs/canvas-android-arm64@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-darwin-arm64@0.1.53':
+  '@napi-rs/canvas-darwin-arm64@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-darwin-x64@0.1.53':
+  '@napi-rs/canvas-darwin-x64@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-linux-arm-gnueabihf@0.1.53':
+  '@napi-rs/canvas-linux-arm-gnueabihf@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-linux-arm64-gnu@0.1.53':
+  '@napi-rs/canvas-linux-arm64-gnu@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-linux-arm64-musl@0.1.53':
+  '@napi-rs/canvas-linux-arm64-musl@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-linux-x64-gnu@0.1.53':
+  '@napi-rs/canvas-linux-x64-gnu@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-linux-x64-musl@0.1.53':
+  '@napi-rs/canvas-linux-x64-musl@0.1.56':
     optional: true
 
-  '@napi-rs/canvas-win32-x64-msvc@0.1.53':
+  '@napi-rs/canvas-win32-x64-msvc@0.1.56':
     optional: true
 
-  '@napi-rs/canvas@0.1.53':
+  '@napi-rs/canvas@0.1.56':
     optionalDependencies:
-      '@napi-rs/canvas-android-arm64': 0.1.53
-      '@napi-rs/canvas-darwin-arm64': 0.1.53
-      '@napi-rs/canvas-darwin-x64': 0.1.53
-      '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.53
-      '@napi-rs/canvas-linux-arm64-gnu': 0.1.53
-      '@napi-rs/canvas-linux-arm64-musl': 0.1.53
-      '@napi-rs/canvas-linux-x64-gnu': 0.1.53
-      '@napi-rs/canvas-linux-x64-musl': 0.1.53
-      '@napi-rs/canvas-win32-x64-msvc': 0.1.53
-
-  '@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1)':
+      '@napi-rs/canvas-android-arm64': 0.1.56
+      '@napi-rs/canvas-darwin-arm64': 0.1.56
+      '@napi-rs/canvas-darwin-x64': 0.1.56
+      '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.56
+      '@napi-rs/canvas-linux-arm64-gnu': 0.1.56
+      '@napi-rs/canvas-linux-arm64-musl': 0.1.56
+      '@napi-rs/canvas-linux-x64-gnu': 0.1.56
+      '@napi-rs/canvas-linux-x64-musl': 0.1.56
+      '@napi-rs/canvas-win32-x64-msvc': 0.1.56
+
+  '@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1)':
     dependencies:
       iterare: 1.2.1
       reflect-metadata: 0.2.2
       rxjs: 7.8.1
-      tslib: 2.6.3
+      tslib: 2.7.0
       uid: 2.0.2
 
-  '@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)':
+  '@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)':
     dependencies:
-      '@nestjs/common': 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1)
       '@nuxtjs/opencollective': 0.3.2(encoding@0.1.13)
       fast-safe-stringify: 2.1.1
       iterare: 1.2.1
-      path-to-regexp: 3.2.0
+      path-to-regexp: 3.3.0
       reflect-metadata: 0.2.2
       rxjs: 7.8.1
-      tslib: 2.6.3
+      tslib: 2.7.0
       uid: 2.0.2
     optionalDependencies:
-      '@nestjs/platform-express': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)
+      '@nestjs/platform-express': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4)
     transitivePeerDependencies:
       - encoding
 
-  '@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)':
+  '@nestjs/platform-express@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4)':
     dependencies:
-      '@nestjs/common': 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1)
-      '@nestjs/core': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
-      body-parser: 1.20.2
+      '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      '@nestjs/core': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      body-parser: 1.20.3
       cors: 2.8.5
-      express: 4.19.2
+      express: 4.21.0
       multer: 1.4.4-lts.1
-      tslib: 2.6.3
+      tslib: 2.7.0
     transitivePeerDependencies:
       - supports-color
 
-  '@nestjs/testing@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10))':
+  '@nestjs/testing@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4))':
     dependencies:
-      '@nestjs/common': 10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1)
-      '@nestjs/core': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
-      tslib: 2.6.3
+      '@nestjs/common': 10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      '@nestjs/core': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.4)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      tslib: 2.7.0
     optionalDependencies:
-      '@nestjs/platform-express': 10.3.10(@nestjs/common@10.3.10(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)
+      '@nestjs/platform-express': 10.4.4(@nestjs/common@10.4.4(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.4)
 
-  '@noble/hashes@1.4.0': {}
+  '@noble/hashes@1.5.0': {}
 
   '@nodelib/fs.scandir@2.1.5':
     dependencies:
@@ -14857,7 +13858,7 @@ snapshots:
     dependencies:
       agent-base: 7.1.0
       http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.4
+      https-proxy-agent: 7.0.5
       lru-cache: 10.2.2
       socks-proxy-agent: 8.0.2
     transitivePeerDependencies:
@@ -14865,7 +13866,7 @@ snapshots:
 
   '@npmcli/fs@3.1.0':
     dependencies:
-      semver: 7.6.0
+      semver: 7.6.3
 
   '@nsfw-filter/gif-frames@1.0.2':
     dependencies:
@@ -14970,7 +13971,7 @@ snapshots:
       '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0)
       '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0)
       '@opentelemetry/semantic-conventions': 1.25.1
-      semver: 7.6.0
+      semver: 7.6.3
     transitivePeerDependencies:
       - supports-color
 
@@ -15062,7 +14063,7 @@ snapshots:
       '@types/shimmer': 1.0.5
       import-in-the-middle: 1.7.1
       require-in-the-middle: 7.3.0
-      semver: 7.6.0
+      semver: 7.6.3
       shimmer: 1.2.1
     transitivePeerDependencies:
       - supports-color
@@ -15075,7 +14076,7 @@ snapshots:
       '@types/shimmer': 1.0.5
       import-in-the-middle: 1.10.0
       require-in-the-middle: 7.3.0
-      semver: 7.6.0
+      semver: 7.6.3
       shimmer: 1.2.1
     transitivePeerDependencies:
       - supports-color
@@ -15121,27 +14122,27 @@ snapshots:
     dependencies:
       '@peculiar/asn1-schema': 2.3.8
       asn1js: 3.0.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@peculiar/asn1-ecc@2.3.8':
     dependencies:
       '@peculiar/asn1-schema': 2.3.8
       '@peculiar/asn1-x509': 2.3.8
       asn1js: 3.0.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@peculiar/asn1-rsa@2.3.8':
     dependencies:
       '@peculiar/asn1-schema': 2.3.8
       '@peculiar/asn1-x509': 2.3.8
       asn1js: 3.0.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@peculiar/asn1-schema@2.3.8':
     dependencies:
       asn1js: 3.0.5
       pvtsutils: 1.3.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@peculiar/asn1-x509@2.3.8':
     dependencies:
@@ -15149,7 +14150,7 @@ snapshots:
       asn1js: 3.0.5
       ipaddr.js: 2.2.0
       pvtsutils: 1.3.5
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@peertube/http-signature@1.7.0':
     dependencies:
@@ -15170,7 +14171,7 @@ snapshots:
 
   '@readme/better-ajv-errors@1.6.0(ajv@8.17.1)':
     dependencies:
-      '@babel/code-frame': 7.23.5
+      '@babel/code-frame': 7.24.7
       '@babel/runtime': 7.23.4
       '@humanwhocodes/momoa': 2.0.4
       ajv: 8.17.1
@@ -15182,92 +14183,96 @@ snapshots:
   '@readme/json-schema-ref-parser@1.2.0':
     dependencies:
       '@jsdevtools/ono': 7.1.3
-      '@types/json-schema': 7.0.12
+      '@types/json-schema': 7.0.15
       call-me-maybe: 1.0.2
       js-yaml: 4.1.0
 
-  '@readme/openapi-parser@2.5.0(openapi-types@12.1.3)':
+  '@readme/openapi-parser@2.6.0(openapi-types@12.1.3)':
     dependencies:
-      '@apidevtools/openapi-schemas': 2.1.0
       '@apidevtools/swagger-methods': 3.0.2
       '@jsdevtools/ono': 7.1.3
       '@readme/better-ajv-errors': 1.6.0(ajv@8.17.1)
       '@readme/json-schema-ref-parser': 1.2.0
+      '@readme/openapi-schemas': 3.1.0
       ajv: 8.17.1
       ajv-draft-04: 1.0.0(ajv@8.17.1)
       call-me-maybe: 1.0.2
       openapi-types: 12.1.3
 
-  '@rollup/plugin-json@6.1.0(rollup@4.19.1)':
+  '@readme/openapi-schemas@3.1.0': {}
+
+  '@rollup/plugin-json@6.1.0(rollup@4.22.5)':
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.19.1)
+      '@rollup/pluginutils': 5.1.2(rollup@4.22.5)
     optionalDependencies:
-      rollup: 4.19.1
+      rollup: 4.22.5
 
-  '@rollup/plugin-replace@5.0.7(rollup@4.19.1)':
+  '@rollup/plugin-replace@5.0.7(rollup@4.22.5)':
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.19.1)
+      '@rollup/pluginutils': 5.1.2(rollup@4.22.5)
       magic-string: 0.30.10
     optionalDependencies:
-      rollup: 4.19.1
+      rollup: 4.22.5
 
-  '@rollup/pluginutils@5.1.0(rollup@4.19.1)':
+  '@rollup/pluginutils@5.1.2(rollup@4.22.5)':
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.6
       estree-walker: 2.0.2
       picomatch: 2.3.1
     optionalDependencies:
-      rollup: 4.19.1
+      rollup: 4.22.5
 
-  '@rollup/rollup-android-arm-eabi@4.19.1':
+  '@rollup/rollup-android-arm-eabi@4.22.5':
     optional: true
 
-  '@rollup/rollup-android-arm64@4.19.1':
+  '@rollup/rollup-android-arm64@4.22.5':
     optional: true
 
-  '@rollup/rollup-darwin-arm64@4.19.1':
+  '@rollup/rollup-darwin-arm64@4.22.5':
     optional: true
 
-  '@rollup/rollup-darwin-x64@4.19.1':
+  '@rollup/rollup-darwin-x64@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-arm-gnueabihf@4.19.1':
+  '@rollup/rollup-linux-arm-gnueabihf@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-arm-musleabihf@4.19.1':
+  '@rollup/rollup-linux-arm-musleabihf@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-arm64-gnu@4.19.1':
+  '@rollup/rollup-linux-arm64-gnu@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-arm64-musl@4.19.1':
+  '@rollup/rollup-linux-arm64-musl@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
+  '@rollup/rollup-linux-powerpc64le-gnu@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-riscv64-gnu@4.19.1':
+  '@rollup/rollup-linux-riscv64-gnu@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-s390x-gnu@4.19.1':
+  '@rollup/rollup-linux-s390x-gnu@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-x64-gnu@4.19.1':
+  '@rollup/rollup-linux-x64-gnu@4.22.5':
     optional: true
 
-  '@rollup/rollup-linux-x64-musl@4.19.1':
+  '@rollup/rollup-linux-x64-musl@4.22.5':
     optional: true
 
-  '@rollup/rollup-win32-arm64-msvc@4.19.1':
+  '@rollup/rollup-win32-arm64-msvc@4.22.5':
     optional: true
 
-  '@rollup/rollup-win32-ia32-msvc@4.19.1':
+  '@rollup/rollup-win32-ia32-msvc@4.22.5':
     optional: true
 
-  '@rollup/rollup-win32-x64-msvc@4.19.1':
+  '@rollup/rollup-win32-x64-msvc@4.22.5':
     optional: true
 
-  '@rushstack/node-core-library@5.5.1(@types/node@20.14.12)':
+  '@rtsao/scc@1.1.0': {}
+
+  '@rushstack/node-core-library@5.9.0(@types/node@20.14.12)':
     dependencies:
       ajv: 8.13.0
       ajv-draft-04: 1.0.0(ajv@8.13.0)
@@ -15285,16 +14290,16 @@ snapshots:
       resolve: 1.22.8
       strip-json-comments: 3.1.1
 
-  '@rushstack/terminal@0.13.3(@types/node@20.14.12)':
+  '@rushstack/terminal@0.14.2(@types/node@20.14.12)':
     dependencies:
-      '@rushstack/node-core-library': 5.5.1(@types/node@20.14.12)
+      '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12)
       supports-color: 8.1.1
     optionalDependencies:
       '@types/node': 20.14.12
 
-  '@rushstack/ts-command-line@4.22.3(@types/node@20.14.12)':
+  '@rushstack/ts-command-line@4.22.8(@types/node@20.14.12)':
     dependencies:
-      '@rushstack/terminal': 0.13.3(@types/node@20.14.12)
+      '@rushstack/terminal': 0.14.2(@types/node@20.14.12)
       '@types/argparse': 1.0.38
       argparse: 1.0.10
       string-argv: 0.3.1
@@ -15371,14 +14376,41 @@ snapshots:
     dependencies:
       '@sentry/types': 8.20.0
 
-  '@shikijs/core@1.12.0':
+  '@shikijs/core@1.21.0':
+    dependencies:
+      '@shikijs/engine-javascript': 1.21.0
+      '@shikijs/engine-oniguruma': 1.21.0
+      '@shikijs/types': 1.21.0
+      '@shikijs/vscode-textmate': 9.2.2
+      '@types/hast': 3.0.4
+      hast-util-to-html: 9.0.3
+
+  '@shikijs/engine-javascript@1.21.0':
     dependencies:
+      '@shikijs/types': 1.21.0
+      '@shikijs/vscode-textmate': 9.2.2
+      oniguruma-to-js: 0.4.3
+
+  '@shikijs/engine-oniguruma@1.21.0':
+    dependencies:
+      '@shikijs/types': 1.21.0
+      '@shikijs/vscode-textmate': 9.2.2
+
+  '@shikijs/types@1.21.0':
+    dependencies:
+      '@shikijs/vscode-textmate': 9.2.2
       '@types/hast': 3.0.4
 
+  '@shikijs/vscode-textmate@9.2.2': {}
+
   '@sideway/address@4.1.4':
     dependencies:
       '@hapi/hoek': 9.3.0
 
+  '@sideway/address@4.1.5':
+    dependencies:
+      '@hapi/hoek': 9.3.0
+
   '@sideway/formula@3.0.1': {}
 
   '@sideway/pinpoint@2.0.0': {}
@@ -15407,8 +14439,6 @@ snapshots:
 
   '@sindresorhus/is@7.0.0': {}
 
-  '@sindresorhus/merge-streams@2.3.0': {}
-
   '@sindresorhus/merge-streams@4.0.0': {}
 
   '@sinonjs/commons@2.0.0':
@@ -15438,21 +14468,21 @@ snapshots:
   '@smithy/abort-controller@2.2.0':
     dependencies:
       '@smithy/types': 2.12.0
-      tslib: 2.6.2
+      tslib: 2.7.0
 
   '@smithy/abort-controller@3.1.1':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/chunked-blob-reader-native@3.0.0':
     dependencies:
       '@smithy/util-base64': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/chunked-blob-reader@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/config-resolver@3.0.5':
     dependencies:
@@ -15460,7 +14490,7 @@ snapshots:
       '@smithy/types': 3.3.0
       '@smithy/util-config-provider': 3.0.0
       '@smithy/util-middleware': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/core@2.3.1':
     dependencies:
@@ -15471,7 +14501,7 @@ snapshots:
       '@smithy/smithy-client': 3.1.11
       '@smithy/types': 3.3.0
       '@smithy/util-middleware': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/credential-provider-imds@3.2.0':
     dependencies:
@@ -15479,37 +14509,37 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/types': 3.3.0
       '@smithy/url-parser': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/eventstream-codec@3.1.2':
     dependencies:
       '@aws-crypto/crc32': 5.2.0
       '@smithy/types': 3.3.0
       '@smithy/util-hex-encoding': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/eventstream-serde-browser@3.0.5':
     dependencies:
       '@smithy/eventstream-serde-universal': 3.0.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/eventstream-serde-config-resolver@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/eventstream-serde-node@3.0.4':
     dependencies:
       '@smithy/eventstream-serde-universal': 3.0.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/eventstream-serde-universal@3.0.4':
     dependencies:
       '@smithy/eventstream-codec': 3.1.2
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/fetch-http-handler@3.2.4':
     dependencies:
@@ -15517,52 +14547,52 @@ snapshots:
       '@smithy/querystring-builder': 3.0.3
       '@smithy/types': 3.3.0
       '@smithy/util-base64': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/hash-blob-browser@3.1.2':
     dependencies:
       '@smithy/chunked-blob-reader': 3.0.0
       '@smithy/chunked-blob-reader-native': 3.0.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/hash-node@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
       '@smithy/util-buffer-from': 3.0.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/hash-stream-node@3.1.2':
     dependencies:
       '@smithy/types': 3.3.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/invalid-dependency@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/is-array-buffer@2.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/is-array-buffer@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/md5-js@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/middleware-content-length@3.0.5':
     dependencies:
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/middleware-endpoint@3.1.0':
     dependencies:
@@ -15572,7 +14602,7 @@ snapshots:
       '@smithy/types': 3.3.0
       '@smithy/url-parser': 3.0.3
       '@smithy/util-middleware': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/middleware-retry@3.0.13':
     dependencies:
@@ -15583,25 +14613,25 @@ snapshots:
       '@smithy/types': 3.3.0
       '@smithy/util-middleware': 3.0.3
       '@smithy/util-retry': 3.0.3
-      tslib: 2.6.3
+      tslib: 2.7.0
       uuid: 9.0.1
 
   '@smithy/middleware-serde@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/middleware-stack@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/node-config-provider@3.1.4':
     dependencies:
       '@smithy/property-provider': 3.1.3
       '@smithy/shared-ini-file-loader': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/node-http-handler@2.5.0':
     dependencies:
@@ -15617,39 +14647,39 @@ snapshots:
       '@smithy/protocol-http': 4.1.0
       '@smithy/querystring-builder': 3.0.3
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/property-provider@3.1.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/protocol-http@3.3.0':
     dependencies:
       '@smithy/types': 2.12.0
-      tslib: 2.6.2
+      tslib: 2.7.0
 
   '@smithy/protocol-http@4.1.0':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/querystring-builder@2.2.0':
     dependencies:
       '@smithy/types': 2.12.0
       '@smithy/util-uri-escape': 2.2.0
-      tslib: 2.6.2
+      tslib: 2.7.0
 
   '@smithy/querystring-builder@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
       '@smithy/util-uri-escape': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/querystring-parser@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/service-error-classification@3.0.3':
     dependencies:
@@ -15658,7 +14688,7 @@ snapshots:
   '@smithy/shared-ini-file-loader@3.1.4':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/signature-v4@4.1.0':
     dependencies:
@@ -15669,7 +14699,7 @@ snapshots:
       '@smithy/util-middleware': 3.0.3
       '@smithy/util-uri-escape': 3.0.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/smithy-client@3.1.11':
     dependencies:
@@ -15678,49 +14708,49 @@ snapshots:
       '@smithy/protocol-http': 4.1.0
       '@smithy/types': 3.3.0
       '@smithy/util-stream': 3.1.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/types@2.12.0':
     dependencies:
-      tslib: 2.6.2
+      tslib: 2.7.0
 
   '@smithy/types@3.3.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/url-parser@3.0.3':
     dependencies:
       '@smithy/querystring-parser': 3.0.3
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-base64@3.0.0':
     dependencies:
       '@smithy/util-buffer-from': 3.0.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-body-length-browser@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-body-length-node@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-buffer-from@2.0.0':
     dependencies:
       '@smithy/is-array-buffer': 2.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-buffer-from@3.0.0':
     dependencies:
       '@smithy/is-array-buffer': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-config-provider@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-defaults-mode-browser@3.0.13':
     dependencies:
@@ -15728,7 +14758,7 @@ snapshots:
       '@smithy/smithy-client': 3.1.11
       '@smithy/types': 3.3.0
       bowser: 2.11.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-defaults-mode-node@3.0.13':
     dependencies:
@@ -15738,28 +14768,28 @@ snapshots:
       '@smithy/property-provider': 3.1.3
       '@smithy/smithy-client': 3.1.11
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-endpoints@2.0.5':
     dependencies:
       '@smithy/node-config-provider': 3.1.4
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-hex-encoding@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-middleware@3.0.3':
     dependencies:
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-retry@3.0.3':
     dependencies:
       '@smithy/service-error-classification': 3.0.3
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-stream@3.1.3':
     dependencies:
@@ -15770,162 +14800,152 @@ snapshots:
       '@smithy/util-buffer-from': 3.0.0
       '@smithy/util-hex-encoding': 3.0.0
       '@smithy/util-utf8': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-uri-escape@2.2.0':
     dependencies:
-      tslib: 2.6.2
+      tslib: 2.7.0
 
   '@smithy/util-uri-escape@3.0.0':
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-utf8@2.0.0':
     dependencies:
       '@smithy/util-buffer-from': 2.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-utf8@3.0.0':
     dependencies:
       '@smithy/util-buffer-from': 3.0.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@smithy/util-waiter@3.1.2':
     dependencies:
       '@smithy/abort-controller': 3.1.1
       '@smithy/types': 3.3.0
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   '@sqltools/formatter@1.2.5': {}
 
-  '@storybook/addon-actions@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-actions@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
       '@types/uuid': 9.0.8
       dequal: 2.0.3
       polished: 4.2.2
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       uuid: 9.0.1
 
-  '@storybook/addon-backgrounds@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-backgrounds@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
       memoizerific: 1.11.3
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
 
-  '@storybook/addon-controls@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-controls@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
+      '@storybook/global': 5.0.0
       dequal: 2.0.3
       lodash: 4.17.21
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
 
-  '@storybook/addon-docs@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-docs@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@babel/core': 7.24.7
       '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@18.3.1)
-      '@storybook/blocks': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/csf-plugin': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/blocks': 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/csf-plugin': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/global': 5.0.0
-      '@storybook/react-dom-shim': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/react-dom-shim': 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@types/react': 18.0.28
       fs-extra: 11.1.1
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       rehype-external-links: 3.0.0
       rehype-slug: 6.0.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
-    transitivePeerDependencies:
-      - supports-color
 
-  '@storybook/addon-essentials@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
-    dependencies:
-      '@storybook/addon-actions': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-backgrounds': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-controls': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-docs': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-highlight': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-measure': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-outline': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-toolbars': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/addon-viewport': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+  '@storybook/addon-essentials@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+    dependencies:
+      '@storybook/addon-actions': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-backgrounds': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-controls': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-docs': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-highlight': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-measure': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-outline': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-toolbars': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-viewport': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
-    transitivePeerDependencies:
-      - supports-color
 
-  '@storybook/addon-highlight@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-highlight@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/addon-interactions@8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))':
+  '@storybook/addon-interactions@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
-      '@storybook/instrumenter': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/test': 8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
+      '@storybook/instrumenter': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/test': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       polished: 4.2.2
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
-    transitivePeerDependencies:
-      - '@jest/globals'
-      - '@types/bun'
-      - '@types/jest'
-      - jest
-      - vitest
 
-  '@storybook/addon-links@8.2.6(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-links@8.3.4(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/csf': 0.1.11
       '@storybook/global': 5.0.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
     optionalDependencies:
       react: 18.3.1
 
-  '@storybook/addon-mdx-gfm@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-mdx-gfm@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       remark-gfm: 4.0.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
     transitivePeerDependencies:
       - supports-color
 
-  '@storybook/addon-measure@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-measure@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       tiny-invariant: 1.3.3
 
-  '@storybook/addon-outline@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-outline@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
 
-  '@storybook/addon-storysource@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-storysource@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@storybook/source-loader': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/source-loader': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       estraverse: 5.3.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       tiny-invariant: 1.3.3
 
-  '@storybook/addon-toolbars@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-toolbars@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/addon-viewport@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/addon-viewport@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       memoizerific: 1.11.3
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/blocks@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/blocks@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/csf': 0.1.11
       '@storybook/global': 5.0.0
-      '@storybook/icons': 1.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+      '@storybook/icons': 1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       '@types/lodash': 4.14.191
       color-convert: 2.0.1
       dequal: 2.0.3
@@ -15934,7 +14954,7 @@ snapshots:
       memoizerific: 1.11.3
       polished: 4.2.2
       react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       telejson: 7.2.0
       ts-dedent: 2.2.0
       util-deprecate: 1.0.2
@@ -15942,221 +14962,45 @@ snapshots:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
 
-  '@storybook/builder-manager@8.1.11(encoding@0.1.13)(prettier@3.3.3)':
-    dependencies:
-      '@fal-works/esbuild-plugin-global-externals': 2.1.2
-      '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/manager': 8.1.11
-      '@storybook/node-logger': 8.1.11
-      '@types/ejs': 3.1.2
-      '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.19.11)
-      browser-assert: 1.2.1
-      ejs: 3.1.10
-      esbuild: 0.19.11
-      esbuild-plugin-alias: 0.2.1
-      express: 4.19.2
-      fs-extra: 11.1.1
-      process: 0.11.10
-      util: 0.12.5
-    transitivePeerDependencies:
-      - encoding
-      - prettier
-      - supports-color
-
-  '@storybook/builder-vite@8.1.11(encoding@0.1.13)(prettier@3.3.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))':
-    dependencies:
-      '@storybook/channels': 8.1.11
-      '@storybook/client-logger': 8.1.11
-      '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/core-events': 8.1.11
-      '@storybook/csf-plugin': 8.1.11
-      '@storybook/node-logger': 8.1.11
-      '@storybook/preview': 8.1.11
-      '@storybook/preview-api': 8.1.11
-      '@storybook/types': 8.1.11
-      '@types/find-cache-dir': 3.2.1
-      browser-assert: 1.2.1
-      es-module-lexer: 1.5.4
-      express: 4.19.2
-      find-cache-dir: 3.3.2
-      fs-extra: 11.1.1
-      magic-string: 0.30.10
-      ts-dedent: 2.2.0
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
-    optionalDependencies:
-      typescript: 5.5.4
-    transitivePeerDependencies:
-      - encoding
-      - prettier
-      - supports-color
-
-  '@storybook/builder-vite@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))':
+  '@storybook/builder-vite@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))':
     dependencies:
-      '@storybook/csf-plugin': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/csf-plugin': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@types/find-cache-dir': 3.2.1
       browser-assert: 1.2.1
       es-module-lexer: 1.5.4
-      express: 4.19.2
-      find-cache-dir: 3.3.2
-      fs-extra: 11.1.1
-      magic-string: 0.30.10
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-      ts-dedent: 2.2.0
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
-    optionalDependencies:
-      typescript: 5.5.4
-    transitivePeerDependencies:
-      - supports-color
-
-  '@storybook/channels@8.1.11':
-    dependencies:
-      '@storybook/client-logger': 8.1.11
-      '@storybook/core-events': 8.1.11
-      '@storybook/global': 5.0.0
-      telejson: 7.2.0
-      tiny-invariant: 1.3.3
-
-  '@storybook/client-logger@8.1.11':
-    dependencies:
-      '@storybook/global': 5.0.0
-
-  '@storybook/codemod@8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/preset-env': 7.24.7(@babel/core@7.24.7)
-      '@babel/types': 7.24.7
-      '@storybook/core': 8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-      '@storybook/csf': 0.1.11
-      '@types/cross-spawn': 6.0.2
-      cross-spawn: 7.0.3
-      globby: 14.0.1
-      jscodeshift: 0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7))
-      lodash: 4.17.21
-      prettier: 3.3.3
-      recast: 0.23.6
-      tiny-invariant: 1.3.3
-    transitivePeerDependencies:
-      - bufferutil
-      - supports-color
-      - utf-8-validate
-
-  '@storybook/components@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
-    dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-
-  '@storybook/core-common@8.1.11(encoding@0.1.13)(prettier@3.3.3)':
-    dependencies:
-      '@storybook/core-events': 8.1.11
-      '@storybook/csf-tools': 8.1.11
-      '@storybook/node-logger': 8.1.11
-      '@storybook/types': 8.1.11
-      '@yarnpkg/fslib': 2.10.3
-      '@yarnpkg/libzip': 2.3.0
-      chalk: 4.1.2
-      cross-spawn: 7.0.3
-      esbuild: 0.19.11
-      esbuild-register: 3.5.0(esbuild@0.19.11)
-      execa: 5.1.1
-      file-system-cache: 2.3.0
+      express: 4.21.0
       find-cache-dir: 3.3.2
-      find-up: 5.0.0
       fs-extra: 11.1.1
-      glob: 10.3.10
-      handlebars: 4.7.7
-      lazy-universal-dotenv: 4.0.0
-      node-fetch: 2.7.0(encoding@0.1.13)
-      picomatch: 2.3.1
-      pkg-dir: 5.0.0
-      prettier-fallback: prettier@3.3.3
-      pretty-hrtime: 1.0.3
-      resolve-from: 5.0.0
-      semver: 7.6.0
-      tempy: 3.1.0
-      tiny-invariant: 1.3.3
+      magic-string: 0.30.11
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
-      util: 0.12.5
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
     optionalDependencies:
-      prettier: 3.3.3
+      typescript: 5.6.2
     transitivePeerDependencies:
-      - encoding
       - supports-color
 
-  '@storybook/core-events@8.1.11':
-    dependencies:
-      '@storybook/csf': 0.1.9
-      ts-dedent: 2.2.0
-
-  '@storybook/core-events@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/components@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/core-server@8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)':
+  '@storybook/core-events@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@aw-web-design/x-default-browser': 1.4.126
-      '@babel/core': 7.24.7
-      '@babel/parser': 7.24.7
-      '@discoveryjs/json-ext': 0.5.7
-      '@storybook/builder-manager': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/channels': 8.1.11
-      '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/core-events': 8.1.11
-      '@storybook/csf': 0.1.9
-      '@storybook/csf-tools': 8.1.11
-      '@storybook/docs-mdx': 3.1.0-next.0
-      '@storybook/global': 5.0.0
-      '@storybook/manager': 8.1.11
-      '@storybook/manager-api': 8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@storybook/node-logger': 8.1.11
-      '@storybook/preview-api': 8.1.11
-      '@storybook/telemetry': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/types': 8.1.11
-      '@types/detect-port': 1.3.2
-      '@types/diff': 5.2.1
-      '@types/node': 18.17.15
-      '@types/pretty-hrtime': 1.0.1
-      '@types/semver': 7.5.8
-      better-opn: 3.0.2
-      chalk: 4.1.2
-      cli-table3: 0.6.3
-      compression: 1.7.4
-      detect-port: 1.5.1
-      diff: 5.2.0
-      express: 4.19.2
-      fs-extra: 11.1.1
-      globby: 14.0.1
-      lodash: 4.17.21
-      open: 8.4.2
-      pretty-hrtime: 1.0.3
-      prompts: 2.4.2
-      read-pkg-up: 7.0.1
-      semver: 7.6.0
-      telejson: 7.2.0
-      tiny-invariant: 1.3.3
-      ts-dedent: 2.2.0
-      util: 0.12.5
-      util-deprecate: 1.0.2
-      watchpack: 2.4.0
-      ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-    transitivePeerDependencies:
-      - bufferutil
-      - encoding
-      - prettier
-      - react
-      - react-dom
-      - supports-color
-      - utf-8-validate
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/core@8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
+  '@storybook/core@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
     dependencies:
       '@storybook/csf': 0.1.11
       '@types/express': 4.17.21
-      '@types/node': 18.17.15
+      better-opn: 3.0.2
       browser-assert: 1.2.1
-      esbuild: 0.19.11
-      esbuild-register: 3.5.0(esbuild@0.19.11)
-      express: 4.19.2
+      esbuild: 0.23.1
+      esbuild-register: 3.5.0(esbuild@0.23.1)
+      express: 4.21.0
+      jsdoc-type-pratt-parser: 4.1.0
       process: 0.11.10
       recast: 0.23.6
+      semver: 7.6.3
       util: 0.12.5
       ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
     transitivePeerDependencies:
@@ -16164,305 +15008,153 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  '@storybook/csf-plugin@8.1.11':
-    dependencies:
-      '@storybook/csf-tools': 8.1.11
-      unplugin: 1.4.0
-    transitivePeerDependencies:
-      - supports-color
-
-  '@storybook/csf-plugin@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/csf-plugin@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       unplugin: 1.4.0
 
-  '@storybook/csf-tools@8.1.11':
-    dependencies:
-      '@babel/generator': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-      '@storybook/csf': 0.1.9
-      '@storybook/types': 8.1.11
-      fs-extra: 11.1.1
-      recast: 0.23.6
-      ts-dedent: 2.2.0
-    transitivePeerDependencies:
-      - supports-color
-
   '@storybook/csf@0.1.11':
     dependencies:
       type-fest: 2.19.0
 
-  '@storybook/csf@0.1.9':
-    dependencies:
-      type-fest: 2.19.0
-
-  '@storybook/docs-mdx@3.1.0-next.0': {}
-
-  '@storybook/docs-tools@8.1.11(encoding@0.1.13)(prettier@3.3.3)':
-    dependencies:
-      '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/core-events': 8.1.11
-      '@storybook/preview-api': 8.1.11
-      '@storybook/types': 8.1.11
-      '@types/doctrine': 0.0.3
-      assert: 2.1.0
-      doctrine: 3.0.0
-      lodash: 4.17.21
-    transitivePeerDependencies:
-      - encoding
-      - prettier
-      - supports-color
-
   '@storybook/global@5.0.0': {}
 
-  '@storybook/icons@1.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@storybook/icons@1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
     dependencies:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
 
-  '@storybook/instrumenter@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/instrumenter@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/global': 5.0.0
-      '@vitest/utils': 1.6.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      '@vitest/utils': 2.1.1
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       util: 0.12.5
 
-  '@storybook/manager-api@8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
-    dependencies:
-      '@storybook/channels': 8.1.11
-      '@storybook/client-logger': 8.1.11
-      '@storybook/core-events': 8.1.11
-      '@storybook/csf': 0.1.9
-      '@storybook/global': 5.0.0
-      '@storybook/icons': 1.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@storybook/router': 8.1.11
-      '@storybook/theming': 8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      '@storybook/types': 8.1.11
-      dequal: 2.0.3
-      lodash: 4.17.21
-      memoizerific: 1.11.3
-      store2: 2.14.2
-      telejson: 7.2.0
-      ts-dedent: 2.2.0
-    transitivePeerDependencies:
-      - react
-      - react-dom
-
-  '@storybook/manager-api@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
-    dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-
-  '@storybook/manager@8.1.11': {}
-
-  '@storybook/node-logger@8.1.11': {}
-
-  '@storybook/preview-api@8.1.11':
+  '@storybook/manager-api@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@storybook/channels': 8.1.11
-      '@storybook/client-logger': 8.1.11
-      '@storybook/core-events': 8.1.11
-      '@storybook/csf': 0.1.9
-      '@storybook/global': 5.0.0
-      '@storybook/types': 8.1.11
-      '@types/qs': 6.9.7
-      dequal: 2.0.3
-      lodash: 4.17.21
-      memoizerific: 1.11.3
-      qs: 6.11.1
-      tiny-invariant: 1.3.3
-      ts-dedent: 2.2.0
-      util-deprecate: 1.0.2
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/preview-api@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/preview-api@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-
-  '@storybook/preview@8.1.11': {}
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/react-dom-shim@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/react-dom-shim@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/react-vite@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.19.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))':
+  '@storybook/react-vite@8.3.4(@storybook/test@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.5)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))':
     dependencies:
-      '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.1(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
-      '@rollup/pluginutils': 5.1.0(rollup@4.19.1)
-      '@storybook/builder-vite': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
-      '@storybook/react': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)
+      '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))
+      '@rollup/pluginutils': 5.1.2(rollup@4.22.5)
+      '@storybook/builder-vite': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))
+      '@storybook/react': 8.3.4(@storybook/test@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)
       find-up: 5.0.0
-      magic-string: 0.30.10
+      magic-string: 0.30.11
       react: 18.3.1
       react-docgen: 7.0.1
       react-dom: 18.3.1(react@18.3.1)
       resolve: 1.22.8
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       tsconfig-paths: 4.2.0
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
     transitivePeerDependencies:
       - '@preact/preset-vite'
+      - '@storybook/test'
       - rollup
       - supports-color
       - typescript
       - vite-plugin-glimmerx
 
-  '@storybook/react@8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.5.4)':
+  '@storybook/react@8.3.4(@storybook/test@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)':
     dependencies:
-      '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/components': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/preview-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/react-dom-shim': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/theming': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/manager-api': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/preview-api': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/react-dom-shim': 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/theming': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@types/escodegen': 0.0.6
       '@types/estree': 0.0.51
-      '@types/node': 18.17.15
+      '@types/node': 22.5.5
       acorn: 7.4.1
       acorn-jsx: 5.3.2(acorn@7.4.1)
       acorn-walk: 7.2.0
       escodegen: 2.1.0
       html-tags: 3.2.0
-      lodash: 4.17.21
       prop-types: 15.8.1
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
       react-element-to-jsx-string: 15.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
-      semver: 7.6.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      semver: 7.6.3
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
       type-fest: 2.19.0
       util-deprecate: 1.0.2
     optionalDependencies:
-      typescript: 5.5.4
-
-  '@storybook/router@8.1.11':
-    dependencies:
-      '@storybook/client-logger': 8.1.11
-      memoizerific: 1.11.3
-      qs: 6.11.1
+      '@storybook/test': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      typescript: 5.6.2
 
-  '@storybook/source-loader@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/source-loader@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/csf': 0.1.11
       estraverse: 5.3.0
       lodash: 4.17.21
       prettier: 3.3.3
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-
-  '@storybook/telemetry@8.1.11(encoding@0.1.13)(prettier@3.3.3)':
-    dependencies:
-      '@storybook/client-logger': 8.1.11
-      '@storybook/core-common': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/csf-tools': 8.1.11
-      chalk: 4.1.2
-      detect-package-manager: 2.0.1
-      fetch-retry: 5.0.4
-      fs-extra: 11.1.1
-      read-pkg-up: 7.0.1
-    transitivePeerDependencies:
-      - encoding
-      - prettier
-      - supports-color
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/test@8.2.6(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))':
+  '@storybook/test@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
       '@storybook/csf': 0.1.11
-      '@storybook/instrumenter': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@testing-library/dom': 10.1.0
-      '@testing-library/jest-dom': 6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))
-      '@testing-library/user-event': 14.5.2(@testing-library/dom@10.1.0)
-      '@vitest/expect': 1.6.0
-      '@vitest/spy': 1.6.0
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-      util: 0.12.5
-    transitivePeerDependencies:
-      - '@jest/globals'
-      - '@types/bun'
-      - '@types/jest'
-      - jest
-      - vitest
-
-  '@storybook/theming@8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
-    dependencies:
-      '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1)
-      '@storybook/client-logger': 8.1.11
       '@storybook/global': 5.0.0
-      memoizerific: 1.11.3
-    optionalDependencies:
-      react: 18.3.1
-      react-dom: 18.3.1(react@18.3.1)
-
-  '@storybook/theming@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
-    dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      '@storybook/instrumenter': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@testing-library/dom': 10.4.0
+      '@testing-library/jest-dom': 6.5.0
+      '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0)
+      '@vitest/expect': 2.0.5
+      '@vitest/spy': 2.0.5
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      util: 0.12.5
 
-  '@storybook/types@8.1.11':
+  '@storybook/theming@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@storybook/channels': 8.1.11
-      '@types/express': 4.17.17
-      file-system-cache: 2.3.0
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/types@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+  '@storybook/types@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/vue3-vite@8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))':
+  '@storybook/vue3-vite@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.11(typescript@5.6.2))':
     dependencies:
-      '@storybook/builder-vite': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(typescript@5.5.4)(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))
-      '@storybook/core-server': 8.1.11(bufferutil@4.0.8)(encoding@0.1.13)(prettier@3.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(utf-8-validate@6.0.4)
-      '@storybook/types': 8.1.11
-      '@storybook/vue3': 8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.37(typescript@5.5.4))
+      '@storybook/builder-vite': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))
+      '@storybook/vue3': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.11(typescript@5.6.2))
       find-package-json: 1.2.0
-      magic-string: 0.30.10
-      typescript: 5.5.4
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
-      vue-component-meta: 2.0.16(typescript@5.5.4)
-      vue-docgen-api: 4.75.1(vue@3.4.37(typescript@5.5.4))
+      magic-string: 0.30.11
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      typescript: 5.6.2
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
+      vue-component-meta: 2.0.16(typescript@5.6.2)
+      vue-docgen-api: 4.75.1(vue@3.5.11(typescript@5.6.2))
     transitivePeerDependencies:
       - '@preact/preset-vite'
-      - bufferutil
-      - encoding
-      - prettier
-      - react
-      - react-dom
       - supports-color
-      - utf-8-validate
       - vite-plugin-glimmerx
       - vue
 
-  '@storybook/vue3@8.1.11(encoding@0.1.13)(prettier@3.3.3)(vue@3.4.37(typescript@5.5.4))':
+  '@storybook/vue3@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.11(typescript@5.6.2))':
     dependencies:
-      '@storybook/docs-tools': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
+      '@storybook/components': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 8.1.11
-      '@storybook/types': 8.1.11
-      '@vue/compiler-core': 3.4.37
-      lodash: 4.17.21
-      ts-dedent: 2.2.0
-      type-fest: 2.19.0
-      vue: 3.4.37(typescript@5.5.4)
-      vue-component-type-helpers: 2.1.6
-    transitivePeerDependencies:
-      - encoding
-      - prettier
-      - supports-color
-
-  '@storybook/vue3@8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.4.37(typescript@5.5.4))':
-    dependencies:
-      '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/global': 5.0.0
-      '@storybook/manager-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/preview-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/theming': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@vue/compiler-core': 3.4.31
-      lodash: 4.17.21
-      storybook: 8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      '@storybook/manager-api': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/preview-api': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/theming': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@vue/compiler-core': 3.5.10
+      storybook: 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.11(typescript@5.6.2)
       vue-component-type-helpers: 2.1.6
 
   '@swc/cli@0.3.12(@swc/core@1.6.6)(chokidar@3.5.3)':
@@ -16734,7 +15426,7 @@ snapshots:
       - encoding
       - seedrandom
 
-  '@testing-library/dom@10.1.0':
+  '@testing-library/dom@10.4.0':
     dependencies:
       '@babel/code-frame': 7.24.7
       '@babel/runtime': 7.23.4
@@ -16756,34 +15448,28 @@ snapshots:
       lz-string: 1.5.0
       pretty-format: 27.5.1
 
-  '@testing-library/jest-dom@6.4.5(@jest/globals@29.7.0)(@types/jest@29.5.12)(jest@29.7.0(@types/node@20.14.12))(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))':
+  '@testing-library/jest-dom@6.5.0':
     dependencies:
-      '@adobe/css-tools': 4.3.3
-      '@babel/runtime': 7.23.4
+      '@adobe/css-tools': 4.4.0
       aria-query: 5.3.0
       chalk: 3.0.0
       css.escape: 1.5.1
       dom-accessibility-api: 0.6.3
       lodash: 4.17.21
       redent: 3.0.0
-    optionalDependencies:
-      '@jest/globals': 29.7.0
-      '@types/jest': 29.5.12
-      jest: 29.7.0(@types/node@20.14.12)
-      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)
 
-  '@testing-library/user-event@14.5.2(@testing-library/dom@10.1.0)':
+  '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)':
     dependencies:
-      '@testing-library/dom': 10.1.0
+      '@testing-library/dom': 10.4.0
 
-  '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.4.37)(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))':
+  '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.11)(@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2))':
     dependencies:
       '@babel/runtime': 7.23.4
       '@testing-library/dom': 9.3.4
-      '@vue/test-utils': 2.4.1(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))
-      vue: 3.4.37(typescript@5.5.4)
+      '@vue/test-utils': 2.4.1(@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2))
+      vue: 3.5.11(typescript@5.6.2)
     optionalDependencies:
-      '@vue/compiler-sfc': 3.4.37
+      '@vue/compiler-sfc': 3.5.11
     transitivePeerDependencies:
       - '@vue/server-renderer'
 
@@ -16795,6 +15481,8 @@ snapshots:
 
   '@twemoji/parser@15.0.0': {}
 
+  '@twemoji/parser@15.1.0': {}
+
   '@twemoji/parser@15.1.1': {}
 
   '@types/accepts@1.3.7':
@@ -16846,7 +15534,9 @@ snapshots:
       '@types/node': 20.14.12
       '@types/responselike': 1.0.0
 
-  '@types/color-convert@2.0.3':
+  '@types/canvas-confetti@1.6.4': {}
+
+  '@types/color-convert@2.0.4':
     dependencies:
       '@types/color-name': 1.1.1
 
@@ -16864,40 +15554,24 @@ snapshots:
 
   '@types/cookie@0.6.0': {}
 
-  '@types/cross-spawn@6.0.2':
-    dependencies:
-      '@types/node': 20.14.12
-
   '@types/debug@4.1.12':
     dependencies:
       '@types/ms': 0.7.34
 
-  '@types/detect-port@1.3.2': {}
-
-  '@types/diff@5.2.1': {}
-
   '@types/disposable-email-domains@1.0.2': {}
 
-  '@types/doctrine@0.0.3': {}
-
   '@types/doctrine@0.0.9': {}
 
-  '@types/ejs@3.1.2': {}
-
-  '@types/emscripten@1.39.7': {}
-
-  '@types/escape-regexp@0.0.3': {}
-
   '@types/escodegen@0.0.6': {}
 
   '@types/eslint@7.29.0':
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.6
       '@types/json-schema': 7.0.15
 
   '@types/estree@0.0.51': {}
 
-  '@types/estree@1.0.5': {}
+  '@types/estree@1.0.6': {}
 
   '@types/express-serve-static-core@4.17.33':
     dependencies:
@@ -16921,7 +15595,7 @@ snapshots:
 
   '@types/find-cache-dir@3.2.1': {}
 
-  '@types/fluent-ffmpeg@2.1.24':
+  '@types/fluent-ffmpeg@2.1.26':
     dependencies:
       '@types/node': 20.14.12
 
@@ -16956,7 +15630,7 @@ snapshots:
     dependencies:
       '@types/istanbul-lib-report': 3.0.0
 
-  '@types/jest@29.5.12':
+  '@types/jest@29.5.13':
     dependencies:
       expect: 29.7.0
       pretty-format: 29.7.0
@@ -17024,8 +15698,6 @@ snapshots:
       '@types/node': 20.14.12
       form-data: 4.0.0
 
-  '@types/node@18.17.15': {}
-
   '@types/node@20.11.5':
     dependencies:
       undici-types: 5.26.5
@@ -17038,7 +15710,11 @@ snapshots:
     dependencies:
       undici-types: 5.26.5
 
-  '@types/nodemailer@6.4.15':
+  '@types/node@22.5.5':
+    dependencies:
+      undici-types: 6.19.8
+
+  '@types/nodemailer@6.4.16':
     dependencies:
       '@types/node': 20.14.12
 
@@ -17063,9 +15739,9 @@ snapshots:
 
   '@types/pg-pool@2.0.4':
     dependencies:
-      '@types/pg': 8.11.6
+      '@types/pg': 8.11.10
 
-  '@types/pg@8.11.6':
+  '@types/pg@8.11.10':
     dependencies:
       '@types/node': 20.14.12
       pg-protocol: 1.6.1
@@ -17077,8 +15753,6 @@ snapshots:
       pg-protocol: 1.6.1
       pg-types: 2.2.0
 
-  '@types/pretty-hrtime@1.0.1': {}
-
   '@types/prop-types@15.7.5': {}
 
   '@types/pug@2.0.10': {}
@@ -17115,7 +15789,7 @@ snapshots:
     dependencies:
       '@types/node': 20.14.12
 
-  '@types/sanitize-html@2.11.0':
+  '@types/sanitize-html@2.13.0':
     dependencies:
       htmlparser2: 8.0.1
 
@@ -17180,7 +15854,7 @@ snapshots:
 
   '@types/wrap-ansi@3.0.0': {}
 
-  '@types/ws@8.5.11':
+  '@types/ws@8.5.12':
     dependencies:
       '@types/node': 20.14.12
 
@@ -17195,107 +15869,131 @@ snapshots:
       '@types/node': 20.14.12
     optional: true
 
-  '@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3))(eslint@9.11.0)(typescript@5.3.3)':
     dependencies:
       '@eslint-community/regexpp': 4.6.2
-      '@typescript-eslint/parser': 6.11.0(eslint@9.8.0)(typescript@5.3.3)
-      '@typescript-eslint/scope-manager': 6.11.0
-      '@typescript-eslint/type-utils': 6.11.0(eslint@9.8.0)(typescript@5.3.3)
-      '@typescript-eslint/utils': 6.11.0(eslint@9.8.0)(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 6.11.0
+      '@typescript-eslint/parser': 7.1.0(eslint@9.11.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 7.1.0
+      '@typescript-eslint/type-utils': 7.1.0(eslint@9.11.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 7.1.0(eslint@9.11.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 7.1.0
       debug: 4.3.4(supports-color@5.5.0)
-      eslint: 9.8.0
+      eslint: 9.11.0
       graphemer: 1.4.0
       ignore: 5.2.4
       natural-compare: 1.4.0
-      semver: 7.5.4
+      semver: 7.6.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
     optionalDependencies:
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.5.4))(eslint@9.11.0)(typescript@5.5.4)':
     dependencies:
-      '@eslint-community/regexpp': 4.6.2
-      '@typescript-eslint/parser': 7.1.0(eslint@9.8.0)(typescript@5.3.3)
-      '@typescript-eslint/scope-manager': 7.1.0
-      '@typescript-eslint/type-utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3)
-      '@typescript-eslint/utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 7.1.0
-      debug: 4.3.4(supports-color@5.5.0)
-      eslint: 9.8.0
+      '@eslint-community/regexpp': 4.11.0
+      '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.5.4)
+      '@typescript-eslint/scope-manager': 7.17.0
+      '@typescript-eslint/type-utils': 7.17.0(eslint@9.11.0)(typescript@5.5.4)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.5.4)
+      '@typescript-eslint/visitor-keys': 7.17.0
+      eslint: 9.11.0
       graphemer: 1.4.0
-      ignore: 5.2.4
+      ignore: 5.3.1
       natural-compare: 1.4.0
-      semver: 7.6.0
-      ts-api-utils: 1.0.1(typescript@5.3.3)
+      ts-api-utils: 1.3.0(typescript@5.5.4)
     optionalDependencies:
-      typescript: 5.3.3
+      typescript: 5.5.4
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)':
+  '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0)(typescript@5.6.2)':
     dependencies:
       '@eslint-community/regexpp': 4.11.0
-      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/scope-manager': 7.17.0
-      '@typescript-eslint/type-utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
-      '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+      '@typescript-eslint/type-utils': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       '@typescript-eslint/visitor-keys': 7.17.0
-      eslint: 9.8.0
+      eslint: 9.11.0
       graphemer: 1.4.0
       ignore: 5.3.1
       natural-compare: 1.4.0
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@6.11.0(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/scope-manager': 6.11.0
-      '@typescript-eslint/types': 6.11.0
-      '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.4(supports-color@5.5.0)
+      '@eslint-community/regexpp': 4.11.0
+      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
+      '@typescript-eslint/scope-manager': 7.17.0
+      '@typescript-eslint/type-utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
+      '@typescript-eslint/visitor-keys': 7.17.0
       eslint: 9.8.0
+      graphemer: 1.4.0
+      ignore: 5.3.1
+      natural-compare: 1.4.0
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.3.3
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/parser@7.1.0(eslint@9.11.0)(typescript@5.3.3)':
     dependencies:
       '@typescript-eslint/scope-manager': 7.1.0
       '@typescript-eslint/types': 7.1.0
       '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
       '@typescript-eslint/visitor-keys': 7.1.0
       debug: 4.3.4(supports-color@5.5.0)
-      eslint: 9.8.0
+      eslint: 9.11.0
     optionalDependencies:
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4)':
+  '@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.5.4)':
     dependencies:
       '@typescript-eslint/scope-manager': 7.17.0
       '@typescript-eslint/types': 7.17.0
       '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
       '@typescript-eslint/visitor-keys': 7.17.0
       debug: 4.3.5(supports-color@5.5.0)
-      eslint: 9.8.0
+      eslint: 9.11.0
     optionalDependencies:
       typescript: 5.5.4
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@6.11.0':
+  '@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2)':
+    dependencies:
+      '@typescript-eslint/scope-manager': 7.17.0
+      '@typescript-eslint/types': 7.17.0
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      '@typescript-eslint/visitor-keys': 7.17.0
+      debug: 4.3.5(supports-color@5.5.0)
+      eslint: 9.11.0
+    optionalDependencies:
+      typescript: 5.6.2
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/types': 6.11.0
-      '@typescript-eslint/visitor-keys': 6.11.0
+      '@typescript-eslint/scope-manager': 7.17.0
+      '@typescript-eslint/types': 7.17.0
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      '@typescript-eslint/visitor-keys': 7.17.0
+      debug: 4.3.5(supports-color@5.5.0)
+      eslint: 9.8.0
+    optionalDependencies:
+      typescript: 5.6.2
+    transitivePeerDependencies:
+      - supports-color
 
   '@typescript-eslint/scope-manager@7.1.0':
     dependencies:
@@ -17307,62 +16005,58 @@ snapshots:
       '@typescript-eslint/types': 7.17.0
       '@typescript-eslint/visitor-keys': 7.17.0
 
-  '@typescript-eslint/type-utils@6.11.0(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/type-utils@7.1.0(eslint@9.11.0)(typescript@5.3.3)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
-      '@typescript-eslint/utils': 6.11.0(eslint@9.8.0)(typescript@5.3.3)
+      '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
+      '@typescript-eslint/utils': 7.1.0(eslint@9.11.0)(typescript@5.3.3)
       debug: 4.3.5(supports-color@5.5.0)
-      eslint: 9.8.0
+      eslint: 9.11.0
       ts-api-utils: 1.0.1(typescript@5.3.3)
     optionalDependencies:
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/type-utils@7.1.0(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/type-utils@7.17.0(eslint@9.11.0)(typescript@5.5.4)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
-      '@typescript-eslint/utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3)
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.5.4)
       debug: 4.3.5(supports-color@5.5.0)
-      eslint: 9.8.0
-      ts-api-utils: 1.0.1(typescript@5.3.3)
+      eslint: 9.11.0
+      ts-api-utils: 1.3.0(typescript@5.5.4)
     optionalDependencies:
-      typescript: 5.3.3
+      typescript: 5.5.4
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.5.4)':
+  '@typescript-eslint/type-utils@7.17.0(eslint@9.11.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
-      '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
       debug: 4.3.5(supports-color@5.5.0)
-      eslint: 9.8.0
-      ts-api-utils: 1.3.0(typescript@5.5.4)
+      eslint: 9.11.0
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@6.11.0': {}
-
-  '@typescript-eslint/types@7.1.0': {}
-
-  '@typescript-eslint/types@7.17.0': {}
-
-  '@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3)':
+  '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/types': 6.11.0
-      '@typescript-eslint/visitor-keys': 6.11.0
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
       debug: 4.3.5(supports-color@5.5.0)
-      globby: 11.1.0
-      is-glob: 4.0.3
-      semver: 7.6.0
-      ts-api-utils: 1.0.1(typescript@5.3.3)
+      eslint: 9.8.0
+      ts-api-utils: 1.3.0(typescript@5.6.2)
     optionalDependencies:
-      typescript: 5.3.3
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
 
+  '@typescript-eslint/types@7.1.0': {}
+
+  '@typescript-eslint/types@7.17.0': {}
+
   '@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3)':
     dependencies:
       '@typescript-eslint/types': 7.1.0
@@ -17393,49 +16087,67 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/utils@6.11.0(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/typescript-estree@7.17.0(typescript@5.6.2)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
-      '@types/json-schema': 7.0.12
-      '@types/semver': 7.5.8
-      '@typescript-eslint/scope-manager': 6.11.0
-      '@typescript-eslint/types': 6.11.0
-      '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
-      eslint: 9.8.0
+      '@typescript-eslint/types': 7.17.0
+      '@typescript-eslint/visitor-keys': 7.17.0
+      debug: 4.3.5(supports-color@5.5.0)
+      globby: 11.1.0
+      is-glob: 4.0.3
+      minimatch: 9.0.4
       semver: 7.6.0
+      ts-api-utils: 1.3.0(typescript@5.6.2)
+    optionalDependencies:
+      typescript: 5.6.2
     transitivePeerDependencies:
       - supports-color
-      - typescript
 
-  '@typescript-eslint/utils@7.1.0(eslint@9.8.0)(typescript@5.3.3)':
+  '@typescript-eslint/utils@7.1.0(eslint@9.11.0)(typescript@5.3.3)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0)
       '@types/json-schema': 7.0.12
       '@types/semver': 7.5.8
       '@typescript-eslint/scope-manager': 7.1.0
       '@typescript-eslint/types': 7.1.0
       '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
-      eslint: 9.8.0
+      eslint: 9.11.0
       semver: 7.6.0
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  '@typescript-eslint/utils@7.17.0(eslint@9.8.0)(typescript@5.5.4)':
+  '@typescript-eslint/utils@7.17.0(eslint@9.11.0)(typescript@5.5.4)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0)
       '@typescript-eslint/scope-manager': 7.17.0
       '@typescript-eslint/types': 7.17.0
       '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4)
-      eslint: 9.8.0
+      eslint: 9.11.0
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  '@typescript-eslint/visitor-keys@6.11.0':
+  '@typescript-eslint/utils@7.17.0(eslint@9.11.0)(typescript@5.6.2)':
     dependencies:
-      '@typescript-eslint/types': 6.11.0
-      eslint-visitor-keys: 3.4.3
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0)
+      '@typescript-eslint/scope-manager': 7.17.0
+      '@typescript-eslint/types': 7.17.0
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      eslint: 9.11.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+
+  '@typescript-eslint/utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)':
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
+      '@typescript-eslint/scope-manager': 7.17.0
+      '@typescript-eslint/types': 7.17.0
+      '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2)
+      eslint: 9.8.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
 
   '@typescript-eslint/visitor-keys@7.1.0':
     dependencies:
@@ -17449,12 +16161,17 @@ snapshots:
 
   '@ungap/structured-clone@1.2.0': {}
 
-  '@vitejs/plugin-vue@5.1.0(vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3))(vue@3.4.37(typescript@5.5.4))':
+  '@vitejs/plugin-vue@5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.11(typescript@5.6.2))':
     dependencies:
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
-      vue: 3.4.37(typescript@5.5.4)
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
+      vue: 3.5.11(typescript@5.6.2)
 
-  '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3))':
+  '@vitejs/plugin-vue@5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0))(vue@3.5.11(typescript@5.6.2))':
+    dependencies:
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0)
+      vue: 3.5.11(typescript@5.6.2)
+
+  '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0))':
     dependencies:
       '@ampproject/remapping': 2.2.1
       '@bcoe/v8-coverage': 0.2.3
@@ -17469,11 +16186,11 @@ snapshots:
       std-env: 3.7.0
       strip-literal: 2.1.0
       test-exclude: 6.0.0
-      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)
+      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3))':
+  '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.33.0))':
     dependencies:
       '@ampproject/remapping': 2.2.1
       '@bcoe/v8-coverage': 0.2.3
@@ -17488,7 +16205,7 @@ snapshots:
       std-env: 3.7.0
       strip-literal: 2.1.0
       test-exclude: 6.0.0
-      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3)
+      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.33.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -17498,6 +16215,21 @@ snapshots:
       '@vitest/utils': 1.6.0
       chai: 4.3.10
 
+  '@vitest/expect@2.0.5':
+    dependencies:
+      '@vitest/spy': 2.0.5
+      '@vitest/utils': 2.0.5
+      chai: 5.1.1
+      tinyrainbow: 1.2.0
+
+  '@vitest/pretty-format@2.0.5':
+    dependencies:
+      tinyrainbow: 1.2.0
+
+  '@vitest/pretty-format@2.1.1':
+    dependencies:
+      tinyrainbow: 1.2.0
+
   '@vitest/runner@1.6.0':
     dependencies:
       '@vitest/utils': 1.6.0
@@ -17506,7 +16238,7 @@ snapshots:
 
   '@vitest/snapshot@1.6.0':
     dependencies:
-      magic-string: 0.30.10
+      magic-string: 0.30.11
       pathe: 1.1.2
       pretty-format: 29.7.0
 
@@ -17514,6 +16246,10 @@ snapshots:
     dependencies:
       tinyspy: 2.2.0
 
+  '@vitest/spy@2.0.5':
+    dependencies:
+      tinyspy: 3.0.2
+
   '@vitest/utils@1.6.0':
     dependencies:
       diff-sequences: 29.6.3
@@ -17521,52 +16257,83 @@ snapshots:
       loupe: 2.3.7
       pretty-format: 29.7.0
 
+  '@vitest/utils@2.0.5':
+    dependencies:
+      '@vitest/pretty-format': 2.0.5
+      estree-walker: 3.0.3
+      loupe: 3.1.1
+      tinyrainbow: 1.2.0
+
+  '@vitest/utils@2.1.1':
+    dependencies:
+      '@vitest/pretty-format': 2.1.1
+      loupe: 3.1.1
+      tinyrainbow: 1.2.0
+
   '@volar/language-core@2.2.0':
     dependencies:
       '@volar/source-map': 2.2.0
 
-  '@volar/language-core@2.4.0-alpha.18':
+  '@volar/language-core@2.4.5':
     dependencies:
-      '@volar/source-map': 2.4.0-alpha.18
+      '@volar/source-map': 2.4.5
 
   '@volar/source-map@2.2.0':
     dependencies:
       muggle-string: 0.4.1
 
-  '@volar/source-map@2.4.0-alpha.18': {}
+  '@volar/source-map@2.4.5': {}
 
   '@volar/typescript@2.2.0':
     dependencies:
       '@volar/language-core': 2.2.0
       path-browserify: 1.0.1
 
-  '@volar/typescript@2.4.0-alpha.18':
+  '@volar/typescript@2.4.5':
     dependencies:
-      '@volar/language-core': 2.4.0-alpha.18
+      '@volar/language-core': 2.4.5
       path-browserify: 1.0.1
       vscode-uri: 3.0.8
 
-  '@vue/compiler-core@3.4.31':
+  '@vue/compiler-core@3.4.37':
     dependencies:
-      '@babel/parser': 7.24.7
-      '@vue/shared': 3.4.31
+      '@babel/parser': 7.25.6
+      '@vue/shared': 3.4.37
+      entities: 5.0.0
+      estree-walker: 2.0.2
+      source-map-js: 1.2.1
+
+  '@vue/compiler-core@3.5.10':
+    dependencies:
+      '@babel/parser': 7.25.6
+      '@vue/shared': 3.5.10
       entities: 4.5.0
       estree-walker: 2.0.2
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
-  '@vue/compiler-core@3.4.37':
+  '@vue/compiler-core@3.5.11':
     dependencies:
-      '@babel/parser': 7.24.7
-      '@vue/shared': 3.4.37
-      entities: 5.0.0
+      '@babel/parser': 7.25.6
+      '@vue/shared': 3.5.11
+      entities: 4.5.0
       estree-walker: 2.0.2
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   '@vue/compiler-dom@3.4.37':
     dependencies:
       '@vue/compiler-core': 3.4.37
       '@vue/shared': 3.4.37
 
+  '@vue/compiler-dom@3.5.10':
+    dependencies:
+      '@vue/compiler-core': 3.5.10
+      '@vue/shared': 3.5.10
+
+  '@vue/compiler-dom@3.5.11':
+    dependencies:
+      '@vue/compiler-core': 3.5.11
+      '@vue/shared': 3.5.11
+
   '@vue/compiler-sfc@3.4.37':
     dependencies:
       '@babel/parser': 7.24.7
@@ -17575,56 +16342,80 @@ snapshots:
       '@vue/compiler-ssr': 3.4.37
       '@vue/shared': 3.4.37
       estree-walker: 2.0.2
-      magic-string: 0.30.10
-      postcss: 8.4.40
+      magic-string: 0.30.11
+      postcss: 8.4.47
       source-map-js: 1.2.0
 
+  '@vue/compiler-sfc@3.5.11':
+    dependencies:
+      '@babel/parser': 7.25.6
+      '@vue/compiler-core': 3.5.11
+      '@vue/compiler-dom': 3.5.11
+      '@vue/compiler-ssr': 3.5.11
+      '@vue/shared': 3.5.11
+      estree-walker: 2.0.2
+      magic-string: 0.30.11
+      postcss: 8.4.47
+      source-map-js: 1.2.1
+
   '@vue/compiler-ssr@3.4.37':
     dependencies:
       '@vue/compiler-dom': 3.4.37
       '@vue/shared': 3.4.37
 
+  '@vue/compiler-ssr@3.5.11':
+    dependencies:
+      '@vue/compiler-dom': 3.5.11
+      '@vue/shared': 3.5.11
+
   '@vue/compiler-vue2@2.7.16':
     dependencies:
       de-indent: 1.0.2
       he: 1.2.0
 
-  '@vue/devtools-api@6.6.1': {}
-
-  '@vue/language-core@2.0.16(typescript@5.5.4)':
+  '@vue/language-core@2.0.16(typescript@5.6.2)':
     dependencies:
       '@volar/language-core': 2.2.0
-      '@vue/compiler-dom': 3.4.37
-      '@vue/shared': 3.4.37
+      '@vue/compiler-dom': 3.5.11
+      '@vue/shared': 3.5.11
       computeds: 0.0.1
       minimatch: 9.0.4
       path-browserify: 1.0.1
       vue-template-compiler: 2.7.14
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
-  '@vue/language-core@2.0.29(typescript@5.5.4)':
+  '@vue/language-core@2.1.6(typescript@5.6.2)':
     dependencies:
-      '@volar/language-core': 2.4.0-alpha.18
+      '@volar/language-core': 2.4.5
       '@vue/compiler-dom': 3.4.37
       '@vue/compiler-vue2': 2.7.16
-      '@vue/shared': 3.4.37
+      '@vue/shared': 3.5.11
       computeds: 0.0.1
       minimatch: 9.0.4
       muggle-string: 0.4.1
       path-browserify: 1.0.1
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   '@vue/reactivity@3.4.37':
     dependencies:
       '@vue/shared': 3.4.37
 
+  '@vue/reactivity@3.5.11':
+    dependencies:
+      '@vue/shared': 3.5.11
+
   '@vue/runtime-core@3.4.37':
     dependencies:
       '@vue/reactivity': 3.4.37
       '@vue/shared': 3.4.37
 
+  '@vue/runtime-core@3.5.11':
+    dependencies:
+      '@vue/reactivity': 3.5.11
+      '@vue/shared': 3.5.11
+
   '@vue/runtime-dom@3.4.37':
     dependencies:
       '@vue/reactivity': 3.4.37
@@ -17632,41 +16423,41 @@ snapshots:
       '@vue/shared': 3.4.37
       csstype: 3.1.3
 
+  '@vue/runtime-dom@3.5.11':
+    dependencies:
+      '@vue/reactivity': 3.5.11
+      '@vue/runtime-core': 3.5.11
+      '@vue/shared': 3.5.11
+      csstype: 3.1.3
+
   '@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4))':
     dependencies:
       '@vue/compiler-ssr': 3.4.37
       '@vue/shared': 3.4.37
       vue: 3.4.37(typescript@5.5.4)
 
-  '@vue/shared@3.4.31': {}
+  '@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2))':
+    dependencies:
+      '@vue/compiler-ssr': 3.5.11
+      '@vue/shared': 3.5.11
+      vue: 3.5.11(typescript@5.6.2)
 
   '@vue/shared@3.4.37': {}
 
-  '@vue/test-utils@2.4.1(@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4)))(vue@3.4.37(typescript@5.5.4))':
+  '@vue/shared@3.5.10': {}
+
+  '@vue/shared@3.5.11': {}
+
+  '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.11(vue@3.5.11(typescript@5.6.2)))(vue@3.5.11(typescript@5.6.2))':
     dependencies:
       js-beautify: 1.14.9
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.11(typescript@5.6.2)
       vue-component-type-helpers: 1.8.4
     optionalDependencies:
-      '@vue/server-renderer': 3.4.37(vue@3.4.37(typescript@5.5.4))
+      '@vue/server-renderer': 3.5.11(vue@3.5.11(typescript@5.6.2))
 
   '@webgpu/types@0.1.30': {}
 
-  '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.19.11)':
-    dependencies:
-      esbuild: 0.19.11
-      tslib: 2.6.3
-
-  '@yarnpkg/fslib@2.10.3':
-    dependencies:
-      '@yarnpkg/libzip': 2.3.0
-      tslib: 1.14.1
-
-  '@yarnpkg/libzip@2.3.0':
-    dependencies:
-      '@types/emscripten': 1.39.7
-      tslib: 1.14.1
-
   abbrev@1.1.1: {}
 
   abbrev@2.0.0: {}
@@ -17707,8 +16498,6 @@ snapshots:
 
   acorn@8.12.1: {}
 
-  address@1.2.2: {}
-
   adm-zip@0.5.10:
     optional: true
 
@@ -17719,9 +16508,10 @@ snapshots:
 
   agent-base@6.0.2:
     dependencies:
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
+    optional: true
 
   agent-base@7.1.0:
     dependencies:
@@ -17752,14 +16542,14 @@ snapshots:
     optionalDependencies:
       ajv: 8.17.1
 
-  ajv-formats@2.1.1(ajv@8.17.1):
-    optionalDependencies:
-      ajv: 8.17.1
-
   ajv-formats@3.0.1(ajv@8.13.0):
     optionalDependencies:
       ajv: 8.13.0
 
+  ajv-formats@3.0.1(ajv@8.17.1):
+    optionalDependencies:
+      ajv: 8.17.1
+
   ajv@6.12.6:
     dependencies:
       fast-deep-equal: 3.1.3
@@ -17817,8 +16607,6 @@ snapshots:
       normalize-path: 3.0.0
       picomatch: 2.3.1
 
-  app-root-dir@1.0.2: {}
-
   app-root-path@3.1.0: {}
 
   append-field@1.0.0: {}
@@ -17848,8 +16636,6 @@ snapshots:
       tar-stream: 3.1.6
       zip-stream: 6.0.1
 
-  archy@1.0.0: {}
-
   are-we-there-yet@2.0.0:
     dependencies:
       delegates: 1.0.0
@@ -17874,39 +16660,46 @@ snapshots:
 
   array-buffer-byte-length@1.0.0:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       is-array-buffer: 3.0.2
 
+  array-buffer-byte-length@1.0.1:
+    dependencies:
+      call-bind: 1.0.7
+      is-array-buffer: 3.0.4
+
   array-flatten@1.1.1: {}
 
-  array-includes@3.1.7:
+  array-includes@3.1.8:
     dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-      es-abstract: 1.22.1
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+      es-object-atoms: 1.0.0
+      get-intrinsic: 1.2.4
       is-string: 1.0.7
 
   array-union@2.1.0: {}
 
-  array.prototype.findlastindex@1.2.3:
+  array.prototype.findlastindex@1.2.5:
     dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-      es-abstract: 1.22.1
-      es-shim-unscopables: 1.0.0
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+      es-errors: 1.3.0
+      es-object-atoms: 1.0.0
+      es-shim-unscopables: 1.0.2
 
   array.prototype.flat@1.3.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
       es-abstract: 1.22.1
       es-shim-unscopables: 1.0.0
 
   array.prototype.flatmap@1.3.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
       es-abstract: 1.22.1
       es-shim-unscopables: 1.0.0
@@ -17914,12 +16707,23 @@ snapshots:
   arraybuffer.prototype.slice@1.0.1:
     dependencies:
       array-buffer-byte-length: 1.0.0
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
-      get-intrinsic: 1.2.1
+      get-intrinsic: 1.2.4
       is-array-buffer: 3.0.2
       is-shared-array-buffer: 1.0.2
 
+  arraybuffer.prototype.slice@1.0.3:
+    dependencies:
+      array-buffer-byte-length: 1.0.1
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+      es-errors: 1.3.0
+      get-intrinsic: 1.2.4
+      is-array-buffer: 3.0.4
+      is-shared-array-buffer: 1.0.3
+
   arrify@1.0.1: {}
 
   asap@2.0.6: {}
@@ -17939,29 +16743,23 @@ snapshots:
     dependencies:
       pvtsutils: 1.3.5
       pvutils: 1.1.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   assert-never@1.2.1: {}
 
   assert-plus@1.0.0: {}
 
-  assert@2.1.0:
-    dependencies:
-      call-bind: 1.0.2
-      is-nan: 1.3.2
-      object-is: 1.1.5
-      object.assign: 4.1.4
-      util: 0.12.5
-
   assertion-error@1.1.0: {}
 
+  assertion-error@2.0.1: {}
+
   ast-types@0.16.1:
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   astral-regex@2.0.0: {}
 
-  astring@1.8.6: {}
+  astring@1.9.0: {}
 
   async-mutex@0.5.0:
     dependencies:
@@ -17979,14 +16777,14 @@ snapshots:
 
   available-typed-arrays@1.0.5: {}
 
-  avvio@8.3.0:
+  available-typed-arrays@1.0.7:
     dependencies:
-      '@fastify/error': 3.4.0
-      archy: 1.0.0
-      debug: 4.3.5(supports-color@5.5.0)
+      possible-typed-array-names: 1.0.0
+
+  avvio@9.0.0:
+    dependencies:
+      '@fastify/error': 4.0.0
       fastq: 1.17.1
-    transitivePeerDependencies:
-      - supports-color
 
   aws-sdk-client-mock@4.0.1:
     dependencies:
@@ -18000,13 +16798,13 @@ snapshots:
 
   axios@0.24.0:
     dependencies:
-      follow-redirects: 1.15.2(debug@4.3.5)
+      follow-redirects: 1.15.2
     transitivePeerDependencies:
       - debug
 
-  axios@1.6.2(debug@4.3.5):
+  axios@1.7.7(debug@4.3.7):
     dependencies:
-      follow-redirects: 1.15.2(debug@4.3.5)
+      follow-redirects: 1.15.9(debug@4.3.7)
       form-data: 4.0.0
       proxy-from-env: 1.1.0
     transitivePeerDependencies:
@@ -18014,10 +16812,6 @@ snapshots:
 
   b4a@1.6.4: {}
 
-  babel-core@7.0.0-bridge.0(@babel/core@7.24.7):
-    dependencies:
-      '@babel/core': 7.24.7
-
   babel-jest@29.7.0(@babel/core@7.23.5):
     dependencies:
       '@babel/core': 7.23.5
@@ -18048,30 +16842,6 @@ snapshots:
       '@types/babel__core': 7.20.0
       '@types/babel__traverse': 7.20.0
 
-  babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.7):
-    dependencies:
-      '@babel/compat-data': 7.24.7
-      '@babel/core': 7.24.7
-      '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7)
-      semver: 6.3.1
-    transitivePeerDependencies:
-      - supports-color
-
-  babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.7):
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7)
-      core-js-compat: 3.37.1
-    transitivePeerDependencies:
-      - supports-color
-
-  babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.7):
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
   babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.5):
     dependencies:
       '@babel/core': 7.23.5
@@ -18114,8 +16884,6 @@ snapshots:
     dependencies:
       open: 8.4.2
 
-  big-integer@1.6.51: {}
-
   bin-check@4.1.0:
     dependencies:
       execa: 0.7.0
@@ -18134,12 +16902,6 @@ snapshots:
 
   binary-extensions@2.2.0: {}
 
-  bl@4.1.0:
-    dependencies:
-      buffer: 5.7.1
-      inherits: 2.0.4
-      readable-stream: 3.6.0
-
   blob-util@2.0.2: {}
 
   bluebird@3.7.2: {}
@@ -18148,7 +16910,7 @@ snapshots:
 
   bn.js@4.12.0: {}
 
-  body-parser@1.20.2:
+  body-parser@1.20.3:
     dependencies:
       bytes: 3.1.2
       content-type: 1.0.5
@@ -18158,7 +16920,7 @@ snapshots:
       http-errors: 2.0.0
       iconv-lite: 0.4.24
       on-finished: 2.4.1
-      qs: 6.11.0
+      qs: 6.13.0
       raw-body: 2.5.2
       type-is: 1.6.18
       unpipe: 1.0.0
@@ -18169,10 +16931,6 @@ snapshots:
 
   bowser@2.11.0: {}
 
-  bplist-parser@0.2.0:
-    dependencies:
-      big-integer: 1.6.51
-
   brace-expansion@1.1.11:
     dependencies:
       balanced-match: 1.0.2
@@ -18250,14 +17008,14 @@ snapshots:
       node-gyp-build: 4.6.0
     optional: true
 
-  bullmq@5.10.4:
+  bullmq@5.15.0:
     dependencies:
       cron-parser: 4.8.1
       ioredis: 5.4.1
       msgpackr: 1.10.1
       node-abort-controller: 3.1.1
-      semver: 7.6.0
-      tslib: 2.6.3
+      semver: 7.6.3
+      tslib: 2.7.0
       uuid: 9.0.1
     transitivePeerDependencies:
       - supports-color
@@ -18268,8 +17026,6 @@ snapshots:
     dependencies:
       streamsearch: 1.1.0
 
-  bytes@3.0.0: {}
-
   bytes@3.1.2: {}
 
   cac@6.7.14: {}
@@ -18330,6 +17086,14 @@ snapshots:
       function-bind: 1.1.2
       get-intrinsic: 1.2.1
 
+  call-bind@1.0.7:
+    dependencies:
+      es-define-property: 1.0.0
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.2.4
+      set-function-length: 1.2.2
+
   call-me-maybe@1.0.2: {}
 
   callsites@3.1.0: {}
@@ -18377,6 +17141,14 @@ snapshots:
       pathval: 1.1.1
       type-detect: 4.0.8
 
+  chai@5.1.1:
+    dependencies:
+      assertion-error: 2.0.1
+      check-error: 2.1.1
+      deep-eql: 5.0.2
+      loupe: 3.1.1
+      pathval: 2.0.0
+
   chalk-template@1.1.0:
     dependencies:
       chalk: 5.3.0
@@ -18401,38 +17173,44 @@ snapshots:
 
   char-regex@1.0.2: {}
 
+  character-entities-html4@2.1.0: {}
+
+  character-entities-legacy@3.0.0: {}
+
   character-entities@2.0.2: {}
 
   character-parser@2.2.0:
     dependencies:
       is-regex: 1.1.4
 
-  chart.js@4.4.3:
+  chart.js@4.4.4:
     dependencies:
       '@kurkle/color': 0.3.2
 
-  chartjs-adapter-date-fns@3.0.0(chart.js@4.4.3)(date-fns@2.30.0):
+  chartjs-adapter-date-fns@3.0.0(chart.js@4.4.4)(date-fns@2.30.0):
     dependencies:
-      chart.js: 4.4.3
+      chart.js: 4.4.4
       date-fns: 2.30.0
 
-  chartjs-chart-matrix@2.0.1(chart.js@4.4.3):
+  chartjs-chart-matrix@2.0.1(chart.js@4.4.4):
     dependencies:
-      chart.js: 4.4.3
+      chart.js: 4.4.4
 
-  chartjs-plugin-gradient@0.6.1(chart.js@4.4.3):
+  chartjs-plugin-gradient@0.6.1(chart.js@4.4.4):
     dependencies:
-      chart.js: 4.4.3
+      chart.js: 4.4.4
 
-  chartjs-plugin-zoom@2.0.1(chart.js@4.4.3):
+  chartjs-plugin-zoom@2.0.1(chart.js@4.4.4):
     dependencies:
-      chart.js: 4.4.3
+      chart.js: 4.4.4
       hammerjs: 2.0.8
 
   check-error@1.0.3:
     dependencies:
       get-func-name: 2.0.2
 
+  check-error@2.1.1: {}
+
   check-more-types@2.24.0: {}
 
   cheerio-select@2.1.0:
@@ -18444,6 +17222,20 @@ snapshots:
       domhandler: 5.0.3
       domutils: 3.0.1
 
+  cheerio@1.0.0:
+    dependencies:
+      cheerio-select: 2.1.0
+      dom-serializer: 2.0.0
+      domhandler: 5.0.3
+      domutils: 3.1.0
+      encoding-sniffer: 0.2.0
+      htmlparser2: 9.1.0
+      parse5: 7.1.2
+      parse5-htmlparser2-tree-adapter: 7.0.0
+      parse5-parser-stream: 7.1.2
+      undici: 6.19.8
+      whatwg-mimetype: 4.0.0
+
   cheerio@1.0.0-rc.12:
     dependencies:
       cheerio-select: 2.1.0
@@ -18471,7 +17263,7 @@ snapshots:
 
   chownr@2.0.0: {}
 
-  chromatic@11.5.6: {}
+  chromatic@11.11.0: {}
 
   ci-info@3.7.1: {}
 
@@ -18529,18 +17321,10 @@ snapshots:
       strip-ansi: 6.0.1
       wrap-ansi: 7.0.0
 
-  clone-deep@4.0.1:
-    dependencies:
-      is-plain-object: 2.0.4
-      kind-of: 6.0.3
-      shallow-clone: 3.0.1
-
   clone-response@1.0.3:
     dependencies:
       mimic-response: 1.0.1
 
-  clone@1.0.4: {}
-
   cluster-key-slot@1.1.2: {}
 
   co@4.6.0: {}
@@ -18582,8 +17366,12 @@ snapshots:
     dependencies:
       delayed-stream: 1.0.0
 
+  comma-separated-tokens@2.0.3: {}
+
   commander@10.0.1: {}
 
+  commander@12.1.0: {}
+
   commander@2.20.3: {}
 
   commander@6.2.1: {}
@@ -18603,26 +17391,10 @@ snapshots:
   compress-commons@6.0.2:
     dependencies:
       crc-32: 1.2.2
-      crc32-stream: 6.0.0
-      is-stream: 2.0.1
-      normalize-path: 3.0.0
-      readable-stream: 4.3.0
-
-  compressible@2.0.18:
-    dependencies:
-      mime-db: 1.52.0
-
-  compression@1.7.4:
-    dependencies:
-      accepts: 1.3.8
-      bytes: 3.0.0
-      compressible: 2.0.18
-      debug: 2.6.9
-      on-headers: 1.0.2
-      safe-buffer: 5.1.2
-      vary: 1.1.2
-    transitivePeerDependencies:
-      - supports-color
+      crc32-stream: 6.0.0
+      is-stream: 2.0.1
+      normalize-path: 3.0.0
+      readable-stream: 4.3.0
 
   computeds@0.0.1: {}
 
@@ -18666,10 +17438,6 @@ snapshots:
 
   cookie@0.6.0: {}
 
-  core-js-compat@3.37.1:
-    dependencies:
-      browserslist: 4.23.0
-
   core-js@3.29.1: {}
 
   core-util-is@1.0.2: {}
@@ -18707,10 +17475,10 @@ snapshots:
     dependencies:
       luxon: 3.3.0
 
-  cropperjs@2.0.0-rc.1:
+  cropperjs@2.0.0-rc.2:
     dependencies:
-      '@cropper/elements': 2.0.0-rc.1
-      '@cropper/utils': 2.0.0-rc.1
+      '@cropper/elements': 2.0.0-rc.2
+      '@cropper/utils': 2.0.0-rc.2
 
   cross-env@7.0.3:
     dependencies:
@@ -18740,13 +17508,9 @@ snapshots:
       shebang-command: 2.0.0
       which: 2.0.2
 
-  crypto-random-string@4.0.0:
-    dependencies:
-      type-fest: 1.4.0
-
-  css-declaration-sorter@7.2.0(postcss@8.4.40):
+  css-declaration-sorter@7.2.0(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
 
   css-select@5.1.0:
     dependencies:
@@ -18759,12 +17523,12 @@ snapshots:
   css-tree@2.2.1:
     dependencies:
       mdn-data: 2.0.28
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   css-tree@2.3.1:
     dependencies:
       mdn-data: 2.0.30
-      source-map-js: 1.2.0
+      source-map-js: 1.2.1
 
   css-what@6.1.0: {}
 
@@ -18772,49 +17536,49 @@ snapshots:
 
   cssesc@3.0.0: {}
 
-  cssnano-preset-default@6.1.2(postcss@8.4.40):
+  cssnano-preset-default@6.1.2(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
-      css-declaration-sorter: 7.2.0(postcss@8.4.40)
-      cssnano-utils: 4.0.2(postcss@8.4.40)
-      postcss: 8.4.40
-      postcss-calc: 9.0.1(postcss@8.4.40)
-      postcss-colormin: 6.1.0(postcss@8.4.40)
-      postcss-convert-values: 6.1.0(postcss@8.4.40)
-      postcss-discard-comments: 6.0.2(postcss@8.4.40)
-      postcss-discard-duplicates: 6.0.3(postcss@8.4.40)
-      postcss-discard-empty: 6.0.3(postcss@8.4.40)
-      postcss-discard-overridden: 6.0.2(postcss@8.4.40)
-      postcss-merge-longhand: 6.0.5(postcss@8.4.40)
-      postcss-merge-rules: 6.1.1(postcss@8.4.40)
-      postcss-minify-font-values: 6.1.0(postcss@8.4.40)
-      postcss-minify-gradients: 6.0.3(postcss@8.4.40)
-      postcss-minify-params: 6.1.0(postcss@8.4.40)
-      postcss-minify-selectors: 6.0.4(postcss@8.4.40)
-      postcss-normalize-charset: 6.0.2(postcss@8.4.40)
-      postcss-normalize-display-values: 6.0.2(postcss@8.4.40)
-      postcss-normalize-positions: 6.0.2(postcss@8.4.40)
-      postcss-normalize-repeat-style: 6.0.2(postcss@8.4.40)
-      postcss-normalize-string: 6.0.2(postcss@8.4.40)
-      postcss-normalize-timing-functions: 6.0.2(postcss@8.4.40)
-      postcss-normalize-unicode: 6.1.0(postcss@8.4.40)
-      postcss-normalize-url: 6.0.2(postcss@8.4.40)
-      postcss-normalize-whitespace: 6.0.2(postcss@8.4.40)
-      postcss-ordered-values: 6.0.2(postcss@8.4.40)
-      postcss-reduce-initial: 6.1.0(postcss@8.4.40)
-      postcss-reduce-transforms: 6.0.2(postcss@8.4.40)
-      postcss-svgo: 6.0.3(postcss@8.4.40)
-      postcss-unique-selectors: 6.0.4(postcss@8.4.40)
-
-  cssnano-utils@4.0.2(postcss@8.4.40):
-    dependencies:
-      postcss: 8.4.40
-
-  cssnano@6.1.2(postcss@8.4.40):
-    dependencies:
-      cssnano-preset-default: 6.1.2(postcss@8.4.40)
+      css-declaration-sorter: 7.2.0(postcss@8.4.47)
+      cssnano-utils: 4.0.2(postcss@8.4.47)
+      postcss: 8.4.47
+      postcss-calc: 9.0.1(postcss@8.4.47)
+      postcss-colormin: 6.1.0(postcss@8.4.47)
+      postcss-convert-values: 6.1.0(postcss@8.4.47)
+      postcss-discard-comments: 6.0.2(postcss@8.4.47)
+      postcss-discard-duplicates: 6.0.3(postcss@8.4.47)
+      postcss-discard-empty: 6.0.3(postcss@8.4.47)
+      postcss-discard-overridden: 6.0.2(postcss@8.4.47)
+      postcss-merge-longhand: 6.0.5(postcss@8.4.47)
+      postcss-merge-rules: 6.1.1(postcss@8.4.47)
+      postcss-minify-font-values: 6.1.0(postcss@8.4.47)
+      postcss-minify-gradients: 6.0.3(postcss@8.4.47)
+      postcss-minify-params: 6.1.0(postcss@8.4.47)
+      postcss-minify-selectors: 6.0.4(postcss@8.4.47)
+      postcss-normalize-charset: 6.0.2(postcss@8.4.47)
+      postcss-normalize-display-values: 6.0.2(postcss@8.4.47)
+      postcss-normalize-positions: 6.0.2(postcss@8.4.47)
+      postcss-normalize-repeat-style: 6.0.2(postcss@8.4.47)
+      postcss-normalize-string: 6.0.2(postcss@8.4.47)
+      postcss-normalize-timing-functions: 6.0.2(postcss@8.4.47)
+      postcss-normalize-unicode: 6.1.0(postcss@8.4.47)
+      postcss-normalize-url: 6.0.2(postcss@8.4.47)
+      postcss-normalize-whitespace: 6.0.2(postcss@8.4.47)
+      postcss-ordered-values: 6.0.2(postcss@8.4.47)
+      postcss-reduce-initial: 6.1.0(postcss@8.4.47)
+      postcss-reduce-transforms: 6.0.2(postcss@8.4.47)
+      postcss-svgo: 6.0.3(postcss@8.4.47)
+      postcss-unique-selectors: 6.0.4(postcss@8.4.47)
+
+  cssnano-utils@4.0.2(postcss@8.4.47):
+    dependencies:
+      postcss: 8.4.47
+
+  cssnano@6.1.2(postcss@8.4.47):
+    dependencies:
+      cssnano-preset-default: 6.1.2(postcss@8.4.47)
       lilconfig: 3.1.1
-      postcss: 8.4.40
+      postcss: 8.4.47
 
   csso@5.0.5:
     dependencies:
@@ -18830,9 +17594,9 @@ snapshots:
     dependencies:
       uniq: 1.0.1
 
-  cypress@13.13.1:
+  cypress@13.14.2:
     dependencies:
-      '@cypress/request': 3.0.0
+      '@cypress/request': 3.0.5
       '@cypress/xvfb': 1.2.4(supports-color@8.1.1)
       '@types/sinonjs__fake-timers': 8.1.1
       '@types/sizzle': 2.3.3
@@ -18875,6 +17639,51 @@ snapshots:
       untildify: 4.0.0
       yauzl: 2.10.0
 
+  cypress@13.15.0:
+    dependencies:
+      '@cypress/request': 3.0.5
+      '@cypress/xvfb': 1.2.4(supports-color@8.1.1)
+      '@types/sinonjs__fake-timers': 8.1.1
+      '@types/sizzle': 2.3.3
+      arch: 2.2.0
+      blob-util: 2.0.2
+      bluebird: 3.7.2
+      buffer: 5.7.1
+      cachedir: 2.3.0
+      chalk: 4.1.2
+      check-more-types: 2.24.0
+      cli-cursor: 3.1.0
+      cli-table3: 0.6.3
+      commander: 6.2.1
+      common-tags: 1.8.2
+      dayjs: 1.11.10
+      debug: 4.3.7(supports-color@8.1.1)
+      enquirer: 2.3.6
+      eventemitter2: 6.4.7
+      execa: 4.1.0
+      executable: 4.1.1
+      extract-zip: 2.0.1(supports-color@8.1.1)
+      figures: 3.2.0
+      fs-extra: 9.1.0
+      getos: 3.2.1
+      is-ci: 3.0.1
+      is-installed-globally: 0.4.0
+      lazy-ass: 1.6.0
+      listr2: 3.14.0(enquirer@2.3.6)
+      lodash: 4.17.21
+      log-symbols: 4.1.0
+      minimist: 1.2.8
+      ospath: 1.2.2
+      pretty-bytes: 5.6.0
+      process: 0.11.10
+      proxy-from-env: 1.0.0
+      request-progress: 3.0.0
+      semver: 7.6.3
+      supports-color: 8.1.1
+      tmp: 0.2.3
+      untildify: 4.0.0
+      yauzl: 2.10.0
+
   dashdash@1.14.1:
     dependencies:
       assert-plus: 1.0.0
@@ -18888,6 +17697,24 @@ snapshots:
       whatwg-mimetype: 4.0.0
       whatwg-url: 14.0.0
 
+  data-view-buffer@1.0.1:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      is-data-view: 1.0.1
+
+  data-view-byte-length@1.0.1:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      is-data-view: 1.0.1
+
+  data-view-byte-offset@1.0.0:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      is-data-view: 1.0.1
+
   date-fns@2.30.0:
     dependencies:
       '@babel/runtime': 7.23.4
@@ -18924,6 +17751,12 @@ snapshots:
     optionalDependencies:
       supports-color: 8.1.1
 
+  debug@4.3.7(supports-color@8.1.1):
+    dependencies:
+      ms: 2.1.3
+    optionalDependencies:
+      supports-color: 8.1.1
+
   decamelize-keys@1.1.1:
     dependencies:
       decamelize: 1.2.0
@@ -18967,6 +17800,8 @@ snapshots:
     dependencies:
       type-detect: 4.0.8
 
+  deep-eql@5.0.2: {}
+
   deep-equal@2.2.0:
     dependencies:
       call-bind: 1.0.2
@@ -18991,16 +17826,13 @@ snapshots:
 
   deepmerge@4.2.2: {}
 
-  default-browser-id@3.0.0:
-    dependencies:
-      bplist-parser: 0.2.0
-      untildify: 4.0.0
+  defer-to-connect@2.0.1: {}
 
-  defaults@1.0.4:
+  define-data-property@1.1.4:
     dependencies:
-      clone: 1.0.4
-
-  defer-to-connect@2.0.1: {}
+      es-define-property: 1.0.0
+      es-errors: 1.3.0
+      gopd: 1.0.1
 
   define-lazy-prop@2.0.0: {}
 
@@ -19009,7 +17841,11 @@ snapshots:
       has-property-descriptors: 1.0.0
       object-keys: 1.1.1
 
-  defu@6.1.4: {}
+  define-properties@1.2.1:
+    dependencies:
+      define-data-property: 1.1.4
+      has-property-descriptors: 1.0.2
+      object-keys: 1.1.1
 
   delayed-stream@1.0.0: {}
 
@@ -19024,23 +17860,10 @@ snapshots:
 
   destroy@1.2.0: {}
 
-  detect-indent@6.1.0: {}
-
   detect-libc@2.0.3: {}
 
   detect-newline@3.1.0: {}
 
-  detect-package-manager@2.0.1:
-    dependencies:
-      execa: 5.1.1
-
-  detect-port@1.5.1:
-    dependencies:
-      address: 1.2.2
-      debug: 4.3.5(supports-color@5.5.0)
-    transitivePeerDependencies:
-      - supports-color
-
   devlop@1.1.0:
     dependencies:
       dequal: 2.0.3
@@ -19075,6 +17898,12 @@ snapshots:
 
   dom-accessibility-api@0.6.3: {}
 
+  dom-serializer@1.4.1:
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+      entities: 2.2.0
+
   dom-serializer@2.0.0:
     dependencies:
       domelementtype: 2.3.0
@@ -19083,17 +17912,35 @@ snapshots:
 
   domelementtype@2.3.0: {}
 
+  domhandler@3.3.0:
+    dependencies:
+      domelementtype: 2.3.0
+
+  domhandler@4.3.1:
+    dependencies:
+      domelementtype: 2.3.0
+
   domhandler@5.0.3:
     dependencies:
       domelementtype: 2.3.0
 
+  domutils@2.8.0:
+    dependencies:
+      dom-serializer: 1.4.1
+      domelementtype: 2.3.0
+      domhandler: 4.3.1
+
   domutils@3.0.1:
     dependencies:
       dom-serializer: 2.0.0
       domelementtype: 2.3.0
       domhandler: 5.0.3
 
-  dotenv-expand@10.0.0: {}
+  domutils@3.1.0:
+    dependencies:
+      dom-serializer: 2.0.0
+      domelementtype: 2.3.0
+      domhandler: 5.0.3
 
   dotenv@16.0.3: {}
 
@@ -19115,7 +17962,7 @@ snapshots:
       '@one-ini/wasm': 0.1.1
       commander: 10.0.1
       minimatch: 9.0.1
-      semver: 7.6.0
+      semver: 7.6.3
 
   ee-first@1.1.1: {}
 
@@ -19133,10 +17980,15 @@ snapshots:
 
   emoji-regex@9.2.2: {}
 
-  encode-utf8@1.0.3: {}
-
   encodeurl@1.0.2: {}
 
+  encodeurl@2.0.0: {}
+
+  encoding-sniffer@0.2.0:
+    dependencies:
+      iconv-lite: 0.6.3
+      whatwg-encoding: 3.1.1
+
   encoding@0.1.13:
     dependencies:
       iconv-lite: 0.6.3
@@ -19158,8 +18010,6 @@ snapshots:
 
   env-paths@2.2.1: {}
 
-  envinfo@7.8.1: {}
-
   err-code@2.0.3: {}
 
   error-ex@1.3.2:
@@ -19171,16 +18021,16 @@ snapshots:
       array-buffer-byte-length: 1.0.0
       arraybuffer.prototype.slice: 1.0.1
       available-typed-arrays: 1.0.5
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       es-set-tostringtag: 2.0.1
       es-to-primitive: 1.2.1
       function.prototype.name: 1.1.5
-      get-intrinsic: 1.2.1
+      get-intrinsic: 1.2.4
       get-symbol-description: 1.0.0
       globalthis: 1.0.3
       gopd: 1.0.1
       has: 1.0.3
-      has-property-descriptors: 1.0.0
+      has-property-descriptors: 1.0.2
       has-proto: 1.0.1
       has-symbols: 1.0.3
       internal-slot: 1.0.5
@@ -19192,7 +18042,7 @@ snapshots:
       is-string: 1.0.7
       is-typed-array: 1.1.10
       is-weakref: 1.0.2
-      object-inspect: 1.12.3
+      object-inspect: 1.13.2
       object-keys: 1.1.1
       object.assign: 4.1.4
       regexp.prototype.flags: 1.5.0
@@ -19208,6 +18058,61 @@ snapshots:
       unbox-primitive: 1.0.2
       which-typed-array: 1.1.11
 
+  es-abstract@1.23.3:
+    dependencies:
+      array-buffer-byte-length: 1.0.1
+      arraybuffer.prototype.slice: 1.0.3
+      available-typed-arrays: 1.0.7
+      call-bind: 1.0.7
+      data-view-buffer: 1.0.1
+      data-view-byte-length: 1.0.1
+      data-view-byte-offset: 1.0.0
+      es-define-property: 1.0.0
+      es-errors: 1.3.0
+      es-object-atoms: 1.0.0
+      es-set-tostringtag: 2.0.3
+      es-to-primitive: 1.2.1
+      function.prototype.name: 1.1.6
+      get-intrinsic: 1.2.4
+      get-symbol-description: 1.0.2
+      globalthis: 1.0.3
+      gopd: 1.0.1
+      has-property-descriptors: 1.0.2
+      has-proto: 1.0.3
+      has-symbols: 1.0.3
+      hasown: 2.0.2
+      internal-slot: 1.0.7
+      is-array-buffer: 3.0.4
+      is-callable: 1.2.7
+      is-data-view: 1.0.1
+      is-negative-zero: 2.0.3
+      is-regex: 1.1.4
+      is-shared-array-buffer: 1.0.3
+      is-string: 1.0.7
+      is-typed-array: 1.1.13
+      is-weakref: 1.0.2
+      object-inspect: 1.13.2
+      object-keys: 1.1.1
+      object.assign: 4.1.5
+      regexp.prototype.flags: 1.5.2
+      safe-array-concat: 1.1.2
+      safe-regex-test: 1.0.3
+      string.prototype.trim: 1.2.9
+      string.prototype.trimend: 1.0.8
+      string.prototype.trimstart: 1.0.8
+      typed-array-buffer: 1.0.2
+      typed-array-byte-length: 1.0.1
+      typed-array-byte-offset: 1.0.2
+      typed-array-length: 1.0.6
+      unbox-primitive: 1.0.2
+      which-typed-array: 1.1.15
+
+  es-define-property@1.0.0:
+    dependencies:
+      get-intrinsic: 1.2.4
+
+  es-errors@1.3.0: {}
+
   es-get-iterator@1.1.3:
     dependencies:
       call-bind: 1.0.2
@@ -19222,16 +18127,30 @@ snapshots:
 
   es-module-lexer@1.5.4: {}
 
+  es-object-atoms@1.0.0:
+    dependencies:
+      es-errors: 1.3.0
+
   es-set-tostringtag@2.0.1:
     dependencies:
-      get-intrinsic: 1.2.1
+      get-intrinsic: 1.2.4
       has: 1.0.3
       has-tostringtag: 1.0.0
 
+  es-set-tostringtag@2.0.3:
+    dependencies:
+      get-intrinsic: 1.2.4
+      has-tostringtag: 1.0.2
+      hasown: 2.0.2
+
   es-shim-unscopables@1.0.0:
     dependencies:
       has: 1.0.3
 
+  es-shim-unscopables@1.0.2:
+    dependencies:
+      hasown: 2.0.2
+
   es-to-primitive@1.2.1:
     dependencies:
       is-callable: 1.2.7
@@ -19246,12 +18165,10 @@ snapshots:
       es6-promise: 4.2.8
     optional: true
 
-  esbuild-plugin-alias@0.2.1: {}
-
-  esbuild-register@3.5.0(esbuild@0.19.11):
+  esbuild-register@3.5.0(esbuild@0.23.1):
     dependencies:
-      debug: 4.3.5(supports-color@5.5.0)
-      esbuild: 0.19.11
+      debug: 4.3.7(supports-color@8.1.1)
+      esbuild: 0.23.1
     transitivePeerDependencies:
       - supports-color
 
@@ -19359,8 +18276,37 @@ snapshots:
       '@esbuild/win32-ia32': 0.23.0
       '@esbuild/win32-x64': 0.23.0
 
+  esbuild@0.23.1:
+    optionalDependencies:
+      '@esbuild/aix-ppc64': 0.23.1
+      '@esbuild/android-arm': 0.23.1
+      '@esbuild/android-arm64': 0.23.1
+      '@esbuild/android-x64': 0.23.1
+      '@esbuild/darwin-arm64': 0.23.1
+      '@esbuild/darwin-x64': 0.23.1
+      '@esbuild/freebsd-arm64': 0.23.1
+      '@esbuild/freebsd-x64': 0.23.1
+      '@esbuild/linux-arm': 0.23.1
+      '@esbuild/linux-arm64': 0.23.1
+      '@esbuild/linux-ia32': 0.23.1
+      '@esbuild/linux-loong64': 0.23.1
+      '@esbuild/linux-mips64el': 0.23.1
+      '@esbuild/linux-ppc64': 0.23.1
+      '@esbuild/linux-riscv64': 0.23.1
+      '@esbuild/linux-s390x': 0.23.1
+      '@esbuild/linux-x64': 0.23.1
+      '@esbuild/netbsd-x64': 0.23.1
+      '@esbuild/openbsd-arm64': 0.23.1
+      '@esbuild/openbsd-x64': 0.23.1
+      '@esbuild/sunos-x64': 0.23.1
+      '@esbuild/win32-arm64': 0.23.1
+      '@esbuild/win32-ia32': 0.23.1
+      '@esbuild/win32-x64': 0.23.1
+
   escalade@3.1.1: {}
 
+  escape-goat@3.0.0: {}
+
   escape-html@1.0.3: {}
 
   escape-regexp@0.0.1: {}
@@ -19395,58 +18341,151 @@ snapshots:
   eslint-import-resolver-node@0.3.9:
     dependencies:
       debug: 3.2.7(supports-color@8.1.1)
-      is-core-module: 2.13.1
+      is-core-module: 2.15.1
       resolve: 1.22.8
     transitivePeerDependencies:
       - supports-color
 
-  eslint-module-utils@2.8.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0):
+  eslint-module-utils@2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.0):
+    dependencies:
+      debug: 3.2.7(supports-color@8.1.1)
+    optionalDependencies:
+      '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
+      eslint: 9.11.0
+      eslint-import-resolver-node: 0.3.9
+    transitivePeerDependencies:
+      - supports-color
+
+  eslint-module-utils@2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.0):
     dependencies:
       debug: 3.2.7(supports-color@8.1.1)
     optionalDependencies:
-      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
+      eslint: 9.11.0
+      eslint-import-resolver-node: 0.3.9
+    transitivePeerDependencies:
+      - supports-color
+
+  eslint-module-utils@2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0):
+    dependencies:
+      debug: 3.2.7(supports-color@8.1.1)
+    optionalDependencies:
+      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
       eslint: 9.8.0
       eslint-import-resolver-node: 0.3.9
     transitivePeerDependencies:
       - supports-color
 
-  eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0):
+  eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0):
     dependencies:
-      array-includes: 3.1.7
-      array.prototype.findlastindex: 1.2.3
+      '@rtsao/scc': 1.1.0
+      array-includes: 3.1.8
+      array.prototype.findlastindex: 1.2.5
       array.prototype.flat: 1.3.2
       array.prototype.flatmap: 1.3.2
       debug: 3.2.7(supports-color@8.1.1)
       doctrine: 2.1.0
-      eslint: 9.8.0
+      eslint: 9.11.0
       eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0)
-      hasown: 2.0.0
-      is-core-module: 2.13.1
+      eslint-module-utils: 2.11.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.0)
+      hasown: 2.0.2
+      is-core-module: 2.15.1
       is-glob: 4.0.3
       minimatch: 3.1.2
-      object.fromentries: 2.0.7
-      object.groupby: 1.0.1
-      object.values: 1.1.7
+      object.fromentries: 2.0.8
+      object.groupby: 1.0.3
+      object.values: 1.2.0
       semver: 6.3.1
       tsconfig-paths: 3.15.0
     optionalDependencies:
-      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4)
+      '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
     transitivePeerDependencies:
       - eslint-import-resolver-typescript
       - eslint-import-resolver-webpack
       - supports-color
 
-  eslint-plugin-vue@9.27.0(eslint@9.8.0):
+  eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint@9.11.0):
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
+      '@rtsao/scc': 1.1.0
+      array-includes: 3.1.8
+      array.prototype.findlastindex: 1.2.5
+      array.prototype.flat: 1.3.2
+      array.prototype.flatmap: 1.3.2
+      debug: 3.2.7(supports-color@8.1.1)
+      doctrine: 2.1.0
+      eslint: 9.11.0
+      eslint-import-resolver-node: 0.3.9
+      eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.11.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.11.0)
+      hasown: 2.0.2
+      is-core-module: 2.15.1
+      is-glob: 4.0.3
+      minimatch: 3.1.2
+      object.fromentries: 2.0.8
+      object.groupby: 1.0.3
+      object.values: 1.2.0
+      semver: 6.3.1
+      string.prototype.trimend: 1.0.8
+      tsconfig-paths: 3.15.0
+    optionalDependencies:
+      '@typescript-eslint/parser': 7.17.0(eslint@9.11.0)(typescript@5.6.2)
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
+
+  eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0):
+    dependencies:
+      '@rtsao/scc': 1.1.0
+      array-includes: 3.1.8
+      array.prototype.findlastindex: 1.2.5
+      array.prototype.flat: 1.3.2
+      array.prototype.flatmap: 1.3.2
+      debug: 3.2.7(supports-color@8.1.1)
+      doctrine: 2.1.0
       eslint: 9.8.0
+      eslint-import-resolver-node: 0.3.9
+      eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0)
+      hasown: 2.0.2
+      is-core-module: 2.15.1
+      is-glob: 4.0.3
+      minimatch: 3.1.2
+      object.fromentries: 2.0.8
+      object.groupby: 1.0.3
+      object.values: 1.2.0
+      semver: 6.3.1
+      string.prototype.trimend: 1.0.8
+      tsconfig-paths: 3.15.0
+    optionalDependencies:
+      '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2)
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
+
+  eslint-plugin-vue@9.27.0(eslint@9.11.0):
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0)
+      eslint: 9.11.0
       globals: 13.24.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
       postcss-selector-parser: 6.0.16
       semver: 7.6.0
-      vue-eslint-parser: 9.4.3(eslint@9.8.0)
+      vue-eslint-parser: 9.4.3(eslint@9.11.0)
+      xml-name-validator: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  eslint-plugin-vue@9.28.0(eslint@9.11.0):
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0)
+      eslint: 9.11.0
+      globals: 13.24.0
+      natural-compare: 1.4.0
+      nth-check: 2.1.1
+      postcss-selector-parser: 6.0.16
+      semver: 7.6.3
+      vue-eslint-parser: 9.4.3(eslint@9.11.0)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -19467,6 +18506,45 @@ snapshots:
 
   eslint-visitor-keys@4.0.0: {}
 
+  eslint@9.11.0:
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.11.0)
+      '@eslint-community/regexpp': 4.11.0
+      '@eslint/config-array': 0.18.0
+      '@eslint/eslintrc': 3.1.0
+      '@eslint/js': 9.11.0
+      '@eslint/plugin-kit': 0.2.0
+      '@humanwhocodes/module-importer': 1.0.1
+      '@humanwhocodes/retry': 0.3.0
+      '@nodelib/fs.walk': 1.2.8
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.3
+      debug: 4.3.7(supports-color@8.1.1)
+      escape-string-regexp: 4.0.0
+      eslint-scope: 8.0.2
+      eslint-visitor-keys: 4.0.0
+      espree: 10.1.0
+      esquery: 1.6.0
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 8.0.0
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      ignore: 5.3.1
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      is-path-inside: 3.0.3
+      json-stable-stringify-without-jsonify: 1.0.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.4
+      strip-ansi: 6.0.1
+      text-table: 0.2.0
+    transitivePeerDependencies:
+      - supports-color
+
   eslint@9.8.0:
     dependencies:
       '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
@@ -19480,7 +18558,7 @@ snapshots:
       ajv: 6.12.6
       chalk: 4.1.2
       cross-spawn: 7.0.3
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       escape-string-regexp: 4.0.0
       eslint-scope: 8.0.2
       eslint-visitor-keys: 4.0.0
@@ -19534,7 +18612,7 @@ snapshots:
 
   estree-walker@3.0.3:
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.6
 
   esutils@2.0.3: {}
 
@@ -19618,16 +18696,16 @@ snapshots:
       signal-exit: 4.1.0
       strip-final-newline: 3.0.0
 
-  execa@9.3.0:
+  execa@9.4.0:
     dependencies:
       '@sindresorhus/merge-streams': 4.0.0
       cross-spawn: 7.0.3
       figures: 6.1.0
       get-stream: 9.0.1
-      human-signals: 7.0.0
+      human-signals: 8.0.0
       is-plain-obj: 4.1.0
       is-stream: 4.0.1
-      npm-run-path: 5.3.0
+      npm-run-path: 6.0.0
       pretty-ms: 9.0.0
       signal-exit: 4.1.0
       strip-final-newline: 4.0.0
@@ -19649,34 +18727,34 @@ snapshots:
 
   exponential-backoff@3.1.1: {}
 
-  express@4.19.2:
+  express@4.21.0:
     dependencies:
       accepts: 1.3.8
       array-flatten: 1.1.1
-      body-parser: 1.20.2
+      body-parser: 1.20.3
       content-disposition: 0.5.4
       content-type: 1.0.5
       cookie: 0.6.0
       cookie-signature: 1.0.6
       debug: 2.6.9
       depd: 2.0.0
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       etag: 1.8.1
-      finalhandler: 1.2.0
+      finalhandler: 1.3.1
       fresh: 0.5.2
       http-errors: 2.0.0
-      merge-descriptors: 1.0.1
+      merge-descriptors: 1.0.3
       methods: 1.1.2
       on-finished: 2.4.1
       parseurl: 1.3.3
-      path-to-regexp: 0.1.7
+      path-to-regexp: 0.1.10
       proxy-addr: 2.0.7
-      qs: 6.11.0
+      qs: 6.13.0
       range-parser: 1.2.1
       safe-buffer: 5.2.1
-      send: 0.18.0
-      serve-static: 1.15.0
+      send: 0.19.0
+      serve-static: 1.16.2
       setprototypeof: 1.2.0
       statuses: 2.0.1
       type-is: 1.6.18
@@ -19708,7 +18786,7 @@ snapshots:
 
   extsprintf@1.3.0: {}
 
-  fast-content-type-parse@1.1.0: {}
+  fast-content-type-parse@2.0.0: {}
 
   fast-decode-uri-component@1.0.1: {}
 
@@ -19722,18 +18800,19 @@ snapshots:
       '@nodelib/fs.walk': 1.2.8
       glob-parent: 5.1.2
       merge2: 1.4.1
-      micromatch: 4.0.7
+      micromatch: 4.0.8
 
   fast-json-stable-stringify@2.1.0: {}
 
-  fast-json-stringify@5.8.0:
+  fast-json-stringify@6.0.0:
     dependencies:
-      '@fastify/deepmerge': 1.3.0
+      '@fastify/merge-json-schemas': 0.1.1
       ajv: 8.17.1
-      ajv-formats: 2.1.1(ajv@8.17.1)
+      ajv-formats: 3.0.1(ajv@8.17.1)
       fast-deep-equal: 3.1.3
-      fast-uri: 2.2.0
-      rfdc: 1.3.0
+      fast-uri: 2.4.0
+      json-schema-ref-resolver: 1.0.1
+      rfdc: 1.4.1
 
   fast-levenshtein@2.0.6: {}
 
@@ -19745,7 +18824,7 @@ snapshots:
 
   fast-safe-stringify@2.1.1: {}
 
-  fast-uri@2.2.0: {}
+  fast-uri@2.4.0: {}
 
   fast-uri@3.0.1: {}
 
@@ -19753,34 +18832,37 @@ snapshots:
     dependencies:
       strnum: 1.0.5
 
-  fastify-plugin@4.5.0: {}
+  fast-xml-parser@4.5.0:
+    dependencies:
+      strnum: 1.0.5
+
+  fastify-plugin@4.5.1: {}
 
-  fastify-raw-body@4.3.0:
+  fastify-plugin@5.0.0: {}
+
+  fastify-raw-body@5.0.0:
     dependencies:
-      fastify-plugin: 4.5.0
-      raw-body: 2.5.2
+      fastify-plugin: 5.0.0
+      raw-body: 3.0.0
       secure-json-parse: 2.7.0
 
-  fastify@4.28.1:
+  fastify@5.0.0:
     dependencies:
-      '@fastify/ajv-compiler': 3.5.0
-      '@fastify/error': 3.4.0
-      '@fastify/fast-json-stringify-compiler': 4.3.0
+      '@fastify/ajv-compiler': 4.0.0
+      '@fastify/error': 4.0.0
+      '@fastify/fast-json-stringify-compiler': 5.0.0
       abstract-logging: 2.0.1
-      avvio: 8.3.0
-      fast-content-type-parse: 1.1.0
-      fast-json-stringify: 5.8.0
-      find-my-way: 8.2.0
-      light-my-request: 5.11.0
+      avvio: 9.0.0
+      fast-json-stringify: 6.0.0
+      find-my-way: 9.0.1
+      light-my-request: 6.0.0
       pino: 9.2.0
-      process-warning: 3.0.0
+      process-warning: 4.0.0
       proxy-addr: 2.0.7
-      rfdc: 1.3.0
+      rfdc: 1.4.1
       secure-json-parse: 2.7.0
       semver: 7.6.0
       toad-cache: 3.7.0
-    transitivePeerDependencies:
-      - supports-color
 
   fastq@1.17.1:
     dependencies:
@@ -19790,10 +18872,6 @@ snapshots:
     dependencies:
       bser: 2.1.1
 
-  fd-package-json@1.2.0:
-    dependencies:
-      walk-up-path: 3.0.1
-
   fd-slicer@1.1.0:
     dependencies:
       pend: 1.2.0
@@ -19807,8 +18885,6 @@ snapshots:
       node-domexception: 1.0.0
       web-streams-polyfill: 3.2.1
 
-  fetch-retry@5.0.4: {}
-
   figures@3.2.0:
     dependencies:
       escape-string-regexp: 1.0.5
@@ -19821,20 +18897,16 @@ snapshots:
     dependencies:
       flat-cache: 4.0.1
 
-  file-system-cache@2.3.0:
-    dependencies:
-      fs-extra: 11.1.1
-      ramda: 0.29.0
-
   file-type@17.1.6:
     dependencies:
       readable-web-to-node-stream: 3.0.2
       strtok3: 7.0.0
       token-types: 5.0.1
 
-  file-type@19.3.0:
+  file-type@19.5.0:
     dependencies:
-      strtok3: 8.0.1
+      get-stream: 9.0.1
+      strtok3: 8.1.0
       token-types: 6.0.0
       uint8array-extras: 1.4.0
 
@@ -19858,10 +18930,10 @@ snapshots:
     dependencies:
       to-regex-range: 5.0.1
 
-  finalhandler@1.2.0:
+  finalhandler@1.3.1:
     dependencies:
       debug: 2.6.9
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       on-finished: 2.4.1
       parseurl: 1.3.3
@@ -19870,30 +18942,20 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  find-cache-dir@2.1.0:
-    dependencies:
-      commondir: 1.0.1
-      make-dir: 2.1.0
-      pkg-dir: 3.0.0
-
   find-cache-dir@3.3.2:
     dependencies:
       commondir: 1.0.1
       make-dir: 3.1.0
       pkg-dir: 4.2.0
 
-  find-my-way@8.2.0:
+  find-my-way@9.0.1:
     dependencies:
       fast-deep-equal: 3.1.3
       fast-querystring: 1.1.2
-      safe-regex2: 3.1.0
+      safe-regex2: 4.0.0
 
   find-package-json@1.2.0: {}
 
-  find-up@3.0.0:
-    dependencies:
-      locate-path: 3.0.0
-
   find-up@4.1.0:
     dependencies:
       locate-path: 5.0.0
@@ -19924,16 +18986,16 @@ snapshots:
 
   flatted@3.3.1: {}
 
-  flow-parser@0.202.0: {}
-
   fluent-ffmpeg@2.1.3:
     dependencies:
       async: 0.2.10
       which: 1.3.1
 
-  follow-redirects@1.15.2(debug@4.3.5):
+  follow-redirects@1.15.2: {}
+
+  follow-redirects@1.15.9(debug@4.3.7):
     optionalDependencies:
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
 
   for-each@0.3.3:
     dependencies:
@@ -20019,11 +19081,18 @@ snapshots:
 
   function.prototype.name@1.1.5:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
       es-abstract: 1.22.1
       functions-have-names: 1.2.3
 
+  function.prototype.name@1.1.6:
+    dependencies:
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+      functions-have-names: 1.2.3
+
   functions-have-names@1.2.3: {}
 
   gauge@3.0.2:
@@ -20052,6 +19121,14 @@ snapshots:
       has-proto: 1.0.1
       has-symbols: 1.0.3
 
+  get-intrinsic@1.2.4:
+    dependencies:
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      has-proto: 1.0.1
+      has-symbols: 1.0.3
+      hasown: 2.0.0
+
   get-package-type@0.1.0: {}
 
   get-pixels-frame-info-update@3.3.2:
@@ -20085,8 +19162,14 @@ snapshots:
 
   get-symbol-description@1.0.0:
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      get-intrinsic: 1.2.4
+
+  get-symbol-description@1.0.2:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      get-intrinsic: 1.2.4
 
   get-tsconfig@4.7.2:
     dependencies:
@@ -20104,18 +19187,6 @@ snapshots:
     dependencies:
       readable-stream: 1.1.14
 
-  giget@1.1.2:
-    dependencies:
-      colorette: 2.0.19
-      defu: 6.1.4
-      https-proxy-agent: 5.0.1
-      mri: 1.2.0
-      node-fetch-native: 1.0.2
-      pathe: 1.1.2
-      tar: 6.2.1
-    transitivePeerDependencies:
-      - supports-color
-
   github-slugger@2.0.0: {}
 
   glob-parent@5.1.2:
@@ -20131,8 +19202,6 @@ snapshots:
       '@types/glob': 7.2.0
       glob: 7.2.3
 
-  glob-to-regexp@0.4.1: {}
-
   glob@10.3.10:
     dependencies:
       foreground-child: 3.1.1
@@ -20141,15 +19210,6 @@ snapshots:
       minipass: 7.0.4
       path-scurry: 1.10.1
 
-  glob@10.4.2:
-    dependencies:
-      foreground-child: 3.1.1
-      jackspeak: 3.4.0
-      minimatch: 9.0.4
-      minipass: 7.1.2
-      package-json-from-dist: 1.0.0
-      path-scurry: 1.11.1
-
   glob@11.0.0:
     dependencies:
       foreground-child: 3.1.1
@@ -20188,7 +19248,7 @@ snapshots:
 
   globals@14.0.0: {}
 
-  globals@15.8.0: {}
+  globals@15.9.0: {}
 
   globalthis@1.0.3:
     dependencies:
@@ -20203,21 +19263,12 @@ snapshots:
       merge2: 1.4.1
       slash: 3.0.0
 
-  globby@14.0.1:
-    dependencies:
-      '@sindresorhus/merge-streams': 2.3.0
-      fast-glob: 3.3.2
-      ignore: 5.3.1
-      path-type: 5.0.0
-      slash: 5.1.0
-      unicorn-magic: 0.1.0
-
   google-protobuf@3.21.2:
     optional: true
 
   gopd@1.0.1:
     dependencies:
-      get-intrinsic: 1.2.1
+      get-intrinsic: 1.2.4
 
   got@11.8.5:
     dependencies:
@@ -20271,15 +19322,6 @@ snapshots:
 
   hammerjs@2.0.8: {}
 
-  handlebars@4.7.7:
-    dependencies:
-      minimist: 1.2.8
-      neo-async: 2.6.2
-      source-map: 0.6.1
-      wordwrap: 1.0.0
-    optionalDependencies:
-      uglify-js: 3.17.4
-
   happy-dom@10.0.3:
     dependencies:
       css.escape: 1.5.1
@@ -20289,6 +19331,12 @@ snapshots:
       whatwg-encoding: 2.0.0
       whatwg-mimetype: 3.0.0
 
+  happy-dom@15.7.4:
+    dependencies:
+      entities: 4.5.0
+      webidl-conversions: 7.0.0
+      whatwg-mimetype: 3.0.0
+
   har-schema@2.0.0: {}
 
   har-validator@5.1.5:
@@ -20308,14 +19356,24 @@ snapshots:
     dependencies:
       get-intrinsic: 1.2.1
 
+  has-property-descriptors@1.0.2:
+    dependencies:
+      es-define-property: 1.0.0
+
   has-proto@1.0.1: {}
 
+  has-proto@1.0.3: {}
+
   has-symbols@1.0.3: {}
 
   has-tostringtag@1.0.0:
     dependencies:
       has-symbols: 1.0.3
 
+  has-tostringtag@1.0.2:
+    dependencies:
+      has-symbols: 1.0.3
+
   has-unicode@2.0.1:
     optional: true
 
@@ -20325,12 +19383,14 @@ snapshots:
 
   hash-sum@2.0.0: {}
 
-  hashlru@2.3.0: {}
-
   hasown@2.0.0:
     dependencies:
       function-bind: 1.1.2
 
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
   hast-util-heading-rank@3.0.0:
     dependencies:
       '@types/hast': 3.0.4
@@ -20339,17 +19399,35 @@ snapshots:
     dependencies:
       '@types/hast': 3.0.4
 
+  hast-util-to-html@9.0.3:
+    dependencies:
+      '@types/hast': 3.0.4
+      '@types/unist': 3.0.2
+      ccount: 2.0.1
+      comma-separated-tokens: 2.0.3
+      hast-util-whitespace: 3.0.0
+      html-void-elements: 3.0.0
+      mdast-util-to-hast: 13.2.0
+      property-information: 6.5.0
+      space-separated-tokens: 2.0.2
+      stringify-entities: 4.0.4
+      zwitch: 2.0.4
+
   hast-util-to-string@3.0.0:
     dependencies:
       '@types/hast': 3.0.4
 
+  hast-util-whitespace@3.0.0:
+    dependencies:
+      '@types/hast': 3.0.4
+
   he@1.2.0: {}
 
   headers-polyfill@4.0.2: {}
 
   highlight.js@10.7.3: {}
 
-  highlight.js@11.9.0: {}
+  highlight.js@11.10.0: {}
 
   hosted-git-info@2.8.9: {}
 
@@ -20369,8 +19447,17 @@ snapshots:
 
   html-tags@3.2.0: {}
 
+  html-void-elements@3.0.0: {}
+
   htmlescape@1.1.1: {}
 
+  htmlparser2@5.0.1:
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 3.3.0
+      domutils: 2.8.0
+      entities: 2.2.0
+
   htmlparser2@8.0.1:
     dependencies:
       domelementtype: 2.3.0
@@ -20378,6 +19465,13 @@ snapshots:
       domutils: 3.0.1
       entities: 4.5.0
 
+  htmlparser2@9.1.0:
+    dependencies:
+      domelementtype: 2.3.0
+      domhandler: 5.0.3
+      domutils: 3.1.0
+      entities: 4.5.0
+
   http-cache-semantics@4.1.1: {}
 
   http-errors@2.0.0:
@@ -20403,11 +19497,11 @@ snapshots:
       jsprim: 1.4.2
       sshpk: 1.17.0
 
-  http-signature@1.3.6:
+  http-signature@1.4.0:
     dependencies:
       assert-plus: 1.0.0
       jsprim: 2.0.2
-      sshpk: 1.17.0
+      sshpk: 1.18.0
 
   http2-wrapper@1.0.3:
     dependencies:
@@ -20435,6 +19529,7 @@ snapshots:
       debug: 4.3.5(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
+    optional: true
 
   https-proxy-agent@7.0.2:
     dependencies:
@@ -20443,13 +19538,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  https-proxy-agent@7.0.4:
-    dependencies:
-      agent-base: 7.1.0
-      debug: 4.3.5(supports-color@5.5.0)
-    transitivePeerDependencies:
-      - supports-color
-
   https-proxy-agent@7.0.5:
     dependencies:
       agent-base: 7.1.0
@@ -20465,7 +19553,7 @@ snapshots:
 
   human-signals@5.0.0: {}
 
-  human-signals@7.0.0: {}
+  human-signals@8.0.0: {}
 
   iconv-lite@0.4.24:
     dependencies:
@@ -20545,6 +19633,12 @@ snapshots:
       has: 1.0.3
       side-channel: 1.0.4
 
+  internal-slot@1.0.7:
+    dependencies:
+      es-errors: 1.3.0
+      hasown: 2.0.2
+      side-channel: 1.0.6
+
   intersection-observer@0.12.2: {}
 
   ioredis@5.4.1:
@@ -20568,7 +19662,7 @@ snapshots:
       jsbn: 1.1.0
       sprintf-js: 1.1.3
 
-  ip-cidr@4.0.1:
+  ip-cidr@4.0.2:
     dependencies:
       ip-address: 9.0.5
 
@@ -20595,6 +19689,11 @@ snapshots:
       get-intrinsic: 1.2.1
       is-typed-array: 1.1.10
 
+  is-array-buffer@3.0.4:
+    dependencies:
+      call-bind: 1.0.7
+      get-intrinsic: 1.2.4
+
   is-arrayish@0.2.1: {}
 
   is-arrayish@0.3.2: {}
@@ -20609,7 +19708,7 @@ snapshots:
 
   is-boolean-object@1.1.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       has-tostringtag: 1.0.0
 
   is-buffer@1.1.6: {}
@@ -20624,6 +19723,14 @@ snapshots:
     dependencies:
       hasown: 2.0.0
 
+  is-core-module@2.15.1:
+    dependencies:
+      hasown: 2.0.2
+
+  is-data-view@1.0.1:
+    dependencies:
+      is-typed-array: 1.1.13
+
   is-date-object@1.0.5:
     dependencies:
       has-tostringtag: 1.0.0
@@ -20645,7 +19752,7 @@ snapshots:
 
   is-generator-function@1.0.10:
     dependencies:
-      has-tostringtag: 1.0.0
+      has-tostringtag: 1.0.2
 
   is-glob@4.0.3:
     dependencies:
@@ -20656,8 +19763,6 @@ snapshots:
       global-dirs: 3.0.1
       is-path-inside: 3.0.3
 
-  is-interactive@1.0.0: {}
-
   is-ip@3.1.0:
     dependencies:
       ip-regex: 4.3.0
@@ -20666,13 +19771,10 @@ snapshots:
 
   is-map@2.0.2: {}
 
-  is-nan@1.3.2:
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-
   is-negative-zero@2.0.2: {}
 
+  is-negative-zero@2.0.3: {}
+
   is-node-process@1.2.0: {}
 
   is-number-object@1.0.7:
@@ -20687,10 +19789,6 @@ snapshots:
 
   is-plain-obj@4.1.0: {}
 
-  is-plain-object@2.0.4:
-    dependencies:
-      isobject: 3.0.1
-
   is-plain-object@5.0.0: {}
 
   is-potential-custom-element-name@1.0.1: {}
@@ -20708,6 +19806,10 @@ snapshots:
     dependencies:
       call-bind: 1.0.2
 
+  is-shared-array-buffer@1.0.3:
+    dependencies:
+      call-bind: 1.0.7
+
   is-stream@1.1.0: {}
 
   is-stream@2.0.1: {}
@@ -20720,9 +19822,9 @@ snapshots:
     dependencies:
       has-tostringtag: 1.0.0
 
-  is-svg@5.0.1:
+  is-svg@5.1.0:
     dependencies:
-      fast-xml-parser: 4.2.5
+      fast-xml-parser: 4.5.0
 
   is-symbol@1.0.4:
     dependencies:
@@ -20736,6 +19838,10 @@ snapshots:
       gopd: 1.0.1
       has-tostringtag: 1.0.0
 
+  is-typed-array@1.1.13:
+    dependencies:
+      which-typed-array: 1.1.15
+
   is-typedarray@1.0.0: {}
 
   is-unicode-supported@0.1.0: {}
@@ -20746,11 +19852,11 @@ snapshots:
 
   is-weakref@1.0.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
 
   is-weakset@2.0.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       get-intrinsic: 1.2.1
 
   is-wsl@2.2.0:
@@ -20767,8 +19873,6 @@ snapshots:
 
   isexe@3.1.1: {}
 
-  isobject@3.0.1: {}
-
   isstream@0.1.2: {}
 
   istanbul-lib-coverage@3.2.2: {}
@@ -20789,7 +19893,7 @@ snapshots:
       '@babel/parser': 7.24.7
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
-      semver: 7.6.0
+      semver: 7.6.3
     transitivePeerDependencies:
       - supports-color
 
@@ -20801,7 +19905,7 @@ snapshots:
 
   istanbul-lib-source-maps@4.0.1:
     dependencies:
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
@@ -20828,12 +19932,6 @@ snapshots:
     optionalDependencies:
       '@pkgjs/parseargs': 0.11.0
 
-  jackspeak@3.4.0:
-    dependencies:
-      '@isaacs/cliui': 8.0.2
-    optionalDependencies:
-      '@pkgjs/parseargs': 0.11.0
-
   jackspeak@4.0.1:
     dependencies:
       '@isaacs/cliui': 8.0.2
@@ -20917,7 +20015,7 @@ snapshots:
       jest-runner: 29.7.0
       jest-util: 29.7.0
       jest-validate: 29.7.0
-      micromatch: 4.0.7
+      micromatch: 4.0.8
       parse-json: 5.2.0
       pretty-format: 29.7.0
       slash: 3.0.0
@@ -20976,7 +20074,7 @@ snapshots:
       jest-regex-util: 29.6.3
       jest-util: 29.7.0
       jest-worker: 29.7.0
-      micromatch: 4.0.7
+      micromatch: 4.0.8
       walker: 1.0.8
     optionalDependencies:
       fsevents: 2.3.3
@@ -21000,7 +20098,7 @@ snapshots:
       '@types/stack-utils': 2.0.1
       chalk: 4.1.2
       graceful-fs: 4.2.11
-      micromatch: 4.0.7
+      micromatch: 4.0.8
       pretty-format: 29.7.0
       slash: 3.0.0
       stack-utils: 2.0.6
@@ -21177,6 +20275,14 @@ snapshots:
       '@sideway/formula': 3.0.1
       '@sideway/pinpoint': 2.0.0
 
+  joi@17.13.3:
+    dependencies:
+      '@hapi/hoek': 9.3.0
+      '@hapi/topo': 5.1.0
+      '@sideway/address': 4.1.5
+      '@sideway/formula': 3.0.1
+      '@sideway/pinpoint': 2.0.0
+
   jpeg-js@0.3.7: {}
 
   js-beautify@1.14.9:
@@ -21207,61 +20313,7 @@ snapshots:
 
   jschardet@3.0.0: {}
 
-  jscodeshift@0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7)):
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/parser': 7.24.7
-      '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7)
-      '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7)
-      '@babel/preset-flow': 7.23.3(@babel/core@7.24.7)
-      '@babel/preset-typescript': 7.23.3(@babel/core@7.24.7)
-      '@babel/register': 7.22.15(@babel/core@7.24.7)
-      babel-core: 7.0.0-bridge.0(@babel/core@7.24.7)
-      chalk: 4.1.2
-      flow-parser: 0.202.0
-      graceful-fs: 4.2.11
-      micromatch: 4.0.7
-      neo-async: 2.6.2
-      node-dir: 0.1.17
-      recast: 0.23.6
-      temp: 0.8.4
-      write-file-atomic: 2.4.3
-    optionalDependencies:
-      '@babel/preset-env': 7.24.7(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
-
-  jsdom@24.1.1:
-    dependencies:
-      cssstyle: 4.0.1
-      data-urls: 5.0.0
-      decimal.js: 10.4.3
-      form-data: 4.0.0
-      html-encoding-sniffer: 4.0.0
-      http-proxy-agent: 7.0.2
-      https-proxy-agent: 7.0.5
-      is-potential-custom-element-name: 1.0.1
-      nwsapi: 2.2.12
-      parse5: 7.1.2
-      rrweb-cssom: 0.7.1
-      saxes: 6.0.0
-      symbol-tree: 3.2.4
-      tough-cookie: 4.1.4
-      w3c-xmlserializer: 5.0.0
-      webidl-conversions: 7.0.0
-      whatwg-encoding: 3.1.1
-      whatwg-mimetype: 4.0.0
-      whatwg-url: 14.0.0
-      ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-      xml-name-validator: 5.0.0
-    transitivePeerDependencies:
-      - bufferutil
-      - supports-color
-      - utf-8-validate
-    optional: true
+  jsdoc-type-pratt-parser@4.1.0: {}
 
   jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3):
     dependencies:
@@ -21320,14 +20372,16 @@ snapshots:
       - utf-8-validate
     optional: true
 
-  jsesc@0.5.0: {}
-
   jsesc@2.5.2: {}
 
   json-buffer@3.0.1: {}
 
   json-parse-even-better-errors@2.3.1: {}
 
+  json-schema-ref-resolver@1.0.1:
+    dependencies:
+      fast-deep-equal: 3.1.3
+
   json-schema-traverse@0.4.1: {}
 
   json-schema-traverse@1.0.0: {}
@@ -21399,6 +20453,14 @@ snapshots:
       is-promise: 2.2.2
       promise: 7.3.1
 
+  juice@11.0.0:
+    dependencies:
+      cheerio: 1.0.0
+      commander: 12.1.0
+      mensch: 0.3.4
+      slick: 1.12.2
+      web-resource-inliner: 7.0.0
+
   just-extend@4.2.1: {}
 
   jwa@2.0.0:
@@ -21432,12 +20494,6 @@ snapshots:
 
   lazy-ass@1.6.0: {}
 
-  lazy-universal-dotenv@4.0.0:
-    dependencies:
-      app-root-dir: 1.0.2
-      dotenv: 16.0.3
-      dotenv-expand: 10.0.0
-
   lazystream@1.0.1:
     dependencies:
       readable-stream: 2.3.7
@@ -21449,10 +20505,10 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  light-my-request@5.11.0:
+  light-my-request@6.0.0:
     dependencies:
-      cookie: 0.5.0
-      process-warning: 2.2.0
+      cookie: 0.6.0
+      process-warning: 4.0.0
       set-cookie-parser: 2.6.0
 
   lilconfig@3.1.1: {}
@@ -21477,11 +20533,6 @@ snapshots:
       mlly: 1.5.0
       pkg-types: 1.0.3
 
-  locate-path@3.0.0:
-    dependencies:
-      p-locate: 3.0.0
-      path-exists: 3.0.0
-
   locate-path@5.0.0:
     dependencies:
       p-locate: 4.1.0
@@ -21490,8 +20541,6 @@ snapshots:
     dependencies:
       p-locate: 5.0.0
 
-  lodash.debounce@4.0.8: {}
-
   lodash.defaults@4.2.0: {}
 
   lodash.get@4.4.2: {}
@@ -21532,13 +20581,17 @@ snapshots:
     dependencies:
       get-func-name: 2.0.2
 
+  loupe@3.1.1:
+    dependencies:
+      get-func-name: 2.0.2
+
   lowercase-keys@2.0.0: {}
 
   lowercase-keys@3.0.0: {}
 
   lru-cache@10.0.2:
     dependencies:
-      semver: 7.6.0
+      semver: 7.6.3
 
   lru-cache@10.2.2: {}
 
@@ -21565,12 +20618,16 @@ snapshots:
 
   magic-string@0.27.0:
     dependencies:
-      '@jridgewell/sourcemap-codec': 1.4.15
+      '@jridgewell/sourcemap-codec': 1.5.0
 
   magic-string@0.30.10:
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
 
+  magic-string@0.30.11:
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.0
+
   magicast@0.3.4:
     dependencies:
       '@babel/parser': 7.24.7
@@ -21579,18 +20636,13 @@ snapshots:
 
   mailcheck@1.1.1: {}
 
-  make-dir@2.1.0:
-    dependencies:
-      pify: 4.0.1
-      semver: 5.7.1
-
   make-dir@3.1.0:
     dependencies:
       semver: 6.3.1
 
   make-dir@4.0.0:
     dependencies:
-      semver: 7.6.0
+      semver: 7.6.3
 
   make-fetch-happen@13.0.0:
     dependencies:
@@ -21714,6 +20766,18 @@ snapshots:
       '@types/mdast': 4.0.3
       unist-util-is: 6.0.0
 
+  mdast-util-to-hast@13.2.0:
+    dependencies:
+      '@types/hast': 3.0.4
+      '@types/mdast': 4.0.3
+      '@ungap/structured-clone': 1.2.0
+      devlop: 1.1.0
+      micromark-util-sanitize-uri: 2.0.0
+      trim-lines: 3.0.1
+      unist-util-position: 5.0.0
+      unist-util-visit: 5.0.0
+      vfile: 6.0.1
+
   mdast-util-to-markdown@2.1.0:
     dependencies:
       '@types/mdast': 4.0.3
@@ -21735,7 +20799,7 @@ snapshots:
 
   media-typer@0.3.0: {}
 
-  meilisearch@0.41.0(encoding@0.1.13):
+  meilisearch@0.42.0(encoding@0.1.13):
     dependencies:
       cross-fetch: 3.1.6(encoding@0.1.13)
     transitivePeerDependencies:
@@ -21745,6 +20809,8 @@ snapshots:
     dependencies:
       map-or-similar: 1.5.0
 
+  mensch@0.3.4: {}
+
   meow@9.0.0:
     dependencies:
       '@types/minimist': 1.2.2
@@ -21760,7 +20826,7 @@ snapshots:
       type-fest: 0.18.1
       yargs-parser: 20.2.9
 
-  merge-descriptors@1.0.1: {}
+  merge-descriptors@1.0.3: {}
 
   merge-stream@2.0.0: {}
 
@@ -21948,7 +21014,7 @@ snapshots:
   micromark@4.0.0:
     dependencies:
       '@types/debug': 4.1.12
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       decode-named-character-reference: 1.0.2
       devlop: 1.1.0
       micromark-core-commonmark: 2.0.0
@@ -21967,7 +21033,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  micromatch@4.0.7:
+  micromatch@4.0.8:
     dependencies:
       braces: 3.0.3
       picomatch: 2.3.1
@@ -21980,6 +21046,8 @@ snapshots:
 
   mime@1.6.0: {}
 
+  mime@2.6.0: {}
+
   mime@3.0.0: {}
 
   mimic-fn@2.1.0: {}
@@ -22097,7 +21165,7 @@ snapshots:
       pkg-types: 1.0.3
       ufo: 1.3.2
 
-  mnemonist@0.39.6:
+  mnemonist@0.39.8:
     dependencies:
       obliterator: 2.0.4
 
@@ -22105,8 +21173,6 @@ snapshots:
 
   module-details-from-path@1.0.3: {}
 
-  mri@1.2.0: {}
-
   ms@2.0.0: {}
 
   ms@2.1.2: {}
@@ -22131,12 +21197,12 @@ snapshots:
     optionalDependencies:
       msgpackr-extract: 3.0.2
 
-  msw-storybook-addon@2.0.3(msw@2.3.4(typescript@5.5.4)):
+  msw-storybook-addon@2.0.3(msw@2.4.9(typescript@5.6.2)):
     dependencies:
       is-node-process: 1.2.0
-      msw: 2.3.4(typescript@5.5.4)
+      msw: 2.4.9(typescript@5.6.2)
 
-  msw@2.3.4(typescript@5.5.4):
+  msw@2.3.4(typescript@5.6.2):
     dependencies:
       '@bundled-es-modules/cookie': 2.0.0
       '@bundled-es-modules/statuses': 1.0.1
@@ -22156,7 +21222,29 @@ snapshots:
       type-fest: 4.20.1
       yargs: 17.7.2
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
+
+  msw@2.4.9(typescript@5.6.2):
+    dependencies:
+      '@bundled-es-modules/cookie': 2.0.0
+      '@bundled-es-modules/statuses': 1.0.1
+      '@bundled-es-modules/tough-cookie': 0.1.6
+      '@inquirer/confirm': 3.1.6
+      '@mswjs/interceptors': 0.35.8
+      '@open-draft/until': 2.1.0
+      '@types/cookie': 0.6.0
+      '@types/statuses': 2.0.4
+      chalk: 4.1.2
+      graphql: 16.8.1
+      headers-polyfill: 4.0.2
+      is-node-process: 1.2.0
+      outvariant: 1.4.2
+      path-to-regexp: 6.3.0
+      strict-event-emitter: 0.5.1
+      type-fest: 4.20.1
+      yargs: 17.7.2
+    optionalDependencies:
+      typescript: 5.6.2
 
   muggle-string@0.4.1: {}
 
@@ -22221,8 +21309,6 @@ snapshots:
 
   negotiator@0.6.3: {}
 
-  neo-async@2.6.2: {}
-
   nested-property@4.0.0: {}
 
   netmask@2.0.2: {}
@@ -22252,14 +21338,8 @@ snapshots:
 
   node-bitmap@0.0.1: {}
 
-  node-dir@0.1.17:
-    dependencies:
-      minimatch: 3.1.2
-
   node-domexception@1.0.0: {}
 
-  node-fetch-native@1.0.2: {}
-
   node-fetch@2.6.13(encoding@0.1.13):
     dependencies:
       whatwg-url: 5.0.0
@@ -22284,7 +21364,7 @@ snapshots:
   node-gyp-build@4.6.0:
     optional: true
 
-  node-gyp@10.1.0:
+  node-gyp@10.2.0:
     dependencies:
       env-paths: 2.2.1
       exponential-backoff: 3.1.1
@@ -22292,7 +21372,7 @@ snapshots:
       graceful-fs: 4.2.11
       make-fetch-happen: 13.0.0
       nopt: 7.2.0
-      proc-log: 3.0.0
+      proc-log: 4.2.0
       semver: 7.6.0
       tar: 6.2.1
       which: 4.0.0
@@ -22303,7 +21383,7 @@ snapshots:
 
   node-releases@2.0.14: {}
 
-  nodemailer@6.9.14: {}
+  nodemailer@6.9.15: {}
 
   nodemon@3.0.2:
     dependencies:
@@ -22318,7 +21398,7 @@ snapshots:
       touch: 3.1.0
       undefsafe: 2.0.5
 
-  nodemon@3.1.4:
+  nodemon@3.1.7:
     dependencies:
       chokidar: 3.5.3
       debug: 4.3.5(supports-color@5.5.0)
@@ -22361,7 +21441,7 @@ snapshots:
     dependencies:
       hosted-git-info: 4.1.0
       is-core-module: 2.13.1
-      semver: 7.6.0
+      semver: 7.6.3
       validate-npm-package-license: 3.0.4
 
   normalize-path@3.0.0: {}
@@ -22386,6 +21466,11 @@ snapshots:
     dependencies:
       path-key: 4.0.0
 
+  npm-run-path@6.0.0:
+    dependencies:
+      path-key: 4.0.0
+      unicorn-magic: 0.3.0
+
   npmlog@5.0.1:
     dependencies:
       are-we-there-yet: 2.0.0
@@ -22423,6 +21508,8 @@ snapshots:
 
   object-inspect@1.12.3: {}
 
+  object-inspect@1.13.2: {}
+
   object-is@1.1.5:
     dependencies:
       call-bind: 1.0.2
@@ -22437,24 +21524,31 @@ snapshots:
       has-symbols: 1.0.3
       object-keys: 1.1.1
 
-  object.fromentries@2.0.7:
+  object.assign@4.1.5:
     dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-      es-abstract: 1.22.1
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      has-symbols: 1.0.3
+      object-keys: 1.1.1
 
-  object.groupby@1.0.1:
+  object.fromentries@2.0.8:
     dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-      es-abstract: 1.22.1
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+      es-object-atoms: 1.0.0
 
-  object.values@1.1.7:
+  object.groupby@1.0.3:
     dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-      es-abstract: 1.22.1
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+
+  object.values@1.2.0:
+    dependencies:
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-object-atoms: 1.0.0
 
   obliterator@2.0.4: {}
 
@@ -22470,8 +21564,6 @@ snapshots:
     dependencies:
       ee-first: 1.1.1
 
-  on-headers@1.0.2: {}
-
   once@1.4.0:
     dependencies:
       wrappy: 1.0.2
@@ -22484,6 +21576,10 @@ snapshots:
     dependencies:
       mimic-fn: 4.0.0
 
+  oniguruma-to-js@0.4.3:
+    dependencies:
+      regex: 4.3.3
+
   open@8.4.2:
     dependencies:
       define-lazy-prop: 2.0.0
@@ -22515,21 +21611,9 @@ snapshots:
       deep-is: 0.1.4
       fast-levenshtein: 2.0.6
       levn: 0.4.1
-      prelude-ls: 1.2.1
-      type-check: 0.4.0
-      word-wrap: 1.2.5
-
-  ora@5.4.1:
-    dependencies:
-      bl: 4.1.0
-      chalk: 4.1.2
-      cli-cursor: 3.1.0
-      cli-spinners: 2.9.2
-      is-interactive: 1.0.0
-      is-unicode-supported: 0.1.0
-      log-symbols: 4.1.0
-      strip-ansi: 6.0.1
-      wcwidth: 1.0.1
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+      word-wrap: 1.2.5
 
   os-filter-obj@2.0.0:
     dependencies:
@@ -22539,12 +21623,14 @@ snapshots:
 
   ospath@1.2.2: {}
 
-  otpauth@9.3.1:
+  otpauth@9.3.4:
     dependencies:
-      '@noble/hashes': 1.4.0
+      '@noble/hashes': 1.5.0
 
   outvariant@1.4.2: {}
 
+  outvariant@1.4.3: {}
+
   p-cancelable@2.1.1: {}
 
   p-cancelable@3.0.0: {}
@@ -22565,10 +21651,6 @@ snapshots:
     dependencies:
       yocto-queue: 1.0.0
 
-  p-locate@3.0.0:
-    dependencies:
-      p-limit: 2.3.0
-
   p-locate@4.1.0:
     dependencies:
       p-limit: 2.3.0
@@ -22622,6 +21704,10 @@ snapshots:
       domhandler: 5.0.3
       parse5: 7.1.2
 
+  parse5-parser-stream@7.1.2:
+    dependencies:
+      parse5: 7.1.2
+
   parse5@5.1.1: {}
 
   parse5@6.0.1: {}
@@ -22634,8 +21720,6 @@ snapshots:
 
   path-browserify@1.0.1: {}
 
-  path-exists@3.0.0: {}
-
   path-exists@4.0.0: {}
 
   path-is-absolute@1.0.1: {}
@@ -22653,42 +21737,39 @@ snapshots:
       lru-cache: 10.0.2
       minipass: 7.0.4
 
-  path-scurry@1.11.1:
-    dependencies:
-      lru-cache: 10.2.2
-      minipass: 7.1.2
-
   path-scurry@2.0.0:
     dependencies:
       lru-cache: 11.0.0
       minipass: 7.1.2
 
-  path-to-regexp@0.1.7: {}
+  path-to-regexp@0.1.10: {}
 
   path-to-regexp@1.8.0:
     dependencies:
       isarray: 0.0.1
 
-  path-to-regexp@3.2.0: {}
+  path-to-regexp@3.3.0: {}
 
   path-to-regexp@6.2.1: {}
 
-  path-type@4.0.0: {}
+  path-to-regexp@6.3.0: {}
 
-  path-type@5.0.0: {}
+  path-type@4.0.0: {}
 
   pathe@1.1.2: {}
 
   pathval@1.1.1: {}
 
+  pathval@2.0.0: {}
+
   pause-stream@0.0.11:
     dependencies:
       through: 2.3.8
 
-  peek-readable@5.0.0: {}
-
   peek-readable@5.1.3: {}
 
+  peek-readable@5.2.0: {}
+
   pend@1.2.0: {}
 
   performance-now@2.1.0: {}
@@ -22696,18 +21777,20 @@ snapshots:
   pg-cloudflare@1.1.1:
     optional: true
 
-  pg-connection-string@2.6.4: {}
+  pg-connection-string@2.7.0: {}
 
   pg-int8@1.0.1: {}
 
   pg-numeric@1.0.2: {}
 
-  pg-pool@3.6.2(pg@8.12.0):
+  pg-pool@3.7.0(pg@8.13.0):
     dependencies:
-      pg: 8.12.0
+      pg: 8.13.0
 
   pg-protocol@1.6.1: {}
 
+  pg-protocol@1.7.0: {}
+
   pg-types@2.2.0:
     dependencies:
       pg-int8: 1.0.1
@@ -22726,11 +21809,11 @@ snapshots:
       postgres-interval: 3.0.0
       postgres-range: 1.1.3
 
-  pg@8.12.0:
+  pg@8.13.0:
     dependencies:
-      pg-connection-string: 2.6.4
-      pg-pool: 3.6.2(pg@8.12.0)
-      pg-protocol: 1.6.1
+      pg-connection-string: 2.7.0
+      pg-pool: 3.7.0(pg@8.13.0)
+      pg-protocol: 1.7.0
       pg-types: 2.2.0
       pgpass: 1.0.5
     optionalDependencies:
@@ -22746,6 +21829,8 @@ snapshots:
 
   picocolors@1.0.1: {}
 
+  picocolors@1.1.0: {}
+
   picomatch@2.3.1: {}
 
   pid-port@1.0.0:
@@ -22754,8 +21839,6 @@ snapshots:
 
   pify@2.3.0: {}
 
-  pify@4.0.1: {}
-
   pino-abstract-transport@1.2.0:
     dependencies:
       readable-stream: 4.3.0
@@ -22785,18 +21868,10 @@ snapshots:
 
   pkce-challenge@4.1.0: {}
 
-  pkg-dir@3.0.0:
-    dependencies:
-      find-up: 3.0.0
-
   pkg-dir@4.2.0:
     dependencies:
       find-up: 4.1.0
 
-  pkg-dir@5.0.0:
-    dependencies:
-      find-up: 5.0.0
-
   pkg-types@1.0.3:
     dependencies:
       jsonc-parser: 3.2.0
@@ -22821,140 +21896,142 @@ snapshots:
     dependencies:
       '@babel/runtime': 7.23.4
 
-  postcss-calc@9.0.1(postcss@8.4.40):
+  possible-typed-array-names@1.0.0: {}
+
+  postcss-calc@9.0.1(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-selector-parser: 6.0.16
       postcss-value-parser: 4.2.0
 
-  postcss-colormin@6.1.0(postcss@8.4.40):
+  postcss-colormin@6.1.0(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
       caniuse-api: 3.0.0
       colord: 2.9.3
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-convert-values@6.1.0(postcss@8.4.40):
+  postcss-convert-values@6.1.0(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-discard-comments@6.0.2(postcss@8.4.40):
+  postcss-discard-comments@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  postcss-discard-duplicates@6.0.3(postcss@8.4.40):
+  postcss-discard-duplicates@6.0.3(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  postcss-discard-empty@6.0.3(postcss@8.4.40):
+  postcss-discard-empty@6.0.3(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  postcss-discard-overridden@6.0.2(postcss@8.4.40):
+  postcss-discard-overridden@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  postcss-merge-longhand@6.0.5(postcss@8.4.40):
+  postcss-merge-longhand@6.0.5(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
-      stylehacks: 6.1.1(postcss@8.4.40)
+      stylehacks: 6.1.1(postcss@8.4.47)
 
-  postcss-merge-rules@6.1.1(postcss@8.4.40):
+  postcss-merge-rules@6.1.1(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
       caniuse-api: 3.0.0
-      cssnano-utils: 4.0.2(postcss@8.4.40)
-      postcss: 8.4.40
+      cssnano-utils: 4.0.2(postcss@8.4.47)
+      postcss: 8.4.47
       postcss-selector-parser: 6.0.16
 
-  postcss-minify-font-values@6.1.0(postcss@8.4.40):
+  postcss-minify-font-values@6.1.0(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-minify-gradients@6.0.3(postcss@8.4.40):
+  postcss-minify-gradients@6.0.3(postcss@8.4.47):
     dependencies:
       colord: 2.9.3
-      cssnano-utils: 4.0.2(postcss@8.4.40)
-      postcss: 8.4.40
+      cssnano-utils: 4.0.2(postcss@8.4.47)
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-minify-params@6.1.0(postcss@8.4.40):
+  postcss-minify-params@6.1.0(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
-      cssnano-utils: 4.0.2(postcss@8.4.40)
-      postcss: 8.4.40
+      cssnano-utils: 4.0.2(postcss@8.4.47)
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-minify-selectors@6.0.4(postcss@8.4.40):
+  postcss-minify-selectors@6.0.4(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-selector-parser: 6.0.16
 
-  postcss-normalize-charset@6.0.2(postcss@8.4.40):
+  postcss-normalize-charset@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  postcss-normalize-display-values@6.0.2(postcss@8.4.40):
+  postcss-normalize-display-values@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-positions@6.0.2(postcss@8.4.40):
+  postcss-normalize-positions@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-repeat-style@6.0.2(postcss@8.4.40):
+  postcss-normalize-repeat-style@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-string@6.0.2(postcss@8.4.40):
+  postcss-normalize-string@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-timing-functions@6.0.2(postcss@8.4.40):
+  postcss-normalize-timing-functions@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-unicode@6.1.0(postcss@8.4.40):
+  postcss-normalize-unicode@6.1.0(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-url@6.0.2(postcss@8.4.40):
+  postcss-normalize-url@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-normalize-whitespace@6.0.2(postcss@8.4.40):
+  postcss-normalize-whitespace@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-ordered-values@6.0.2(postcss@8.4.40):
+  postcss-ordered-values@6.0.2(postcss@8.4.47):
     dependencies:
-      cssnano-utils: 4.0.2(postcss@8.4.40)
-      postcss: 8.4.40
+      cssnano-utils: 4.0.2(postcss@8.4.47)
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
-  postcss-reduce-initial@6.1.0(postcss@8.4.40):
+  postcss-reduce-initial@6.1.0(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
       caniuse-api: 3.0.0
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  postcss-reduce-transforms@6.0.2(postcss@8.4.40):
+  postcss-reduce-transforms@6.0.2(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
 
   postcss-selector-parser@6.0.16:
@@ -22962,24 +22039,24 @@ snapshots:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
-  postcss-svgo@6.0.3(postcss@8.4.40):
+  postcss-svgo@6.0.3(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-value-parser: 4.2.0
       svgo: 3.2.0
 
-  postcss-unique-selectors@6.0.4(postcss@8.4.40):
+  postcss-unique-selectors@6.0.4(postcss@8.4.47):
     dependencies:
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-selector-parser: 6.0.16
 
   postcss-value-parser@4.2.0: {}
 
-  postcss@8.4.40:
+  postcss@8.4.47:
     dependencies:
       nanoid: 3.3.7
-      picocolors: 1.0.1
-      source-map-js: 1.2.0
+      picocolors: 1.1.0
+      source-map-js: 1.2.1
 
   postgres-array@2.0.0: {}
 
@@ -23021,8 +22098,6 @@ snapshots:
       ansi-styles: 5.2.0
       react-is: 18.2.0
 
-  pretty-hrtime@1.0.3: {}
-
   pretty-ms@9.0.0:
     dependencies:
       parse-ms: 4.0.0
@@ -23042,7 +22117,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  proc-log@3.0.0: {}
+  proc-log@4.2.0: {}
 
   process-exists@5.0.0:
     dependencies:
@@ -23050,10 +22125,10 @@ snapshots:
 
   process-nextick-args@2.0.1: {}
 
-  process-warning@2.2.0: {}
-
   process-warning@3.0.0: {}
 
+  process-warning@4.0.0: {}
+
   process@0.11.10: {}
 
   progress@2.0.3:
@@ -23083,6 +22158,8 @@ snapshots:
       object-assign: 4.1.1
       react-is: 16.13.1
 
+  property-information@6.5.0: {}
+
   proto-list@1.2.4: {}
 
   proxy-addr@2.0.7:
@@ -23186,28 +22263,19 @@ snapshots:
 
   pvtsutils@1.3.5:
     dependencies:
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   pvutils@1.1.3: {}
 
-  qrcode@1.5.3:
+  qrcode@1.5.4:
     dependencies:
       dijkstrajs: 1.0.2
-      encode-utf8: 1.0.3
       pngjs: 5.0.0
       yargs: 15.4.1
 
-  qs@6.10.4:
-    dependencies:
-      side-channel: 1.0.4
-
-  qs@6.11.0:
+  qs@6.13.0:
     dependencies:
-      side-channel: 1.0.4
-
-  qs@6.11.1:
-    dependencies:
-      side-channel: 1.0.4
+      side-channel: 1.0.6
 
   qs@6.5.3: {}
 
@@ -23225,8 +22293,6 @@ snapshots:
 
   quick-lru@5.1.1: {}
 
-  ramda@0.29.0: {}
-
   random-seed@0.3.0:
     dependencies:
       json-stringify-safe: 5.0.1
@@ -23242,15 +22308,22 @@ snapshots:
       iconv-lite: 0.4.24
       unpipe: 1.0.0
 
+  raw-body@3.0.0:
+    dependencies:
+      bytes: 3.1.2
+      http-errors: 2.0.0
+      iconv-lite: 0.6.3
+      unpipe: 1.0.0
+
   rdf-canonize@3.4.0:
     dependencies:
       setimmediate: 1.0.5
 
-  re2@1.21.3:
+  re2@1.21.4:
     dependencies:
       install-artifact-from-github: 1.3.5
       nan: 2.20.0
-      node-gyp: 10.1.0
+      node-gyp: 10.2.0
     transitivePeerDependencies:
       - supports-color
 
@@ -23259,15 +22332,15 @@ snapshots:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
 
-  react-docgen-typescript@2.2.2(typescript@5.5.4):
+  react-docgen-typescript@2.2.2(typescript@5.6.2):
     dependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   react-docgen@7.0.1:
     dependencies:
       '@babel/core': 7.24.7
       '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
+      '@babel/types': 7.25.6
       '@types/babel__core': 7.20.0
       '@types/babel__traverse': 7.20.0
       '@types/doctrine': 0.0.9
@@ -23367,7 +22440,7 @@ snapshots:
       esprima: 4.0.1
       source-map: 0.6.1
       tiny-invariant: 1.3.3
-      tslib: 2.6.3
+      tslib: 2.7.0
 
   reconnecting-websocket@4.4.0: {}
 
@@ -23390,19 +22463,11 @@ snapshots:
 
   reflect-metadata@0.2.2: {}
 
-  regenerate-unicode-properties@10.1.0:
-    dependencies:
-      regenerate: 1.4.2
-
-  regenerate@1.4.2: {}
-
   regenerator-runtime@0.13.11: {}
 
   regenerator-runtime@0.14.0: {}
 
-  regenerator-transform@0.15.2:
-    dependencies:
-      '@babel/runtime': 7.23.4
+  regex@4.3.3: {}
 
   regexp.prototype.flags@1.5.0:
     dependencies:
@@ -23410,18 +22475,12 @@ snapshots:
       define-properties: 1.2.0
       functions-have-names: 1.2.3
 
-  regexpu-core@5.3.2:
-    dependencies:
-      '@babel/regjsgen': 0.8.0
-      regenerate: 1.4.2
-      regenerate-unicode-properties: 10.1.0
-      regjsparser: 0.9.1
-      unicode-match-property-ecmascript: 2.0.0
-      unicode-match-property-value-ecmascript: 2.1.0
-
-  regjsparser@0.9.1:
+  regexp.prototype.flags@1.5.2:
     dependencies:
-      jsesc: 0.5.0
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-errors: 1.3.0
+      set-function-name: 2.0.2
 
   rehype-external-links@3.0.0:
     dependencies:
@@ -23505,7 +22564,7 @@ snapshots:
 
   require-in-the-middle@7.3.0:
     dependencies:
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       module-details-from-path: 1.0.3
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -23548,7 +22607,7 @@ snapshots:
       onetime: 5.1.2
       signal-exit: 3.0.7
 
-  ret@0.4.3: {}
+  ret@0.5.0: {}
 
   retry@0.12.0: {}
 
@@ -23556,9 +22615,7 @@ snapshots:
 
   rfdc@1.3.0: {}
 
-  rimraf@2.6.3:
-    dependencies:
-      glob: 7.2.3
+  rfdc@1.4.1: {}
 
   rimraf@2.7.1:
     dependencies:
@@ -23570,26 +22627,26 @@ snapshots:
       glob: 7.2.3
     optional: true
 
-  rollup@4.19.1:
+  rollup@4.22.5:
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.6
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.19.1
-      '@rollup/rollup-android-arm64': 4.19.1
-      '@rollup/rollup-darwin-arm64': 4.19.1
-      '@rollup/rollup-darwin-x64': 4.19.1
-      '@rollup/rollup-linux-arm-gnueabihf': 4.19.1
-      '@rollup/rollup-linux-arm-musleabihf': 4.19.1
-      '@rollup/rollup-linux-arm64-gnu': 4.19.1
-      '@rollup/rollup-linux-arm64-musl': 4.19.1
-      '@rollup/rollup-linux-powerpc64le-gnu': 4.19.1
-      '@rollup/rollup-linux-riscv64-gnu': 4.19.1
-      '@rollup/rollup-linux-s390x-gnu': 4.19.1
-      '@rollup/rollup-linux-x64-gnu': 4.19.1
-      '@rollup/rollup-linux-x64-musl': 4.19.1
-      '@rollup/rollup-win32-arm64-msvc': 4.19.1
-      '@rollup/rollup-win32-ia32-msvc': 4.19.1
-      '@rollup/rollup-win32-x64-msvc': 4.19.1
+      '@rollup/rollup-android-arm-eabi': 4.22.5
+      '@rollup/rollup-android-arm64': 4.22.5
+      '@rollup/rollup-darwin-arm64': 4.22.5
+      '@rollup/rollup-darwin-x64': 4.22.5
+      '@rollup/rollup-linux-arm-gnueabihf': 4.22.5
+      '@rollup/rollup-linux-arm-musleabihf': 4.22.5
+      '@rollup/rollup-linux-arm64-gnu': 4.22.5
+      '@rollup/rollup-linux-arm64-musl': 4.22.5
+      '@rollup/rollup-linux-powerpc64le-gnu': 4.22.5
+      '@rollup/rollup-linux-riscv64-gnu': 4.22.5
+      '@rollup/rollup-linux-s390x-gnu': 4.22.5
+      '@rollup/rollup-linux-x64-gnu': 4.22.5
+      '@rollup/rollup-linux-x64-musl': 4.22.5
+      '@rollup/rollup-win32-arm64-msvc': 4.22.5
+      '@rollup/rollup-win32-ia32-msvc': 4.22.5
+      '@rollup/rollup-win32-x64-msvc': 4.22.5
       fsevents: 2.3.3
 
   rrweb-cssom@0.6.0: {}
@@ -23611,8 +22668,15 @@ snapshots:
 
   safe-array-concat@1.0.0:
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      get-intrinsic: 1.2.4
+      has-symbols: 1.0.3
+      isarray: 2.0.5
+
+  safe-array-concat@1.1.2:
+    dependencies:
+      call-bind: 1.0.7
+      get-intrinsic: 1.2.4
       has-symbols: 1.0.3
       isarray: 2.0.5
 
@@ -23622,33 +22686,45 @@ snapshots:
 
   safe-regex-test@1.0.0:
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      get-intrinsic: 1.2.4
       is-regex: 1.1.4
 
-  safe-regex2@3.1.0:
+  safe-regex-test@1.0.3:
     dependencies:
-      ret: 0.4.3
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      is-regex: 1.1.4
+
+  safe-regex2@4.0.0:
+    dependencies:
+      ret: 0.5.0
 
   safe-stable-stringify@2.4.2: {}
 
   safer-buffer@2.1.2: {}
 
-  sanitize-html@2.13.0:
+  sanitize-html@2.13.1:
     dependencies:
       deepmerge: 4.2.2
       escape-string-regexp: 4.0.0
       htmlparser2: 8.0.1
       is-plain-object: 5.0.0
       parse-srcset: 1.0.2
-      postcss: 8.4.40
+      postcss: 8.4.47
 
-  sass@1.77.8:
+  sass@1.79.3:
     dependencies:
       chokidar: 3.5.3
       immutable: 4.2.2
       source-map-js: 1.2.0
 
+  sass@1.79.4:
+    dependencies:
+      chokidar: 3.5.3
+      immutable: 4.2.2
+      source-map-js: 1.2.1
+
   sax@1.2.4: {}
 
   saxes@6.0.0:
@@ -23661,6 +22737,8 @@ snapshots:
 
   secure-json-parse@2.7.0: {}
 
+  secure-json-parse@3.0.0: {}
+
   seedrandom@3.0.5: {}
 
   semver-regex@4.0.5: {}
@@ -23681,7 +22759,9 @@ snapshots:
     dependencies:
       lru-cache: 6.0.0
 
-  send@0.18.0:
+  semver@7.6.3: {}
+
+  send@0.19.0:
     dependencies:
       debug: 2.6.9
       depd: 2.0.0
@@ -23699,12 +22779,12 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  serve-static@1.15.0:
+  serve-static@1.16.2:
     dependencies:
-      encodeurl: 1.0.2
+      encodeurl: 2.0.0
       escape-html: 1.0.3
       parseurl: 1.3.3
-      send: 0.18.0
+      send: 0.19.0
     transitivePeerDependencies:
       - supports-color
 
@@ -23712,6 +22792,22 @@ snapshots:
 
   set-cookie-parser@2.6.0: {}
 
+  set-function-length@1.2.2:
+    dependencies:
+      define-data-property: 1.1.4
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.2.4
+      gopd: 1.0.1
+      has-property-descriptors: 1.0.2
+
+  set-function-name@2.0.2:
+    dependencies:
+      define-data-property: 1.1.4
+      es-errors: 1.3.0
+      functions-have-names: 1.2.3
+      has-property-descriptors: 1.0.2
+
   setimmediate@1.0.5: {}
 
   setprototypeof@1.2.0: {}
@@ -23721,35 +22817,31 @@ snapshots:
       inherits: 2.0.4
       safe-buffer: 5.2.1
 
-  shallow-clone@3.0.1:
-    dependencies:
-      kind-of: 6.0.3
-
-  sharp@0.33.4:
+  sharp@0.33.5:
     dependencies:
       color: 4.2.3
       detect-libc: 2.0.3
-      semver: 7.6.0
+      semver: 7.6.3
     optionalDependencies:
-      '@img/sharp-darwin-arm64': 0.33.4
-      '@img/sharp-darwin-x64': 0.33.4
-      '@img/sharp-libvips-darwin-arm64': 1.0.2
-      '@img/sharp-libvips-darwin-x64': 1.0.2
-      '@img/sharp-libvips-linux-arm': 1.0.2
-      '@img/sharp-libvips-linux-arm64': 1.0.2
-      '@img/sharp-libvips-linux-s390x': 1.0.2
-      '@img/sharp-libvips-linux-x64': 1.0.2
-      '@img/sharp-libvips-linuxmusl-arm64': 1.0.2
-      '@img/sharp-libvips-linuxmusl-x64': 1.0.2
-      '@img/sharp-linux-arm': 0.33.4
-      '@img/sharp-linux-arm64': 0.33.4
-      '@img/sharp-linux-s390x': 0.33.4
-      '@img/sharp-linux-x64': 0.33.4
-      '@img/sharp-linuxmusl-arm64': 0.33.4
-      '@img/sharp-linuxmusl-x64': 0.33.4
-      '@img/sharp-wasm32': 0.33.4
-      '@img/sharp-win32-ia32': 0.33.4
-      '@img/sharp-win32-x64': 0.33.4
+      '@img/sharp-darwin-arm64': 0.33.5
+      '@img/sharp-darwin-x64': 0.33.5
+      '@img/sharp-libvips-darwin-arm64': 1.0.4
+      '@img/sharp-libvips-darwin-x64': 1.0.4
+      '@img/sharp-libvips-linux-arm': 1.0.5
+      '@img/sharp-libvips-linux-arm64': 1.0.4
+      '@img/sharp-libvips-linux-s390x': 1.0.4
+      '@img/sharp-libvips-linux-x64': 1.0.4
+      '@img/sharp-libvips-linuxmusl-arm64': 1.0.4
+      '@img/sharp-libvips-linuxmusl-x64': 1.0.4
+      '@img/sharp-linux-arm': 0.33.5
+      '@img/sharp-linux-arm64': 0.33.5
+      '@img/sharp-linux-s390x': 0.33.5
+      '@img/sharp-linux-x64': 0.33.5
+      '@img/sharp-linuxmusl-arm64': 0.33.5
+      '@img/sharp-linuxmusl-x64': 0.33.5
+      '@img/sharp-wasm32': 0.33.5
+      '@img/sharp-win32-ia32': 0.33.5
+      '@img/sharp-win32-x64': 0.33.5
 
   shebang-command@1.2.0:
     dependencies:
@@ -23763,9 +22855,13 @@ snapshots:
 
   shebang-regex@3.0.0: {}
 
-  shiki@1.12.0:
+  shiki@1.21.0:
     dependencies:
-      '@shikijs/core': 1.12.0
+      '@shikijs/core': 1.21.0
+      '@shikijs/engine-javascript': 1.21.0
+      '@shikijs/engine-oniguruma': 1.21.0
+      '@shikijs/types': 1.21.0
+      '@shikijs/vscode-textmate': 9.2.2
       '@types/hast': 3.0.4
 
   shimmer@1.2.1: {}
@@ -23776,6 +22872,13 @@ snapshots:
       get-intrinsic: 1.2.1
       object-inspect: 1.12.3
 
+  side-channel@1.0.6:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      get-intrinsic: 1.2.4
+      object-inspect: 1.13.2
+
   siginfo@2.0.0: {}
 
   signal-exit@3.0.7: {}
@@ -23867,8 +22970,6 @@ snapshots:
 
   slash@3.0.0: {}
 
-  slash@5.1.0: {}
-
   slice-ansi@3.0.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -23881,12 +22982,14 @@ snapshots:
       astral-regex: 2.0.0
       is-fullwidth-code-point: 3.0.0
 
+  slick@1.12.2: {}
+
   smart-buffer@4.2.0: {}
 
   socks-proxy-agent@8.0.2:
     dependencies:
       agent-base: 7.1.0
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       socks: 2.7.1
     transitivePeerDependencies:
       - supports-color
@@ -23912,6 +23015,8 @@ snapshots:
 
   source-map-js@1.2.0: {}
 
+  source-map-js@1.2.1: {}
+
   source-map-support@0.5.13:
     dependencies:
       buffer-from: 1.1.2
@@ -23964,6 +23069,18 @@ snapshots:
       safer-buffer: 2.1.2
       tweetnacl: 0.14.5
 
+  sshpk@1.18.0:
+    dependencies:
+      asn1: 0.2.6
+      assert-plus: 1.0.0
+      bcrypt-pbkdf: 1.0.2
+      dashdash: 1.14.1
+      ecc-jsbn: 0.1.2
+      getpass: 0.1.7
+      jsbn: 0.1.1
+      safer-buffer: 2.1.2
+      tweetnacl: 0.14.5
+
   ssri@10.0.4:
     dependencies:
       minipass: 5.0.0
@@ -23976,16 +23093,16 @@ snapshots:
 
   standard-as-callback@2.1.0: {}
 
-  start-server-and-test@2.0.4:
+  start-server-and-test@2.0.8:
     dependencies:
       arg: 5.0.2
       bluebird: 3.7.2
       check-more-types: 2.24.0
-      debug: 4.3.5(supports-color@5.5.0)
+      debug: 4.3.7(supports-color@8.1.1)
       execa: 5.1.1
       lazy-ass: 1.6.0
       ps-tree: 1.2.0
-      wait-on: 7.2.0(debug@4.3.5)
+      wait-on: 8.0.1(debug@4.3.7)
     transitivePeerDependencies:
       - supports-color
 
@@ -23997,53 +23114,23 @@ snapshots:
     dependencies:
       internal-slot: 1.0.5
 
-  store2@2.14.2: {}
-
-  storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(3rvqj7p7l43ansgshs3zbslm7u):
+  storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
     dependencies:
-      '@storybook/blocks': 8.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/components': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/core-events': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/manager-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/preview-api': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/theming': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
-      '@storybook/types': 8.2.6(storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/blocks': 8.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/components': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/core-events': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/manager-api': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/preview-api': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/theming': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/types': 8.3.4(storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4))
     optionalDependencies:
       react: 18.3.1
       react-dom: 18.3.1(react@18.3.1)
 
-  storybook@8.2.6(@babel/preset-env@7.24.7(@babel/core@7.24.7))(bufferutil@4.0.8)(utf-8-validate@6.0.4):
+  storybook@8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4):
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/types': 7.24.7
-      '@storybook/codemod': 8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-      '@storybook/core': 8.2.6(bufferutil@4.0.8)(utf-8-validate@6.0.4)
-      '@types/semver': 7.5.8
-      '@yarnpkg/fslib': 2.10.3
-      '@yarnpkg/libzip': 2.3.0
-      chalk: 4.1.2
-      commander: 6.2.1
-      cross-spawn: 7.0.3
-      detect-indent: 6.1.0
-      envinfo: 7.8.1
-      execa: 5.1.1
-      fd-package-json: 1.2.0
-      find-up: 5.0.0
-      fs-extra: 11.1.1
-      giget: 1.1.2
-      globby: 14.0.1
-      jscodeshift: 0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7))
-      leven: 3.1.0
-      ora: 5.4.1
-      prettier: 3.3.3
-      prompts: 2.4.2
-      semver: 7.6.0
-      strip-json-comments: 3.1.1
-      tempy: 3.1.0
-      tiny-invariant: 1.3.3
-      ts-dedent: 2.2.0
+      '@storybook/core': 8.3.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
     transitivePeerDependencies:
-      - '@babel/preset-env'
       - bufferutil
       - supports-color
       - utf-8-validate
@@ -24063,8 +23150,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  stream-wormhole@1.1.0: {}
-
   streamsearch@1.1.0: {}
 
   streamx@2.15.0:
@@ -24097,22 +23182,41 @@ snapshots:
 
   string.prototype.trim@1.2.7:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
       es-abstract: 1.22.1
 
+  string.prototype.trim@1.2.9:
+    dependencies:
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-abstract: 1.23.3
+      es-object-atoms: 1.0.0
+
   string.prototype.trimend@1.0.6:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
       es-abstract: 1.22.1
 
+  string.prototype.trimend@1.0.8:
+    dependencies:
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-object-atoms: 1.0.0
+
   string.prototype.trimstart@1.0.6:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       define-properties: 1.2.0
       es-abstract: 1.22.1
 
+  string.prototype.trimstart@1.0.8:
+    dependencies:
+      call-bind: 1.0.7
+      define-properties: 1.2.1
+      es-object-atoms: 1.0.0
+
   string_decoder@0.10.31: {}
 
   string_decoder@1.1.1:
@@ -24123,6 +23227,11 @@ snapshots:
     dependencies:
       safe-buffer: 5.2.1
 
+  stringify-entities@4.0.4:
+    dependencies:
+      character-entities-html4: 2.1.0
+      character-entities-legacy: 3.0.0
+
   stringz@2.1.0:
     dependencies:
       char-regex: 1.0.2
@@ -24168,17 +23277,17 @@ snapshots:
   strtok3@7.0.0:
     dependencies:
       '@tokenizer/token': 0.3.0
-      peek-readable: 5.0.0
+      peek-readable: 5.1.3
 
-  strtok3@8.0.1:
+  strtok3@8.1.0:
     dependencies:
       '@tokenizer/token': 0.3.0
-      peek-readable: 5.1.3
+      peek-readable: 5.2.0
 
-  stylehacks@6.1.1(postcss@8.4.40):
+  stylehacks@6.1.1(postcss@8.4.47):
     dependencies:
       browserslist: 4.23.0
-      postcss: 8.4.40
+      postcss: 8.4.47
       postcss-selector-parser: 6.0.16
 
   supports-color@5.5.0:
@@ -24214,7 +23323,7 @@ snapshots:
 
   symbol-tree@3.2.4: {}
 
-  systeminformation@5.22.11: {}
+  systeminformation@5.23.5: {}
 
   tar-stream@3.1.6:
     dependencies:
@@ -24250,20 +23359,7 @@ snapshots:
     dependencies:
       memoizerific: 1.11.3
 
-  temp-dir@3.0.0: {}
-
-  temp@0.8.4:
-    dependencies:
-      rimraf: 2.6.3
-
-  tempy@3.1.0:
-    dependencies:
-      is-stream: 3.0.0
-      temp-dir: 3.0.0
-      type-fest: 2.19.0
-      unique-string: 3.0.0
-
-  terser@5.31.3:
+  terser@5.33.0:
     dependencies:
       '@jridgewell/source-map': 0.3.6
       acorn: 8.12.1
@@ -24292,7 +23388,7 @@ snapshots:
     dependencies:
       real-require: 0.2.0
 
-  three@0.167.0: {}
+  three@0.169.0: {}
 
   throttle-debounce@5.0.2: {}
 
@@ -24304,16 +23400,18 @@ snapshots:
 
   tiny-invariant@1.3.3: {}
 
-  tiny-lru@10.0.1: {}
-
   tinybench@2.6.0: {}
 
   tinycolor2@1.6.0: {}
 
   tinypool@0.8.4: {}
 
+  tinyrainbow@1.2.0: {}
+
   tinyspy@2.2.0: {}
 
+  tinyspy@3.0.2: {}
+
   tmp@0.2.3: {}
 
   tmpl@1.0.5: {}
@@ -24366,6 +23464,8 @@ snapshots:
 
   trace-redirect@1.0.6: {}
 
+  trim-lines@3.0.1: {}
+
   trim-newlines@3.0.1: {}
 
   trim-repeated@2.0.0:
@@ -24382,7 +23482,11 @@ snapshots:
     dependencies:
       typescript: 5.5.4
 
-  ts-case-convert@2.0.2: {}
+  ts-api-utils@1.3.0(typescript@5.6.2):
+    dependencies:
+      typescript: 5.6.2
+
+  ts-case-convert@2.0.7: {}
 
   ts-dedent@2.2.0: {}
 
@@ -24410,7 +23514,7 @@ snapshots:
       minimist: 1.2.8
       strip-bom: 3.0.0
 
-  tsd@0.31.1:
+  tsd@0.31.2:
     dependencies:
       '@tsd/typescript': 5.4.5
       eslint-formatter-pretty: 4.1.0
@@ -24420,12 +23524,12 @@ snapshots:
       path-exists: 4.0.0
       read-pkg-up: 7.0.1
 
-  tslib@1.14.1: {}
-
   tslib@2.6.2: {}
 
   tslib@2.6.3: {}
 
+  tslib@2.7.0: {}
+
   tsx@4.4.0:
     dependencies:
       esbuild: 0.18.20
@@ -24455,8 +23559,6 @@ snapshots:
 
   type-fest@0.8.1: {}
 
-  type-fest@1.4.0: {}
-
   type-fest@2.19.0: {}
 
   type-fest@4.20.1: {}
@@ -24468,34 +23570,66 @@ snapshots:
 
   typed-array-buffer@1.0.0:
     dependencies:
-      call-bind: 1.0.2
-      get-intrinsic: 1.2.1
+      call-bind: 1.0.7
+      get-intrinsic: 1.2.4
       is-typed-array: 1.1.10
 
+  typed-array-buffer@1.0.2:
+    dependencies:
+      call-bind: 1.0.7
+      es-errors: 1.3.0
+      is-typed-array: 1.1.13
+
   typed-array-byte-length@1.0.0:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       for-each: 0.3.3
       has-proto: 1.0.1
       is-typed-array: 1.1.10
 
+  typed-array-byte-length@1.0.1:
+    dependencies:
+      call-bind: 1.0.7
+      for-each: 0.3.3
+      gopd: 1.0.1
+      has-proto: 1.0.3
+      is-typed-array: 1.1.13
+
   typed-array-byte-offset@1.0.0:
     dependencies:
       available-typed-arrays: 1.0.5
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       for-each: 0.3.3
       has-proto: 1.0.1
       is-typed-array: 1.1.10
 
+  typed-array-byte-offset@1.0.2:
+    dependencies:
+      available-typed-arrays: 1.0.7
+      call-bind: 1.0.7
+      for-each: 0.3.3
+      gopd: 1.0.1
+      has-proto: 1.0.3
+      is-typed-array: 1.1.13
+
   typed-array-length@1.0.4:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       for-each: 0.3.3
       is-typed-array: 1.1.10
 
+  typed-array-length@1.0.6:
+    dependencies:
+      call-bind: 1.0.7
+      for-each: 0.3.3
+      gopd: 1.0.1
+      has-proto: 1.0.3
+      is-typed-array: 1.1.13
+      possible-typed-array-names: 1.0.0
+
   typedarray@0.0.6: {}
 
-  typeorm@0.3.20(ioredis@5.4.1)(pg@8.12.0):
+  typeorm@0.3.20(ioredis@5.4.1)(pg@8.13.0):
     dependencies:
       '@sqltools/formatter': 1.2.5
       app-root-path: 3.1.0
@@ -24514,7 +23648,7 @@ snapshots:
       yargs: 17.7.2
     optionalDependencies:
       ioredis: 5.4.1
-      pg: 8.12.0
+      pg: 8.13.0
     transitivePeerDependencies:
       - supports-color
 
@@ -24524,10 +23658,9 @@ snapshots:
 
   typescript@5.5.4: {}
 
-  ufo@1.3.2: {}
+  typescript@5.6.2: {}
 
-  uglify-js@3.17.4:
-    optional: true
+  ufo@1.3.2: {}
 
   uid2@0.0.4: {}
 
@@ -24541,7 +23674,7 @@ snapshots:
 
   unbox-primitive@1.0.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
       has-bigints: 1.0.2
       has-symbols: 1.0.3
       which-boxed-primitive: 1.0.2
@@ -24550,22 +23683,15 @@ snapshots:
 
   undici-types@5.26.5: {}
 
+  undici-types@6.19.8: {}
+
   undici@5.28.2:
     dependencies:
       '@fastify/busboy': 2.1.0
 
-  unicode-canonical-property-names-ecmascript@2.0.0: {}
-
-  unicode-match-property-ecmascript@2.0.0:
-    dependencies:
-      unicode-canonical-property-names-ecmascript: 2.0.0
-      unicode-property-aliases-ecmascript: 2.1.0
-
-  unicode-match-property-value-ecmascript@2.1.0: {}
-
-  unicode-property-aliases-ecmascript@2.1.0: {}
+  undici@6.19.8: {}
 
-  unicorn-magic@0.1.0: {}
+  unicorn-magic@0.3.0: {}
 
   unified@11.0.4:
     dependencies:
@@ -24587,11 +23713,11 @@ snapshots:
     dependencies:
       imurmurhash: 0.1.4
 
-  unique-string@3.0.0:
+  unist-util-is@6.0.0:
     dependencies:
-      crypto-random-string: 4.0.0
+      '@types/unist': 3.0.2
 
-  unist-util-is@6.0.0:
+  unist-util-position@5.0.0:
     dependencies:
       '@types/unist': 3.0.2
 
@@ -24667,8 +23793,8 @@ snapshots:
       inherits: 2.0.4
       is-arguments: 1.1.1
       is-generator-function: 1.0.10
-      is-typed-array: 1.1.10
-      which-typed-array: 1.1.11
+      is-typed-array: 1.1.13
+      which-typed-array: 1.1.15
 
   utils-merge@1.0.1: {}
 
@@ -24680,14 +23806,13 @@ snapshots:
 
   uuid@9.0.1: {}
 
-  v-code-diff@1.12.0(vue@3.4.37(typescript@5.5.4)):
+  v-code-diff@1.13.1(vue@3.5.11(typescript@5.6.2)):
     dependencies:
-      diff: 5.1.0
+      diff: 5.2.0
       diff-match-patch: 1.0.5
-      highlight.js: 11.9.0
-      vue: 3.4.37(typescript@5.5.4)
-      vue-demi: 0.14.7(vue@3.4.37(typescript@5.5.4))
-      vue-i18n: 9.13.1(vue@3.4.37(typescript@5.5.4))
+      highlight.js: 11.10.0
+      vue: 3.5.11(typescript@5.6.2)
+      vue-demi: 0.14.7(vue@3.5.11(typescript@5.6.2))
 
   v8-to-istanbul@9.2.0:
     dependencies:
@@ -24695,6 +23820,8 @@ snapshots:
       '@types/istanbul-lib-coverage': 2.0.4
       convert-source-map: 2.0.0
 
+  valid-data-url@3.0.1: {}
+
   validate-npm-package-license@3.0.4:
     dependencies:
       spdx-correct: 3.1.1
@@ -24719,18 +23846,37 @@ snapshots:
       unist-util-stringify-position: 4.0.0
       vfile-message: 4.0.2
 
-  vite-node@1.6.0(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3):
+  vite-node@1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0):
+    dependencies:
+      cac: 6.7.14
+      debug: 4.3.5(supports-color@5.5.0)
+      pathe: 1.1.2
+      picocolors: 1.0.1
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
+    transitivePeerDependencies:
+      - '@types/node'
+      - less
+      - lightningcss
+      - sass
+      - sass-embedded
+      - stylus
+      - sugarss
+      - supports-color
+      - terser
+
+  vite-node@1.6.0(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0):
     dependencies:
       cac: 6.7.14
       debug: 4.3.5(supports-color@5.5.0)
       pathe: 1.1.2
       picocolors: 1.0.1
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0)
     transitivePeerDependencies:
       - '@types/node'
       - less
       - lightningcss
       - sass
+      - sass-embedded
       - stylus
       - sugarss
       - supports-color
@@ -24738,25 +23884,36 @@ snapshots:
 
   vite-plugin-turbosnap@1.0.3: {}
 
-  vite@5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3):
+  vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0):
+    dependencies:
+      esbuild: 0.21.5
+      postcss: 8.4.47
+      rollup: 4.22.5
+    optionalDependencies:
+      '@types/node': 20.14.12
+      fsevents: 2.3.3
+      sass: 1.79.3
+      terser: 5.33.0
+
+  vite@5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0):
     dependencies:
       esbuild: 0.21.5
-      postcss: 8.4.40
-      rollup: 4.19.1
+      postcss: 8.4.47
+      rollup: 4.22.5
     optionalDependencies:
       '@types/node': 20.14.12
       fsevents: 2.3.3
-      sass: 1.77.8
-      terser: 5.31.3
+      sass: 1.79.4
+      terser: 5.33.0
 
-  vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)):
+  vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)):
     dependencies:
       cross-fetch: 3.1.6(encoding@0.1.13)
-      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3)
+      vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)
     transitivePeerDependencies:
       - encoding
 
-  vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.77.8)(terser@5.31.3):
+  vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0):
     dependencies:
       '@vitest/expect': 1.6.0
       '@vitest/runner': 1.6.0
@@ -24775,8 +23932,8 @@ snapshots:
       strip-literal: 2.1.0
       tinybench: 2.6.0
       tinypool: 0.8.4
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
-      vite-node: 1.6.0(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
+      vite-node: 1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
       why-is-node-running: 2.2.2
     optionalDependencies:
       '@types/node': 20.14.12
@@ -24786,12 +23943,13 @@ snapshots:
       - less
       - lightningcss
       - sass
+      - sass-embedded
       - stylus
       - sugarss
       - supports-color
       - terser
 
-  vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.77.8)(terser@5.31.3):
+  vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.4)(terser@5.33.0):
     dependencies:
       '@vitest/expect': 1.6.0
       '@vitest/runner': 1.6.0
@@ -24810,17 +23968,18 @@ snapshots:
       strip-literal: 2.1.0
       tinybench: 2.6.0
       tinypool: 0.8.4
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
-      vite-node: 1.6.0(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      vite: 5.4.8(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0)
+      vite-node: 1.6.0(@types/node@20.14.12)(sass@1.79.4)(terser@5.33.0)
       why-is-node-running: 2.2.2
     optionalDependencies:
       '@types/node': 20.14.12
       happy-dom: 10.0.3
-      jsdom: 24.1.1
+      jsdom: 24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
     transitivePeerDependencies:
       - less
       - lightningcss
       - sass
+      - sass-embedded
       - stylus
       - sugarss
       - supports-color
@@ -24833,7 +23992,7 @@ snapshots:
   vscode-languageclient@9.0.1:
     dependencies:
       minimatch: 5.1.2
-      semver: 7.6.0
+      semver: 7.6.3
       vscode-languageserver-protocol: 3.17.5
 
   vscode-languageserver-protocol@3.17.5:
@@ -24851,46 +24010,44 @@ snapshots:
 
   vscode-uri@3.0.8: {}
 
-  vue-component-meta@2.0.16(typescript@5.5.4):
+  vue-component-meta@2.0.16(typescript@5.6.2):
     dependencies:
       '@volar/typescript': 2.2.0
-      '@vue/language-core': 2.0.16(typescript@5.5.4)
+      '@vue/language-core': 2.0.16(typescript@5.6.2)
       path-browserify: 1.0.1
       vue-component-type-helpers: 2.0.16
     optionalDependencies:
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   vue-component-type-helpers@1.8.4: {}
 
   vue-component-type-helpers@2.0.16: {}
 
-  vue-component-type-helpers@2.0.29: {}
-
   vue-component-type-helpers@2.1.6: {}
 
-  vue-demi@0.14.7(vue@3.4.37(typescript@5.5.4)):
+  vue-demi@0.14.7(vue@3.5.11(typescript@5.6.2)):
     dependencies:
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.11(typescript@5.6.2)
 
-  vue-docgen-api@4.75.1(vue@3.4.37(typescript@5.5.4)):
+  vue-docgen-api@4.75.1(vue@3.5.11(typescript@5.6.2)):
     dependencies:
-      '@babel/parser': 7.24.7
-      '@babel/types': 7.24.7
-      '@vue/compiler-dom': 3.4.37
-      '@vue/compiler-sfc': 3.4.37
+      '@babel/parser': 7.25.6
+      '@babel/types': 7.25.6
+      '@vue/compiler-dom': 3.5.10
+      '@vue/compiler-sfc': 3.5.11
       ast-types: 0.16.1
       hash-sum: 2.0.0
       lru-cache: 8.0.4
       pug: 3.0.3
       recast: 0.23.6
       ts-map: 1.0.3
-      vue: 3.4.37(typescript@5.5.4)
-      vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.37(typescript@5.5.4))
+      vue: 3.5.11(typescript@5.6.2)
+      vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.11(typescript@5.6.2))
 
-  vue-eslint-parser@9.4.3(eslint@9.8.0):
+  vue-eslint-parser@9.4.3(eslint@9.11.0):
     dependencies:
       debug: 4.3.5(supports-color@5.5.0)
-      eslint: 9.8.0
+      eslint: 9.11.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
       espree: 9.6.1
@@ -24900,28 +24057,21 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  vue-i18n@9.13.1(vue@3.4.37(typescript@5.5.4)):
-    dependencies:
-      '@intlify/core-base': 9.13.1
-      '@intlify/shared': 9.13.1
-      '@vue/devtools-api': 6.6.1
-      vue: 3.4.37(typescript@5.5.4)
-
-  vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.4.37(typescript@5.5.4)):
+  vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.11(typescript@5.6.2)):
     dependencies:
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.11(typescript@5.6.2)
 
   vue-template-compiler@2.7.14:
     dependencies:
       de-indent: 1.0.2
       he: 1.2.0
 
-  vue-tsc@2.0.29(typescript@5.5.4):
+  vue-tsc@2.1.6(typescript@5.6.2):
     dependencies:
-      '@volar/typescript': 2.4.0-alpha.18
-      '@vue/language-core': 2.0.29(typescript@5.5.4)
+      '@volar/typescript': 2.4.5
+      '@vue/language-core': 2.1.6(typescript@5.6.2)
       semver: 7.6.0
-      typescript: 5.5.4
+      typescript: 5.6.2
 
   vue@3.4.37(typescript@5.5.4):
     dependencies:
@@ -24933,40 +24083,39 @@ snapshots:
     optionalDependencies:
       typescript: 5.5.4
 
-  vuedraggable@4.1.0(vue@3.4.37(typescript@5.5.4)):
+  vue@3.5.11(typescript@5.6.2):
+    dependencies:
+      '@vue/compiler-dom': 3.5.11
+      '@vue/compiler-sfc': 3.5.11
+      '@vue/runtime-dom': 3.5.11
+      '@vue/server-renderer': 3.5.11(vue@3.5.11(typescript@5.6.2))
+      '@vue/shared': 3.5.11
+    optionalDependencies:
+      typescript: 5.6.2
+
+  vuedraggable@4.1.0(vue@3.5.11(typescript@5.6.2)):
     dependencies:
       sortablejs: 1.14.0
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.11(typescript@5.6.2)
 
   w3c-xmlserializer@5.0.0:
     dependencies:
       xml-name-validator: 5.0.0
 
-  wait-on@7.2.0(debug@4.3.5):
+  wait-on@8.0.1(debug@4.3.7):
     dependencies:
-      axios: 1.6.2(debug@4.3.5)
-      joi: 17.11.0
+      axios: 1.7.7(debug@4.3.7)
+      joi: 17.13.3
       lodash: 4.17.21
       minimist: 1.2.8
       rxjs: 7.8.1
     transitivePeerDependencies:
       - debug
 
-  walk-up-path@3.0.1: {}
-
   walker@1.0.8:
     dependencies:
       makeerror: 1.0.12
 
-  watchpack@2.4.0:
-    dependencies:
-      glob-to-regexp: 0.4.1
-      graceful-fs: 4.2.11
-
-  wcwidth@1.0.1:
-    dependencies:
-      defaults: 1.0.4
-
   web-push@3.6.7:
     dependencies:
       asn1.js: 5.4.1
@@ -24977,6 +24126,14 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  web-resource-inliner@7.0.0:
+    dependencies:
+      ansi-colors: 4.1.3
+      escape-goat: 3.0.0
+      htmlparser2: 5.0.1
+      mime: 2.6.0
+      valid-data-url: 3.0.1
+
   web-streams-polyfill@3.2.1: {}
 
   web-streams-polyfill@4.0.0:
@@ -25037,6 +24194,14 @@ snapshots:
       gopd: 1.0.1
       has-tostringtag: 1.0.0
 
+  which-typed-array@1.1.15:
+    dependencies:
+      available-typed-arrays: 1.0.7
+      call-bind: 1.0.7
+      for-each: 0.3.3
+      gopd: 1.0.1
+      has-tostringtag: 1.0.2
+
   which@1.3.1:
     dependencies:
       isexe: 2.0.0
@@ -25068,8 +24233,6 @@ snapshots:
 
   word-wrap@1.2.5: {}
 
-  wordwrap@1.0.0: {}
-
   wrap-ansi@6.2.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -25090,12 +24253,6 @@ snapshots:
 
   wrappy@1.0.2: {}
 
-  write-file-atomic@2.4.3:
-    dependencies:
-      graceful-fs: 4.2.11
-      imurmurhash: 0.1.4
-      signal-exit: 3.0.7
-
   write-file-atomic@4.0.2:
     dependencies:
       imurmurhash: 0.1.4
diff --git a/scripts/changelog-checker/package-lock.json b/scripts/changelog-checker/package-lock.json
index 6ad3273e6007..b7ec909abead 100644
--- a/scripts/changelog-checker/package-lock.json
+++ b/scripts/changelog-checker/package-lock.json
@@ -16,7 +16,7 @@
         "remark-parse": "11.0.0",
         "typescript": "5.3.3",
         "unified": "11.0.4",
-        "vite": "5.0.12",
+        "vite": "5.4.6",
         "vite-node": "1.1.3",
         "vitest": "1.1.3"
       }
@@ -85,9 +85,9 @@
       "dev": true
     },
     "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz",
-      "integrity": "sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
       "cpu": [
         "ppc64"
       ],
@@ -101,9 +101,9 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.11.tgz",
-      "integrity": "sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+      "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
       "cpu": [
         "arm"
       ],
@@ -117,9 +117,9 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz",
-      "integrity": "sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+      "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
       "cpu": [
         "arm64"
       ],
@@ -133,9 +133,9 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.11.tgz",
-      "integrity": "sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+      "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
       "cpu": [
         "x64"
       ],
@@ -149,9 +149,9 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz",
-      "integrity": "sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+      "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
       "cpu": [
         "arm64"
       ],
@@ -165,9 +165,9 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz",
-      "integrity": "sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+      "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
       "cpu": [
         "x64"
       ],
@@ -181,9 +181,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz",
-      "integrity": "sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+      "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
       "cpu": [
         "arm64"
       ],
@@ -197,9 +197,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz",
-      "integrity": "sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+      "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
       "cpu": [
         "x64"
       ],
@@ -213,9 +213,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz",
-      "integrity": "sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+      "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
       "cpu": [
         "arm"
       ],
@@ -229,9 +229,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz",
-      "integrity": "sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+      "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
       "cpu": [
         "arm64"
       ],
@@ -245,9 +245,9 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz",
-      "integrity": "sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+      "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
       "cpu": [
         "ia32"
       ],
@@ -261,9 +261,9 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz",
-      "integrity": "sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+      "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
       "cpu": [
         "loong64"
       ],
@@ -277,9 +277,9 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz",
-      "integrity": "sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+      "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
       "cpu": [
         "mips64el"
       ],
@@ -293,9 +293,9 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz",
-      "integrity": "sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+      "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
       "cpu": [
         "ppc64"
       ],
@@ -309,9 +309,9 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz",
-      "integrity": "sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+      "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
       "cpu": [
         "riscv64"
       ],
@@ -325,9 +325,9 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz",
-      "integrity": "sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+      "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
       "cpu": [
         "s390x"
       ],
@@ -341,9 +341,9 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz",
-      "integrity": "sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+      "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
       "cpu": [
         "x64"
       ],
@@ -357,9 +357,9 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz",
-      "integrity": "sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
       "cpu": [
         "x64"
       ],
@@ -373,9 +373,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz",
-      "integrity": "sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+      "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
       "cpu": [
         "x64"
       ],
@@ -389,9 +389,9 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz",
-      "integrity": "sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+      "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
       "cpu": [
         "x64"
       ],
@@ -405,9 +405,9 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz",
-      "integrity": "sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+      "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
       "cpu": [
         "arm64"
       ],
@@ -421,9 +421,9 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz",
-      "integrity": "sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+      "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
       "cpu": [
         "ia32"
       ],
@@ -437,9 +437,9 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz",
-      "integrity": "sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+      "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
       "cpu": [
         "x64"
       ],
@@ -522,9 +522,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz",
-      "integrity": "sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.3.tgz",
+      "integrity": "sha512-MmKSfaB9GX+zXl6E8z4koOr/xU63AMVleLEa64v7R0QF/ZloMs5vcD1sHgM64GXXS1csaJutG+ddtzcueI/BLg==",
       "cpu": [
         "arm"
       ],
@@ -535,9 +535,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz",
-      "integrity": "sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.3.tgz",
+      "integrity": "sha512-zrt8ecH07PE3sB4jPOggweBjJMzI1JG5xI2DIsUbkA+7K+Gkjys6eV7i9pOenNSDJH3eOr/jLb/PzqtmdwDq5g==",
       "cpu": [
         "arm64"
       ],
@@ -548,9 +548,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz",
-      "integrity": "sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.3.tgz",
+      "integrity": "sha512-P0UxIOrKNBFTQaXTxOH4RxuEBVCgEA5UTNV6Yz7z9QHnUJ7eLX9reOd/NYMO3+XZO2cco19mXTxDMXxit4R/eQ==",
       "cpu": [
         "arm64"
       ],
@@ -561,9 +561,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz",
-      "integrity": "sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.3.tgz",
+      "integrity": "sha512-L1M0vKGO5ASKntqtsFEjTq/fD91vAqnzeaF6sfNAy55aD+Hi2pBI5DKwCO+UNDQHWsDViJLqshxOahXyLSh3EA==",
       "cpu": [
         "x64"
       ],
@@ -574,9 +574,22 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz",
-      "integrity": "sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.3.tgz",
+      "integrity": "sha512-btVgIsCjuYFKUjopPoWiDqmoUXQDiW2A4C3Mtmp5vACm7/GnyuprqIDPNczeyR5W8rTXEbkmrJux7cJmD99D2g==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.3.tgz",
+      "integrity": "sha512-zmjbSphplZlau6ZTkxd3+NMtE4UKVy7U4aVFMmHcgO5CUbw17ZP6QCgyxhzGaU/wFFdTfiojjbLG3/0p9HhAqA==",
       "cpu": [
         "arm"
       ],
@@ -587,9 +600,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz",
-      "integrity": "sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.3.tgz",
+      "integrity": "sha512-nSZfcZtAnQPRZmUkUQwZq2OjQciR6tEoJaZVFvLHsj0MF6QhNMg0fQ6mUOsiCUpTqxTx0/O6gX0V/nYc7LrgPw==",
       "cpu": [
         "arm64"
       ],
@@ -600,9 +613,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz",
-      "integrity": "sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.3.tgz",
+      "integrity": "sha512-MnvSPGO8KJXIMGlQDYfvYS3IosFN2rKsvxRpPO2l2cum+Z3exiExLwVU+GExL96pn8IP+GdH8Tz70EpBhO0sIQ==",
       "cpu": [
         "arm64"
       ],
@@ -612,10 +625,23 @@
         "linux"
       ]
     },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.3.tgz",
+      "integrity": "sha512-+W+p/9QNDr2vE2AXU0qIy0qQE75E8RTwTwgqS2G5CRQ11vzq0tbnfBd6brWhS9bCRjAjepJe2fvvkvS3dno+iw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz",
-      "integrity": "sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.3.tgz",
+      "integrity": "sha512-yXH6K6KfqGXaxHrtr+Uoy+JpNlUlI46BKVyonGiaD74ravdnF9BUNC+vV+SIuB96hUMGShhKV693rF9QDfO6nQ==",
       "cpu": [
         "riscv64"
       ],
@@ -625,10 +651,23 @@
         "linux"
       ]
     },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.3.tgz",
+      "integrity": "sha512-R8cwY9wcnApN/KDYWTH4gV/ypvy9yZUHlbJvfaiXSB48JO3KpwSpjOGqO4jnGkLDSk1hgjYkTbTt6Q7uvPf8eg==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz",
-      "integrity": "sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.3.tgz",
+      "integrity": "sha512-kZPbX/NOPh0vhS5sI+dR8L1bU2cSO9FgxwM8r7wHzGydzfSjLRCFAT87GR5U9scj2rhzN3JPYVC7NoBbl4FZ0g==",
       "cpu": [
         "x64"
       ],
@@ -639,9 +678,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz",
-      "integrity": "sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.3.tgz",
+      "integrity": "sha512-S0Yq+xA1VEH66uiMNhijsWAafffydd2X5b77eLHfRmfLsRSpbiAWiRHV6DEpz6aOToPsgid7TI9rGd6zB1rhbg==",
       "cpu": [
         "x64"
       ],
@@ -652,9 +691,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz",
-      "integrity": "sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.3.tgz",
+      "integrity": "sha512-9isNzeL34yquCPyerog+IMCNxKR8XYmGd0tHSV+OVx0TmE0aJOo9uw4fZfUuk2qxobP5sug6vNdZR6u7Mw7Q+Q==",
       "cpu": [
         "arm64"
       ],
@@ -665,9 +704,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz",
-      "integrity": "sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.3.tgz",
+      "integrity": "sha512-nMIdKnfZfzn1Vsk+RuOvl43ONTZXoAPUUxgcU0tXooqg4YrAqzfKzVenqqk2g5efWh46/D28cKFrOzDSW28gTA==",
       "cpu": [
         "ia32"
       ],
@@ -678,9 +717,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz",
-      "integrity": "sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.3.tgz",
+      "integrity": "sha512-fOvu7PCQjAj4eWDEuD8Xz5gpzFqXzGlxHZozHP4b9Jxv9APtdxL6STqztDzMLuRXEc4UpXGGhx029Xgm91QBeA==",
       "cpu": [
         "x64"
       ],
@@ -1060,9 +1099,9 @@
       }
     },
     "node_modules/esbuild": {
-      "version": "0.19.11",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.11.tgz",
-      "integrity": "sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==",
+      "version": "0.21.5",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+      "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
       "dev": true,
       "hasInstallScript": true,
       "bin": {
@@ -1072,29 +1111,29 @@
         "node": ">=12"
       },
       "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.19.11",
-        "@esbuild/android-arm": "0.19.11",
-        "@esbuild/android-arm64": "0.19.11",
-        "@esbuild/android-x64": "0.19.11",
-        "@esbuild/darwin-arm64": "0.19.11",
-        "@esbuild/darwin-x64": "0.19.11",
-        "@esbuild/freebsd-arm64": "0.19.11",
-        "@esbuild/freebsd-x64": "0.19.11",
-        "@esbuild/linux-arm": "0.19.11",
-        "@esbuild/linux-arm64": "0.19.11",
-        "@esbuild/linux-ia32": "0.19.11",
-        "@esbuild/linux-loong64": "0.19.11",
-        "@esbuild/linux-mips64el": "0.19.11",
-        "@esbuild/linux-ppc64": "0.19.11",
-        "@esbuild/linux-riscv64": "0.19.11",
-        "@esbuild/linux-s390x": "0.19.11",
-        "@esbuild/linux-x64": "0.19.11",
-        "@esbuild/netbsd-x64": "0.19.11",
-        "@esbuild/openbsd-x64": "0.19.11",
-        "@esbuild/sunos-x64": "0.19.11",
-        "@esbuild/win32-arm64": "0.19.11",
-        "@esbuild/win32-ia32": "0.19.11",
-        "@esbuild/win32-x64": "0.19.11"
+        "@esbuild/aix-ppc64": "0.21.5",
+        "@esbuild/android-arm": "0.21.5",
+        "@esbuild/android-arm64": "0.21.5",
+        "@esbuild/android-x64": "0.21.5",
+        "@esbuild/darwin-arm64": "0.21.5",
+        "@esbuild/darwin-x64": "0.21.5",
+        "@esbuild/freebsd-arm64": "0.21.5",
+        "@esbuild/freebsd-x64": "0.21.5",
+        "@esbuild/linux-arm": "0.21.5",
+        "@esbuild/linux-arm64": "0.21.5",
+        "@esbuild/linux-ia32": "0.21.5",
+        "@esbuild/linux-loong64": "0.21.5",
+        "@esbuild/linux-mips64el": "0.21.5",
+        "@esbuild/linux-ppc64": "0.21.5",
+        "@esbuild/linux-riscv64": "0.21.5",
+        "@esbuild/linux-s390x": "0.21.5",
+        "@esbuild/linux-x64": "0.21.5",
+        "@esbuild/netbsd-x64": "0.21.5",
+        "@esbuild/openbsd-x64": "0.21.5",
+        "@esbuild/sunos-x64": "0.21.5",
+        "@esbuild/win32-arm64": "0.21.5",
+        "@esbuild/win32-ia32": "0.21.5",
+        "@esbuild/win32-x64": "0.21.5"
       }
     },
     "node_modules/estree-walker": {
@@ -2086,9 +2125,9 @@
       }
     },
     "node_modules/picocolors": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
-      "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
+      "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
       "dev": true
     },
     "node_modules/pkg-types": {
@@ -2103,9 +2142,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.33",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
-      "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
+      "version": "8.4.47",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+      "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
       "dev": true,
       "funding": [
         {
@@ -2123,8 +2162,8 @@
       ],
       "dependencies": {
         "nanoid": "^3.3.7",
-        "picocolors": "^1.0.0",
-        "source-map-js": "^1.0.2"
+        "picocolors": "^1.1.0",
+        "source-map-js": "^1.2.1"
       },
       "engines": {
         "node": "^10 || ^12 || >=14"
@@ -2198,9 +2237,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "4.9.4",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.4.tgz",
-      "integrity": "sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g==",
+      "version": "4.21.3",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.3.tgz",
+      "integrity": "sha512-7sqRtBNnEbcBtMeRVc6VRsJMmpI+JU1z9VTvW8D4gXIYQFz0aLcsE6rRkyghZkLfEgUZgVvOG7A5CVz/VW5GIA==",
       "dev": true,
       "dependencies": {
         "@types/estree": "1.0.5"
@@ -2213,19 +2252,22 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.9.4",
-        "@rollup/rollup-android-arm64": "4.9.4",
-        "@rollup/rollup-darwin-arm64": "4.9.4",
-        "@rollup/rollup-darwin-x64": "4.9.4",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.9.4",
-        "@rollup/rollup-linux-arm64-gnu": "4.9.4",
-        "@rollup/rollup-linux-arm64-musl": "4.9.4",
-        "@rollup/rollup-linux-riscv64-gnu": "4.9.4",
-        "@rollup/rollup-linux-x64-gnu": "4.9.4",
-        "@rollup/rollup-linux-x64-musl": "4.9.4",
-        "@rollup/rollup-win32-arm64-msvc": "4.9.4",
-        "@rollup/rollup-win32-ia32-msvc": "4.9.4",
-        "@rollup/rollup-win32-x64-msvc": "4.9.4",
+        "@rollup/rollup-android-arm-eabi": "4.21.3",
+        "@rollup/rollup-android-arm64": "4.21.3",
+        "@rollup/rollup-darwin-arm64": "4.21.3",
+        "@rollup/rollup-darwin-x64": "4.21.3",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.21.3",
+        "@rollup/rollup-linux-arm-musleabihf": "4.21.3",
+        "@rollup/rollup-linux-arm64-gnu": "4.21.3",
+        "@rollup/rollup-linux-arm64-musl": "4.21.3",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.21.3",
+        "@rollup/rollup-linux-riscv64-gnu": "4.21.3",
+        "@rollup/rollup-linux-s390x-gnu": "4.21.3",
+        "@rollup/rollup-linux-x64-gnu": "4.21.3",
+        "@rollup/rollup-linux-x64-musl": "4.21.3",
+        "@rollup/rollup-win32-arm64-msvc": "4.21.3",
+        "@rollup/rollup-win32-ia32-msvc": "4.21.3",
+        "@rollup/rollup-win32-x64-msvc": "4.21.3",
         "fsevents": "~2.3.2"
       }
     },
@@ -2293,9 +2335,9 @@
       }
     },
     "node_modules/source-map-js": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
-      "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
       "dev": true,
       "engines": {
         "node": ">=0.10.0"
@@ -2558,14 +2600,14 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.0.12",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz",
-      "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==",
+      "version": "5.4.6",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
+      "integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
       "dev": true,
       "dependencies": {
-        "esbuild": "^0.19.3",
-        "postcss": "^8.4.32",
-        "rollup": "^4.2.0"
+        "esbuild": "^0.21.3",
+        "postcss": "^8.4.43",
+        "rollup": "^4.20.0"
       },
       "bin": {
         "vite": "bin/vite.js"
@@ -2584,6 +2626,7 @@
         "less": "*",
         "lightningcss": "^1.21.0",
         "sass": "*",
+        "sass-embedded": "*",
         "stylus": "*",
         "sugarss": "*",
         "terser": "^5.4.0"
@@ -2601,6 +2644,9 @@
         "sass": {
           "optional": true
         },
+        "sass-embedded": {
+          "optional": true
+        },
         "stylus": {
           "optional": true
         },
diff --git a/scripts/changelog-checker/package.json b/scripts/changelog-checker/package.json
index 8b3c9843b70a..dccb47d03764 100644
--- a/scripts/changelog-checker/package.json
+++ b/scripts/changelog-checker/package.json
@@ -17,7 +17,7 @@
     "remark-parse": "11.0.0",
     "typescript": "5.3.3",
     "unified": "11.0.4",
-    "vite": "5.0.12",
+    "vite": "5.4.6",
     "vite-node": "1.1.3",
     "vitest": "1.1.3"
   }