diff --git a/.config/ci.yml b/.config/ci.yml
index 44092d36623b4ff32fec95a83473623eccac2e2f..d20ede8d3575dff504363d7bd2b10d5c94da879a 100644
--- a/.config/ci.yml
+++ b/.config/ci.yml
@@ -167,8 +167,18 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
-# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1)
-maxNoteLength: 3000
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
 
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d8013a1c95050cfd19430c7a53af874a2496a339
--- /dev/null
+++ b/.config/cypress-devcontainer.yml
@@ -0,0 +1,224 @@
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+# Misskey configuration
+#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+#   ┌─────┐
+#───┘ URL └─────────────────────────────────────────────────────
+
+# Final accessible URL seen by a user.
+url: 'http://misskey.local'
+
+# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
+# URL SETTINGS AFTER THAT!
+
+#   ┌───────────────────────┐
+#───┘ Port and TLS settings └───────────────────────────────────
+
+#
+# Misskey requires a reverse proxy to support HTTPS connections.
+#
+#                 +----- https://example.tld/ ------------+
+#   +------+      |+-------------+      +----------------+|
+#   | User | ---> || Proxy (443) | ---> | Misskey (3000) ||
+#   +------+      |+-------------+      +----------------+|
+#                 +---------------------------------------+
+#
+#   You need to set up a reverse proxy. (e.g. nginx)
+#   An encrypted connection with HTTPS is highly recommended
+#   because tokens may be transferred in GET requests.
+
+# The port that your Misskey server should listen on.
+port: 61812
+
+#   ┌──────────────────────────┐
+#───┘ PostgreSQL configuration └────────────────────────────────
+
+db:
+  host: db
+  port: 5432
+
+  # Database name
+  db: misskey
+
+  # Auth
+  user: postgres
+  pass: postgres
+
+  # Whether disable Caching queries
+  #disableCache: true
+
+  # Extra Connection options
+  #extra:
+  #  ssl: true
+
+dbReplications: false
+
+# You can configure any number of replicas here
+#dbSlaves:
+#  -
+#    host:
+#    port:
+#    db:
+#    user:
+#    pass:
+#  -
+#    host:
+#    port:
+#    db:
+#    user:
+#    pass:
+
+#   ┌─────────────────────┐
+#───┘ Redis configuration └─────────────────────────────────────
+
+redis:
+  host: redis
+  port: 6379
+  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+  #pass: example-pass
+  #prefix: example-prefix
+  #db: 1
+
+#redisForPubsub:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
+#redisForJobQueue:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
+#redisForTimelines:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #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 └─────────────────────────────
+
+#meilisearch:
+#  host: meilisearch
+#  port: 7700
+#  apiKey: ''
+#  ssl: true
+#  index: ''
+
+#   ┌───────────────┐
+#───┘ ID generation └───────────────────────────────────────────
+
+# You can select the ID generation method.
+# You don't usually need to change this setting, but you can
+# change it according to your preferences.
+
+# Available methods:
+# aid ... Short, Millisecond accuracy
+# aidx ... Millisecond accuracy
+# meid ... Similar to ObjectID, Millisecond accuracy
+# ulid ... Millisecond accuracy
+# objectid ... This is left for backward compatibility
+
+# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
+# ID SETTINGS AFTER THAT!
+
+id: 'aidx'
+
+#   ┌────────────────┐
+#───┘ Error tracking └──────────────────────────────────────────
+
+# Sentry is available for error tracking.
+# See the Sentry documentation for more details on options.
+
+#sentryForBackend:
+#  enableNodeProfiling: true
+#  options:
+#    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
+
+#sentryForFrontend:
+#  options:
+#    dsn: 'https://examplePublicKey@o0.ingest.sentry.io/0'
+
+#   ┌─────────────────────┐
+#───┘ Other configuration └─────────────────────────────────────
+
+# Whether disable HSTS
+#disableHsts: true
+
+# Number of worker processes
+#clusterLimit: 1
+
+# Job concurrency per worker
+# deliverJobConcurrency: 128
+# inboxJobConcurrency: 16
+
+# Job rate limiter
+# deliverJobPerSec: 128
+# inboxJobPerSec: 32
+
+# Job attempts
+# deliverJobMaxAttempts: 12
+# inboxJobMaxAttempts: 8
+
+# IP address family used for outgoing request (ipv4, ipv6 or dual)
+#outgoingAddressFamily: ipv4
+
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
+
+# Proxy for HTTP/HTTPS
+#proxy: http://127.0.0.1:3128
+
+proxyBypassHosts:
+  - api.deepl.com
+  - api-free.deepl.com
+  - www.recaptcha.net
+  - hcaptcha.com
+  - challenges.cloudflare.com
+
+# Proxy for SMTP/SMTPS
+#proxySmtp: http://127.0.0.1:3128   # use HTTP/1.1 CONNECT
+#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4
+#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5
+
+# Media Proxy
+#mediaProxy: https://example.com/proxy
+
+# Proxy remote files (default: true)
+proxyRemoteFiles: true
+
+# Sign to ActivityPub GET request (default: true)
+signToActivityPubGet: true
+
+allowedPrivateNetworks: [
+  '127.0.0.1/32'
+]
+
+# Upload or download file size limits (bytes)
+#maxFileSize: 262144000
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index de95f1b21a2d736aa675dbec4ff3e7449e01dd7d..5fac3dc41e29dfa112c53f109583f7e369cb531c 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -163,6 +163,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 └─────────────────────────────
 
@@ -242,8 +250,18 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
-# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1)
-maxNoteLength: 3000
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
 
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
diff --git a/.config/example.yml b/.config/example.yml
index 21e85b7b8904566136155c4fbed59f6d83d6076a..0062b6670c0f7b65a36f247410f205b6dd5e158b 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -172,6 +172,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 └─────────────────────────────
 
@@ -251,8 +261,18 @@ id: 'aidx'
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
 
-# Amount of characters that can be used when writing notes (maximum: 8192, minimum: 1)
-maxNoteLength: 3000
+# Amount of characters that can be used when writing notes. Longer notes will be rejected. (minimum: 1)
+#maxNoteLength: 3000
+# Amount of characters that will be saved for remote notes. Longer notes will be truncated to this length. (minimum: 1)
+#maxRemoteNoteLength: 100000
+# Amount of characters that can be used when writing content warnings. Longer warnings will be rejected. (minimum: 1)
+#maxCwLength: 500
+# Amount of characters that will be saved for remote content warnings. Longer warnings will be truncated to this length. (minimum: 1)
+#maxRemoteCwLength: 5000
+# Amount of characters that can be used when writing media descriptions (alt text). Longer descriptions will be rejected. (minimum: 1)
+#maxAltTextLength: 20000
+# Amount of characters that will be saved for remote media descriptions (alt text). Longer descriptions will be truncated to this length. (minimum: 1)
+#maxRemoteAltTextLength: 100000
 
 # Proxy for HTTP/HTTPS
 #proxy: http://127.0.0.1:3128
diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml
index beefcfd0a2d5094528d1ec859c7f28a6999e8dad..3eb4fc28794be38e42b27b269b9f140939c00e0b 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/.devcontainer/init.sh b/.devcontainer/init.sh
index 55fb1e6fa6873c51aea3b54ca25c233b6eeae520..e02a533c15913117b0b6f24068ee592b82ccb7b8 100755
--- a/.devcontainer/init.sh
+++ b/.devcontainer/init.sh
@@ -3,6 +3,8 @@
 set -xe
 
 sudo chown node node_modules
+sudo apt-get update
+sudo apt-get -y install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libnss3 libxss1 libasound2 libxtst6 xauth xvfb
 git config --global --add safe.directory /workspace
 git submodule update --init
 corepack install
@@ -12,3 +14,4 @@ pnpm install --frozen-lockfile
 cp .devcontainer/devcontainer.yml .config/default.yml
 pnpm build
 pnpm migrate
+pnpm exec cypress install
diff --git a/.gitignore b/.gitignore
index 758d36cea4fcc603dafa2743f04f8ca6f84c3b04..7cc7354a4a01d721e3621799f55ed9e6168c6d61 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,6 +38,7 @@ coverage
 !/.config/example.yml
 !/.config/docker_example.yml
 !/.config/docker_example.env
+!/.config/cypress-devcontainer.yml
 docker-compose.yml
 compose.yml
 .devcontainer/compose.yml
@@ -47,6 +48,7 @@ compose.yml
 /build
 built
 built-test
+js-built
 /data
 /.cache-loader
 /db
@@ -66,8 +68,9 @@ temp
 tsdoc-metadata.json
 misskey-assets
 
-# Sharkey
-/packages/megalodon/lib
+# Vite temporary files
+vite.config.js.timestamp-*
+vite.config.ts.timestamp-*
 
 # blender backups
 *.blend1
@@ -78,3 +81,6 @@ misskey-assets
 
 # VSCode addon
 .favorites.json
+
+# Sharkey
+/packages/megalodon/lib
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2e773eddf9aecc1e3f9efa3322a75f27b33a6550..4db8bda32ec026bad7fe3ec15b74f8b40fe34d5e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -20,9 +20,9 @@ testCommit:
     - pnpm install --frozen-lockfile
     - pnpm run build
     - pnpm run migrate
-    - pnpm run --filter='!megalodon' --workspace-concurrency=1 test
-    - pnpm run --filter=backend lint
-    - pnpm run --filter=frontend eslint
+    - pnpm run --filter='!megalodon' test
+    - pnpm run --filter=backend --filter=misskey-js lint
+    - pnpm run --filter=frontend --filter=frontend-embed eslint
   cache:
     key: test
     policy: pull-push
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0986b6eae32c1206d2771e5e266f8ace94cb468d..cf0437e51a8d0f702e91a3147e7fd49da4a62bf4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,56 @@
+## 2024.9.0
+
+### General
+- Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能
+  - 埋め込みコードやウェブサイトへの実装方法の詳細は 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
 
 ### General
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a50b550b3a482cc42b8ccf916bf6cb706707ef1c..f2e48ec61ddbc4314b701234187c49a84ff7a140 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -529,7 +529,8 @@ enumの列挙の内容の削除は、その値をもつレコードを全て削
 ### Migration作成方法
 packages/backendで:
 ```sh
-pnpm dlx typeorm migration:generate -d ormconfig.js -o <migration name>
+pnpm run build
+pnpm dlx typeorm migration:generate -d ormconfig.js -o migration/<migration name>
 ```
 
 - 生成後、ファイルをmigration下に移してください
@@ -573,6 +574,26 @@ marginはそのコンポーネントを使う側が設定する
 ### indexというファイル名を使うな
 ESMではディレクトリインポートは廃止されているのと、ディレクトリインポートせずともファイル名が index だと何故か一部のライブラリ?でディレクトリインポートだと見做されてエラーになる
 
+## CSS Recipe
+
+### Lighten CSS vars
+
+``` css
+color: hsl(from var(--accent) h s calc(l + 10));
+```
+
+### Darken CSS vars
+
+``` css
+color: hsl(from var(--accent) h s calc(l - 10));
+```
+
+### Add alpha to CSS vars
+
+``` css
+color: color(from var(--accent) srgb r g b / 0.5);
+```
+
 ## Merging from Misskey into Sharkey
 
 Make sure you have both remotes in the same clone (`git remote add misskey
@@ -590,15 +611,11 @@ seems to do a decent job)
 *after that commit*, do all the extra work, on the same branch:
 
 * copy all changes (commit after each step):
-  * in `packages/backend/src/core/NoteCreateService.ts`, from `create` to
-    `import` (and vice versa if `git` got confused!)
   * in
     `packages/backend/src/core/activitypub/models/ApNoteService.ts`,
     from `createNote` to `updateNote`
   * from `packages/backend/src/core/NoteCreateService.ts` to
     `packages/backend/src/core/NoteEditService.vue`
-  * in `packages/backend/src/core/activitypub/models/ApNoteService.ts`,
-    from `createNote` to `updateNote`
   * from `packages/backend/src/server/api/endpoints/notes/create.ts`
     to `packages/backend/src/server/api/endpoints/notes/edit.ts`
   * from `packages/frontend/src/components/MkNote*.vue` to
@@ -614,9 +631,9 @@ seems to do a decent job)
 * check the changes against our `develop` (`git diff develop`) and
   against Misskey (`git diff misskey/develop`)
 * re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit
-* build the frontend: `rm -rf built/; NODE_ENV=development pnpm --filter=frontend
-  build` (the `development` tells it to keep some of the original
-  filenames in the built files)
+* build the frontend: `rm -rf built/; NODE_ENV=development pnpm
+  --filter=frontend --filter=frontend-embed build` (the `development`
+  tells it to keep some of the original filenames in the built files)
 * make sure there aren't any new `ti-*` classes (Tabler Icons), and
   replace them with appropriate `ph-*` ones (Phosphor Icons):
   `grep -rP '["'\'']ti[ -](?!fw)' -- built/` should show you what to change.
diff --git a/Dockerfile b/Dockerfile
index 288e97481c298e53a41cb32c1ff8227aa6d6bd5a..acef95deab2bd193b4bc31e0c848acbded0a43ec 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -23,9 +23,10 @@ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
 RUN pnpm build
 RUN node scripts/trim-deps.mjs
 RUN mv packages/frontend/assets sharkey-assets
+RUN mv packages/frontend-embed/assets sharkey-embed-assets
 RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
 	pnpm prune
-RUN rm -r node_modules packages/frontend packages/sw
+RUN rm -r node_modules packages/frontend packages/frontend-shared packages/frontend-embed packages/sw
 RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
 	pnpm i --prod --frozen-lockfile --aggregate-output
 RUN rm -rf .git
@@ -64,6 +65,7 @@ COPY --chown=sharkey:sharkey --from=build /sharkey/packages/megalodon/lib ./pack
 COPY --chown=sharkey:sharkey --from=build /sharkey/fluent-emojis ./fluent-emojis
 COPY --chown=sharkey:sharkey --from=build /sharkey/tossface-emojis/dist ./tossface-emojis/dist
 COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-assets ./packages/frontend/assets
+COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-embed-assets ./packages/frontend-embed/assets
 
 COPY --chown=sharkey:sharkey pnpm-workspace.yaml ./pnpm-workspace.yaml
 COPY --chown=sharkey:sharkey packages/backend/package.json ./packages/backend/package.json
diff --git a/UPGRADE_NOTES.md b/UPGRADE_NOTES.md
new file mode 100644
index 0000000000000000000000000000000000000000..8bebd4eb34790dffea739fde66b1daa7864116c8
--- /dev/null
+++ b/UPGRADE_NOTES.md
@@ -0,0 +1,41 @@
+# Upgrade Notes
+
+## 2024.9.0
+
+### Following Feed
+
+When upgrading an existing instance to version 2024.9.0, the Following Feed will initially be empty.
+The feed will gradually fill as new posts federate, but it may be desirable to back-fill the feed with existing data.
+This database script will populate the feed with the latest post of each type for all users, ensuring that data is fully populated after the update.
+Run this after migrations but before starting the instance.
+Warning: the script may take a long time to execute!
+
+```postgresql
+INSERT INTO latest_note (user_id, note_id, is_public, is_reply, is_quote)
+SELECT
+	"userId" as user_id,
+	id as note_id,
+	visibility = 'public' AS is_public,
+	"replyId" IS NOT NULL AS is_reply,
+	(
+		"renoteId" IS NOT NULL
+			AND (
+			text IS NOT NULL
+				OR cw IS NOT NULL
+				OR "replyId" IS NOT NULL
+				OR "hasPoll"
+				OR "fileIds" != '{}'
+			)
+		) AS is_quote
+FROM note
+WHERE ( -- Exclude pure renotes (boosts)
+				"renoteId" IS NULL
+					OR text IS NOT NULL
+					OR cw IS NOT NULL
+					OR "replyId" IS NOT NULL
+					OR "hasPoll"
+					OR "fileIds" != '{}'
+				)
+ORDER BY id DESC -- This part is very important: it ensures that we only load the *latest* notes of each type. Do not remove it!
+ON CONFLICT DO NOTHING; -- Any conflicts are guaranteed to be older notes that we can ignore.
+```
diff --git a/chart/files/default.yml b/chart/files/default.yml
index aab7ed6ce12edd997908bdaafb14d28b8fa30d08..97201aad662dc62d6b2b224b430a87f78b23f53f 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/compose_example.yml b/compose_example.yml
index 15df128eff4dee0aaac3a7e9dd272ce2dc1d2947..0db8b04dc6e2958a0f98474857a63e8909c49f7c 100644
--- a/compose_example.yml
+++ b/compose_example.yml
@@ -53,7 +53,7 @@ services:
 #    restart: always
 #    image: mcaptcha/mcaptcha:latest
 #    networks:
-#      shonks:
+#      shonk:
 #        aliases:
 #          - localhost
 #    ports:
@@ -63,6 +63,8 @@ services:
 #    environment:
 #      PORT: 7493
 #      MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
+#      MCAPTCHA_allow_registration: true
+#      MCAPTCHA_server_DOMAIN: "example.tld"
 #    depends_on:
 #      db:
 #        condition: service_healthy
@@ -72,7 +74,7 @@ services:
 #  mcaptcha_redis:
 #    image: mcaptcha/cache:latest
 #    networks:
-#      - shonks
+#      - shonk
 #    healthcheck:
 #      test: "redis-cli ping"
 #      interval: 5s
diff --git a/crowdin.yml b/crowdin.yml
index 774ddc7a630f46ee114a12e31eff3099f378150a..0525ac7b0b54b2a1fdf33a536e2ea13554e3f8cd 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -1,4 +1,4 @@
 files:
-  - source: /locales/ja-JP.yml
-    translation: /locales/%locale%.yml
+  - source: /sharkey-locales/en-US.yml
+    translation: /sharkey-locales/%locale%.yml
     update_option: update_as_unapproved
diff --git a/eslint/locale.js b/eslint/locale.js
new file mode 100644
index 0000000000000000000000000000000000000000..dbb807b71421e06c66763cb735cd3be8c4182ac5
--- /dev/null
+++ b/eslint/locale.js
@@ -0,0 +1,251 @@
+/*
+ * SPDX-FileCopyrightText: dakkar and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+*/
+
+/* This is a ESLint rule to report use of the `i18n.ts` and `i18n.tsx`
+ * objects that reference translation items that don't actually exist
+ * in the lexicon (the `locale/` files)
+ */
+
+/* given a MemberExpression node, collects all the member names
+ *
+ * e.g. for a bit of code like `foo=one.two.three`, `collectMembers`
+ * called on the node for `three` would return `['one', 'two',
+ * 'three']`
+ */
+function collectMembers(node) {
+	if (!node) return [];
+	if (node.type !== 'MemberExpression') return [];
+	// this is something like `foo[bar]`
+	if (node.computed) return [];
+	return [ node.property.name, ...collectMembers(node.parent) ];
+}
+
+/* given an object and an array of names, recursively descends the
+ * object via those names
+ *
+ * e.g. `walkDown({one:{two:{three:15}}},['one','two','three'])` would
+ * return 15
+ */
+function walkDown(locale, path) {
+	if (!locale) return null;
+	if (!path || path.length === 0 || !path[0]) return locale;
+	return walkDown(locale[path[0]], path.slice(1));
+}
+
+/* given a MemberExpression node, returns its attached CallExpression
+ * node if present
+ *
+ * e.g. for a bit of code like `foo=one.two.three()`,
+ * `findCallExpression` called on the node for `three` would return
+ * the node for function call (which is the parent of the `one` and
+ * `two` nodes, and holds the nodes for the argument list)
+ *
+ * if the code had been `foo=one.two.three`, `findCallExpression`
+ * would have returned null, because there's no function call attached
+ * to the MemberExpressions
+ */
+function findCallExpression(node) {
+	if (!node.parent) return null;
+
+	// the second half of this guard protects from cases like
+	// `foo(one.two.three)` where the CallExpression is parent of the
+	// MemberExpressions, but via `arguments`, not `callee`
+	if (node.parent.type === 'CallExpression' && node.parent.callee === node) return node.parent;
+	if (node.parent.type === 'MemberExpression') return findCallExpression(node.parent);
+	return null;
+}
+
+// same, but for Vue expressions (`<I18n :src="i18n.ts.foo">`)
+function findVueExpression(node) {
+	if (!node.parent) return null;
+
+	if (node.parent.type.match(/^VExpr/) && node.parent.expression === node) return node.parent;
+	if (node.parent.type === 'MemberExpression') return findVueExpression(node.parent);
+	return null;
+}
+
+function areArgumentsOneObject(node) {
+	return node.arguments.length === 1 &&
+		node.arguments[0].type === 'ObjectExpression';
+}
+
+// only call if `areArgumentsOneObject(node)` is true
+function getArgumentObjectProperties(node) {
+	return new Set(node.arguments[0].properties.map(
+		p => {
+			if (p.key && p.key.type === 'Identifier') return p.key.name;
+			return null;
+		},
+	));
+}
+
+function getTranslationParameters(translation) {
+	return new Set(Array.from(translation.matchAll(/\{(\w+)\}/g)).map( m => m[1] ));
+}
+
+function setDifference(a,b) {
+	const result = [];
+	for (const element of a.values()) {
+		if (!b.has(element)) {
+			result.push(element);
+		}
+	}
+
+	return result;
+}
+
+/* the actual rule body
+ */
+function theRuleBody(context,node) {
+	// we get the locale/translations via the options; it's the data
+	// that goes into a specific language's JSON file, see
+	// `scripts/build-assets.mjs`
+	const locale = context.options[0];
+
+	// sometimes we get MemberExpression nodes that have a
+	// *descendent* with the right identifier: skip them, we'll get
+	// the right ones as well
+	if (node.object?.name !== 'i18n') {
+		return;
+	}
+
+	// `method` is going to be `'ts'` or `'tsx'`, `path` is going to
+	// be the various translation steps/names
+	const [ method, ...path ] = collectMembers(node);
+	const pathStr = `i18n.${method}.${path.join('.')}`;
+
+	// does that path point to a real translation?
+	const translation = walkDown(locale, path);
+	if (!translation) {
+		context.report({
+			node,
+			message: `translation missing for ${pathStr}`,
+		});
+		return;
+	}
+
+	// we hit something weird, assume the programmers know what
+	// they're doing (this is usually some complicated slicing of
+	// the translation structure)
+	if (typeof(translation) !== 'string') return;
+
+	const callExpression = findCallExpression(node);
+	const vueExpression = findVueExpression(node);
+
+	// some more checks on how the translation is called
+	if (method === 'ts') {
+		// the `<I18n> component gets parametric translations via
+		// `i18n.ts.*`, but we error out elsewhere
+		if (translation.match(/\{/) && !vueExpression) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} is parametric, but called via 'ts'`,
+			});
+			return;
+		}
+
+		if (callExpression) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} is not parametric, but is called as a function`,
+			});
+		}
+	}
+
+	if (method === 'tsx') {
+		if (!translation.match(/\{/)) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} is not parametric, but called via 'tsx'`,
+			});
+			return;
+		}
+
+		if (!callExpression && !vueExpression) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} is parametric, but not called as a function`,
+			});
+			return;
+		}
+
+		// we're not currently checking arguments when used via the
+		// `<I18n>` component, because it's too complicated (also, it
+		// would have to be done inside the `if (method === 'ts')`)
+		if (!callExpression) return;
+
+		if (!areArgumentsOneObject(callExpression)) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} should be called with a single object as argument`,
+			});
+			return;
+		}
+
+		const translationParameters = getTranslationParameters(translation);
+		const parameterCount = translationParameters.size;
+		const callArguments = getArgumentObjectProperties(callExpression);
+		const argumentCount = callArguments.size;
+
+		if (parameterCount !== argumentCount) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} has ${parameterCount} parameters, but is called with ${argumentCount} arguments`,
+			});
+		}
+
+		// node 20 doesn't have `Set.difference`...
+		const extraArguments = setDifference(callArguments, translationParameters);
+		const missingArguments = setDifference(translationParameters, callArguments);
+
+		if (extraArguments.length > 0) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} passes unused arguments ${extraArguments.join(' ')}`,
+			});
+		}
+
+		if (missingArguments.length > 0) {
+			context.report({
+				node,
+				message: `translation for ${pathStr} does not pass arguments ${missingArguments.join(' ')}`,
+			});
+		}
+	}
+}
+
+function theRule(context) {
+	// we get the locale/translations via the options; it's the data
+	// that goes into a specific language's JSON file, see
+	// `scripts/build-assets.mjs`
+	const locale = context.options[0];
+
+	// for all object member access that have an identifier 'i18n'...
+	return context.getSourceCode().parserServices.defineTemplateBodyVisitor(
+		{
+			// this is for <template> bits, needs work
+			'MemberExpression:has(Identifier[name=i18n])': (node) => theRuleBody(context, node),
+		},
+		{
+			// this is for normal code
+			'MemberExpression:has(Identifier[name=i18n])': (node) => theRuleBody(context, node),
+		},
+	);
+}
+
+module.exports = {
+	meta: {
+		type: 'problem',
+		docs: {
+			description: 'assert that all translations used are present in the locale files',
+		},
+		schema: [
+			// here we declare that we need the locale/translation as a
+			// generic object
+			{ type: 'object', additionalProperties: true },
+		],
+	},
+	create: theRule,
+};
diff --git a/eslint/locale.test.js b/eslint/locale.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..2b69672d27b80c11c763f9182b6750d4a09328ee
--- /dev/null
+++ b/eslint/locale.test.js
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: dakkar and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+*/
+
+const {RuleTester} = require("eslint");
+const localeRule = require("./locale");
+
+const locale = { foo: { bar: 'ok', baz: 'good {x}' }, top: '123' };
+
+const ruleTester = new RuleTester({
+  languageOptions: {
+    parser: require('vue-eslint-parser'),
+    ecmaVersion: 2015,
+  },
+});
+
+function testCase(code,errors) {
+  return { code, errors, options: [ locale ], filename: 'test.ts' };
+}
+function testCaseVue(code,errors) {
+  return { code, errors, options: [ locale ], filename: 'test.vue' };
+}
+
+ruleTester.run(
+  'sharkey-locale',
+  localeRule,
+  {
+    valid: [
+      testCase('i18n.ts.foo.bar'),
+      testCase('i18n.ts.top'),
+      testCase('i18n.tsx.foo.baz({x:1})'),
+      testCase('whatever.i18n.ts.blah.blah'),
+      testCase('whatever.i18n.tsx.does.not.matter'),
+      testCase('whatever(i18n.ts.foo.bar)'),
+      testCaseVue('<template><p>{{ i18n.ts.foo.bar }}</p></template>'),
+      testCaseVue('<template><I18n :src="i18n.ts.foo.baz"/></template>'),
+      // we don't detect the problem here, but should still accept it
+      testCase('i18n.ts.foo["something"]'),
+      testCase('i18n.ts.foo[something]'),
+    ],
+    invalid: [
+      testCase('i18n.ts.not', 1),
+      testCase('i18n.tsx.deep.not', 1),
+      testCase('i18n.tsx.deep.not({x:12})', 1),
+      testCase('i18n.tsx.top({x:1})', 1),
+      testCase('i18n.ts.foo.baz', 1),
+      testCase('i18n.tsx.foo.baz', 1),
+      testCase('i18n.tsx.foo.baz({y:2})', 2),
+      testCaseVue('<template><p>{{ i18n.ts.not }}</p></template>', 1),
+      testCaseVue('<template><I18n :src="i18n.ts.not"/></template>', 1),
+    ],
+  },
+);
diff --git a/idea/MkDisableSection.vue b/idea/MkDisableSection.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d177886569a209174e9272e77717a768e4f21de8
--- /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(--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 0000000000000000000000000000000000000000..f64d16800a98502e377f9d9085146942f7a87d29
--- /dev/null
+++ b/idea/README.md
@@ -0,0 +1 @@
+使われなくなったけど消すのは勿体ない(将来使えるかもしれない)コードを入れておくとこ
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index b6bfbfa68281d05b84fc19ae1a3371a722b37eb8..de24ad4bb96317d3b962d56adb79cbce68f416b3 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 6fb51ea5d8606930dbb07c4bce80c4b1e83b86df..0e761b074393c75ba5c78599473442cad673d7e7 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 0b2acf8f05e75926e6cc9a7be93a8d9901856809..b9f3fecc76f9618b4b9301df593f83aa82b117d5 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -8,6 +8,8 @@ search: "Cercar"
 notifications: "Notificacions"
 username: "Nom d'usuari"
 password: "Contrasenya"
+initialPasswordForSetup: "Contrasenya inicial per la configuració inicial"
+initialPasswordIsIncorrect: "La contrasenya no és correcta."
 forgotPassword: "Contrasenya oblidada"
 fetchingAsApObject: "Cercant en el Fediverse..."
 ok: "OK"
@@ -60,6 +62,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 +111,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 +157,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 +184,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 +198,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 +214,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 +236,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 +330,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 +338,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 +453,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 +488,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 +515,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"
@@ -540,10 +565,10 @@ objectStorageRegion: "Regió "
 objectStorageRegionDesc: "Especifica una regió com 'xx-east-1'. Si el teu servei no diferència regions has de posar 'us-east-1'. Deixa'l buit si fas servir variables d'entorn o un arxiu de configuració d'AWS."
 objectStorageUseSSL: "Fes servir SSL"
 objectStorageUseSSLDesc: "Desactiva'l si no tens pensat fer servir HTTPS per les connexions de l'API"
-objectStorageUseProxy: "Connectar-se mitjançant un Proxy"
+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 +601,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 +719,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 +856,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 +921,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 "
@@ -1021,6 +1047,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 "
@@ -1094,6 +1121,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 +1133,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ó "
@@ -1146,7 +1178,7 @@ currentAnnouncements: "Informes actuals"
 pastAnnouncements: "Informes passats"
 youHaveUnreadAnnouncements: "Tens informes per llegir."
 useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
-replies: "Respostes"
+replies: "Respondre"
 renotes: "Impulsa"
 loadReplies: "Mostrar les respostes"
 loadConversation: "Mostrar la conversació "
@@ -1172,7 +1204,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 +1246,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 +1260,60 @@ 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>"
+_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."
@@ -1281,10 +1365,10 @@ _initialTutorial:
   _reaction:
     title: "Què són les Reaccions?"
     description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada."
-    letsTryReacting: "Es poden afegir reaccions fent clic al botó '{reaction}'. Prova reaccionant a aquesta nota!"
+    letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
     reactToContinue: "Afegeix una reacció per continuar."
     reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
-    reactDone: "Pots desfer una reacció fent clic al botó '{undo}'."
+    reactDone: "Pots desfer una reacció fent clic al botó '-'."
   _timeline:
     title: "El concepte de les línies de temps"
     description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)"
@@ -1344,6 +1428,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 +1739,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 +1748,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 +1763,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"
@@ -1739,12 +1840,13 @@ _email:
   _follow:
     title: "t'ha seguit"
   _receiveFollowRequest:
-    title: "Has rebut una sol·licitud de seguiment"
+    title: "Has rebut una sol·licitud  de seguiment"
 _plugin:
   install: "Instal·lar un afegit "
   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 +1876,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 +1985,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 +2004,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 +2057,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 +2131,73 @@ _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ó"
+  denied: "Accés denegat"
+  pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació."
 _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 +2261,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 +2356,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 +2367,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 +2398,54 @@ _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 {n}"
   _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 +2456,82 @@ _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"
+  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 +2541,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 +2577,54 @@ _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 "
 _reversi:
   total: "Total"
+_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."
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 0983e05ad9fd56c92bae9d771ecc2f56ab72477b..caf6d6e16349d2a77aa5955797ac75732d2a6c4b 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"
@@ -1093,7 +1089,7 @@ doYouAgree: "Souhlasíte?"
 beSureToReadThisAsItIsImportant: "Přečtěte si prosím tyto důležité informace."
 iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním."
 icon: "Avatar"
-replies: "Odpovědi"
+replies: "Odpovědět"
 renotes: "Přeposlat"
 sourceCode: "Zdrojový kód"
 flip: "Otočit"
@@ -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 8c7d372c3f420672ff33e39c01ed7cb4e9bab14d..4e2bd069340a7d161f29ed24629df01eb0493a70 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1,8 +1,8 @@
 ---
 _lang_: "Deutsch"
 headlineMisskey: "Ein durch Notizen verbundenes Netzwerk"
-introMisskey: "Willkommen! Sharkey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
-poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Sharkey</b> betriebenen Dienste die auf Misskey basiert ist (meist als \"Misskey-Instanz\" bezeichnet)."
+introMisskey: "Willkommen! Misskey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
+poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Misskey</b> betriebenen Dienste."
 monthAndDay: "{day}.{month}."
 search: "Suchen"
 notifications: "Benachrichtigungen"
@@ -15,7 +15,7 @@ gotIt: "Verstanden!"
 cancel: "Abbrechen"
 noThankYou: "Nein, danke"
 enterUsername: "Benutzername eingeben"
-renotedBy: "Geboostet von {user}"
+renotedBy: "Renote von {user}"
 noNotes: "Keine Notizen gefunden"
 noNotifications: "Keine Benachrichtigungen gefunden"
 instance: "Instanz"
@@ -105,13 +105,13 @@ followRequests: "Follow-Anfragen"
 unfollow: "Entfolgen"
 followRequestPending: "Follow-Anfrage ausstehend"
 enterEmoji: "Gib ein Emoji ein"
-renote: "Boost"
-unrenote: "Boost zurücknehmen"
-renoted: "Boost getätigt."
-cantRenote: "Boosten dieses Beitrags nicht möglich."
-cantReRenote: "Boosten von einen Boost nicht möglich."
+renote: "Renote"
+unrenote: "Renote zurücknehmen"
+renoted: "Renote getätigt."
+cantRenote: "Renote dieses Beitrags nicht möglich."
+cantReRenote: "Renote einer Renote nicht möglich."
 quote: "Zitieren"
-inChannelRenote: "Kanal-interner Boost"
+inChannelRenote: "Kanal-interner Renote"
 inChannelQuote: "Kanal-internes Zitat"
 pinnedNote: "Angeheftete Notiz"
 pinned: "Angeheftet"
@@ -166,7 +166,7 @@ youCanCleanRemoteFilesCache: "Klicke auf den 🗑️-Knopf der Dateiverwaltungsa
 cacheRemoteSensitiveFiles: "Sensitive Dateien von fremden Instanzen im Cache speichern"
 cacheRemoteSensitiveFilesDescription: "Ist diese Einstellung deaktiviert, so werden sensitive Dateien fremder Instanzen direkt von dort ohne Zwischenspeicherung geladen."
 flagAsBot: "Als Bot markieren"
-flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Sharkeys interne Systeme dieses Benutzerkonto als Bot behandeln."
+flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Misskeys interne Systeme dieses Benutzerkonto als Bot behandeln."
 flagAsCat: "Als Katze markieren"
 flagAsCatDescription: "Aktiviere diese Option, um dieses Benutzerkonto als Katze zu markieren."
 flagShowTimelineReplies: "Antworten in der Chronik anzeigen"
@@ -229,7 +229,7 @@ noUsers: "Keine Benutzer gefunden"
 editProfile: "Profil bearbeiten"
 noteDeleteConfirm: "Möchtest du diese Notiz wirklich löschen?"
 pinLimitExceeded: "Du kannst nicht noch mehr Notizen anheften."
-intro: "Sharkey ist installiert! Lass uns nun ein Administratorkonto einrichten."
+intro: "Misskey ist installiert! Lass uns nun ein Administratorkonto einrichten."
 done: "Fertig"
 processing: "In Bearbeitung …"
 preview: "Vorschau"
@@ -422,7 +422,7 @@ exploreFediverse: "Das Fediverse erkunden"
 popularTags: "Beliebte Schlagwörter"
 userList: "Liste"
 about: "Ãœber"
-aboutMisskey: "Ãœber Sharkey"
+aboutMisskey: "Ãœber Misskey"
 administrator: "Administrator"
 token: "Token"
 2fa: "Zwei-Faktor-Authentifizierung"
@@ -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"
@@ -572,7 +571,7 @@ sort: "Sortieren"
 ascendingOrder: "Aufsteigende Reihenfolge"
 descendingOrder: "Absteigende Reihenfolge"
 scratchpad: "Testumgebung"
-scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Sharkey überprüfen."
+scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen."
 output: "Ausgabe"
 script: "Skript"
 disablePagesScript: "AiScript auf Seiten deaktivieren"
@@ -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"
@@ -709,14 +705,14 @@ unclip: "Aus Clip entfernen"
 confirmToUnclipAlreadyClippedNote: "Diese Notiz ist bereits im \"{name}\" Clip enthalten. Möchtest du sie aus diesem Clip entfernen?"
 public: "Öffentlich"
 private: "Privat"
-i18nInfo: "Sharkey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen."
+i18nInfo: "Misskey wird durch freiwillige Helfer in viele verschiedene Sprachen übersetzt. Auf {link} kannst du mithelfen."
 manageAccessTokens: "Zugriffstokens verwalten"
 accountInfo: "Benutzerkonto-Informationen"
 notesCount: "Anzahl der Notizen"
 repliesCount: "Anzahl gesendeter Antworten"
 renotesCount: "Anzahl getätigter Renotes"
 repliedCount: "Anzahl erhaltener Antworten"
-renotedCount: "Anzahl erhaltener Boosts"
+renotedCount: "Anzahl erhaltener Renotes"
 followingCount: "Anzahl gefolgter Benutzer"
 followersCount: "Anzahl an Followern"
 sentReactionsCount: "Anzahl gesendeter Reaktionen"
@@ -763,7 +759,7 @@ onlineUsersCount: "{n} Benutzer sind online"
 nUsers: "{n} Benutzer"
 nNotes: "{n} Notizen"
 sendErrorReports: "Fehlerberichte senden"
-sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Sharkey weitergegeben, was zur Verbesserung der Qualität von Sharkey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Sharkey."
+sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Misskey weitergegeben, was zur Verbesserung der Qualität von Misskey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Misskey."
 myTheme: "Mein Farbschema"
 backgroundColor: "Hintergrundfarbe"
 accentColor: "Akzentfarbe"
@@ -857,7 +853,7 @@ hashtags: "Hashtags"
 troubleshooting: "Problembehandlung"
 useBlurEffect: "Weichzeichnungseffekt in der Benutzeroberfläche verwenden"
 learnMore: "Mehr erfahren"
-misskeyUpdated: "Sharkey wurde aktualisiert!"
+misskeyUpdated: "Misskey wurde aktualisiert!"
 whatIsNew: "Änderungen anzeigen"
 translate: "Ãœbersetzen"
 translatedFrom: "Aus {x} übersetzt"
@@ -984,9 +980,8 @@ numberOfLikes: "\"Gefällt mir\"-Anzahl"
 show: "Anzeigen"
 neverShow: "Nicht wieder anzeigen"
 remindMeLater: "Vielleicht später"
-didYouLikeMisskey: "Gefällt dir Sharkey?"
-pleaseDonate: "Sharkey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
-pleaseDonateInstance: "Du kannst {host} auch direkt unterstützen, indem du an deine Instanz Administration spendest."
+didYouLikeMisskey: "Gefällt dir Misskey?"
+pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
 roles: "Rollen"
 role: "Rolle"
 noRole: "Rolle nicht gefunden"
@@ -1006,7 +1001,7 @@ permissionDeniedError: "Aktion verweigert"
 permissionDeniedErrorDescription: "Dieses Benutzerkonto besitzt nicht die Berechtigung, um diese Aktion auszuführen."
 preset: "Vorlage"
 selectFromPresets: "Aus Vorlagen wählen"
-achievements: "Erfolge"
+achievements: "Errungenschaften"
 gotInvalidResponseError: "Ungültige Antwort des Servers"
 gotInvalidResponseErrorDescription: "Eventuell ist der Server momentan nicht erreichbar oder untergeht Wartungsarbeiten. Bitte versuche es später noch einmal."
 thisPostMayBeAnnoying: "Dieser Beitrag stört eventuell andere Benutzer."
@@ -1101,7 +1096,7 @@ rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Diese Rollen müssen öffe
 cancelReactionConfirm: "Möchtest du deine Reaktion wirklich löschen?"
 changeReactionConfirm: "Möchtest du deine Reaktion wirklich ändern?"
 later: "Später"
-goToMisskey: "Zu Sharkey"
+goToMisskey: "Zu Misskey"
 additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher"
 installed: "Installiert"
 branding: "Branding"
@@ -1166,8 +1161,6 @@ impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung,
 privacyPolicy: "Datenschutzerklärung"
 privacyPolicyUrl: "Datenschutzerklärungs-URL"
 tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung"
-donation: "Spenden"
-donationUrl: "Spenden-URL"
 avatarDecorations: "Profilbilddekoration"
 attach: "Anbringen"
 detach: "Entfernen"
@@ -1271,7 +1264,7 @@ _accountMigration:
   moveTo: "Dieses Konto zu einem neuen migrieren"
   moveToLabel: "Umzugsziel:"
   moveCannotBeUndone: "Die Migration eines Benutzerkontos ist unwiderruflich."
-  moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Sharkey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon,  kann hiervon abweichen.)"
+  moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Misskey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon,  kann hiervon abweichen.)"
   moveAccountHowTo: "Um ein Konto zu migrieren, erstelle zuerst auf dem Umzugsziel einen Alias für dieses Konto.\nGib dann das Umzugsziel in folgendem Format ein: @username@server.example.com"
   startMigration: "Migrieren"
   migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden."
@@ -1282,26 +1275,26 @@ _achievements:
   earnedAt: "Freigeschaltet am"
   _types:
     _notes1:
-      title: "Hallo Sharkey!"
+      title: "Hallo Misskey!"
       description: "Sende deine erste Notiz"
-      flavor: "Hab eine schöne Zeit mit Sharkey!"
+      flavor: "Hab eine schöne Zeit mit Misskey!"
     _notes10:
-      title: "Notizblog"
+      title: "Ein paar Notizen"
       description: "10 Notizen gesendet"
     _notes100:
-      title: "Schreiberling"
+      title: "Viele Notizen"
       description: "100 Notizen gesendet"
     _notes500:
-      title: "Autor"
+      title: "Überschüttet mit Notizen"
       description: "500 Notizen gesendet"
     _notes1000:
-      title: "Bestseller"
+      title: "Berg an Notizen"
       description: "1.000 Notizen gesendet"
     _notes5000:
-      title: "Dicker Wälzer"
+      title: "Ãœberquellende Notizen"
       description: "5.000 Notizen gesendet"
     _notes10000:
-      title: "Verlagshaus"
+      title: "Supernotiz"
       description: "10.000 Notizen gesendet"
     _notes20000:
       title: "Brauche... mehr... Notizen..."
@@ -1328,28 +1321,28 @@ _achievements:
       title: "Notizversum"
       description: "90.000 Notizen gesendet"
     _notes100000:
-      title: "ALL YOUR NOTES ARE BELONG TO US"
+      title: "ALL YOUR NOTE ARE BELONG TO US"
       description: "100.000 Notizen gesendet"
       flavor: "Du hast wirklich viel zu sagen."
     _login3:
-      title: "Neuling â… "
+      title: "Anfänger Ⅰ"
       description: "An 3 Tagen eingeloggt"
       flavor: "Nenn' mich ab heute Misskist"
     _login7:
-      title: "Neuling â…¡"
+      title: "Anfänger Ⅱ"
       description: "An 7 Tagen eingeloggt"
       flavor: "Na, eingewöht?"
     _login15:
-      title: "Neuling â…¢"
+      title: "Anfänger Ⅲ"
       description: "An 15 Tagen eingeloggt"
     _login30:
-      title: "Fedizen â… "
+      title: "Misskist â… "
       description: "An 30 Tagen eingeloggt"
     _login60:
-      title: "Fedizen â…¡"
+      title: "Misskist â…¡"
       description: "An 60 Tagen eingeloggt"
     _login100:
-      title: "Fedizen â…¢"
+      title: "Misskist â…¢"
       description: "An 100 Tagen eingeloggt"
       flavor: "Violent Misskist"
     _login200:
@@ -1362,14 +1355,14 @@ _achievements:
       title: "Stammbesucher â…¢"
       description: "An 400 Tagen eingeloggt"
     _login500:
-      title: "Alter Hase â… "
+      title: "Veteran â… "
       description: "An 500 Tagen eingeloggt"
       flavor: "Meine Kameraden, ich liebe sie, die Notizen."
     _login600:
-      title: "Alter Hase â…¡"
+      title: "Veteran â…¡"
       description: "An 600 Tagen eingeloggt"
     _login700:
-      title: "Alter Hase â…¢"
+      title: "Veteran â…¢"
       description: "An 700 Tagen eingeloggt"
     _login800:
       title: "Meister der Notizen â… "
@@ -1378,39 +1371,39 @@ _achievements:
       title: "Meister der Notizen â…¡"
       description: "An 900 Tagen eingeloggt"
     _login1000:
-      title: "Wie die Zeit vergeht"
+      title: "Meister der Notizen â…¢"
       description: "An 1000 Tagen eingeloggt"
-      flavor: "Danke, dass du Sharkey nutzt!"
+      flavor: "Danke, dass du Misskey nutzt!"
     _noteClipped1:
-      title: "Das merk ich mir"
+      title: "Muss... clippen..."
       description: "Die erste Notiz geclippt"
     _noteFavorited1:
       title: "Sternengucker"
       description: "Eine Notiz als Favorit markiert"
     _myNoteFavorited1:
-      title: "Hilfreich"
+      title: "Sternensucher"
       description: "Ein anderer Benutzer hat eine deiner Notizen als Favoriten markiert"
     _profileFilled:
       title: "Perfekte Vorbereitung"
       description: "Fülle dein Profil aus"
     _markedAsCat:
-      title: "Das Königreich der Katzen"
-      description: "Markiere dein Profil als Katze"
-      flavor: "Einen Namen bekommst du später~ "
+      title: "Ich der Kater"
+      description: "Markiere dein Konto als Katze"
+      flavor: "Einen Namen bekommst du später. "
     _following1:
-      title: "Immer auf dem neusten Stand"
+      title: "Das Folgen beginnt"
       description: "Du folgst deiner ersten Person"
     _following10:
       title: "Folge ihnen... folge ihnen..."
       description: "Du folgst über 10 Leuten"
     _following50:
-      title: "Lieblingsposter"
+      title: "Viele Freunde"
       description: "Du folgst über 50 Leuten"
     _following100:
-      title: "Die Top 100"
+      title: "100 Freunde"
       description: "Du folgst über 100 Leuten"
     _following300:
-      title: "Folgen, folgen, folgen!"
+      title: "Freundeüberschuss"
       description: "Du folgst über 300 Leuten"
     _followers1:
       title: "Der erste Follower"
@@ -1425,7 +1418,7 @@ _achievements:
       title: "Beliebt"
       description: "Die Anzahl deiner Follower hat 100 überschritten"
     _followers300:
-      title: "Teil des Schiffs, Teil der Crew"
+      title: "Eine geordnete Reihe, bitte!"
       description: "Die Anzahl deiner Follower hat 300 überschritten"
     _followers500:
       title: "Funkmast"
@@ -1434,31 +1427,31 @@ _achievements:
       title: "Influencer"
       description: "Die Anzahl deiner Follower hat 1000 überschritten"
     _collectAchievements30:
-      title: "Sammler der Erfolge"
+      title: "Sammler der Errungenschaften"
       description: "Schalte 30 Errungenschaften frei"
     _viewAchievements3min:
-      title: "Ausstellung"
+      title: "Fan von Errungenschaften"
       description: "Schau dir die Liste deiner Errungenschaften für mindestens 3 Minuten an"
     _iLoveMisskey:
-      title: "I Love Sharkey"
-      description: "Sende \"I ❤ #Sharkey\""
-      flavor: "Danke, dass du Sharkey verwendest! - vom Entwicklerteam"
+      title: "I Love Misskey"
+      description: "Sende \"I ❤ #Misskey\""
+      flavor: "Danke, dass du Misskey verwendest! - vom Entwicklerteam"
     _foundTreasure:
       title: "Schatzsuche"
       description: "Du hast einen verborgenen Schatz gefunden"
     _client30min:
       title: "Kurze Pause"
-      description: "Habe Sharkey für mindestens 30 Minuten geöffnet"
+      description: "Habe Misskey für mindestens 30 Minuten geöffnet"
     _client60min:
-      title: "Munter mit Sharkey"
-      description: "Habe Sharkey für mindestens 60 Minuten geöffnet"
+      title: "Munter mit Misskey"
+      description: "Habe Misskey für mindestens 60 Minuten geöffnet"
     _noteDeletedWithin1min:
-      title: "Katze auf der Tastatur"
+      title: "Ups"
       description: "Lösche eine Notiz innerhalb von 1 Minute nachdem sie gesendet wurde"
     _postedAtLateNight:
       title: "Nachtaktiv"
       description: "Sende mitten in der Nacht eine Notiz"
-      flavor: "Echte Katzen sind nunmal nachtaktiv~"
+      flavor: "Geh bald schlafen."
     _postedAt0min0sec:
       title: "Zeitansage"
       description: "Sende um 00:00 eine Notiz"
@@ -1473,7 +1466,7 @@ _achievements:
       title: "Analyst"
       description: "Schau dir die Messwerte der Instanz an"
     _outputHelloWorldOnScratchpad:
-      title: "Junghacker"
+      title: "Hallo Welt!"
       description: "Gib \"hello world\" in der Testumgebung aus"
     _open3windows:
       title: "Splitscreen"
@@ -1482,25 +1475,25 @@ _achievements:
       title: "Zyklischer Verweis"
       description: "Versuche, in Drive einen Zirkelbezug von Ordnern herzustellen"
     _reactWithoutRead:
-      title: "Erst reagieren, dann lesen"
+      title: "Hast du das wirklich gelesen?"
       description: "Reagiere auf eine Notiz mit mindestens 100 Zeichen innerhalb von 3 Sekunden der Erstellung der Notiz"
     _clickedClickHere:
       title: "Klicke hier"
       description: "Du hast hier geklickt"
     _justPlainLucky:
-      title: "Glück 100"
+      title: "Pures Glück"
       description: "Kann alle 10 Sekunden mit einer Warscheinlichkeit von 0.005% erhalten werden"
     _setNameToSyuilo:
       title: "Gottkomplex"
       description: "Setze deinen Namen auf \"syuilo\""
     _passedSinceAccountCreated1:
-      title: "Davon erzähle ich meinen Enkeln"
+      title: "Einjahresjubiläum"
       description: "Seit der Erstellung deines Kontos ist 1 Jahr vergangen"
     _passedSinceAccountCreated2:
-      title: "Diese Software hat mich zur Katze gemacht"
+      title: "Zweijahresjubiläum"
       description: "Seit der Erstellung deines Kontos sind 2 Jahre vergangen"
     _passedSinceAccountCreated3:
-      title: "Goldene Zeit"
+      title: "Dreijahresjubiläum"
       description: "Seit der Erstellung deines Kontos sind 3 Jahre vergangen"
     _loggedInOnBirthday:
       title: "Alles Gute Zum Geburtstag"
@@ -1508,15 +1501,15 @@ _achievements:
     _loggedInOnNewYearsDay:
       title: "Frohes Neujahr"
       description: "Logge dich am Neujahrstag ein"
-      flavor: "Auf ein weiteres tolles Jahr in diesem Universum!"
+      flavor: "Auf ein weiteres tolles Jahr in dieser Instanz"
     _cookieClicked:
-      title: "Kreise klicken für Omas"
+      title: "Ein Spiel, in dem du auf einen Keks klickst"
       description: "Den Keks geklickt"
       flavor: "Bist du hier richtig?"
     _brainDiver:
       title: "Brain Diver"
       description: "Sende den Link zu Brain Diver"
-      flavor: "Sharkey-Sharkey La-Tu-Ma"
+      flavor: "Misskey-Misskey La-Tu-Ma"
     _smashTestNotificationButton:
       title: "Testüberfluss"
       description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
@@ -1681,12 +1674,12 @@ _registry:
   domain: "Domain"
   createKey: "Schlüssel erstellen"
 _aboutMisskey:
-  about: "Sharkey ist Open-Source-Software basiert auf Misskey welche von syuilo seit 2014 entwickelt wird."
+  about: "Misskey ist Open-Source-Software, welche von syuilo seit 2014 entwickelt wird."
   contributors: "Hauptmitwirkende"
   allContributors: "Alle Mitwirkenden"
   source: "Quellcode"
-  translation: "Sharkey übersetzen"
-  donate: "An Sharkey spenden"
+  translation: "Misskey übersetzen"
+  donate: "An Misskey spenden"
   morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰"
   patrons: "UnterstützerInnen"
   projectMembers: "Projektmitglieder"
@@ -1791,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"
@@ -2116,7 +2108,7 @@ _notification:
   youGotMention: "{name} hat dich erwähnt"
   youGotReply: "{name} hat dir geantwortet"
   youGotQuote: "{name} hat dich zitiert"
-  youRenoted: "Boost deiner Notiz von {name}"
+  youRenoted: "Renote deiner Notiz von {name}"
   youWereFollowed: "ist dir gefolgt"
   youReceivedFollowRequest: "Du hast eine Follow-Anfrage erhalten"
   yourFollowRequestAccepted: "Deine Follow-Anfrage wurde akzeptiert"
@@ -2145,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 5eca348e186f883ff54dcaf9e69f8a5465696aa5..4657842ca5bf73ceffa4a13e51b2a36572bc189e 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 6d24bb5b41de15df1cede6ec5ef4348270d0d474..6ea7fb4f8ddefe2f67e90d1763f610dec830f1aa 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1,13 +1,16 @@
 ---
 _lang_: "English"
 headlineMisskey: "A network connected by notes"
-introMisskey: "Welcome! Sharkey is an open source, decentralized microblogging service.\nCreate \"notes\" to share your thoughts with everyone around you. 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes. 👍\nLet's explore a new world! 🚀"
-poweredByMisskeyDescription: "{name} is one of the services powered by the open source platform <b>Sharkey</b> which is based on Misskey (referred to as a \"Misskey instance\")."
+introMisskey: "Welcome! Misskey is an open source, decentralized microblogging service.\nCreate \"notes\" to share your thoughts with everyone around you. 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes. 👍\nLet's explore a new world! 🚀"
+poweredByMisskeyDescription: "{name} is one of the services powered by the open source platform <b>Misskey</b> (referred to as a \"Misskey instance\")."
 monthAndDay: "{month}/{day}"
 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"
@@ -15,7 +18,7 @@ gotIt: "Got it!"
 cancel: "Cancel"
 noThankYou: "Not now"
 enterUsername: "Enter username"
-renotedBy: "Boosted by {user}"
+renotedBy: "Renoted by {user}"
 noNotes: "No notes"
 noNotifications: "No notifications"
 instance: "Instance"
@@ -34,7 +37,6 @@ signup: "Sign Up"
 uploading: "Uploading..."
 save: "Save"
 users: "Users"
-approvals: "Approvals"
 addUser: "Add a user"
 favorite: "Add to favorites"
 favorites: "Favorites"
@@ -46,16 +48,15 @@ pin: "Pin to profile"
 unpin: "Unpin from profile"
 copyContent: "Copy contents"
 copyLink: "Copy link"
-copyLinkRenote: "Copy boost link"
+copyLinkRenote: "Copy renote link"
 delete: "Delete"
 deleteAndEdit: "Delete and edit"
-deleteAndEditConfirm: "Are you sure you want to redraft this note? This means you will lose all reactions, boosts, and replies to it."
+deleteAndEditConfirm: "Are you sure you want to redraft this note? This means you will lose all reactions, renotes, and replies to it."
 addToList: "Add to list"
 addToAntenna: "Add to antenna"
 sendMessage: "Send a message"
 copyRSS: "Copy RSS"
 copyUsername: "Copy username"
-openRemoteProfile: "Open remote profile"
 copyUserId: "Copy user ID"
 copyNoteId: "Copy note ID"
 copyFileId: "Copy file ID"
@@ -108,16 +109,14 @@ followRequests: "Follow requests"
 unfollow: "Unfollow"
 followRequestPending: "Follow request pending"
 enterEmoji: "Enter an emoji"
-renote: "Boost"
-unrenote: "Remove boost"
-renoted: "Boosted."
-quoted: "Quoted."
-rmboost: "Unboosted."
-renotedToX: "Boosted to {name}"
-cantRenote: "This post can't be boosted."
-cantReRenote: "A boost can't be boosted."
+renote: "Renote"
+unrenote: "Remove renote"
+renoted: "Renoted."
+renotedToX: "Renoted to {name}."
+cantRenote: "This post can't be renoted."
+cantReRenote: "A renote can't be renoted."
 quote: "Quote"
-inChannelRenote: "Channel-only Boost"
+inChannelRenote: "Channel-only Renote"
 inChannelQuote: "Channel-only Quote"
 renoteToChannel: "Renote to channel"
 renoteToOtherChannel: "Renote to other channel"
@@ -144,19 +143,15 @@ unmarkAsSensitive: "Unmark as sensitive"
 enterFileName: "Enter filename"
 mute: "Mute"
 unmute: "Unmute"
-renoteMute: "Mute Boosts"
-renoteUnmute: "Unmute Boosts"
+renoteMute: "Mute Renotes"
+renoteUnmute: "Unmute Renotes"
 block: "Block"
 unblock: "Unblock"
-markAsNSFW: "Mark all media from user as NSFW"
 suspend: "Suspend"
 unsuspend: "Unsuspend"
 blockConfirm: "Are you sure that you want to block this account?"
 unblockConfirm: "Are you sure that you want to unblock this account?"
-nsfwConfirm: "Are you sure that you want to mark all media from this account as NSFW?"
-unNsfwConfirm: "Are you sure that you want to unmark all media from this account as NSFW?"
 suspendConfirm: "Are you sure that you want to suspend this account?"
-approveConfirm: "Are you sure that you want to approve this account?"
 unsuspendConfirm: "Are you sure that you want to unsuspend this account?"
 selectList: "Select a list"
 editList: "Edit list"
@@ -180,11 +175,9 @@ youCanCleanRemoteFilesCache: "You can clear the cache by clicking the 🗑️ bu
 cacheRemoteSensitiveFiles: "Cache sensitive remote files"
 cacheRemoteSensitiveFilesDescription: "When this setting is disabled, sensitive remote files are loaded directly from the remote instance without caching."
 flagAsBot: "Mark this account as a bot"
-flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Sharkey's internal systems to treat this account as a bot."
+flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Misskey's internal systems to treat this account as a bot."
 flagAsCat: "Mark this account as a cat"
 flagAsCatDescription: "Enable this option to mark this account as a cat."
-flagSpeakAsCat: "Speak as a cat"
-flagSpeakAsCatDescription: "Your posts will get nyanified when in cat mode."
 flagShowTimelineReplies: "Show replies in timeline"
 flagShowTimelineRepliesDescription: "Shows replies of users to notes of other users in the timeline if turned on."
 autoAcceptFollowed: "Automatically approve follow requests from users you're following"
@@ -192,8 +185,8 @@ addAccount: "Add account"
 reloadAccountsList: "Reload account list"
 loginFailed: "Failed to sign in"
 showOnRemote: "View on remote instance"
-continueOnRemote: "Continue on remote instance"
-chooseServerOnMisskeyHub: "Choose a instance from Misskey Hub"
+continueOnRemote: "Continue on a remote server"
+chooseServerOnMisskeyHub: "Choose a server from the Misskey Hub"
 specifyServerHost: "Specify a server host directly"
 inputHostName: "Enter the domain"
 general: "General"
@@ -222,7 +215,7 @@ perDay: "Per Day"
 stopActivityDelivery: "Stop sending activities"
 blockThisInstance: "Block this instance"
 silenceThisInstance: "Silence this instance"
-mediaSilenceThisInstance: "Silence media from this instance"
+mediaSilenceThisInstance: "Media-silence this server"
 operations: "Operations"
 software: "Software"
 version: "Version"
@@ -243,9 +236,11 @@ clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote
 blockedInstances: "Blocked Instances"
 blockedInstancesDescription: "List the hostnames of the instances you want to block separated by linebreaks. Listed instances will no longer be able to communicate with this instance."
 silencedInstances: "Silenced instances"
-silencedInstancesDescription: "List the host names of the instances that you want to silence, separated by a new line. All accounts belonging to the listed instances 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 instances."
-mediaSilencedInstances: "Media-silenced instances"
-mediaSilencedInstancesDescription: "List the host names of the instances that you want to media-silence, separated by a new line. All accounts belonging to the listed instances will be treated as sensitive, and can't use custom emojis. This will not affect the blocked 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"
@@ -253,7 +248,7 @@ noUsers: "There are no users"
 editProfile: "Edit profile"
 noteDeleteConfirm: "Are you sure you want to delete this note?"
 pinLimitExceeded: "You cannot pin any more notes"
-intro: "Installation of Sharkey has been finished! Please create an admin user."
+intro: "Installation of Misskey has been finished! Please create an admin user."
 done: "Done"
 processing: "Processing..."
 preview: "Preview"
@@ -331,13 +326,12 @@ lightThemes: "Light themes"
 darkThemes: "Dark themes"
 syncDeviceDarkMode: "Sync Dark Mode with your device settings"
 drive: "Drive"
-driveSearchbarPlaceholder: "Search drive"
 fileName: "Filename"
 selectFile: "Select a file"
 selectFiles: "Select files"
 selectFolder: "Select a folder"
 selectFolders: "Select folders"
-fileNotSelected: "No file selected"
+fileNotSelected: ""
 renameFile: "Rename file"
 folderName: "Folder name"
 createFolder: "Create a folder"
@@ -345,6 +339,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"
@@ -357,7 +352,6 @@ copyUrl: "Copy URL"
 rename: "Rename"
 avatar: "Avatar"
 banner: "Banner"
-background: "Background"
 displayOfSensitiveMedia: "Display of sensitive media"
 whenServerDisconnected: "When losing connection to the server"
 disconnectedFromServer: "Connection to server has been lost"
@@ -431,7 +425,7 @@ antennaKeywordsDescription: "Separate with spaces for an AND condition or with l
 notifyAntenna: "Notify about new notes"
 withFileAntenna: "Only notes with files"
 enableServiceworker: "Enable Push-Notifications for your Browser"
-antennaUsersDescription: "List one username per line. Use \"*@instance.com\" to specify all users of an instance"
+antennaUsersDescription: "List one username per line"
 caseSensitive: "Case sensitive"
 withReplies: "Include replies"
 connectedTo: "Following account(s) are connected"
@@ -450,7 +444,7 @@ exploreFediverse: "Explore the Fediverse"
 popularTags: "Popular tags"
 userList: "Lists"
 about: "About"
-aboutMisskey: "About Sharkey"
+aboutMisskey: "About Misskey"
 administrator: "Administrator"
 token: "Token"
 2fa: "Two-factor authentication"
@@ -460,6 +454,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"
@@ -492,16 +487,14 @@ enable: "Enable"
 next: "Next"
 retype: "Enter again"
 noteOf: "Note by {user}"
-expandAllCws: "Show content for all replies"
-collapseAllCws: "Hide content for all replies"
 quoteAttached: "Quote"
 quoteQuestion: "Append as quote?"
-attachAsFileQuestion: "The text in clipboard is long. Would you like to attach it as a text file?"
+attachAsFileQuestion: "The text in clipboard is long. Would you want to attach it as text file?"
 noMessagesYet: "No messages yet"
 newMessageExists: "There are new messages"
 onlyOneFileCanBeAttached: "You can only attach one file to a message"
 signinRequired: "Please register or sign in before continuing"
-signinOrContinueOnRemote: "To continue, you need to go to your instance to perform this action or sign up / log in to the instance you are trying to interact with."
+signinOrContinueOnRemote: "To continue, you need to move your server or sign up / log in to this server."
 invitations: "Invites"
 invitationCode: "Invitation code"
 checking: "Checking..."
@@ -523,9 +516,12 @@ 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: "Show the number of reactions in notes"
+showReactionsCount: "See the number of reactions in notes"
 noHistory: "No history available"
 signinHistory: "Login history"
 enableAdvancedMfm: "Enable advanced MFM"
@@ -538,12 +534,10 @@ createAccount: "Create account"
 existingAccount: "Existing account"
 regenerate: "Regenerate"
 fontSize: "Font size"
-cornerRadius: "Corner roundness"
 mediaListWithOneImageAppearance: "Height of media lists with one image only"
 limitTo: "Limit to {x}"
 noFollowRequests: "You don't have any pending follow requests"
 openImageInNewTab: "Open images in new tab"
-warnForMissingAltText: "Warn you when you forget to put alt text"
 dashboard: "Dashboard"
 local: "Local"
 remote: "Remote"
@@ -576,8 +570,6 @@ objectStorageUseProxy: "Connect over Proxy"
 objectStorageUseProxyDesc: "Turn this off if you are not going to use a Proxy for API connections"
 objectStorageSetPublicRead: "Set \"public-read\" on upload"
 s3ForcePathStyleDesc: "If s3ForcePathStyle is enabled, the bucket name has to included in the path of the URL as opposed to the hostname of the URL. You may need to enable this setting when using services such as a self-hosted Minio instance."
-deeplFreeMode: "Use DeepLX-JS (No Auth Key)"
-deeplFreeModeDescription: "Need Help? Check our documentation to know how to setup DeepLX-JS."
 serverLogs: "Server logs"
 deleteAll: "Delete all"
 showFixedPostForm: "Display the posting form at the top of the timeline"
@@ -593,7 +585,7 @@ popout: "Pop-out"
 volume: "Volume"
 masterVolume: "Master volume"
 notUseSound: "Disable sound"
-useSoundOnlyWhenActive: "Output sounds only if Sharkey is active."
+useSoundOnlyWhenActive: "Output sounds only if Misskey is active."
 details: "Details"
 chooseEmoji: "Select an emoji"
 unableToProcess: "The operation could not be completed"
@@ -609,7 +601,9 @@ sort: "Sorting order"
 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 Sharkey in it."
+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"
@@ -707,11 +701,6 @@ channel: "Channels"
 create: "Create"
 notificationSetting: "Notification settings"
 notificationSettingDesc: "Select the types of notification to display."
-enableFaviconNotificationDot: "Enable favicon notification dot"
-verifyNotificationDotWorkingButton: "Check if the notification dot works on your instance"
-notificationDotNotWorking: "Unfortunately, this instance does not support the notification dot feature at this time."
-notificationDotWorking: "The notification dot is functioning properly on this instance."
-notificationDotNotWorkingAdvice: "If the notification dot doesn't work, ask an admin to check our documentation {link}"
 useGlobalSetting: "Use global settings"
 useGlobalSettingDesc: "If turned on, your account's notification settings will be used. If turned off, individual configurations can be made."
 other: "Other"
@@ -724,17 +713,14 @@ behavior: "Behavior"
 sample: "Sample"
 abuseReports: "Reports"
 reportAbuse: "Report"
-reportAbuseRenote: "Report boost"
+reportAbuseRenote: "Report renote"
 reportAbuseOf: "Report {name}"
 fillAbuseReportDescription: "Please fill in details regarding this report. If it is about a specific note, please include its URL."
 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"
@@ -753,14 +739,14 @@ unclip: "Unclip"
 confirmToUnclipAlreadyClippedNote: "This note is already part of the \"{name}\" clip. Do you want to remove it from this clip instead?"
 public: "Public"
 private: "Private"
-i18nInfo: "Sharkey is being translated into various languages by volunteers. You can help at {link}."
+i18nInfo: "Misskey is being translated into various languages by volunteers. You can help at {link}."
 manageAccessTokens: "Manage access tokens"
 accountInfo: "Account Info"
 notesCount: "Number of notes"
 repliesCount: "Number of replies sent"
-renotesCount: "Number of boosts sent"
+renotesCount: "Number of renotes sent"
 repliedCount: "Number of replies received"
-renotedCount: "Number of boosts received"
+renotedCount: "Number of renotes received"
 followingCount: "Number of followed accounts"
 followersCount: "Number of followers"
 sentReactionsCount: "Number of sent reactions"
@@ -776,11 +762,6 @@ noCrawleDescription: "Ask search engines to not index your profile page, notes,
 lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", your notes will be visible to anyone, even if you require followers to be manually approved."
 alwaysMarkSensitive: "Mark as sensitive by default"
 loadRawImages: "Load original images instead of showing thumbnails"
-showTickerOnReplies: "Show instance ticker on replies"
-searchEngine: "Search Engine For Search MFM"
-searchEngineOther: "Other"
-searchEngineCustomURIDescription: "The custom URI must be input in the format like \"https://www.google.com/search?q=\\{query}\" or \"https://www.google.com/search?q=%s\"."
-searchEngineCusomURI: "Custom URI"
 disableShowingAnimatedImages: "Don't play animated images"
 highlightSensitiveMedia: "Highlight sensitive media"
 verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification."
@@ -798,8 +779,6 @@ thisIsExperimentalFeature: "This is an experimental feature. Its functionality i
 developer: "Developer"
 makeExplorable: "Make account visible in \"Explore\""
 makeExplorableDescription: "If you turn this off, your account will not show up in the \"Explore\" section."
-makeIndexable: "Make public notes not indexable"
-makeIndexableDescription: "Stop note search from indexing your public notes."
 showGapBetweenNotesInTimeline: "Show a gap between posts on the timeline"
 duplicate: "Duplicate"
 left: "Left"
@@ -814,7 +793,7 @@ onlineUsersCount: "{n} users are online"
 nUsers: "{n} Users"
 nNotes: "{n} Notes"
 sendErrorReports: "Send error reports"
-sendErrorReportsDescription: "When turned on, detailed error information will be shared with Sharkey when a problem occurs, helping to improve the quality of Sharkey.\nThis will include information such the version of your OS, what browser you're using, your activity in Sharkey, etc."
+sendErrorReportsDescription: "When turned on, detailed error information will be shared with Misskey when a problem occurs, helping to improve the quality of Misskey.\nThis will include information such the version of your OS, what browser you're using, your activity in Misskey, etc."
 myTheme: "My theme"
 backgroundColor: "Background color"
 accentColor: "Accent color"
@@ -878,7 +857,7 @@ administration: "Management"
 accounts: "Accounts"
 switch: "Switch"
 noMaintainerInformationWarning: "Maintainer information is not configured."
-noInquiryUrlWarning: "Contact URL is not set."
+noInquiryUrlWarning: "Inquiry URL isn’t set"
 noBotProtectionWarning: "Bot protection is not configured."
 configure: "Configure"
 postToGallery: "Create new gallery post"
@@ -909,12 +888,12 @@ hashtags: "Hashtags"
 troubleshooting: "Troubleshooting"
 useBlurEffect: "Use blur effects in the UI"
 learnMore: "Learn more"
-misskeyUpdated: "Sharkey has been updated!"
+misskeyUpdated: "Misskey has been updated!"
 whatIsNew: "Show changes"
 translate: "Translate"
 translatedFrom: "Translated from {x}"
 accountDeletionInProgress: "Account deletion is currently in progress"
-usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later."
+usernameInfo: "A name that identifies your account from others on this server.  You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later."
 aiChanMode: "Ai Mode"
 devMode: "Developer mode"
 keepCw: "Keep content warnings"
@@ -929,7 +908,6 @@ itsOff: "Disabled"
 on: "On"
 off: "Off"
 emailRequiredForSignup: "Require email address for sign-up"
-approvalRequiredForSignup: "Require approval for new users"
 unread: "Unread"
 filter: "Filter"
 controlPanel: "Control Panel"
@@ -944,8 +922,8 @@ 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}\"?"
-voteConfirmMulti: "Confirm your vote for \"{choice}\"?\n You can choose more options after confirmation."
 hide: "Hide"
 useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile"
 welcomeBackWithName: "Welcome back, {name}"
@@ -981,7 +959,6 @@ recentNHours: "Last {n} hours"
 recentNDays: "Last {n} days"
 noEmailServerWarning: "Email server not configured."
 thereIsUnresolvedAbuseReportWarning: "There are unsolved reports."
-pendingUserApprovals: "There are users awaiting approval."
 recommended: "Recommended"
 check: "Check"
 driveCapOverrideLabel: "Change the drive capacity for this user"
@@ -990,20 +967,9 @@ requireAdminForView: "You must log in with an administrator account to view this
 isSystemAccount: "An account created and automatically operated by the system."
 typeToConfirm: "Please enter {x} to confirm"
 deleteAccount: "Delete account"
-approveAccount: "Approve"
-denyAccount: "Deny & Delete"
-approved: "Approved"
-notApproved: "Not Approved"
-approvalStatus: "Approval Status"
 document: "Documentation"
 numberOfPageCache: "Number of cached pages"
 numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device."
-numberOfReplies: "Number of replies in a thread"
-numberOfRepliesDescription: "Increasing this number will display more replies. Setting this too high can cause replies to be cramped and unreadable."
-boostSettings: "Boost Settings"
-showVisibilitySelectorOnBoost: "Show Visibility Selector"
-showVisibilitySelectorOnBoostDescription: "Shows the visiblity selector if enabled when clicking boost, if disabled it will use the default visiblity defined below and the selector will not show up."
-visibilityOnBoost: "Default boost visibility"
 logoutConfirm: "Really log out?"
 lastActiveDate: "Last used at"
 statusbar: "Status bar"
@@ -1048,14 +1014,12 @@ cannotLoad: "Unable to load"
 numberOfProfileView: "Profile views"
 like: "Like"
 unlike: "Unlike"
-defaultLike: "Default like emoji"
 numberOfLikes: "Likes"
 show: "Show"
 neverShow: "Don't show again"
 remindMeLater: "Maybe later"
-didYouLikeMisskey: "Have you taken a liking to Sharkey?"
-pleaseDonate: "{host} uses the free software, Sharkey. We would highly appreciate your donations so development of Sharkey can continue!"
-pleaseDonateInstance: "You can also support {host} directly by donating to your instance administration."
+didYouLikeMisskey: "Have you taken a liking to Misskey?"
+pleaseDonate: "{host} uses the free software, Misskey. We would highly appreciate your donations so development of Misskey can continue!"
 correspondingSourceIsAvailable: "The corresponding source code is available at {anchor}"
 roles: "Roles"
 role: "Role"
@@ -1083,16 +1047,8 @@ thisPostMayBeAnnoying: "This note may annoy others."
 thisPostMayBeAnnoyingHome: "Post to home timeline"
 thisPostMayBeAnnoyingCancel: "Cancel"
 thisPostMayBeAnnoyingIgnore: "Post anyway"
-thisPostIsMissingAltTextCancel: "Cancel"
-thisPostIsMissingAltTextIgnore: "Post anyway"
-thisPostIsMissingAltText: "One of the files attached to this post is missing alt text. Please ensure all the attachments have alt text."
-collapseRenotes: "Collapse boosts you've already seen"
-collapseRenotesDescription: "Collapse boosts that you have boosted or reacted to"
-collapseNotesRepliedTo: "Collapse notes replied to"
-collapseFiles: "Collapse files"
-uncollapseCW: "Uncollapse CWs on notes"
-expandLongNote: "Always expand long notes"
-autoloadConversation: "Load conversation on replies"
+collapseRenotes: "Collapse renotes you've already seen"
+collapseRenotesDescription: "Collapse notes that you've reacted to or renoted before."
 internalServerError: "Internal Server Error"
 internalServerErrorDescription: "The server has run into an unexpected error."
 copyErrorInfo: "Copy error details"
@@ -1103,7 +1059,6 @@ disableFederationConfirm: "Really disable federation?"
 disableFederationConfirmWarn: "Even if defederated, posts will continue to be public unless set otherwise. You usually do not need to do this."
 disableFederationOk: "Disable"
 invitationRequiredToRegister: "This instance is invite-only. You must enter a valid invite code sign up."
-approvalRequiredToRegister: "This instance is only accepting users who specify a reason for registration."
 emailNotSupported: "This instance does not support sending emails"
 postToTheChannel: "Post to channel"
 cannotBeChangedLater: "This cannot be changed later."
@@ -1134,7 +1089,7 @@ enableChartsForRemoteUser: "Generate remote user data charts"
 enableChartsForFederatedInstances: "Generate remote instance data charts"
 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"
@@ -1146,11 +1101,10 @@ accountMoved: "This user has moved to a new account:"
 accountMovedShort: "This account has been migrated."
 operationForbidden: "Operation forbidden"
 forceShowAds: "Always show ads"
-oneko: "Cat friend :3"
 addMemo: "Add memo"
 editMemo: "Edit memo"
 reactionsList: "Reactions"
-renotesList: "Boosts"
+renotesList: "Renotes"
 notificationDisplay: "Notifications"
 leftTop: "Top left"
 rightTop: "Top right"
@@ -1180,9 +1134,9 @@ preventAiLearning: "Reject usage in Machine Learning (Generative AI)"
 preventAiLearningDescription: "Requests crawlers to not use posted text or image material etc. in machine learning (Predictive / Generative AI) data sets. This is achieved by adding a \"noai\" HTML-Response flag to the respective content. A complete prevention can however not be achieved through this flag, as it may simply be ignored."
 options: "Options"
 specifyUser: "Specific user"
-lookupConfirm: "Are you sure that you want to look this up?"
-openTagPageConfirm: "Are you sure you want to open this hashtags page?"
-specifyHost: "Specify a host"
+lookupConfirm: "Do you want to look up?"
+openTagPageConfirm: "Do you want to open a hashtag page?"
+specifyHost: "Specific host"
 failedToPreviewUrl: "Could not preview"
 update: "Update"
 rolesThatCanBeUsedThisEmojiAsReaction: "Roles that can use this emoji as reaction"
@@ -1191,15 +1145,11 @@ rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "These roles must be public
 cancelReactionConfirm: "Really delete your reaction?"
 changeReactionConfirm: "Really change your reaction?"
 later: "Later"
-goToMisskey: "To Sharkey"
+goToMisskey: "To Misskey"
 additionalEmojiDictionary: "Additional emoji dictionaries"
 installed: "Installed"
 branding: "Branding"
 enableServerMachineStats: "Publish server hardware stats"
-enableAchievements: "Enable Achievements"
-turnOffAchievements: "Turning this off will disable the achievement system"
-enableBotTrending: "Populate Hashtags with Bots"
-turnOffBotTrending: "Turning this off will stop Bots from populating Hashtags"
 enableIdenticonGeneration: "Enable user identicon generation"
 turnOffToImprovePerformance: "Turning this off can increase performance."
 createInviteCode: "Generate invite"
@@ -1229,21 +1179,19 @@ currentAnnouncements: "Current announcements"
 pastAnnouncements: "Past announcements"
 youHaveUnreadAnnouncements: "There are unread announcements."
 useSecurityKey: "Please follow your browser's or device's instructions to use your security- or passkey."
-replies: "Replies"
-renotes: "Boosts"
+replies: "Reply"
+renotes: "Renotes"
 loadReplies: "Show replies"
 loadConversation: "Show conversation"
 pinnedList: "Pinned list"
 keepScreenOn: "Keep screen on"
-clickToOpen: "Click to open notes"
-showBots: "Show bots in timeline"
 verifiedLink: "Link ownership has been verified"
 notifyNotes: "Notify about new notes"
 unnotifyNotes: "Stop notifying about new notes"
 authentication: "Authentication"
 authenticationRequiredToContinue: "Please authenticate to continue"
 dateAndTime: "Timestamp"
-showRenotes: "Show boosts"
+showRenotes: "Show renotes"
 edited: "Edited"
 notificationRecieveConfig: "Notification Settings"
 mutualFollow: "Mutual follow"
@@ -1257,9 +1205,9 @@ confirmShowRepliesAll: "This operation is irreversible. Would you really like to
 confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?"
 externalServices: "External Services"
 sourceCode: "Source code"
-sourceCodeIsNotYetProvided: "The source code is not yet available. Please contact your administrator to fix this problem."
+sourceCodeIsNotYetProvided: "Source code is not yet available. Contact the administrator to fix this problem."
 repositoryUrl: "Repository URL"
-repositoryUrlDescription: "If there is a repository where the source code is publicly available, enter its URL. If you are using Sharkey as-is (without any changes to the source code), enter https://activitypub.software/TransFem-org/Sharkey/."
+repositoryUrlDescription: "If you are using Misskey as is (without any changes to the source code), enter https://github.com/misskey-dev/misskey"
 repositoryUrlOrTarballRequired: "If you have not published a repository, you must provide a tarball instead. See .config/example.yml for more information."
 feedback: "Feedback"
 feedbackUrl: "Feedback URL"
@@ -1269,8 +1217,6 @@ impressumDescription: "In some countries, like germany, the inclusion of operato
 privacyPolicy: "Privacy Policy"
 privacyPolicyUrl: "Privacy Policy URL"
 tosAndPrivacyPolicy: "Terms of Service and Privacy Policy"
-donation: "Donate"
-donationUrl: "Donation URL"
 avatarDecorations: "Avatar decorations"
 attach: "Attach"
 detach: "Remove"
@@ -1290,7 +1236,7 @@ code: "Code"
 reloadRequiredToApplySettings: "Reloading is required to apply the settings."
 remainingN: "Remaining: {n}"
 overwriteContentConfirm: "Are you sure you want to overwrite the current content?"
-seasonalScreenEffect: "Seasonal screen effects"
+seasonalScreenEffect: "Seasonal Screen Effect"
 decorate: "Decorate"
 addMfmFunction: "Add MFM"
 enableQuickAddMfmFunction: "Show advanced MFM picker"
@@ -1319,18 +1265,39 @@ launchApp: "Launch the app"
 useNativeUIForVideoAudioPlayer: "Use UI of browser when play video and audio"
 keepOriginalFilename: "Keep original file name"
 keepOriginalFilenameDescription: "If you turn off this setting, files names will be replaced with random string automatically when you upload files."
-noDescription: "No description"
+noDescription: "There is no explanation"
 alwaysConfirmFollow: "Always confirm when following"
 inquiry: "Contact"
 tryAgain: "Please try again later"
 confirmWhenRevealingSensitiveMedia: "Confirm when revealing sensitive media"
-sensitiveMediaRevealConfirm: "This media might be sensitive. Are you sure you want to reveal it?"
+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"
+_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: "Suspend delivery"
-  resume: "Resume delivery"
+  stop: "Suspended"
+  resume: "Delivery resume"
   _type:
     none: "Publishing"
     manuallySuspended: "Manually suspended"
@@ -1376,7 +1343,7 @@ _initialAccountSetting:
   pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
   initialAccountSettingCompleted: "Profile setup complete!"
   haveFun: "Enjoy {name}!"
-  youCanContinueTutorial: "You can proceed to a tutorial on how to use {name} (Sharkey) or you can exit the setup here and start using it immediately."
+  youCanContinueTutorial: "You can proceed to a tutorial on how to use {name} (Misskey) or you can exit the setup here and start using it immediately."
   startTutorial: "Start Tutorial"
   skipAreYouSure: "Really skip profile setup?"
   laterAreYouSure: "Really do profile setup later?"
@@ -1387,10 +1354,10 @@ _initialTutorial:
   skipAreYouSure: "Quit Tutorial?"
   _landing:
     title: "Welcome to the Tutorial"
-    description: "Here, you can learn the basics of using Sharkey and its features."
+    description: "Here, you can learn the basics of using Misskey and its features."
   _note:
     title: "What is a Note?"
-    description: "Posts on Sharkey are called 'Notes.' Notes are arranged chronologically on the timeline and are updated in real-time."
+    description: "Posts on Misskey are called 'Notes.' Notes are arranged chronologically on the timeline and are updated in real-time."
     reply: "Click on this button to reply to a message. It's also possible to reply to replies, continuing the conversation like a thread."
     renote: "You can share that note to your own timeline. You can also quote them with your comments."
     reaction: "You can add reactions to the Note. More details will be explained on the next page."
@@ -1398,13 +1365,13 @@ _initialTutorial:
   _reaction:
     title: "What are Reactions?"
     description: "Notes can be reacted to with various emojis. Reactions allow you to express nuances that may not be conveyed with just a 'like.'"
-    letsTryReacting: "Reactions can be added by clicking the '{reaction}' button on the note. Try reacting to this sample note!"
+    letsTryReacting: "Reactions can be added by clicking the '+' button on the note. Try reacting to this sample note!"
     reactToContinue: "Add a reaction to proceed."
     reactNotification: "You'll receive real-time notifications when someone reacts to your note."
-    reactDone: "You can undo a reaction by pressing the '{undo}' button."
+    reactDone: "You can undo a reaction by pressing the '-' button."
   _timeline:
     title: "The Concept of Timelines"
-    description1: "Sharkey provides multiple timelines based on usage (some may not be available depending on the server's policies)."
+    description1: "Misskey provides multiple timelines based on usage (some may not be available depending on the server's policies)."
     home: "You can view notes from accounts you follow."
     local: "You can view notes from all users on this server."
     social: "Notes from the Home and Local timelines will be displayed."
@@ -1413,12 +1380,12 @@ _initialTutorial:
     description3: "Additionally, there are list timelines and channel timelines. For more details, please refer to {link}."
   _postNote:
     title: "Note Posting Settings"
-    description1: "When posting a note on Sharkey, various options are available. The posting form looks like this."
+    description1: "When posting a note on Misskey, various options are available. The posting form looks like this."
     _visibility:
       description: "You can limit who can view your note."
       public: "Your note will be visible for all users."
-      home: "Public only on the Home timeline. People visiting your profile, via followers, and through boosts can see it."
-      followers: "Visible to followers only. Only followers can see it and no one else, and it cannot be boosted by others."
+      home: "Public only on the Home timeline. People visiting your profile, via followers, and through renotes can see it."
+      followers: "Visible to followers only. Only followers can see it and no one else, and it cannot be renoted by others."
       direct: "Visible only to specified users, and the recipient will be notified. It can be used as an alternative to direct messaging."
       doNotSendConfidencialOnDirect1: "Be careful when sending sensitive information!"
       doNotSendConfidencialOnDirect2: "Administrators of the server can see what you write. Be careful with sensitive information when sending direct notes to users on untrusted servers."
@@ -1440,8 +1407,8 @@ _initialTutorial:
     sensitiveSucceeded: "When attaching files, please set sensitivities in accordance with the server guidelines."
     doItToContinue: "Mark the attachment file as sensitive to proceed."
   _done:
-    title: "The tutorial is complete! 🎉"
-    description: "The functions introduced here are just a small part. For a more detailed understanding of using Sharkey, please refer to {link}."
+    title: "You've completed the tutorial! 🎉"
+    description: "The functions introduced here are just a small part. For a more detailed understanding of using Misskey, please refer to {link}."
 _timelineDescription:
   home: "In the Home timeline, you can see notes from accounts you follow."
   local: "In the Local timeline, you can see notes from all users on this server."
@@ -1461,8 +1428,9 @@ _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."
-  inquiryUrl: "Contact URL"
-  inquiryUrlDescription: "Specify the URL of a web page that contains a contact form or the instance operators' contact information."
+  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."
 _accountMigration:
   moveFrom: "Migrate another account to this one"
   moveFromSub: "Create alias to another account"
@@ -1471,7 +1439,7 @@ _accountMigration:
   moveTo: "Migrate this account to a different one"
   moveToLabel: "Account to move to:"
   moveCannotBeUndone: "Account migration cannot be undone."
-  moveAccountDescription: "This will migrate your account to a different one.\n ・Followers from this account will automatically be migrated to the new account\n ・This account will unfollow all users it is currently following\n ・You will be unable to create new notes etc. on this account\n\nWhile migration of followers is automatic, you must manually prepare some steps to migrate the list of users you are following. To do so, carry out a follows export that you will later import on the new account in the settings menu. The same procedure applies to your lists as well as your muted and blocked users.\n\n(This explanation applies to Sharkey v13.12.0 and later. Other ActivityPub software, such as Mastodon, might function differently.)"
+  moveAccountDescription: "This will migrate your account to a different one.\n ・Followers from this account will automatically be migrated to the new account\n ・This account will unfollow all users it is currently following\n ・You will be unable to create new notes etc. on this account\n\nWhile migration of followers is automatic, you must manually prepare some steps to migrate the list of users you are following. To do so, carry out a follows export that you will later import on the new account in the settings menu. The same procedure applies to your lists as well as your muted and blocked users.\n\n(This explanation applies to Misskey v13.12.0 and later. Other ActivityPub software, such as Mastodon, might function differently.)"
   moveAccountHowTo: "To migrate, first create an alias for this account on the account to move to.\nAfter you have created the alias, enter the account to move to in the following format: @username@server.example.com"
   startMigration: "Migrate"
   migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore."
@@ -1482,9 +1450,9 @@ _achievements:
   earnedAt: "Unlocked at"
   _types:
     _notes1:
-      title: "just setting up my shonk"
+      title: "just setting up my msky"
       description: "Post your first note"
-      flavor: "Have a good time with Sharkey!"
+      flavor: "Have a good time with Misskey!"
     _notes10:
       title: "Some notes"
       description: "Post 10 notes"
@@ -1580,7 +1548,7 @@ _achievements:
     _login1000:
       title: "Master of Notes III"
       description: "Log in for a total of 1,000 days"
-      flavor: "Thank you for using Sharkey!"
+      flavor: "Thank you for using Misskey!"
     _noteClipped1:
       title: "Must... clip..."
       description: "Clip your first note"
@@ -1640,18 +1608,18 @@ _achievements:
       title: "Likes Achievements"
       description: "Look at your list of achievements for at least 3 minutes"
     _iLoveMisskey:
-      title: "I Love Sharkey"
-      description: "Post \"I ❤ #Sharkey\""
-      flavor: "Sharkey's development team greatly appreciates your support!"
+      title: "I Love Misskey"
+      description: "Post \"I ❤ #Misskey\""
+      flavor: "Misskey's development team greatly appreciates your support!"
     _foundTreasure:
       title: "Treasure Hunt"
       description: "You've found the hidden treasure"
     _client30min:
       title: "Short break"
-      description: "Keep Sharkey opened for at least 30 minutes"
+      description: "Keep Misskey opened for at least 30 minutes"
     _client60min:
-      title: "No \"Miss\" in Sharkey"
-      description: "Keep Sharkey opened for at least 60 minutes"
+      title: "No \"Miss\" in Misskey"
+      description: "Keep Misskey opened for at least 60 minutes"
     _noteDeletedWithin1min:
       title: "Nevermind"
       description: "Delete a note within a minute of posting it"
@@ -1716,12 +1684,12 @@ _achievements:
     _brainDiver:
       title: "Brain Diver"
       description: "Post the link to Brain Diver"
-      flavor: "Sharkey-Sharkey La-Tu-Ma"
+      flavor: "Misskey-Misskey La-Tu-Ma"
     _smashTestNotificationButton:
       title: "Test overflow"
       description: "Trigger the notification test repeatedly within an extremely short time"
     _tutorialCompleted:
-      title: "Sharkey Elementary Course Diploma"
+      title: "Misskey Elementary Course Diploma"
       description: "Tutorial completed"
     _bubbleGameExplodingHead:
       title: "🤯"
@@ -1768,10 +1736,8 @@ _role:
     high: "High"
   _options:
     gtlAvailable: "Can view the global timeline"
-    btlAvailable: "Can view the bubble timeline"
     ltlAvailable: "Can view the local timeline"
     canPublicNote: "Can send public notes"
-    canImportNotes: "Can import notes"
     mentionMax: "Maximum number of mentions in a note"
     canInvite: "Can create instance invite codes"
     inviteLimit: "Invite limit"
@@ -1781,7 +1747,7 @@ _role:
     canManageAvatarDecorations: "Manage avatar decorations"
     driveCapacity: "Drive capacity"
     alwaysMarkNsfw: "Always mark files as NSFW"
-    canUpdateBioMedia: "Allow users to edit their avatar or banner"
+    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"
@@ -1796,6 +1762,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"
@@ -1803,8 +1774,8 @@ _role:
     isCat: "Cat Users"
     isBot: "Bot Users"
     isSuspended: "Suspended user"
-    isLocked: "Private account"
-    isExplorable: "Account is discoverable"
+    isLocked: "Private accounts"
+    isExplorable: "Effective user of \"make an account discoverable\""
     createdLessThan: "Less than X has passed since account creation"
     createdMoreThan: "More than X has passed since account creation"
     followersLessThanOrEq: "Has X or fewer followers"
@@ -1830,7 +1801,7 @@ _emailUnavailable:
   disposable: "Disposable email addresses may not be used"
   mx: "This email server is invalid"
   smtp: "This email server is not responding"
-  banned: "This email address is banned"
+  banned: "You cannot register with this email address"
 _ffVisibility:
   public: "Public"
   followers: "Visible to followers only"
@@ -1839,8 +1810,6 @@ _signup:
   almostThere: "Almost there"
   emailAddressInfo: "Please enter your email address. It will not be made public."
   emailSent: "A confirmation email has been sent to your email address ({email}). Please click the included link to complete account creation."
-  approvalPending: "Your account has been created and is awaiting approval."
-  reasonInfo: "Please enter a reason as to why you want to join the instance."
 _accountDelete:
   accountDelete: "Delete account"
   mayTakeTime: "As account deletion is a resource-heavy process, it may take some time to complete depending on how much content you have created and how many files you have uploaded."
@@ -1902,20 +1871,17 @@ _registry:
   domain: "Domain"
   createKey: "Create key"
 _aboutMisskey:
-  about: "Sharkey is open-source software based on Misskey which has been in development by syuilo since 2014."
+  about: "Misskey is open-source software being developed by syuilo since 2014."
   contributors: "Main contributors"
   allContributors: "All contributors"
   source: "Source code"
-  original: "Misskey original"
-  original_sharkey: "Sharkey original"
-  thisIsModifiedVersion: "{name} uses a modified version of the original Sharkey."
-  translation: "Translate Sharkey"
+  original: "Original"
+  thisIsModifiedVersion: "{name} uses a modified version of the original Misskey."
+  translation: "Translate Misskey"
   donate: "Donate to Misskey"
-  donate_sharkey: "Donate to Sharkey"
   morePatrons: "We also appreciate the support of many other helpers not listed here. Thank you! 🥰"
   patrons: "Patrons"
   projectMembers: "Project members"
-  testers: "Testers"
 _displayOfSensitiveMedia:
   respect: "Hide media marked as sensitive"
   ignore: "Display media marked as sensitive"
@@ -1928,7 +1894,6 @@ _serverDisconnectedBehavior:
   reload: "Automatically reload"
   dialog: "Show warning dialog"
   quiet: "Show unobtrusive warning"
-  disabled: "Disable warning"
 _channel:
   create: "Create channel"
   edit: "Edit channel"
@@ -1941,7 +1906,7 @@ _channel:
   notesCount: "{n} Notes"
   nameAndDescription: "Name and description"
   nameOnly: "Name only"
-  allowRenoteToExternal: "Allow boosts and quote outside the channel"
+  allowRenoteToExternal: "Allow renote and quote outside the channel"
 _menuDisplay:
   sideFull: "Side"
   sideIcon: "Side (Icons)"
@@ -1952,7 +1917,7 @@ _wordMute:
   muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
   muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
 _instanceMute:
-  instanceMuteDescription: "This will mute any notes/boosts from the listed instances, including those of users replying to a user from a muted instance."
+  instanceMuteDescription: "This will mute any notes/renotes from the listed instances, including those of users replying to a user from a muted instance."
   instanceMuteDescription2: "Separate with newlines"
   title: "Hides notes from listed instances."
   heading: "List of instances to be muted"
@@ -2004,7 +1969,7 @@ _theme:
     hashtag: "Hashtag"
     mention: "Mention"
     mentionMe: "Mentions (Me)"
-    renote: "Boost"
+    renote: "Renote"
     modalBg: "Modal background"
     divider: "Divider"
     scrollbarHandle: "Scrollbar handle"
@@ -2019,7 +1984,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"
@@ -2038,8 +2002,8 @@ _soundSettings:
   driveFileTypeWarn: "This file is not supported"
   driveFileTypeWarnDescription: "Select an audio file"
   driveFileDurationWarn: "The audio is too long."
-  driveFileDurationWarnDescription: "Long audio may disrupt using Sharkey. Still continue?"
-  driveFileError: "The audio couldn't be loaded. Please make sure you selected an audio file."
+  driveFileDurationWarnDescription: "Long audio may disrupt using Misskey. Still continue?"
+  driveFileError: "It couldn't load the sound. Please change the setting."
 _ago:
   future: "Future"
   justNow: "Just now"
@@ -2092,7 +2056,7 @@ _2fa:
   backupCodesDescription: "You can use these codes to gain access to your account in case of becoming unable to use your two-factor authentificator app. Each can only be used once. Please keep them in a safe place."
   backupCodeUsedWarning: "A backup code has been used. Please reconfigure two-factor authentification as soon as possible if you are no longer able to use it."
   backupCodesExhaustedWarning: "All backup codes have been used. Should you lose access to your two-factor authentification app, you will be unable to access this account. Please reconfigure two-factor authentification."
-  moreDetailedGuideHere: "Click here for a detailed guide"
+  moreDetailedGuideHere: "Here is detailed guide"
 _permissions:
   "read:account": "View your account information"
   "write:account": "Edit your account information"
@@ -2232,7 +2196,6 @@ _widgets:
   _userList:
     chooseList: "Select a list"
   clicker: "Clicker"
-  search: "Search"
   birthdayFollowings: "Users who celebrate their birthday today"
 _cw:
   hide: "Hide"
@@ -2261,7 +2224,6 @@ _poll:
   remainingHours: "{h} hour(s) {m} minute(s) remaining"
   remainingMinutes: "{m} minute(s) {s} second(s) remaining"
   remainingSeconds: "{s} second(s) remaining"
-  multiple: "Multiple choices"
 _visibility:
   public: "Public"
   publicDescription: "Your note will be visible for all users"
@@ -2296,13 +2258,11 @@ _profile:
   metadataContent: "Content"
   changeAvatar: "Change avatar"
   changeBanner: "Change banner"
-  updateBanner: "Update banner"
-  removeBanner: "Remove banner"
-  changeBackground: "Change background"
-  updateBackground: "Update background"
-  removeBackground: "Remove background"
   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"
@@ -2345,7 +2305,6 @@ _timelines:
   local: "Local"
   social: "Social"
   global: "Global"
-  bubble: "Bubble"
 _play:
   new: "Create Play"
   edit: "Edit Play"
@@ -2408,7 +2367,7 @@ _pages:
     image: "Images"
     button: "Button"
     dynamic: "Dynamic Blocks"
-    dynamicDescription: "This block type has been removed. Please use {play} from now on."
+    dynamicDescription: "This block has been abolished. Please use {play} from now on."
     note: "Embedded note"
     _note:
       id: "Note ID"
@@ -2423,7 +2382,7 @@ _notification:
   youGotMention: "{name} mentioned you"
   youGotReply: "{name} replied to you"
   youGotQuote: "{name} quoted you"
-  youRenoted: "Boost from {name}"
+  youRenoted: "Renote from {name}"
   youWereFollowed: "followed you"
   youReceivedFollowRequest: "You've received a follow request"
   yourFollowRequestAccepted: "Your follow request was accepted"
@@ -2439,17 +2398,18 @@ _notification:
   notificationWillBeDisplayedLikeThis: "Notifications look like this"
   reactedBySomeUsers: "{n} users reacted"
   likedBySomeUsers: "{n} users liked your note"
-  renotedBySomeUsers: "Boosted by {n} users"
+  renotedBySomeUsers: "Renote from {n} users"
   followedBySomeUsers: "Followed by {n} users"
   flushNotification: "Clear notifications"
-  edited: "Note got edited"
+  exportOfXCompleted: "Export of {x} has been completed"
+  login: "Someone logged in"
   _types:
     all: "All"
     note: "New notes"
     follow: "New followers"
     mention: "Mentions"
     reply: "Replies"
-    renote: "Boosts"
+    renote: "Renotes"
     quote: "Quotes"
     reaction: "Reactions"
     pollEnded: "Polls ending"
@@ -2457,12 +2417,14 @@ _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"
-    edited: "Edits"
   _actions:
     followBack: "followed you back"
     reply: "Reply"
-    renote: "Boost"
+    renote: "Renote"
 _deck:
   alwaysShowMainColumn: "Always show main column"
   columnAlign: "Align columns"
@@ -2516,25 +2478,26 @@ _webhookSettings:
     followed: "When being followed"
     note: "When posting a note"
     reply: "When receiving a reply"
-    renote: "When boosted"
+    renote: "When renoted"
     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"
   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 an email to the moderators when an abuse report is received."
-        webhook: "Send a notification to the SystemWebhook when an abuse report is received or resolved."
+        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"
@@ -2545,7 +2508,6 @@ _moderationLogTypes:
   updateRole: "Role updated"
   assignRole: "Assigned to role"
   unassignRole: "Removed from role"
-  approve: "Approved"
   suspend: "Suspended"
   unsuspend: "Unsuspended"
   addCustomEmoji: "Custom emoji added"
@@ -2568,6 +2530,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"
@@ -2575,100 +2539,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"
-
-_mfm:
-  uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks"
-  intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax."
-  dummy: "Sharkey expands the world of the Fediverse"
-  mention: "Mention"
-  mentionDescription: "You can specify a user by using an At-Symbol and a username."
-  hashtag: "Hashtag"
-  hashtagDescription: "You can specify a hashtag using a number sign and text."
-  url: "URL"
-  urlDescription: "URLs can be displayed."
-  link: "Link"
-  linkDescription: "Specific parts of text can be displayed as a URL."
-  bold: "Bold"
-  boldDescription: "Highlights letters by making them thicker."
-  small: "Small"
-  smallDescription: "Displays content small and thin."
-  center: "Center"
-  centerDescription: "Displays content centered."
-  inlineCode: "Code (Inline)"
-  inlineCodeDescription: "Displays inline syntax highlighting for (program) code."
-  blockCode: "Code (Block)"
-  blockCodeDescription: "Displays syntax highlighting for multi-line (program) code in a block."
-  inlineMath: "Math (Inline)"
-  inlineMathDescription: "Display math formulas (KaTeX) in-line"
-  blockMath: "Math (Block)"
-  blockMathDescription: "Display math formulas (KaTeX) in a block"
-  quote: "Quote"
-  quoteDescription: "Displays content as a quote."
-  emoji: "Custom Emoji"
-  emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed."
-  search: "Search"
-  searchDescription: "Displays a search box with pre-entered text."
-  flip: "Flip"
-  flipDescription: "Flips content horizontally or vertically."
-  jelly: "Animation (Jelly)"
-  jellyDescription: "Gives content a jelly-like animation."
-  tada: "Animation (Tada)"
-  tadaDescription: "Gives content a \"Tada!\"-like animation."
-  jump: "Animation (Jump)"
-  jumpDescription: "Gives content a jumping animation."
-  bounce: "Animation (Bounce)"
-  bounceDescription: "Gives content a bouncy animation."
-  shake: "Animation (Shake)"
-  shakeDescription: "Gives content a shaking animation."
-  twitch: "Animation (Twitch)"
-  twitchDescription: "Gives content a strongly twitching animation."
-  spin: "Animation (Spin)"
-  spinDescription: "Gives content a spinning animation."
-  x2: "Big"
-  x2Description: "Displays content bigger."
-  x3: "Very big"
-  x3Description: "Displays content even bigger."
-  x4: "Unbelievably big"
-  x4Description: "Displays content even bigger than bigger than big."
-  blur: "Blur"
-  blurDescription: "Blurs content. It will be displayed clearly when hovered over."
-  font: "Font"
-  fontDescription: "Sets the font to display content in."
-  rainbow: "Rainbow"
-  rainbowDescription: "Makes the content appear in rainbow colors."
-  sparkle: "Sparkle"
-  sparkleDescription: "Gives content a sparkling particle effect."
-  rotate: "Rotate"
-  rotateDescription: "Turns content by a specified angle."
-  position: "Position"
-  positionDescription: "Move content by a specified amount."
-  crop: "Crop"
-  cropDescription: "Crop content."
-  followMouse: "Follow Mouse"
-  followMouseDescription: "Content will follow the mouse. On mobile it will follow wherever the user taps."
-  scale: "Scale"
-  scaleDescription: "Scale content by a specified amount."
-  foreground: "Foreground color"
-  foregroundDescription: "Change the foreground color of text."
-  fade: 'Fade'
-  fadeDescription: 'Fade text in and out.'
-  background: "Background color"
-  backgroundDescription: "Change the background color of text."
-  plain: "Plain"
-  plainDescription: "Deactivates the effects of all MFM contained within this MFM effect."
-
+  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"
@@ -2718,27 +2600,13 @@ _externalResourceInstaller:
     _themeInstallFailed:
       title: "Failed to install theme"
       description: "A problem occurred during theme installation. Please try again. Error details can be viewed in the Javascript console."
-
-_animatedMFM:
-  play: "Play MFM Animation"
-  stop: "Stop MFM Animation"
-  _alert:
-    text: "Animated MFMs could include flashing lights and fast moving text/emojis."
-    confirm: "Animate"
-
-_dataRequest:
-  title: "Request Data"
-  warn: "Data requests are only possible every 3 days."
-  text: "Once the data is ready to download, an email will be sent to the email address registered to this account."
-  button: "Request"
-
 _dataSaver:
   _media:
     title: "Loading Media"
     description: "Prevents images/videos from being loaded automatically. Hidden images/videos will be loaded when tapped."
   _avatar:
     title: "Avatar image"
-    description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic."
+    description: "Stop avatar image animation. Animated images can be larger in file size than normal  images, potentially leading to further reductions in data traffic."
   _urlPreview:
     title: "URL preview thumbnails"
     description: "URL preview thumbnail images will no longer be loaded."
@@ -2800,16 +2668,16 @@ _urlPreviewSetting:
   title: "URL preview settings"
   enable: "Enable URL preview"
   timeout: "Time out when getting preview (ms)"
-  timeoutDescription: "If it takes longer than this value to get the preview, the preview won't be generated."
+  timeoutDescription: "If it takes longer than this value to get the preview, the preview won’t be generated."
   maximumContentLength: "Maximum Content-Length (bytes)"
   maximumContentLengthDescription: "If Content-Length is higher than this value, the preview won't be generated."
-  requireContentLength: "Generate the preview only if we can get Content-Length"
+  requireContentLength: "Generate the preview only if you could get Content-Length"
   requireContentLengthDescription: "If other server doesn't return Content-Length, the preview won't be generated."
   userAgent: "User-Agent"
   userAgentDescription: "Sets the User-Agent to be used when retrieving previews. If left blank, the default User-Agent will be used."
-  summaryProxy: "Endpoint for proxy to generate previews"
-  summaryProxyDescription: "Generate previews using Summaly Proxy, instead of Sharkey itself."
-  summaryProxyDescription2: "The following parameters are sent to the proxy as a query string. If the proxy does not support them, the values are ignored."
+  summaryProxy: "Proxy endpoints that generate previews"
+  summaryProxyDescription: "Not Misskey itself, but generate previews using Summaly Proxy."
+  summaryProxyDescription2: "The following parameters are linked to the proxy as a query string. If the proxy does not support them, the values are ignored."
 _mediaControls:
   pip: "Picture in Picture"
   playbackRate: "Playback Speed"
@@ -2819,3 +2687,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 f5d1d11036916e75bd0f1b946755e74d3925034c..d574999e400bcb98dfcba7b409b5c5938264a8bf 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -109,11 +109,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 +155,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 +182,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"
@@ -406,7 +414,7 @@ antennaKeywordsDescription: "Separar con espacios es una declaración AND, separ
 notifyAntenna: "Notificar nueva nota"
 withFileAntenna: "Sólo notas con archivos adjuntados"
 enableServiceworker: "Activar ServiceWorker"
-antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva. Utilice \"*@instance.com\" para especificar todos los usuarios de una instancia."
+antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva"
 caseSensitive: "Distinguir mayúsculas de minúsculas"
 withReplies: "Incluir respuestas"
 connectedTo: "Estas cuentas están conectadas"
@@ -494,7 +502,6 @@ 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"
 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 +700,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"
@@ -868,7 +872,7 @@ whatIsNew: "Mostrar cambios"
 translate: "Traducir"
 translatedFrom: "Traducido de {x}"
 accountDeletionInProgress: "La eliminación de la cuenta está en curso"
-usernameInfo: "Un nombre que identifique su cuenta de otras en este servidor. Puede utilizar el alfabeto (a~z, A~Z), dígitos (0~9) o guiones bajos (_). Los nombres de usuario no se pueden cambiar posteriormente."
+usernameInfo: "Un nombre que identifique su cuenta de otras en este servidor.  Puede utilizar el alfabeto (a~z, A~Z), dígitos (0~9) o guiones bajos (_). Los nombres de usuario no se pueden cambiar posteriormente."
 aiChanMode: "Modo Ai"
 devMode: "Modo de desarrollador"
 keepCw: "Mantener la advertencia de contenido"
@@ -1095,6 +1099,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."
@@ -1147,8 +1153,8 @@ currentAnnouncements: "Anuncios actuales"
 pastAnnouncements: "Anuncios anteriores"
 youHaveUnreadAnnouncements: "Hay anuncios sin leer"
 useSecurityKey: "Por favor, sigue las instrucciones de tu dispositivo o navegador para usar tu clave de seguridad o tu clave de paso."
-replies: "Respuestas"
-renotes: "Renotas"
+replies: "Responder"
+renotes: "Renotar"
 loadReplies: "Ver respuestas"
 loadConversation: "Ver conversación"
 pinnedList: "Lista fijada"
@@ -1300,10 +1306,10 @@ _initialTutorial:
   _reaction:
     title: "¿Qué son las reacciones?"
     description: "Se puede reaccionar a las Notas con diferentes emojis. Las reacciones te permiten expresar matices que no se pueden transmitir con un simple 'me gusta'."
-    letsTryReacting: "Puedes añadir reacciones pulsando en el botón '{reaction}' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!"
+    letsTryReacting: "Puedes añadir reacciones pulsando en el botón '+' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!"
     reactToContinue: "Añade una reacción para continuar."
     reactNotification: "Recibirás notificaciones en tiempo real cuando alguien reaccione a tu nota."
-    reactDone: "Puedes deshacer una reacción pulsando en el botón '{undo}'."
+    reactDone: "Puedes deshacer una reacción pulsando en el botón '-'."
   _timeline:
     title: "El concepto de Línea de tiempo"
     description1: "Misskey proporciona múltiples líneas de tiempo basadas en su uso (algunas pueden no estar disponibles dependiendo de las políticas de la instancia)."
@@ -1909,7 +1915,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"
@@ -2149,7 +2154,6 @@ _poll:
   remainingHours: "Quedan {h} horas y {m} minutos para que finalice"
   remainingMinutes: "Quedan {m} minutos y {s} segundos para que finalice"
   remainingSeconds: "Quedan {s} segundos para que finalice"
-  multiple: "Opciones múltiples"
 _visibility:
   public: "Público"
   publicDescription: "Visible para todos los usuarios"
@@ -2335,6 +2339,7 @@ _notification:
     followRequestAccepted: "El seguimiento fue aceptado"
     roleAssigned: "Rol asignado"
     achievementEarned: "Logro desbloqueado"
+    login: "Iniciar sesión"
     app: "Notificaciones desde aplicaciones"
   _actions:
     followBack: "Te sigue de vuelta"
@@ -2355,7 +2360,7 @@ _deck:
   newProfile: "Nuevo perfil"
   deleteProfile: "Eliminar perfil"
   introduction: "¡Crea la interfaz perfecta para tí organizando las columnas libremente!"
-  introduction2: "Presiona en la + de la derecha de la pantalla para añadir nuevas columnas donde quieras."
+  introduction2: "Presiona en la  + de la derecha de la pantalla para añadir nuevas columnas donde quieras."
   widgetsIntroduction: "Por favor selecciona \"Editar Widgets\" en el menú columna y agrega un widget."
   useSimpleUiForNonRootPages: "Mostrar páginas no pertenecientes a la raíz con la interfaz simple"
   usedAsMinWidthWhenFlexible: "Se usará el ancho mínimo cuando la opción \"Autoajustar ancho\" esté habilitada"
@@ -2397,6 +2402,8 @@ _abuseReport:
   _notificationRecipient:
     _recipientType:
       mail: "Correo"
+      webhook: "Webhook"
+    keywords: "Palabras Clave"
 _moderationLogTypes:
   createRole: "Rol creado"
   deleteRole: "Rol eliminado"
@@ -2500,6 +2507,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 f615349b89b259f7a6ed43bb05d069b134a452a3..a7060c06fc81cdc72adc0ce12afda651b20b68b5 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"
@@ -1250,7 +1246,7 @@ _announcement:
   end: "Archiver l'annonce"
   tooManyActiveAnnouncementDescription: "Un grand nombre d'annonces actives peut baisser l'expérience utilisateur. Considérez d'archiver les annonces obsolètes."
   readConfirmTitle: "Marquer comme lu ?"
-  readConfirmText: "Cela marquera le contenu de « {title} » comme lu."
+  readConfirmText: "Cela marquera le contenu de  « {title} » comme lu."
   shouldNotBeUsedToPresentPermanentInfo: "Puisque cela pourrait nuire considérablement à l'expérience utilisateur pour les nouveaux utilisateurs, il est recommandé d'utiliser les annonces pour afficher des informations temporaires plutôt que des informations persistantes."
   dialogAnnouncementUxWarn: "Avoir deux ou plus annonces de style dialogue en même temps pourrait nuire considérablement à l'expérience utilisateur. Veuillez les utiliser avec caution."
   silence: "Ne pas me notifier"
@@ -1283,10 +1279,10 @@ _initialTutorial:
   _reaction:
     title: "Qu'est-ce que les réactions ?"
     description: "Vous pouvez ajouter des « réactions » aux notes. Les réactions vous permettent d'exprimer à l'aise des nuances qui ne peuvent pas être exprimées par des mentions j'aime."
-    letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « {reaction} » de la note. Essayez d'ajouter une réaction à cet exemple de note !"
+    letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « + » de la note. Essayez d'ajouter une réaction à cet exemple de note !"
     reactToContinue: "Ajoutez une réaction pour procéder."
     reactNotification: "Vous recevez des notifications en temps réel lorsque quelqu'un réagit à votre note."
-    reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « {undo} » ."
+    reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « - » ."
   _timeline:
     title: "Fonctionnement des fils"
     description1: "Misskey offre plusieurs fils selon l'usage (certains peuvent être désactivés par le serveur)."
@@ -1350,7 +1346,7 @@ _achievements:
   earnedAt: "Date d'obtention"
   _types:
     _notes1:
-      title: "Je viens tout juste de configurer mon shonk"
+      title: "Je viens tout juste de configurer mon msky"
       description: "Publiez votre première note"
       flavor: "Passez un bon moment avec Misskey !"
     _notes10:
@@ -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/generateDTS.js b/locales/generateDTS.js
index a175247445a819976d084442405158559d01de53..571595de202d008127b178d867c0aed819f9c218 100644
--- a/locales/generateDTS.js
+++ b/locales/generateDTS.js
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url';
 import { dirname } from 'node:path';
 import * as yaml from 'js-yaml';
 import ts from 'typescript';
+import { merge } from './index.js';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
@@ -56,7 +57,10 @@ function createMembers(record) {
 }
 
 export default function generateDTS() {
-	const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
+	const sharkeyLocale = yaml.load(fs.readFileSync(`${__dirname}/../sharkey-locales/en-US.yml`, 'utf-8'));
+	const misskeyLocale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
+	const locale = merge(misskeyLocale, sharkeyLocale);
+
 	const members = createMembers(locale);
 	const elements = [
 		ts.factory.createVariableStatement(
diff --git a/locales/hu-HU.yml b/locales/hu-HU.yml
index 023a91494d027743c7242b61ce5b8a874a5f279d..acc27ed09208a9b4551b34540ad16fa6c8ae1ee4 100644
--- a/locales/hu-HU.yml
+++ b/locales/hu-HU.yml
@@ -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 2f225c1199583e556f4931cbca1cc35b337ebaae..ce3958b167fcaa098c55428b00b8582f2211eff7 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"
@@ -502,7 +504,6 @@ uiLanguage: "Bahasa antarmuka pengguna"
 aboutX: "Tentang {x}"
 emojiStyle: "Gaya emoji"
 native: "Native"
-disableDrawer: "Jangan gunakan menu bergaya laci"
 showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk"
 showReactionsCount: "Lihat jumlah reaksi dalam catatan"
 noHistory: "Tidak ada riwayat"
@@ -584,7 +585,7 @@ sort: "Urutkan"
 ascendingOrder: "Urutkan naik"
 descendingOrder: "Urutkan menurun"
 scratchpad: "Scratchpad"
-scratchpadDescription: "Scratchpad menyediakan lingkungan eksperimen untuk AiScript. Kamu bisa menulis, mengeksuksi, serta mengecek hasil yang berinteraksi dengan Misskey."
+scratchpadDescription: "Scratchpad menyediakan lingkungan eksperimen untuk AiScript. Kamu  bisa menulis, mengeksuksi, serta mengecek hasil yang berinteraksi dengan Misskey."
 output: "Keluaran"
 script: "Script"
 disablePagesScript: "Nonaktifkan script pada halaman"
@@ -701,10 +702,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"
@@ -1155,7 +1153,7 @@ currentAnnouncements: "Pengumuman Saat Ini"
 pastAnnouncements: "Pengumuman Terdahulu"
 youHaveUnreadAnnouncements: "Terdapat pengumuman yang belum dibaca"
 useSecurityKey: "Mohon ikuti instruksi peramban atau perangkat kamu untuk menggunakan kunci pengaman atau passkey."
-replies: "Balasan"
+replies: "Balas"
 renotes: "Renote"
 loadReplies: "Tampilkan balasan"
 loadConversation: "Tampilkan percakapan"
@@ -1316,10 +1314,10 @@ _initialTutorial:
   _reaction:
     title: "Apa itu Reaksi?"
     description: "Catatan dapat direaksi dengan berbagai emoji. Reaksi memperbolehkan kamu untuk mengekspresikan nuansa yang tidak dapat disampaikan hanya dengan sebuah \"suka\"."
-    letsTryReacting: "Reaksi dapat ditambahkan dengan mengklik tombol '{reaction}' pada catatan. Coba lakukan mereaksi contoh catatan ini!"
+    letsTryReacting: "Reaksi dapat ditambahkan dengan mengklik tombol '+' pada catatan. Coba lakukan mereaksi contoh catatan ini!"
     reactToContinue: "Tambahkan reaksi untuk melanjutkan."
     reactNotification: "Kamu akan menerima notifikasi real0time ketika seseorang mereaksi catatan kamu."
-    reactDone: "Kamu dapat mengurungkan reaksi dengan menekan tombol '{undo}'."
+    reactDone: "Kamu dapat mengurungkan reaksi dengan menekan tombol '-'."
   _timeline:
     title: "Konsep Lini Masa"
     description1: "Misskey menyediakan berbagai lini masa sesuai dengan penggunaan (beberapa mungkin tidak tersedia karena bergantung dengan kebijakan peladen)."
@@ -1926,7 +1924,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"
@@ -2353,6 +2350,7 @@ _notification:
     followRequestAccepted: "Permintaan mengikuti disetujui"
     roleAssigned: "Peran Diberikan"
     achievementEarned: "Pencapaian didapatkan"
+    login: "Masuk"
     app: "Notifikasi dari aplikasi tertaut"
   _actions:
     followBack: "Ikuti Kembali"
diff --git a/locales/index.d.ts b/locales/index.d.ts
index 177a3c8160c830d3f30a8f124f48723d3d3fddc1..d1cb1f97ea6622c0ca257cca8a6ced9229c378d1 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -18,14 +18,14 @@ export interface Locale extends ILocale {
      */
     "headlineMisskey": string;
     /**
-     * ようこそ!Sharkeyは、オープンソースの分散型マイクロブログサービスです。
-     * 「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡
-     * 「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍
-     * 新しい世界を探検しよう🚀
+     * Welcome! Sharkey is an open source, decentralized microblogging service.
+     * Create "notes" to share your thoughts with everyone around you. 📡
+     * With "reactions", you can also quickly express your feelings about everyone's notes. 👍
+     * Let's explore a new world! 🚀
      */
     "introMisskey": string;
     /**
-     * {name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつです。
+     * {name} is one of the services powered by the open source platform <b>Sharkey</b> which is based on Misskey (referred to as a "Misskey instance").
      */
     "poweredByMisskeyDescription": ParameterizedString<"name">;
     /**
@@ -48,6 +48,20 @@ export interface Locale extends ILocale {
      * パスワード
      */
     "password": string;
+    /**
+     * 初期設定開始用パスワード
+     */
+    "initialPasswordForSetup": string;
+    /**
+     * 初期設定開始用のパスワードが違います。
+     */
+    "initialPasswordIsIncorrect": string;
+    /**
+     * Misskeyを自分でインストールした場合は、設定ファイルに入力したパスワードを使用してください。
+     * Misskeyのホスティングサービスなどを使用している場合は、提供されたパスワードを使用してください。
+     * パスワードを設定していない場合は、空欄にしたまま続行してください。
+     */
+    "initialPasswordForSetupDescription": string;
     /**
      * パスワードを忘れた
      */
@@ -77,7 +91,7 @@ export interface Locale extends ILocale {
      */
     "enterUsername": string;
     /**
-     * {user}がブースト
+     * Boosted by {user}
      */
     "renotedBy": ParameterizedString<"user">;
     /**
@@ -152,10 +166,6 @@ export interface Locale extends ILocale {
      * ユーザー
      */
     "users": string;
-    /**
-     * 承認
-     */
-    "approvals": string;
     /**
      * ユーザーを追加
      */
@@ -201,7 +211,7 @@ export interface Locale extends ILocale {
      */
     "copyLink": string;
     /**
-     * ブーストのリンクをコピー
+     * Copy boost link
      */
     "copyLinkRenote": string;
     /**
@@ -213,7 +223,7 @@ export interface Locale extends ILocale {
      */
     "deleteAndEdit": string;
     /**
-     * このノートを削除してもう一度編集しますか?このノートへのリアクション、ブースト、返信も全て削除されます。
+     * Are you sure you want to redraft this note? This means you will lose all reactions, boosts, and replies to it.
      */
     "deleteAndEditConfirm": string;
     /**
@@ -236,10 +246,6 @@ export interface Locale extends ILocale {
      * ユーザー名をコピー
      */
     "copyUsername": string;
-    /**
-     * リモートプロフィールを開く
-     */
-    "openRemoteProfile": string;
     /**
      * ユーザーIDをコピー
      */
@@ -449,35 +455,27 @@ export interface Locale extends ILocale {
      */
     "enterEmoji": string;
     /**
-     * ブースト
+     * Boost
      */
     "renote": string;
     /**
-     * ブースト解除
+     * Remove boost
      */
     "unrenote": string;
     /**
-     * ブーストしました。
+     * Boosted.
      */
     "renoted": string;
     /**
-     * {name} にブーストしました。
+     * Boosted to {name}
      */
     "renotedToX": ParameterizedString<"name">;
     /**
-     * 引用。
-     */
-    "quoted": string;
-    /**
-     * ブースト解除しました。
-     */
-    "rmboost": string;
-    /**
-     * この投稿はブーストできません。
+     * This post can't be boosted.
      */
     "cantRenote": string;
     /**
-     * ブーストをブーストすることはできません。
+     * A boost can't be boosted.
      */
     "cantReRenote": string;
     /**
@@ -485,7 +483,7 @@ export interface Locale extends ILocale {
      */
     "quote": string;
     /**
-     * チャンネル内ブースト
+     * Channel-only Boost
      */
     "inChannelRenote": string;
     /**
@@ -593,11 +591,11 @@ export interface Locale extends ILocale {
      */
     "unmute": string;
     /**
-     * ブーストをミュート
+     * Mute Boosts
      */
     "renoteMute": string;
     /**
-     * ブーストのミュートを解除
+     * Unmute Boosts
      */
     "renoteUnmute": string;
     /**
@@ -608,10 +606,6 @@ export interface Locale extends ILocale {
      * ブロック解除
      */
     "unblock": string;
-    /**
-     * ユーザーのすべてのメディアをNSFWとしてマークする
-     */
-    "markAsNSFW": string;
     /**
      * 凍結
      */
@@ -628,22 +622,10 @@ export interface Locale extends ILocale {
      * ブロック解除しますか?
      */
     "unblockConfirm": string;
-    /**
-     * このアカウントからのすべてのメディアをNSFWとしてマークしてもよろしいですか?
-     */
-    "nsfwConfirm": string;
-    /**
-     * このアカウントのすべてのメディアをNSFWとしてマーク解除してもよろしいですか?
-     */
-    "unNsfwConfirm": string;
     /**
      * 凍結しますか?
      */
     "suspendConfirm": string;
-    /**
-     * このアカウントを承認してもよろしいですか?
-     */
-    "approveConfirm": string;
     /**
      * 解凍しますか?
      */
@@ -737,7 +719,7 @@ export interface Locale extends ILocale {
      */
     "flagAsBot": string;
     /**
-     * このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Sharkeyのシステム上での扱いがBotに合ったものになります。
+     * Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Sharkey's internal systems to treat this account as a bot.
      */
     "flagAsBotDescription": string;
     /**
@@ -748,14 +730,6 @@ export interface Locale extends ILocale {
      * にゃにゃにゃ??
      */
     "flagAsCatDescription": string;
-    /**
-     * 猫語で話す
-     */
-    "flagSpeakAsCat": string;
-    /**
-     * 有効にすると、あなたの投稿の 「な」を「にゃ」にします。
-     */
-    "flagSpeakAsCatDescription": string;
     /**
      * タイムラインにノートへの返信を表示する
      */
@@ -785,11 +759,11 @@ export interface Locale extends ILocale {
      */
     "showOnRemote": string;
     /**
-     * リモートで続行
+     * Continue on remote instance
      */
     "continueOnRemote": string;
     /**
-     * Misskey Hubからサーバーを選択
+     * Choose a instance from Misskey Hub
      */
     "chooseServerOnMisskeyHub": string;
     /**
@@ -905,7 +879,7 @@ export interface Locale extends ILocale {
      */
     "silenceThisInstance": string;
     /**
-     * サーバーをメディアサイレンス
+     * Silence media from this instance
      */
     "mediaSilenceThisInstance": string;
     /**
@@ -989,17 +963,25 @@ export interface Locale extends ILocale {
      */
     "silencedInstances": string;
     /**
-     * サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになります。ブロックしたインスタンスには影響しません。
+     * List the host names of the instances that you want to silence, separated by a new line. All accounts belonging to the listed instances 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 instances.
      */
     "silencedInstancesDescription": string;
     /**
-     * メディアサイレンスしたサーバー
+     * Media-silenced instances
      */
     "mediaSilencedInstances": string;
     /**
-     * メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。
+     * List the host names of the instances that you want to media-silence, separated by a new line. All accounts belonging to the listed instances will be treated as sensitive, and can't use custom emojis. This will not affect the blocked instances.
      */
     "mediaSilencedInstancesDescription": string;
+    /**
+     * 連合を許可するサーバー
+     */
+    "federationAllowedHosts": string;
+    /**
+     * 連合を許可するサーバーのホストを改行で区切って設定します。
+     */
+    "federationAllowedHostsDescription": string;
     /**
      * ミュートとブロック
      */
@@ -1029,7 +1011,7 @@ export interface Locale extends ILocale {
      */
     "pinLimitExceeded": string;
     /**
-     * Sharkeyのインストールが完了しました!管理者アカウントを作成しましょう。
+     * Installation of Sharkey has been finished! Please create an admin user.
      */
     "intro": string;
     /**
@@ -1340,10 +1322,6 @@ export interface Locale extends ILocale {
      * ドライブ
      */
     "drive": string;
-    /**
-     * 検索ドライブ
-     */
-    "driveSearchbarPlaceholder": string;
     /**
      * ファイル名
      */
@@ -1365,7 +1343,7 @@ export interface Locale extends ILocale {
      */
     "selectFolders": string;
     /**
-     * ファイルが選択されていません
+     * No file selected
      */
     "fileNotSelected": string;
     /**
@@ -1396,6 +1374,10 @@ export interface Locale extends ILocale {
      * ファイルを追加
      */
     "addFile": string;
+    /**
+     * ファイルを表示
+     */
+    "showFile": string;
     /**
      * ドライブは空です
      */
@@ -1444,10 +1426,6 @@ export interface Locale extends ILocale {
      * バナー
      */
     "banner": string;
-    /**
-     * 背景
-     */
-    "background": string;
     /**
      * センシティブなメディアの表示
      */
@@ -1741,7 +1719,7 @@ export interface Locale extends ILocale {
      */
     "enableServiceworker": string;
     /**
-     * ユーザー名を改行で区切って指定します
+     * List one username per line. Use "*@instance.com" to specify all users of an instance
      */
     "antennaUsersDescription": string;
     /**
@@ -1817,7 +1795,7 @@ export interface Locale extends ILocale {
      */
     "about": string;
     /**
-     * Sharkeyについて
+     * About Sharkey
      */
     "aboutMisskey": string;
     /**
@@ -1856,6 +1834,10 @@ export interface Locale extends ILocale {
      * モデレーションノート
      */
     "moderationNote": string;
+    /**
+     * モデレーター間でだけ共有されるメモを記入することができます。
+     */
+    "moderationNoteDescription": string;
     /**
      * モデレーションノートを追加する
      */
@@ -1984,14 +1966,6 @@ export interface Locale extends ILocale {
      * {user}のノート
      */
     "noteOf": ParameterizedString<"user">;
-    /**
-     * すべての返信の内容を表示する
-     */
-    "expandAllCws": string;
-    /**
-     * すべての返信の内容を隠す
-     */
-    "collapseAllCws": string;
     /**
      * 引用付き
      */
@@ -2001,7 +1975,7 @@ export interface Locale extends ILocale {
      */
     "quoteQuestion": string;
     /**
-     * クリップボードのテキストが長いです。テキストファイルとして添付しますか?
+     * The text in clipboard is long. Would you like to attach it as a text file?
      */
     "attachAsFileQuestion": string;
     /**
@@ -2021,7 +1995,7 @@ export interface Locale extends ILocale {
      */
     "signinRequired": string;
     /**
-     * 続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります
+     * To continue, you need to go to your instance to perform this action or sign up / log in to the instance you are trying to interact with.
      */
     "signinOrContinueOnRemote": string;
     /**
@@ -2109,15 +2083,27 @@ export interface Locale extends ILocale {
      */
     "native": string;
     /**
-     * メニューをドロワーで表示しない
+     * メニューのスタイル
      */
-    "disableDrawer": string;
+    "menuStyle": string;
+    /**
+     * スタイル
+     */
+    "style": string;
+    /**
+     * ドロワー
+     */
+    "drawer": string;
+    /**
+     * ポップアップ
+     */
+    "popup": string;
     /**
      * ノートのアクションをホバー時のみ表示する
      */
     "showNoteActionsOnlyHover": string;
     /**
-     * ノートのリアクション数を表示する
+     * Show the number of reactions in notes
      */
     "showReactionsCount": string;
     /**
@@ -2168,10 +2154,6 @@ export interface Locale extends ILocale {
      * フォントサイズ
      */
     "fontSize": string;
-    /**
-     * コーナーの丸み
-     */
-    "cornerRadius": string;
     /**
      * 画像が1枚のみのメディアリストの高さ
      */
@@ -2188,10 +2170,6 @@ export interface Locale extends ILocale {
      * 画像を新しいタブで開く
      */
     "openImageInNewTab": string;
-    /**
-     * 代替テキストを入れ忘れたときに警告する
-     */
-    "warnForMissingAltText": string;
     /**
      * ダッシュボード
      */
@@ -2320,14 +2298,6 @@ export interface Locale extends ILocale {
      * s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。
      */
     "s3ForcePathStyleDesc": string;
-    /**
-     * DeepLX-JS を使用する (認証キー不要)
-     */
-    "deeplFreeMode": string;
-    /**
-     * DeepLX-JSの設定方法については、ドキュメントを参照してください。
-     */
-    "deeplFreeModeDescription": string;
     /**
      * サーバーログ
      */
@@ -2389,7 +2359,7 @@ export interface Locale extends ILocale {
      */
     "notUseSound": string;
     /**
-     * Sharkeyがアクティブな時のみサウンドを出力する
+     * Output sounds only if Sharkey is active.
      */
     "useSoundOnlyWhenActive": string;
     /**
@@ -2453,9 +2423,17 @@ export interface Locale extends ILocale {
      */
     "scratchpad": string;
     /**
-     * スクラッチパッドは、AiScriptの実験環境を提供します。Sharkeyと対話するコードの記述、実行、結果の確認ができます。
+     * The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Sharkey in it.
      */
     "scratchpadDescription": string;
+    /**
+     * UIインスペクター
+     */
+    "uiInspector": string;
+    /**
+     * メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。
+     */
+    "uiInspectorDescription": string;
     /**
      * 出力
      */
@@ -2844,26 +2822,6 @@ export interface Locale extends ILocale {
      * 表示する通知の種別を選択してください。
      */
     "notificationSettingDesc": string;
-    /**
-     * 未読の通知があるときにタブのアイコンを目立たせる
-     */
-    "enableFaviconNotificationDot": string;
-    /**
-     * タブアイコン強調機能の動作確認
-     */
-    "verifyNotificationDotWorkingButton": string;
-    /**
-     * このサーバーは現時点ではタブアイコン強調機能をサポートしていません。
-     */
-    "notificationDotNotWorking": string;
-    /**
-     * タブアイコン強調機能は、このサーバーで正しく機能しています。
-     */
-    "notificationDotWorking": string;
-    /**
-     * タブアイコン強調機能が機能しない場合は、管理者にドキュメントを確認するように依頼してください {link}
-     */
-    "notificationDotNotWorkingAdvice": ParameterizedString<"link">;
     /**
      * グローバル設定を使う
      */
@@ -2913,7 +2871,7 @@ export interface Locale extends ILocale {
      */
     "reportAbuse": string;
     /**
-     * ブーストを通報
+     * Report boost
      */
     "reportAbuseRenote": string;
     /**
@@ -2940,22 +2898,10 @@ export interface Locale extends ILocale {
      * 通報元
      */
     "reporterOrigin": string;
-    /**
-     * リモートサーバーに通報を転送する
-     */
-    "forwardReport": string;
-    /**
-     * リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。
-     */
-    "forwardReportIsAnonymous": string;
     /**
      * 送信
      */
     "send": string;
-    /**
-     * 対応済みにする
-     */
-    "abuseMarkAsResolved": string;
     /**
      * 新しいタブで開く
      */
@@ -3029,7 +2975,7 @@ export interface Locale extends ILocale {
      */
     "private": string;
     /**
-     * Sharkeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。
+     * Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。
      */
     "i18nInfo": ParameterizedString<"link">;
     /**
@@ -3049,7 +2995,7 @@ export interface Locale extends ILocale {
      */
     "repliesCount": string;
     /**
-     * ブーストした数
+     * Number of boosts sent
      */
     "renotesCount": string;
     /**
@@ -3057,7 +3003,7 @@ export interface Locale extends ILocale {
      */
     "repliedCount": string;
     /**
-     * ブーストされた数
+     * Number of boosts received
      */
     "renotedCount": string;
     /**
@@ -3120,26 +3066,6 @@ export interface Locale extends ILocale {
      * 添付画像のサムネイルをオリジナル画質にする
      */
     "loadRawImages": string;
-    /**
-     * 返信にサーバー情報を表示する
-     */
-    "showTickerOnReplies": string;
-    /**
-     * 検索MFMの検索エンジン
-     */
-    "searchEngine": string;
-    /**
-     * カスタム
-     */
-    "searchEngineOther": string;
-    /**
-     * カスタム検索エンジンのURIは、"https://www.google.com/search?q=\{query}" や "https://www.google.com/search?q=%s" のような形式で入力する必要があります。
-     */
-    "searchEngineCustomURIDescription": string;
-    /**
-     * カスタム検索エンジン URI
-     */
-    "searchEngineCusomURI": string;
     /**
      * アニメーション画像を再生しない
      */
@@ -3208,14 +3134,6 @@ export interface Locale extends ILocale {
      * オフにすると、「みつける」にアカウントが載らなくなります。
      */
     "makeExplorableDescription": string;
-    /**
-     * 公開ノートをインデックス不可にする
-     */
-    "makeIndexable": string;
-    /**
-     * ノート検索があなたの公開ノートをインデックス化しないようにします。
-     */
-    "makeIndexableDescription": string;
     /**
      * タイムラインのノートを離して表示
      */
@@ -3241,7 +3159,7 @@ export interface Locale extends ILocale {
      */
     "narrow": string;
     /**
-     * 設定はページリロード後に反映されます。今すぐリロードしますか?
+     * 設定はページリロード後に反映されます。
      */
     "reloadToApplySetting": string;
     /**
@@ -3273,7 +3191,8 @@ export interface Locale extends ILocale {
      */
     "sendErrorReports": string;
     /**
-     * オンにすると、問題が発生したときにエラーの詳細情報がSharkeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。
+     * When turned on, detailed error information will be shared with Sharkey when a problem occurs, helping to improve the quality of Sharkey.
+     * This will include information such the version of your OS, what browser you're using, your activity in Sharkey, etc.
      */
     "sendErrorReportsDescription": string;
     /**
@@ -3529,7 +3448,7 @@ export interface Locale extends ILocale {
      */
     "noMaintainerInformationWarning": string;
     /**
-     * 問い合わせ先URLが設定されていません。
+     * Contact URL is not set.
      */
     "noInquiryUrlWarning": string;
     /**
@@ -3653,7 +3572,7 @@ export interface Locale extends ILocale {
      */
     "learnMore": string;
     /**
-     * Sharkeyが更新されました!
+     * Sharkey has been updated!
      */
     "misskeyUpdated": string;
     /**
@@ -3673,7 +3592,7 @@ export interface Locale extends ILocale {
      */
     "accountDeletionInProgress": string;
     /**
-     * サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。
+     * A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later.
      */
     "usernameInfo": string;
     /**
@@ -3732,10 +3651,6 @@ export interface Locale extends ILocale {
      * アカウント登録にメールアドレスを必須にする
      */
     "emailRequiredForSignup": string;
-    /**
-     * アカウント登録を承認制にする
-     */
-    "approvalRequiredForSignup": string;
     /**
      * 未読
      */
@@ -3793,14 +3708,13 @@ export interface Locale extends ILocale {
      */
     "incorrectPassword": string;
     /**
-     * 「{choice}」に投票しますか?
+     * ワンタイムパスワードが間違っているか、期限切れになっています。
      */
-    "voteConfirm": ParameterizedString<"choice">;
+    "incorrectTotp": string;
     /**
      * 「{choice}」に投票しますか?
-     *  確認後、選択肢を増やすことができます。
      */
-    "voteConfirmMulti": ParameterizedString<"choice">;
+    "voteConfirm": ParameterizedString<"choice">;
     /**
      * 隠す
      */
@@ -3941,10 +3855,6 @@ export interface Locale extends ILocale {
      * 未対応の通報があります。
      */
     "thereIsUnresolvedAbuseReportWarning": string;
-    /**
-     * 承認待ちのユーザーがいます。
-     */
-    "pendingUserApprovals": string;
     /**
      * 推奨
      */
@@ -3977,26 +3887,6 @@ export interface Locale extends ILocale {
      * アカウント削除
      */
     "deleteAccount": string;
-    /**
-     * 承認する
-     */
-    "approveAccount": string;
-    /**
-     * 拒否と削除
-     */
-    "denyAccount": string;
-    /**
-     * 承認済み
-     */
-    "approved": string;
-    /**
-     * 承認されていない
-     */
-    "notApproved": string;
-    /**
-     * 承認状況
-     */
-    "approvalStatus": string;
     /**
      * ドキュメント
      */
@@ -4009,30 +3899,6 @@ export interface Locale extends ILocale {
      * 多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。
      */
     "numberOfPageCacheDescription": string;
-    /**
-     * スレッド内の返信数
-     */
-    "numberOfReplies": string;
-    /**
-     * この数値を大きくすると、より多くの返信が表示されます。この値を大きくしすぎると、UIが窮屈になって読みにくくなることがあります。
-     */
-    "numberOfRepliesDescription": string;
-    /**
-     * ブースト設定
-     */
-    "boostSettings": string;
-    /**
-     * 公開範囲セレクターを表示
-     */
-    "showVisibilitySelectorOnBoost": string;
-    /**
-     * 無効の場合、以下で設定したデフォルトの公開範囲が使用され、セレクターは表示されません。
-     */
-    "showVisibilitySelectorOnBoostDescription": string;
-    /**
-     * デフォルトのブースト公開範囲
-     */
-    "visibilityOnBoost": string;
     /**
      * ログアウトしますか?
      */
@@ -4209,10 +4075,6 @@ export interface Locale extends ILocale {
      * いいねを解除
      */
     "unlike": string;
-    /**
-     * 絵文字のようなデフォルト
-     */
-    "defaultLike": string;
     /**
      * いいね数
      */
@@ -4230,17 +4092,13 @@ export interface Locale extends ILocale {
      */
     "remindMeLater": string;
     /**
-     * Sharkeyを気に入っていただけましたか?
+     * Have you taken a liking to Sharkey?
      */
     "didYouLikeMisskey": string;
     /**
-     * Sharkeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!
+     * {host} uses the free software, Sharkey. We would highly appreciate your donations so development of Sharkey can continue!
      */
     "pleaseDonate": ParameterizedString<"host">;
-    /**
-     * インスタンス管理者への寄付によって{host}を直接サポートすることもできます。
-     */
-    "pleaseDonateInstance": ParameterizedString<"host">;
     /**
      * 対応するソースコードは{anchor}から利用可能です。
      */
@@ -4350,45 +4208,13 @@ export interface Locale extends ILocale {
      */
     "thisPostMayBeAnnoyingIgnore": string;
     /**
-     * やめる
-     */
-    "thisPostIsMissingAltTextCancel": string;
-    /**
-     * このまま投稿
-     */
-    "thisPostIsMissingAltTextIgnore": string;
-    /**
-     * 代替テキストがないファイルが添付されています。すべての添付ファイルに代替テキストを含むようにしてください。
-     */
-    "thisPostIsMissingAltText": string;
-    /**
-     * ブーストのスマート省略
+     * Collapse boosts you've already seen
      */
     "collapseRenotes": string;
     /**
-     * リアクションやブーストをしたことがあるノートをたたんで表示します。
+     * Collapse boosts that you have boosted or reacted to
      */
     "collapseRenotesDescription": string;
-    /**
-     * 返信元のノートを折りたたむ
-     */
-    "collapseNotesRepliedTo": string;
-    /**
-     * ファイルを折りたたむ
-     */
-    "collapseFiles": string;
-    /**
-     * CWを展開する
-     */
-    "uncollapseCW": string;
-    /**
-     * 長い投稿を常に展開する
-     */
-    "expandLongNote": string;
-    /**
-     * 会話スレッドを自動で読み込む
-     */
-    "autoloadConversation": string;
     /**
      * サーバー内部エラー
      */
@@ -4429,10 +4255,6 @@ export interface Locale extends ILocale {
      * 現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。
      */
     "invitationRequiredToRegister": string;
-    /**
-     * 現在このサーバーは承認制です。参加したい理由を記入し、承認された方のみ登録できます。
-     */
-    "approvalRequiredToRegister": string;
     /**
      * このサーバーではメール配信はサポートされていません
      */
@@ -4545,6 +4367,10 @@ export interface Locale extends ILocale {
      * リモートサーバーのチャートを生成
      */
     "enableChartsForFederatedInstances": string;
+    /**
+     * リモートサーバーの情報を取得
+     */
+    "enableStatsForFederatedInstances": string;
     /**
      * ノートのアクションにクリップを追加
      */
@@ -4554,7 +4380,7 @@ export interface Locale extends ILocale {
      */
     "reactionsDisplaySize": string;
     /**
-     * リアクションの最大横幅を制限し、縮小して表示する
+     * Limits the maximum width of reactions and display them in reduced size.
      */
     "limitWidthOfReaction": string;
     /**
@@ -4601,10 +4427,6 @@ export interface Locale extends ILocale {
      * 常に広告を表示する
      */
     "forceShowAds": string;
-    /**
-     * にゃんこフレンド :3
-     */
-    "oneko": string;
     /**
      * メモを追加
      */
@@ -4618,7 +4440,7 @@ export interface Locale extends ILocale {
      */
     "reactionsList": string;
     /**
-     * ブースト一覧
+     * Boosts
      */
     "renotesList": string;
     /**
@@ -4738,15 +4560,15 @@ export interface Locale extends ILocale {
      */
     "specifyUser": string;
     /**
-     * 照会しますか?
+     * Are you sure that you want to look this up?
      */
     "lookupConfirm": string;
     /**
-     * ハッシュタグのページを開きますか?
+     * Are you sure you want to open this hashtags page?
      */
     "openTagPageConfirm": string;
     /**
-     * ホスト指定
+     * Specify a host
      */
     "specifyHost": string;
     /**
@@ -4782,7 +4604,7 @@ export interface Locale extends ILocale {
      */
     "later": string;
     /**
-     * Sharkeyへ
+     * To Sharkey
      */
     "goToMisskey": string;
     /**
@@ -4801,22 +4623,6 @@ export interface Locale extends ILocale {
      * サーバーのマシン情報を公開する
      */
     "enableServerMachineStats": string;
-    /**
-     * 実績を有効にする
-     */
-    "enableAchievements": string;
-    /**
-     * オフにすると実績システムは無効になります。
-     */
-    "turnOffAchievements": string;
-    /**
-     * botのハッシュタグ追加を許可する
-     */
-    "enableBotTrending": string;
-    /**
-     * オフにするとボットがハッシュタグを入力しなくなります。
-     */
-    "turnOffBotTrending": string;
     /**
      * ユーザーごとのIdenticon生成を有効にする
      */
@@ -4934,11 +4740,11 @@ export interface Locale extends ILocale {
      */
     "useSecurityKey": string;
     /**
-     * 返信
+     * Replies
      */
     "replies": string;
     /**
-     * ブースト
+     * Boosts
      */
     "renotes": string;
     /**
@@ -4957,14 +4763,6 @@ export interface Locale extends ILocale {
      * デバイスの画面を常にオンにする
      */
     "keepScreenOn": string;
-    /**
-     * クリックしてノートを開く
-     */
-    "clickToOpen": string;
-    /**
-     * ボットをタイムラインに表示
-     */
-    "showBots": string;
     /**
      * このリンク先の所有者であることが確認されました
      */
@@ -4990,7 +4788,7 @@ export interface Locale extends ILocale {
      */
     "dateAndTime": string;
     /**
-     * ブーストを表示
+     * Show boosts
      */
     "showRenotes": string;
     /**
@@ -5046,7 +4844,7 @@ export interface Locale extends ILocale {
      */
     "sourceCode": string;
     /**
-     * ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。
+     * The source code is not yet available. Please contact your administrator to fix this problem.
      */
     "sourceCodeIsNotYetProvided": string;
     /**
@@ -5054,7 +4852,7 @@ export interface Locale extends ILocale {
      */
     "repositoryUrl": string;
     /**
-     * ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Sharkeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://activitypub.software/TransFem-org/Sharkey/ と記入します。
+     * If there is a repository where the source code is publicly available, enter its URL. If you are using Sharkey as-is (without any changes to the source code), enter https://activitypub.software/TransFem-org/Sharkey/.
      */
     "repositoryUrlDescription": string;
     /**
@@ -5093,14 +4891,6 @@ export interface Locale extends ILocale {
      * 利用規約・プライバシーポリシー
      */
     "tosAndPrivacyPolicy": string;
-    /**
-     * 寄付する
-     */
-    "donation": string;
-    /**
-     * 寄付URL
-     */
-    "donationUrl": string;
     /**
      * アイコンデコレーション
      */
@@ -5178,7 +4968,7 @@ export interface Locale extends ILocale {
      */
     "overwriteContentConfirm": string;
     /**
-     * 季節に応じた画面の演出
+     * Seasonal screen effects
      */
     "seasonalScreenEffect": string;
     /**
@@ -5294,7 +5084,7 @@ export interface Locale extends ILocale {
      */
     "keepOriginalFilenameDescription": string;
     /**
-     * 説明文はありません
+     * No description
      */
     "noDescription": string;
     /**
@@ -5314,7 +5104,7 @@ export interface Locale extends ILocale {
      */
     "confirmWhenRevealingSensitiveMedia": string;
     /**
-     * センシティブなメディアです。表示しますか?
+     * This media might be sensitive. Are you sure you want to reveal it?
      */
     "sensitiveMediaRevealConfirm": string;
     /**
@@ -5325,31 +5115,134 @@ export interface Locale extends ILocale {
      * 作成したアンテナ
      */
     "createdAntennas": string;
-    "_delivery": {
-        /**
-         * 配信状態
-         */
-        "status": string;
-        /**
-         * 配信停止
-         */
-        "stop": string;
-        /**
-         * 配信再開
-         */
-        "resume": string;
-        "_type": {
-            /**
-             * 配信中
-             */
-            "none": string;
-            /**
-             * 手動停止中
-             */
-            "manuallySuspended": string;
-            /**
-             * サーバー削除のため停止中
-             */
+    /**
+     * {x}から
+     */
+    "fromX": ParameterizedString<"x">;
+    /**
+     * 埋め込みコードを生成
+     */
+    "genEmbedCode": string;
+    /**
+     * このユーザーのノート一覧
+     */
+    "noteOfThisUser": string;
+    /**
+     * これ以上このクリップにノートを追加できません。
+     */
+    "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;
+    "_abuseUserReport": {
+        /**
+         * 転送
+         */
+        "forward": string;
+        /**
+         * 匿名のシステムアカウントとして、リモートサーバーに通報を転送します。
+         */
+        "forwardDescription": string;
+        /**
+         * 解決
+         */
+        "resolve": string;
+        /**
+         * 是認
+         */
+        "accept": string;
+        /**
+         * 否認
+         */
+        "reject": string;
+        /**
+         * 内容が正当である通報に対応した場合は「是認」を選択し、肯定的にケースが解決されたことをマークします。
+         * 内容が正当でない通報の場合は「否認」を選択し、否定的にケースが解決されたことをマークします。
+         */
+        "resolveTutorial": string;
+    };
+    "_delivery": {
+        /**
+         * 配信状態
+         */
+        "status": string;
+        /**
+         * Suspend delivery
+         */
+        "stop": string;
+        /**
+         * Resume delivery
+         */
+        "resume": string;
+        "_type": {
+            /**
+             * 配信中
+             */
+            "none": string;
+            /**
+             * 手動停止中
+             */
+            "manuallySuspended": string;
+            /**
+             * サーバー削除のため停止中
+             */
             "goneSuspended": string;
             /**
              * サーバー応答なしのため停止中
@@ -5460,6 +5353,10 @@ export interface Locale extends ILocale {
          * オンにすると、このお知らせは通知されず、既読にする必要もなくなります。
          */
         "silenceDescription": string;
+        /**
+         * New
+         */
+        "new": string;
     };
     "_initialAccountSetting": {
         /**
@@ -5507,7 +5404,7 @@ export interface Locale extends ILocale {
          */
         "haveFun": ParameterizedString<"name">;
         /**
-         * このまま{name}(Sharkey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。
+         * You can proceed to a tutorial on how to use {name} (Sharkey) or you can exit the setup here and start using it immediately.
          */
         "youCanContinueTutorial": ParameterizedString<"name">;
         /**
@@ -5546,7 +5443,7 @@ export interface Locale extends ILocale {
              */
             "title": string;
             /**
-             * ここでは、Sharkeyの基本的な使い方や機能を確認できます。
+             * Here, you can learn the basics of using Sharkey and its features.
              */
             "description": string;
         };
@@ -5556,7 +5453,7 @@ export interface Locale extends ILocale {
              */
             "title": string;
             /**
-             * Sharkeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。
+             * Posts on Sharkey are called 'Notes.' Notes are arranged chronologically on the timeline and are updated in real-time.
              */
             "description": string;
             /**
@@ -5586,7 +5483,7 @@ export interface Locale extends ILocale {
              */
             "description": string;
             /**
-             * リアクションは、ノートの「{reaction}」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!
+             * Reactions can be added by clicking the '{reaction}' button on the note. Try reacting to this sample note!
              */
             "letsTryReacting": ParameterizedString<"reaction">;
             /**
@@ -5598,7 +5495,7 @@ export interface Locale extends ILocale {
              */
             "reactNotification": string;
             /**
-             * 「{undo}」ボタンを押すとリアクションを取り消すことができます。
+             * You can undo a reaction by pressing the '{undo}' button.
              */
             "reactDone": ParameterizedString<"undo">;
         };
@@ -5608,7 +5505,7 @@ export interface Locale extends ILocale {
              */
             "title": string;
             /**
-             * Sharkeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。
+             * Sharkey provides multiple timelines based on usage (some may not be available depending on the server's policies).
              */
             "description1": string;
             /**
@@ -5635,6 +5532,10 @@ export interface Locale extends ILocale {
              * その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。
              */
             "description3": ParameterizedString<"link">;
+            /**
+             * You can view notes from connected servers picked by your admins.
+             */
+            "bubble": string;
         };
         "_postNote": {
             /**
@@ -5642,7 +5543,7 @@ export interface Locale extends ILocale {
              */
             "title": string;
             /**
-             * Sharkeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。
+             * When posting a note on Sharkey, various options are available. The posting form looks like this.
              */
             "description1": string;
             "_visibility": {
@@ -5655,11 +5556,11 @@ export interface Locale extends ILocale {
                  */
                 "public": string;
                 /**
-                 * ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・ブーストから、他のユーザーも見ることができます。
+                 * Public only on the Home timeline. People visiting your profile, via followers, and through boosts can see it.
                  */
                 "home": string;
                 /**
-                 * フォロワーにのみ公開。本人以外がブーストすることはできず、またフォロワー以外は閲覧できません。
+                 * Visible to followers only. Only followers can see it and no one else, and it cannot be boosted by others.
                  */
                 "followers": string;
                 /**
@@ -5738,11 +5639,11 @@ export interface Locale extends ILocale {
         };
         "_done": {
             /**
-             * チュートリアルは終了です🎉
+             * The tutorial is complete! 🎉
              */
             "title": string;
             /**
-             * ここで紹介した機能はほんの一部にすぎません。Sharkeyの使い方をより詳しく知るには、{link}をご覧ください。
+             * The functions introduced here are just a small part. For a more detailed understanding of using Sharkey, please refer to {link}.
              */
             "description": ParameterizedString<"link">;
         };
@@ -5764,6 +5665,10 @@ export interface Locale extends ILocale {
          * グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。
          */
         "global": string;
+        /**
+         * In the Bubble timeline, you can see notes from connected servers picked by your admins.
+         */
+        "bubble": string;
     };
     "_serverRules": {
         /**
@@ -5817,13 +5722,33 @@ export interface Locale extends ILocale {
          */
         "fanoutTimelineDbFallbackDescription": string;
         /**
-         * 問い合わせ先URL
+         * 有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。
+         */
+        "reactionsBufferingDescription": string;
+        /**
+         * Contact URL
          */
         "inquiryUrl": string;
         /**
-         * サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。
+         * Specify the URL of a web page that contains a contact form or the instance operators' contact information.
          */
         "inquiryUrlDescription": string;
+        /**
+         * 一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。
+         */
+        "thisSettingWillAutomaticallyOffWhenModeratorsInactive": string;
+        /**
+         * Logo URL
+         */
+        "sidebarLogoUrl": string;
+        /**
+         * Specifies the logo to use instead of the regular icon in high definition, dynamic-width scenarios.
+         */
+        "sidebarLogoDescription": string;
+        /**
+         * E.g. In the sidebar, to visitors and in the "About" page.
+         */
+        "sidebarLogoUsageExample": string;
     };
     "_accountMigration": {
         /**
@@ -5857,15 +5782,14 @@ export interface Locale extends ILocale {
          */
         "moveCannotBeUndone": string;
         /**
-         * 新しいアカウントへ移行します。
-         *  ・フォロワーが新しいアカウントを自動でフォローします
-         *  ・このアカウントからのフォローは全て解除されます
-         *  ・このアカウントではノートの作成などができなくなります
+         * This will migrate your account to a different one.
+         *  ・Followers from this account will automatically be migrated to the new account
+         *  ・This account will unfollow all users it is currently following
+         *  ・You will be unable to create new notes etc. on this account
          *
-         * フォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。
-         * リスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。
+         * While migration of followers is automatic, you must manually prepare some steps to migrate the list of users you are following. To do so, carry out a follows export that you will later import on the new account in the settings menu. The same procedure applies to your lists as well as your muted and blocked users.
          *
-         * (この説明はこのサーバー(Sharkey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。)
+         * (This explanation applies to Sharkey v13.12.0 and later. Other ActivityPub software, such as Mastodon, might function differently.)
          */
         "moveAccountDescription": string;
         /**
@@ -5913,7 +5837,7 @@ export interface Locale extends ILocale {
                  */
                 "description": string;
                 /**
-                 * 良いSharkeyライフを!
+                 * Have a good time with Sharkey!
                  */
                 "flavor": string;
             };
@@ -6237,7 +6161,7 @@ export interface Locale extends ILocale {
                  */
                 "description": string;
                 /**
-                 * Sharkeyを使ってくれてありがとう!
+                 * Thank you for using Sharkey!
                  */
                 "flavor": string;
             };
@@ -6441,11 +6365,11 @@ export interface Locale extends ILocale {
                  */
                 "title": string;
                 /**
-                 * "I ❤ #Sharkey"を投稿した
+                 * Post "I ❤ #Sharkey"
                  */
                 "description": string;
                 /**
-                 * Sharkeyを使ってくださりありがとうございます! by 開発チーム
+                 * Sharkey's development team greatly appreciates your support!
                  */
                 "flavor": string;
             };
@@ -6465,17 +6389,17 @@ export interface Locale extends ILocale {
                  */
                 "title": string;
                 /**
-                 * クライアントを起動してから30分以上経過した
+                 * Keep Sharkey opened for at least 30 minutes
                  */
                 "description": string;
             };
             "_client60min": {
                 /**
-                 * Sharkeyの見すぎ
+                 * No "Miss" in Sharkey
                  */
                 "title": string;
                 /**
-                 * クライアントを起動してから60分以上経過した
+                 * Keep Sharkey opened for at least 60 minutes
                  */
                 "description": string;
             };
@@ -6695,7 +6619,7 @@ export interface Locale extends ILocale {
                  */
                 "description": string;
                 /**
-                 * Misskey-Misskey La-Tu-Ma
+                 * Sharkey-Sharkey La-Tu-Ma
                  */
                 "flavor": string;
             };
@@ -6711,7 +6635,7 @@ export interface Locale extends ILocale {
             };
             "_tutorialCompleted": {
                 /**
-                 * Sharkey初心者講座 修了証
+                 * Sharkey Elementary Course Diploma
                  */
                 "title": string;
                 /**
@@ -6891,10 +6815,6 @@ export interface Locale extends ILocale {
              * グローバルタイムラインの閲覧
              */
             "gtlAvailable": string;
-            /**
-             * バブルタイムラインの閲覧
-             */
-            "btlAvailable": string;
             /**
              * ローカルタイムラインの閲覧
              */
@@ -6903,10 +6823,6 @@ export interface Locale extends ILocale {
              * パブリック投稿の許可
              */
             "canPublicNote": string;
-            /**
-             * ノートのインポートが可能
-             */
-            "canImportNotes": string;
             /**
              * ノート内の最大メンション数
              */
@@ -6944,7 +6860,7 @@ export interface Locale extends ILocale {
              */
             "alwaysMarkNsfw": string;
             /**
-             * アイコンとバナーの更新を許可
+             * Allow users to edit their avatar or banner
              */
             "canUpdateBioMedia": string;
             /**
@@ -7003,6 +6919,34 @@ export interface Locale extends ILocale {
              * アイコンデコレーションの最大取付個数
              */
             "avatarDecorationLimit": string;
+            /**
+             * アンテナのインポートを許可
+             */
+            "canImportAntennas": string;
+            /**
+             * ブロックのインポートを許可
+             */
+            "canImportBlocking": string;
+            /**
+             * フォローのインポートを許可
+             */
+            "canImportFollowing": string;
+            /**
+             * ミュートのインポートを許可
+             */
+            "canImportMuting": string;
+            /**
+             * リストのインポートを許可
+             */
+            "canImportUserLists": string;
+            /**
+             * Can view the bubble timeline
+             */
+            "btlAvailable": string;
+            /**
+             * Can import notes
+             */
+            "canImportNotes": string;
         };
         "_condition": {
             /**
@@ -7030,11 +6974,11 @@ export interface Locale extends ILocale {
              */
             "isSuspended": string;
             /**
-             * 鍵アカウントユーザー
+             * Private account
              */
             "isLocked": string;
             /**
-             * 「アカウントを見つけやすくする」が有効なユーザー
+             * Account is discoverable
              */
             "isExplorable": string;
             /**
@@ -7135,7 +7079,7 @@ export interface Locale extends ILocale {
          */
         "smtp": string;
         /**
-         * このメールアドレスでは登録できません
+         * This email address is banned
          */
         "banned": string;
     };
@@ -7167,11 +7111,11 @@ export interface Locale extends ILocale {
          */
         "emailSent": ParameterizedString<"email">;
         /**
-         * アカウントが作成され、承認待ちの状態です。
+         * Your account has been created and is awaiting approval.
          */
         "approvalPending": string;
         /**
-         * インスタンスに参加したい理由を入力してください。
+         * Please enter a reason as to why you want to join the instance.
          */
         "reasonInfo": string;
     };
@@ -7397,11 +7341,11 @@ export interface Locale extends ILocale {
     };
     "_aboutMisskey": {
         /**
-         * Sharkeyは、Misskeyをベースにしたオープンソースのソフトウェアです。
+         * Sharkey is open-source software based on Misskey which has been in development by syuilo since 2014.
          */
         "about": string;
         /**
-         * 主なコントリビューター
+         * コントリビューター
          */
         "contributors": string;
         /**
@@ -7413,29 +7357,21 @@ export interface Locale extends ILocale {
          */
         "source": string;
         /**
-         * Misskey オリジナル
+         * Misskey original
          */
         "original": string;
         /**
-         * Sharkey オリジナル
-         */
-        "original_sharkey": string;
-        /**
-         * {name}はオリジナルのSharkeyを改変したバージョンを使用しています。
+         * {name} uses a modified version of the original Sharkey.
          */
         "thisIsModifiedVersion": ParameterizedString<"name">;
         /**
-         * Sharkeyを翻訳
+         * Translate Sharkey
          */
         "translation": string;
         /**
          * Misskeyに寄付
          */
         "donate": string;
-        /**
-         * Sharkeyに寄付
-         */
-        "donate_sharkey": string;
         /**
          * 他にも多くの方が支援してくれています。ありがとうございます🥰
          */
@@ -7449,7 +7385,15 @@ export interface Locale extends ILocale {
          */
         "projectMembers": string;
         /**
-         * テスター
+         * Sharkey original
+         */
+        "original_sharkey": string;
+        /**
+         * Donate to Sharkey
+         */
+        "donate_sharkey": string;
+        /**
+         * Testers
          */
         "testers": string;
     };
@@ -7495,7 +7439,7 @@ export interface Locale extends ILocale {
          */
         "quiet": string;
         /**
-         * 警告を無効にする
+         * Disable warning
          */
         "disabled": string;
     };
@@ -7545,7 +7489,7 @@ export interface Locale extends ILocale {
          */
         "nameOnly": string;
         /**
-         * チャンネル外へのブーストと引用ブーストを許可する
+         * Allow boosts and quote outside the channel
          */
         "allowRenoteToExternal": string;
     };
@@ -7583,7 +7527,7 @@ export interface Locale extends ILocale {
     };
     "_instanceMute": {
         /**
-         * ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとブーストをミュートします。
+         * This will mute any notes/boosts from the listed instances, including those of users replying to a user from a muted instance.
          */
         "instanceMuteDescription": string;
         /**
@@ -7845,10 +7789,6 @@ export interface Locale extends ILocale {
              * 入力ボックスの縁取り
              */
             "inputBorder": string;
-            /**
-             * リスト項目の背景 (ホバー)
-             */
-            "listItemHoverBg": string;
             /**
              * ドライブフォルダーの背景
              */
@@ -7919,11 +7859,11 @@ export interface Locale extends ILocale {
          */
         "driveFileDurationWarn": string;
         /**
-         * 長い音声を使用するとSharkeyの使用に支障をきたす可能性があります。それでも続行しますか?
+         * Long audio may disrupt using Sharkey. Still continue?
          */
         "driveFileDurationWarnDescription": string;
         /**
-         * 音声が読み込めませんでした。設定を変更してください
+         * The audio couldn't be loaded. Please make sure you selected an audio file.
          */
         "driveFileError": string;
     };
@@ -8127,7 +8067,7 @@ export interface Locale extends ILocale {
          */
         "backupCodesExhaustedWarning": string;
         /**
-         * 詳細なガイドはこちら
+         * Click here for a detailed guide
          */
         "moreDetailedGuideHere": string;
     };
@@ -8506,6 +8446,10 @@ export interface Locale extends ILocale {
          * アプリケーションにアクセス許可を与えるには、ログインが必要です。
          */
         "pleaseLogin": string;
+        /**
+         * Allowed
+         */
+        "allowed": string;
     };
     "_antennaSources": {
         /**
@@ -8674,14 +8618,14 @@ export interface Locale extends ILocale {
          * クリッカー
          */
         "clicker": string;
-        /**
-         * 検索
-         */
-        "search": string;
         /**
          * 今日誕生日のユーザー
          */
         "birthdayFollowings": string;
+        /**
+         * Search
+         */
+        "search": string;
     };
     "_cw": {
         /**
@@ -8787,7 +8731,7 @@ export interface Locale extends ILocale {
          */
         "remainingSeconds": ParameterizedString<"s">;
         /**
-         * 複数の選択肢
+         * Multiple choices
          */
         "multiple": string;
     };
@@ -8919,33 +8863,45 @@ export interface Locale extends ILocale {
          */
         "changeBanner": string;
         /**
-         * 更新バナー
+         * 内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。
+         */
+        "verifiedLinkDescription": string;
+        /**
+         * 最大{max}つまでデコレーションを付けられます。
+         */
+        "avatarDecorationMax": ParameterizedString<"max">;
+        /**
+         * フォローされた時のメッセージ
+         */
+        "followedMessage": string;
+        /**
+         * フォローされた時に相手に表示する短いメッセージを設定できます。
+         */
+        "followedMessageDescription": string;
+        /**
+         * フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。
+         */
+        "followedMessageDescriptionForLockedAccount": string;
+        /**
+         * Update banner
          */
         "updateBanner": string;
         /**
-         * バナーを削除
+         * Remove banner
          */
         "removeBanner": string;
         /**
-         * 背景を変更する
+         * Change background
          */
         "changeBackground": string;
         /**
-         * 背景を更新する
+         * Update background
          */
         "updateBackground": string;
         /**
-         * 背景を削除する
+         * Remove background
          */
         "removeBackground": string;
-        /**
-         * 内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。
-         */
-        "verifiedLinkDescription": string;
-        /**
-         * 最大{max}つまでデコレーションを付けられます。
-         */
-        "avatarDecorationMax": ParameterizedString<"max">;
     };
     "_exportOrImport": {
         /**
@@ -9107,7 +9063,7 @@ export interface Locale extends ILocale {
          */
         "global": string;
         /**
-         * バッッブル
+         * Bubble
          */
         "bubble": string;
     };
@@ -9352,7 +9308,7 @@ export interface Locale extends ILocale {
              */
             "dynamic": string;
             /**
-             * このブロックは廃止されています。今後は{play}を利用してください。
+             * This block type has been removed. Please use {play} from now on.
              */
             "dynamicDescription": ParameterizedString<"play">;
             /**
@@ -9407,7 +9363,7 @@ export interface Locale extends ILocale {
          */
         "youGotQuote": ParameterizedString<"name">;
         /**
-         * {name}がBoostしました
+         * Boost from {name}
          */
         "youRenoted": ParameterizedString<"name">;
         /**
@@ -9426,10 +9382,6 @@ export interface Locale extends ILocale {
          * アンケートの結果が出ました
          */
         "pollEnded": string;
-        /**
-         * 投稿が編集されました
-         */
-        "edited": string;
         /**
          * 新しい投稿
          */
@@ -9475,7 +9427,7 @@ export interface Locale extends ILocale {
          */
         "likedBySomeUsers": ParameterizedString<"n">;
         /**
-         * {n}人がリノートしました
+         * Boosted by {n} users
          */
         "renotedBySomeUsers": ParameterizedString<"n">;
         /**
@@ -9486,6 +9438,14 @@ export interface Locale extends ILocale {
          * 通知の履歴をリセットする
          */
         "flushNotification": string;
+        /**
+         * {x}のエクスポートが完了しました
+         */
+        "exportOfXCompleted": ParameterizedString<"x">;
+        /**
+         * ログインがありました
+         */
+        "login": string;
         "_types": {
             /**
              * すべて
@@ -9508,7 +9468,7 @@ export interface Locale extends ILocale {
              */
             "reply": string;
             /**
-             * Boost
+             * Boosts
              */
             "renote": string;
             /**
@@ -9539,12 +9499,24 @@ export interface Locale extends ILocale {
              * 実績の獲得
              */
             "achievementEarned": string;
+            /**
+             * エクスポートが完了した
+             */
+            "exportCompleted": string;
+            /**
+             * ログイン
+             */
+            "login": string;
+            /**
+             * 通知のテスト
+             */
+            "test": string;
             /**
              * 連携アプリからの通知
              */
             "app": string;
             /**
-             * 編集済み
+             * Edits
              */
             "edited": string;
         };
@@ -9558,10 +9530,14 @@ export interface Locale extends ILocale {
              */
             "reply": string;
             /**
-             * ブースト
+             * Boost
              */
             "renote": string;
         };
+        /**
+         * Note got edited
+         */
+        "edited": string;
     };
     "_deck": {
         /**
@@ -9760,7 +9736,7 @@ export interface Locale extends ILocale {
              */
             "reply": string;
             /**
-             * Boostされたとき
+             * When boosted
              */
             "renote": string;
             /**
@@ -9778,18 +9754,30 @@ export interface Locale extends ILocale {
              */
             "abuseReport": string;
             /**
-             * ユーザーからの通報を処理したとき
+             * When resolved abuse reports
              */
             "abuseReportResolved": string;
             /**
              * ユーザーが作成されたとき
              */
             "userCreated": string;
+            /**
+             * モデレーターが一定期間非アクティブになったとき
+             */
+            "inactiveModeratorsWarning": string;
+            /**
+             * モデレーターが一定期間非アクティブだったため、システムにより招待制へと変更されたとき
+             */
+            "inactiveModeratorsInvitationOnlyChanged": string;
         };
         /**
          * Webhookを削除しますか?
          */
         "deleteConfirm": string;
+        /**
+         * スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。
+         */
+        "testRemarks": string;
     };
     "_abuseReport": {
         "_notificationRecipient": {
@@ -9816,11 +9804,11 @@ export interface Locale extends ILocale {
                 "webhook": string;
                 "_captions": {
                     /**
-                     * モデレーター権限を持つユーザーのメールアドレスに通知を送ります(通報を受けた時のみ)
+                     * Send an email to the moderators when an abuse report is received.
                      */
                     "mail": string;
                     /**
-                     * 指定したSystemWebhookに通知を送ります(通報を受けた時と通報を解決した時にそれぞれ発信)
+                     * Send a notification to the SystemWebhook when an abuse report is received or resolved.
                      */
                     "webhook": string;
                 };
@@ -9864,10 +9852,6 @@ export interface Locale extends ILocale {
          * ロールのアサイン解除
          */
         "unassignRole": string;
-        /**
-         * 承認済み
-         */
-        "approve": string;
         /**
          * 凍結
          */
@@ -9956,6 +9940,14 @@ export interface Locale extends ILocale {
          * 通報を解決
          */
         "resolveAbuseReport": string;
+        /**
+         * 通報を転送
+         */
+        "forwardAbuseReport": string;
+        /**
+         * 通報のモデレーションノート更新
+         */
+        "updateAbuseReportNote": string;
         /**
          * 招待コードを作成
          */
@@ -10032,362 +10024,64 @@ export interface Locale extends ILocale {
          * ギャラリーの投稿を削除
          */
         "deleteGalleryPost": string;
-    };
-    "_mfm": {
-        /**
-         * この機能は一般的に普及していないため、他のMisskeyフォークを含めた多くのFediverseソフトウェアで表示できないことがあります。
-         */
-        "uncommonFeature": string;
-        /**
-         * MFM はMisskey, Sharkey, Firefish, Akkomaなど、多くの場所で使用できるマークアップ言語です。ここでは、利用できるMFM構文の一覧をご覧いただけます。
-         */
-        "intro": string;
-        /**
-         * SharkeyでFediverseの世界が広がります
-         */
-        "dummy": string;
-        /**
-         * メンション
-         */
-        "mention": string;
-        /**
-         * アットマーク + ユーザー名で、特定のユーザーを示すことができます。
-         */
-        "mentionDescription": string;
         /**
-         * ハッシュタグ
+         * Approved
          */
-        "hashtag": string;
-        /**
-         * ナンバーサイン + タグで、ハッシュタグを示すことができます。
-         */
-        "hashtagDescription": string;
-        /**
-         * URL
-         */
-        "url": string;
-        /**
-         * URLを示すことができます。
-         */
-        "urlDescription": string;
+        "approve": string;
         /**
-         * リンク
+         * Set remote instance as NSFW
          */
-        "link": string;
+        "setRemoteInstanceNSFW": string;
         /**
-         * 文章の特定の範囲を、URLに紐づけることができます。
+         * Set remote instance as NSFW
          */
-        "linkDescription": string;
+        "unsetRemoteInstanceNSFW": string;
         /**
-         * 太字
+         * Rejected reports from remote instance
          */
-        "bold": string;
+        "rejectRemoteInstanceReports": string;
         /**
-         * 文字を太く表示して強調することができます。
+         * Accepted reports from remote instance
          */
-        "boldDescription": string;
+        "acceptRemoteInstanceReports": string;
+    };
+    "_fileViewer": {
         /**
-         * 小文字
+         * ファイルの詳細
          */
-        "small": string;
+        "title": string;
         /**
-         * 内容を小さく・薄く表示させることができます。
+         * ファイルタイプ
          */
-        "smallDescription": string;
+        "type": string;
         /**
-         * 中央寄せ
+         * ファイルサイズ
          */
-        "center": string;
+        "size": string;
         /**
-         * 内容を中央寄せで表示させることができます。
+         * URL
          */
-        "centerDescription": string;
+        "url": string;
         /**
-         * コード(インライン)
+         * 追加日
          */
-        "inlineCode": string;
+        "uploadedAt": string;
         /**
-         * プログラムなどのコードをインラインでシンタックスハイライトします。
+         * 添付されているノート
          */
-        "inlineCodeDescription": string;
+        "attachedNotes": string;
         /**
-         * コード(ブロック)
+         * このページは、このファイルをアップロードしたユーザーしか閲覧できません。
          */
-        "blockCode": string;
+        "thisPageCanBeSeenFromTheAuthor": string;
+    };
+    "_externalResourceInstaller": {
         /**
-         * 複数行のプログラムなどのコードをブロックでシンタックスハイライトします。
+         * 外部サイトからインストール
          */
-        "blockCodeDescription": string;
+        "title": string;
         /**
-         * 数式(インライン)
-         */
-        "inlineMath": string;
-        /**
-         * 数式 (KaTeX形式)をインラインで表示します。
-         */
-        "inlineMathDescription": string;
-        /**
-         * 数式(ブロック)
-         */
-        "blockMath": string;
-        /**
-         * 数式 (KaTeX形式)をブロックで表示します。
-         */
-        "blockMathDescription": string;
-        /**
-         * 引用
-         */
-        "quote": string;
-        /**
-         * 内容が引用であることを示すことができます。
-         */
-        "quoteDescription": string;
-        /**
-         * カスタム絵文字
-         */
-        "emoji": string;
-        /**
-         * コロンでカスタム絵文字名を囲むと、カスタム絵文字を表示させることができます。
-         */
-        "emojiDescription": string;
-        /**
-         * 検索
-         */
-        "search": string;
-        /**
-         * 検索ボックスを表示できます。
-         */
-        "searchDescription": string;
-        /**
-         * 反転
-         */
-        "flip": string;
-        /**
-         * 内容を上下または左右に反転させます。
-         */
-        "flipDescription": string;
-        /**
-         * アニメーション(びよんびよん)
-         */
-        "jelly": string;
-        /**
-         * ゼリーが揺れるような感じのアニメーションをさせます。
-         */
-        "jellyDescription": string;
-        /**
-         * アニメーション(じゃーん)
-         */
-        "tada": string;
-        /**
-         * 「じゃーん!」と強調するような感じのアニメーションをさせます。
-         */
-        "tadaDescription": string;
-        /**
-         * アニメーション(ジャンプ)
-         */
-        "jump": string;
-        /**
-         * 跳ねるアニメーションをさせます。
-         */
-        "jumpDescription": string;
-        /**
-         * アニメーション(バウンド)
-         */
-        "bounce": string;
-        /**
-         * 跳ねて着地するようなアニメーションをさせます。
-         */
-        "bounceDescription": string;
-        /**
-         * アニメーション(ぶるぶる)
-         */
-        "shake": string;
-        /**
-         * 震えるアニメーションをさせます。
-         */
-        "shakeDescription": string;
-        /**
-         * アニメーション(ガタガタ)
-         */
-        "twitch": string;
-        /**
-         * より激しく震えるアニメーションをさせます。
-         */
-        "twitchDescription": string;
-        /**
-         * アニメーション(回転)
-         */
-        "spin": string;
-        /**
-         * 内容を回転させます。
-         */
-        "spinDescription": string;
-        /**
-         * 大
-         */
-        "x2": string;
-        /**
-         * 内容を大きく表示させます。
-         */
-        "x2Description": string;
-        /**
-         * 特大
-         */
-        "x3": string;
-        /**
-         * 内容をより大きく表示させます。
-         */
-        "x3Description": string;
-        /**
-         * 超特大
-         */
-        "x4": string;
-        /**
-         * 内容をさらに大きく表示させます。
-         */
-        "x4Description": string;
-        /**
-         * ぼかし
-         */
-        "blur": string;
-        /**
-         * 内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。
-         */
-        "blurDescription": string;
-        /**
-         * フォント
-         */
-        "font": string;
-        /**
-         * 内容のフォントを指定することができます。
-         */
-        "fontDescription": string;
-        /**
-         * レインボー
-         */
-        "rainbow": string;
-        /**
-         * 内容を虹色で表示させます。
-         */
-        "rainbowDescription": string;
-        /**
-         * キラキラ
-         */
-        "sparkle": string;
-        /**
-         * キラキラと星型のパーティクルを表示させます。
-         */
-        "sparkleDescription": string;
-        /**
-         * 角度変更
-         */
-        "rotate": string;
-        /**
-         * 指定した角度で回転させます。
-         */
-        "rotateDescription": string;
-        /**
-         * 位置変更
-         */
-        "position": string;
-        /**
-         * 位置をずらすことができます。
-         */
-        "positionDescription": string;
-        /**
-         * 切り取り
-         */
-        "crop": string;
-        /**
-         * 内容を切り抜きます。
-         */
-        "cropDescription": string;
-        /**
-         * マウス追従
-         */
-        "followMouse": string;
-        /**
-         * 内容がマウスに追従します。スマホの場合はタップした場所に追従します。
-         */
-        "followMouseDescription": string;
-        /**
-         * 拡大
-         */
-        "scale": string;
-        /**
-         * 内容を引き伸ばして表示します。
-         */
-        "scaleDescription": string;
-        /**
-         * 文字色
-         */
-        "foreground": string;
-        /**
-         * 文字色を変更します。
-         */
-        "foregroundDescription": string;
-        /**
-         * フェード
-         */
-        "fade": string;
-        /**
-         * 内容をフェードイン・フェードアウトさせます。
-         */
-        "fadeDescription": string;
-        /**
-         * 背景色
-         */
-        "background": string;
-        /**
-         * 背景色を変更します。
-         */
-        "backgroundDescription": string;
-        /**
-         * Plain
-         */
-        "plain": string;
-        /**
-         * 内側の構文を全て無効にします。
-         */
-        "plainDescription": string;
-    };
-    "_fileViewer": {
-        /**
-         * ファイルの詳細
-         */
-        "title": string;
-        /**
-         * ファイルタイプ
-         */
-        "type": string;
-        /**
-         * ファイルサイズ
-         */
-        "size": string;
-        /**
-         * URL
-         */
-        "url": string;
-        /**
-         * 追加日
-         */
-        "uploadedAt": string;
-        /**
-         * 添付されているノート
-         */
-        "attachedNotes": string;
-        /**
-         * このページは、このファイルをアップロードしたユーザーしか閲覧できません。
-         */
-        "thisPageCanBeSeenFromTheAuthor": string;
-    };
-    "_externalResourceInstaller": {
-        /**
-         * 外部サイトからインストール
-         */
-        "title": string;
-        /**
-         * 配布元が信頼できるかを確認した上でインストールしてください。
+         * 配布元が信頼できるかを確認した上でインストールしてください。
          */
         "checkVendorBeforeInstall": string;
         "_plugin": {
@@ -10517,44 +10211,6 @@ export interface Locale extends ILocale {
             };
         };
     };
-    "_animatedMFM": {
-        /**
-         * MFMアニメーションを再生
-         */
-        "play": string;
-        /**
-         * MFMアニメーション停止
-         */
-        "stop": string;
-        "_alert": {
-            /**
-             * MFMアニメーションには、高速で点滅したり動いたりするテキスト・絵文字を含む場合があります。
-             */
-            "text": string;
-            /**
-             * 再生する
-             */
-            "confirm": string;
-        };
-    };
-    "_dataRequest": {
-        /**
-         * データリクエスト
-         */
-        "title": string;
-        /**
-         * データリクエストは3日ごとに可能です。
-         */
-        "warn": string;
-        /**
-         * データの保存が完了すると、このアカウントに登録されているメールアドレスにメールが送信されます。
-         */
-        "text": string;
-        /**
-         * データリクエスト実行
-         */
-        "button": string;
-    };
     "_dataSaver": {
         "_media": {
             /**
@@ -10572,7 +10228,7 @@ export interface Locale extends ILocale {
              */
             "title": string;
             /**
-             * アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。
+             * Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic.
              */
             "description": string;
         };
@@ -10809,7 +10465,7 @@ export interface Locale extends ILocale {
          */
         "timeout": string;
         /**
-         * プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。
+         * If it takes longer than this value to get the preview, the preview won't be generated.
          */
         "timeoutDescription": string;
         /**
@@ -10821,7 +10477,7 @@ export interface Locale extends ILocale {
          */
         "maximumContentLengthDescription": string;
         /**
-         * Content-Lengthが取得できた場合のみプレビューを生成
+         * Generate the preview only if we can get Content-Length
          */
         "requireContentLength": string;
         /**
@@ -10837,15 +10493,15 @@ export interface Locale extends ILocale {
          */
         "userAgentDescription": string;
         /**
-         * プレビューを生成するプロキシのエンドポイント
+         * Endpoint for proxy to generate previews
          */
         "summaryProxy": string;
         /**
-         * Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。
+         * Generate previews using Summaly Proxy, instead of Sharkey itself.
          */
         "summaryProxyDescription": string;
         /**
-         * プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。
+         * The following parameters are sent to the proxy as a query string. If the proxy does not support them, the values are ignored.
          */
         "summaryProxyDescription2": string;
     };
@@ -10881,6 +10537,843 @@ export interface Locale extends ILocale {
          */
         "native": string;
     };
+    "_embedCodeGen": {
+        /**
+         * 埋め込みコードをカスタマイズ
+         */
+        "title": string;
+        /**
+         * ヘッダーを表示
+         */
+        "header": string;
+        /**
+         * 自動で続きを読み込む(非推奨)
+         */
+        "autoload": string;
+        /**
+         * 高さの最大値
+         */
+        "maxHeight": string;
+        /**
+         * 0で最大値の設定が無効になります。ウィジェットが縦に伸び続けるのを防ぐために、何らかの値に指定してください。
+         */
+        "maxHeightDescription": string;
+        /**
+         * 高さの最大値制限が無効(0)になっています。これが意図した変更ではない場合は、高さの最大値を何らかの値に設定してください。
+         */
+        "maxHeightWarn": string;
+        /**
+         * プレビュー画面で表示可能な範囲を超えたため、実際に埋め込んだ際とは表示が異なります。
+         */
+        "previewIsNotActual": string;
+        /**
+         * 角丸にする
+         */
+        "rounded": string;
+        /**
+         * 外枠に枠線をつける
+         */
+        "border": string;
+        /**
+         * プレビューに反映
+         */
+        "applyToPreview": string;
+        /**
+         * 埋め込みコードを作成
+         */
+        "generateCode": string;
+        /**
+         * コードが生成されました
+         */
+        "codeGenerated": string;
+        /**
+         * 生成されたコードをウェブサイトに貼り付けてご利用ください。
+         */
+        "codeGeneratedDescription": string;
+    };
+    /**
+     * Approvals
+     */
+    "approvals": string;
+    /**
+     * Open remote profile
+     */
+    "openRemoteProfile": string;
+    /**
+     * Link to external site warning exclusion list
+     */
+    "trustedLinkUrlPatterns": string;
+    /**
+     * Separate with spaces for an AND condition or with line breaks for an OR condition. Using surrounding keywords with slashes will turn them into a regular expression. If you write only the domain name, it will be a backward match.
+     */
+    "trustedLinkUrlPatternsDescription": string;
+    /**
+     * Mutuals
+     */
+    "mutuals": string;
+    /**
+     * Private account
+     */
+    "isLocked": string;
+    /**
+     * Administrator
+     */
+    "isAdmin": string;
+    /**
+     * Bot user
+     */
+    "isBot": string;
+    /**
+     * Open
+     */
+    "open": string;
+    /**
+     * Destination address
+     */
+    "emailDestination": string;
+    /**
+     * Date
+     */
+    "date": string;
+    /**
+     * Quoted.
+     */
+    "quoted": string;
+    /**
+     * Unboosted.
+     */
+    "rmboost": string;
+    /**
+     * Muted
+     */
+    "muted": string;
+    /**
+     * Boosts muted
+     */
+    "renoteMuted": string;
+    /**
+     * Mark all media from user as NSFW
+     */
+    "markAsNSFW": string;
+    /**
+     * Mark as NSFW
+     */
+    "markInstanceAsNSFW": string;
+    /**
+     * Are you sure that you want to mark all media from this account as NSFW?
+     */
+    "nsfwConfirm": string;
+    /**
+     * Are you sure that you want to unmark all media from this account as NSFW?
+     */
+    "unNsfwConfirm": string;
+    /**
+     * Are you sure that you want to approve this account?
+     */
+    "approveConfirm": string;
+    /**
+     * Speak as a cat
+     */
+    "flagSpeakAsCat": string;
+    /**
+     * Your posts will get nyanified when in cat mode. If this isn't working, then please check that you dont have 'Disable cat speak' on under General/Note Display
+     */
+    "flagSpeakAsCatDescription": string;
+    /**
+     * Reject reports from this instance
+     */
+    "rejectReports": string;
+    /**
+     * This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s).
+     */
+    "blockedByBase": string;
+    /**
+     * This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s).
+     */
+    "silencedByBase": string;
+    /**
+     * This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s).
+     */
+    "mediaSilencedByBase": string;
+    /**
+     * Search drive
+     */
+    "driveSearchbarPlaceholder": string;
+    /**
+     * Background
+     */
+    "background": string;
+    /**
+     * Show content for all replies
+     */
+    "expandAllCws": string;
+    /**
+     * Hide content for all replies
+     */
+    "collapseAllCws": string;
+    /**
+     * Don't use drawer-style menus
+     */
+    "disableDrawer": string;
+    /**
+     * Corner roundness
+     */
+    "cornerRadius": string;
+    /**
+     * Warn you when you forget to put alt text
+     */
+    "warnForMissingAltText": string;
+    /**
+     * Use DeepLX-JS (No Auth Key)
+     */
+    "deeplFreeMode": string;
+    /**
+     * Need Help? Check our documentation to know how to setup DeepLX-JS.
+     */
+    "deeplFreeModeDescription": string;
+    /**
+     * Deletion of all files queued
+     */
+    "deleteAllFilesQueued": string;
+    /**
+     * This is a system account
+     */
+    "systemAccountTitle": string;
+    /**
+     * This account is created and managed automatically by the system, and cannot be logged into.
+     */
+    "systemAccountDescription": string;
+    /**
+     * post is hidden by a filter
+     */
+    "postFiltered": string;
+    /**
+     * Enable favicon notification dot
+     */
+    "enableFaviconNotificationDot": string;
+    /**
+     * Check if the notification dot works on your instance
+     */
+    "verifyNotificationDotWorkingButton": string;
+    /**
+     * Unfortunately, this instance does not support the notification dot feature at this time.
+     */
+    "notificationDotNotWorking": string;
+    /**
+     * The notification dot is functioning properly on this instance.
+     */
+    "notificationDotWorking": string;
+    /**
+     * If the notification dot doesn't work, ask an admin to check our documentation {link}
+     */
+    "notificationDotNotWorkingAdvice": ParameterizedString<"link">;
+    /**
+     * Forward report to remote instance
+     */
+    "forwardReport": string;
+    /**
+     * Instead of your account, an anonymous system account will be displayed as reporter at the remote instance.
+     */
+    "forwardReportIsAnonymous": string;
+    /**
+     * Mark report as resolved
+     */
+    "abuseMarkAsResolved": string;
+    /**
+     * Sharkey specific changes are translated in its own {link}.
+     */
+    "i18nInfoSharkey": ParameterizedString<"link">;
+    /**
+     * Show instance ticker on replies
+     */
+    "showTickerOnReplies": string;
+    /**
+     * Disable cat speak
+     */
+    "disableCatSpeak": string;
+    /**
+     * Search Engine For Search MFM
+     */
+    "searchEngine": string;
+    /**
+     * Other
+     */
+    "searchEngineOther": string;
+    /**
+     * The custom URI must be input in the format like "https://www.google.com/search?q=\{query}" or "https://www.google.com/search?q=%s".
+     */
+    "searchEngineCustomURIDescription": string;
+    /**
+     * Custom URI
+     */
+    "searchEngineCusomURI": string;
+    /**
+     * Make public notes not indexable
+     */
+    "makeIndexable": string;
+    /**
+     * Stop note search from indexing your public notes.
+     */
+    "makeIndexableDescription": string;
+    /**
+     * Require approval for new users
+     */
+    "approvalRequiredForSignup": string;
+    /**
+     * Confirm your vote for "{choice}"?
+     *  You can choose more options after confirmation.
+     */
+    "voteConfirmMulti": ParameterizedString<"choice">;
+    /**
+     * There are users awaiting approval.
+     */
+    "pendingUserApprovals": string;
+    /**
+     * Approve
+     */
+    "approveAccount": string;
+    /**
+     * Deny & Delete
+     */
+    "denyAccount": string;
+    /**
+     * Approved
+     */
+    "approved": string;
+    /**
+     * Not Approved
+     */
+    "notApproved": string;
+    /**
+     * Approval Status
+     */
+    "approvalStatus": string;
+    /**
+     * Number of replies in a thread
+     */
+    "numberOfReplies": string;
+    /**
+     * Increasing this number will display more replies. Setting this too high can cause replies to be cramped and unreadable.
+     */
+    "numberOfRepliesDescription": string;
+    /**
+     * Boost Settings
+     */
+    "boostSettings": string;
+    /**
+     * Show Visibility Selector
+     */
+    "showVisibilitySelectorOnBoost": string;
+    /**
+     * Shows the visiblity selector if enabled when clicking boost, if disabled it will use the default visiblity defined below and the selector will not show up.
+     */
+    "showVisibilitySelectorOnBoostDescription": string;
+    /**
+     * Default boost visibility
+     */
+    "visibilityOnBoost": string;
+    /**
+     * Default like emoji
+     */
+    "defaultLike": string;
+    /**
+     * You can also support {host} directly by donating to your instance administration.
+     */
+    "pleaseDonateInstance": ParameterizedString<"host">;
+    /**
+     * Cancel
+     */
+    "thisPostIsMissingAltTextCancel": string;
+    /**
+     * Post anyway
+     */
+    "thisPostIsMissingAltTextIgnore": string;
+    /**
+     * One of the files attached to this post is missing alt text. Please ensure all the attachments have alt text.
+     */
+    "thisPostIsMissingAltText": string;
+    /**
+     * Collapse notes replied to
+     */
+    "collapseNotesRepliedTo": string;
+    /**
+     * Collapse files
+     */
+    "collapseFiles": string;
+    /**
+     * Uncollapse CWs on notes
+     */
+    "uncollapseCW": string;
+    /**
+     * Always expand long notes
+     */
+    "expandLongNote": string;
+    /**
+     * Load conversation on replies
+     */
+    "autoloadConversation": string;
+    /**
+     * This instance is only accepting users who specify a reason for registration.
+     */
+    "approvalRequiredToRegister": string;
+    /**
+     * Cat friend :3
+     */
+    "oneko": string;
+    /**
+     * Enable Achievements
+     */
+    "enableAchievements": string;
+    /**
+     * Turning this off will disable the achievement system
+     */
+    "turnOffAchievements": string;
+    /**
+     * Populate Hashtags with Bots
+     */
+    "enableBotTrending": string;
+    /**
+     * Turning this off will stop Bots from populating Hashtags
+     */
+    "turnOffBotTrending": string;
+    /**
+     * Click to open notes
+     */
+    "clickToOpen": string;
+    /**
+     * Show bots in timeline
+     */
+    "showBots": string;
+    /**
+     * Donate
+     */
+    "donation": string;
+    /**
+     * Donation URL
+     */
+    "donationUrl": string;
+    /**
+     * Show Below Avatar
+     */
+    "showBelowAvatar": string;
+    /**
+     * Break following relationships
+     */
+    "severAllFollowRelations": string;
+    /**
+     * Really break all follow relationships? This is irreversible! This will break {followingCount} following and {followersCount} follower relations on {instanceName}!
+     */
+    "severAllFollowRelationsConfirm": ParameterizedString<"followingCount" | "followersCount" | "instanceName">;
+    /**
+     * Severing all follow relations with {host} queued.
+     */
+    "severAllFollowRelationsQueued": ParameterizedString<"host">;
+    /**
+     * Pending follow requests
+     */
+    "pendingFollowRequests": string;
+    /**
+     * Show quotes
+     */
+    "showQuotes": string;
+    /**
+     * Show replies
+     */
+    "showReplies": string;
+    /**
+     * Show non-public
+     */
+    "showNonPublicNotes": string;
+    /**
+     * Allow clicking on pop-up notifications
+     */
+    "allowClickingNotifications": string;
+    /**
+     * Pinned
+     */
+    "pinnedOnly": string;
+    /**
+     * Blocking you
+     */
+    "blockingYou": string;
+    /**
+     * Show warning when opening external URLs
+     */
+    "warnExternalUrl": string;
+    "_mfm": {
+        /**
+         * This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks
+         */
+        "uncommonFeature": string;
+        /**
+         * MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax.
+         */
+        "intro": string;
+        /**
+         * Sharkey expands the world of the Fediverse
+         */
+        "dummy": string;
+        /**
+         * Mention
+         */
+        "mention": string;
+        /**
+         * You can specify a user by using an At-Symbol and a username.
+         */
+        "mentionDescription": string;
+        /**
+         * Hashtag
+         */
+        "hashtag": string;
+        /**
+         * You can specify a hashtag using a number sign and text.
+         */
+        "hashtagDescription": string;
+        /**
+         * URL
+         */
+        "url": string;
+        /**
+         * URLs can be displayed.
+         */
+        "urlDescription": string;
+        /**
+         * Link
+         */
+        "link": string;
+        /**
+         * Specific parts of text can be displayed as a URL.
+         */
+        "linkDescription": string;
+        /**
+         * Bold
+         */
+        "bold": string;
+        /**
+         * Highlights letters by making them thicker.
+         */
+        "boldDescription": string;
+        /**
+         * Small
+         */
+        "small": string;
+        /**
+         * Displays content small and thin.
+         */
+        "smallDescription": string;
+        /**
+         * Center
+         */
+        "center": string;
+        /**
+         * Displays content centered.
+         */
+        "centerDescription": string;
+        /**
+         * Code (Inline)
+         */
+        "inlineCode": string;
+        /**
+         * Displays inline syntax highlighting for (program) code.
+         */
+        "inlineCodeDescription": string;
+        /**
+         * Code (Block)
+         */
+        "blockCode": string;
+        /**
+         * Displays syntax highlighting for multi-line (program) code in a block.
+         */
+        "blockCodeDescription": string;
+        /**
+         * Math (Inline)
+         */
+        "inlineMath": string;
+        /**
+         * Display math formulas (KaTeX) in-line
+         */
+        "inlineMathDescription": string;
+        /**
+         * Math (Block)
+         */
+        "blockMath": string;
+        /**
+         * Display math formulas (KaTeX) in a block
+         */
+        "blockMathDescription": string;
+        /**
+         * Quote
+         */
+        "quote": string;
+        /**
+         * Displays content as a quote.
+         */
+        "quoteDescription": string;
+        /**
+         * Custom Emoji
+         */
+        "emoji": string;
+        /**
+         * By surrounding a custom emoji name with colons, custom emoji can be displayed.
+         */
+        "emojiDescription": string;
+        /**
+         * Search
+         */
+        "search": string;
+        /**
+         * Displays a search box with pre-entered text.
+         */
+        "searchDescription": string;
+        /**
+         * Flip
+         */
+        "flip": string;
+        /**
+         * Flips content horizontally or vertically.
+         */
+        "flipDescription": string;
+        /**
+         * Animation (Jelly)
+         */
+        "jelly": string;
+        /**
+         * Gives content a jelly-like animation.
+         */
+        "jellyDescription": string;
+        /**
+         * Animation (Tada)
+         */
+        "tada": string;
+        /**
+         * Gives content a "Tada!"-like animation.
+         */
+        "tadaDescription": string;
+        /**
+         * Animation (Jump)
+         */
+        "jump": string;
+        /**
+         * Gives content a jumping animation.
+         */
+        "jumpDescription": string;
+        /**
+         * Animation (Bounce)
+         */
+        "bounce": string;
+        /**
+         * Gives content a bouncy animation.
+         */
+        "bounceDescription": string;
+        /**
+         * Animation (Shake)
+         */
+        "shake": string;
+        /**
+         * Gives content a shaking animation.
+         */
+        "shakeDescription": string;
+        /**
+         * Animation (Twitch)
+         */
+        "twitch": string;
+        /**
+         * Gives content a strongly twitching animation.
+         */
+        "twitchDescription": string;
+        /**
+         * Animation (Spin)
+         */
+        "spin": string;
+        /**
+         * Gives content a spinning animation.
+         */
+        "spinDescription": string;
+        /**
+         * Big
+         */
+        "x2": string;
+        /**
+         * Displays content bigger.
+         */
+        "x2Description": string;
+        /**
+         * Very big
+         */
+        "x3": string;
+        /**
+         * Displays content even bigger.
+         */
+        "x3Description": string;
+        /**
+         * Unbelievably big
+         */
+        "x4": string;
+        /**
+         * Displays content even bigger than bigger than big.
+         */
+        "x4Description": string;
+        /**
+         * Blur
+         */
+        "blur": string;
+        /**
+         * Blurs content. It will be displayed clearly when hovered over.
+         */
+        "blurDescription": string;
+        /**
+         * Font
+         */
+        "font": string;
+        /**
+         * Sets the font to display content in.
+         */
+        "fontDescription": string;
+        /**
+         * Rainbow
+         */
+        "rainbow": string;
+        /**
+         * Makes the content appear in rainbow colors.
+         */
+        "rainbowDescription": string;
+        /**
+         * Sparkle
+         */
+        "sparkle": string;
+        /**
+         * Gives content a sparkling particle effect.
+         */
+        "sparkleDescription": string;
+        /**
+         * Rotate
+         */
+        "rotate": string;
+        /**
+         * Turns content by a specified angle.
+         */
+        "rotateDescription": string;
+        /**
+         * Position
+         */
+        "position": string;
+        /**
+         * Move content by a specified amount.
+         */
+        "positionDescription": string;
+        /**
+         * Crop
+         */
+        "crop": string;
+        /**
+         * Crop content.
+         */
+        "cropDescription": string;
+        /**
+         * Follow Mouse
+         */
+        "followMouse": string;
+        /**
+         * Content will follow the mouse. On mobile it will follow wherever the user taps.
+         */
+        "followMouseDescription": string;
+        /**
+         * Scale
+         */
+        "scale": string;
+        /**
+         * Scale content by a specified amount.
+         */
+        "scaleDescription": string;
+        /**
+         * Foreground color
+         */
+        "foreground": string;
+        /**
+         * Change the foreground color of text.
+         */
+        "foregroundDescription": string;
+        /**
+         * Fade
+         */
+        "fade": string;
+        /**
+         * Fade text in and out.
+         */
+        "fadeDescription": string;
+        /**
+         * Background color
+         */
+        "background": string;
+        /**
+         * Change the background color of text.
+         */
+        "backgroundDescription": string;
+        /**
+         * Plain
+         */
+        "plain": string;
+        /**
+         * Deactivates the effects of all MFM contained within this MFM effect.
+         */
+        "plainDescription": string;
+    };
+    "_animatedMFM": {
+        /**
+         * Play MFM Animation
+         */
+        "play": string;
+        /**
+         * Stop MFM Animation
+         */
+        "stop": string;
+        "_alert": {
+            /**
+             * Animated MFMs could include flashing lights and fast moving text/emojis.
+             */
+            "text": string;
+            /**
+             * Animate
+             */
+            "confirm": string;
+        };
+    };
+    "_dataRequest": {
+        /**
+         * Request Data
+         */
+        "title": string;
+        /**
+         * Data requests are only possible every 3 days.
+         */
+        "warn": string;
+        /**
+         * Once the data is ready to download, an email will be sent to the email address registered to this account.
+         */
+        "text": string;
+        /**
+         * Request
+         */
+        "button": string;
+    };
+    "_externalNavigationWarning": {
+        /**
+         * Navigate to an external site
+         */
+        "title": string;
+        /**
+         * Leave {host} and go to an external site
+         */
+        "description": ParameterizedString<"host">;
+        /**
+         * Trust this domain on this device in the future
+         */
+        "trustThisDomain": string;
+    };
+    /**
+     * Remote followers may have incomplete or outdated activity
+     */
+    "remoteFollowersWarning": string;
 }
 declare const locales: {
     [lang: string]: Locale;
diff --git a/locales/index.js b/locales/index.js
index 48ff85f1a5358e99261f8773e57f3e0bb8815976..b08158e55dda9359c2e57e2d662e39a3f0f07650 100644
--- a/locales/index.js
+++ b/locales/index.js
@@ -5,7 +5,7 @@
 import * as fs from 'node:fs';
 import * as yaml from 'js-yaml';
 
-const merge = (...args) => args.reduce((a, c) => ({
+export const merge = (...args) => args.reduce((a, c) => ({
 	...a,
 	...c,
 	...Object.entries(a)
@@ -60,7 +60,10 @@ export function build() {
 	// https://github.com/vitest-dev/vitest/issues/3988#issuecomment-1686599577
 	// https://github.com/misskey-dev/misskey/pull/14057#issuecomment-2192833785
 	const metaUrl = import.meta.url;
-	const locales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
+	const sharkeyLocales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`../sharkey-locales/${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
+	const misskeyLocales = languages.reduce((a, c) => (a[c] = yaml.load(clean(fs.readFileSync(new URL(`${c}.yml`, metaUrl), 'utf-8'))) || {}, a), {});
+	// merge sharkey and misskey's locales. the second argument (sharkey) overwrites the first argument (misskey).
+  const locales = merge(misskeyLocales, sharkeyLocales);
 
 	// 空文字列が入ることがあり、フォールバックが動作しなくなるのでプロパティごと消す
 	const removeEmpty = (obj) => {
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 734b4bd0b19903422bdb004e609573c240f3051f..bcabf1bdb63b4ec0707b9fe2d90cb4ac5a0dd887 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"
@@ -54,13 +57,13 @@ addToAntenna: "Aggiungi all'antenna"
 sendMessage: "Invia messaggio"
 copyRSS: "Copia RSS"
 copyUsername: "Copia nome utente"
-openRemoteProfile: "Apri profilo remoto"
 copyUserId: "Copia ID del profilo"
 copyNoteId: "Copia ID della Nota"
 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"
@@ -155,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"
@@ -195,6 +199,7 @@ followConfirm: "Vuoi seguire {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"
@@ -210,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"
@@ -231,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"
@@ -329,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"
@@ -443,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"
@@ -450,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"
@@ -504,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"
@@ -560,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"
@@ -587,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"
@@ -703,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"
@@ -765,8 +779,6 @@ thisIsExperimentalFeature: "Questa è una funzionalità sperimentale. Potrebbe e
 developer: "Sviluppatore"
 makeExplorable: "Profilo visibile pubblicamente nella pagina \"Esplora\""
 makeExplorableDescription: "Disabilitando questa opzione, il tuo profilo non verrà elencato nella pagina \"Esplora\"."
-makeIndexable: "Non indicizzare le note pubbliche"
-makeIndexableDescription: "Le tue note pubbliche non saranno cercabili"
 showGapBetweenNotesInTimeline: "Mostrare un intervallo tra le note sulla timeline"
 duplicate: "Duplica"
 left: "Sinistra"
@@ -830,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"
@@ -910,6 +922,7 @@ 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"
@@ -1001,7 +1014,6 @@ cannotLoad: "Caricamento impossibile"
 numberOfProfileView: "Visualizzazioni profilo"
 like: "Mi piace!"
 unlike: "Non mi piace"
-defaultLike: "Emoji predefinita per \"mi piace\""
 numberOfLikes: "Numero di Like"
 show: "Visualizza"
 neverShow: "Non mostrare più"
@@ -1075,6 +1087,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"
@@ -1110,6 +1123,8 @@ 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."
@@ -1120,6 +1135,9 @@ 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"
@@ -1254,10 +1272,38 @@ 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."
+_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 distribuzione di attività"
-  stop: "Sospendi la distribuzione di attività"
-  resume: "Riprendi la distribuzione di attività"
+  status: "Stato della consegna"
+  stop: "Sospensione"
+  resume: "Riprendi la consegna"
   _type:
     none: "Pubblicazione"
     manuallySuspended: "Sospesa manualmente"
@@ -1281,16 +1327,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."
@@ -1308,7 +1354,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?"
@@ -1318,17 +1364,17 @@ _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."
-    letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"{reaction}\" della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!"
+    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."
-    reactDone: "Annulla la tua Reazione premendo il bottone \"{undo}\""
+    reactDone: "Annulla la tua Reazione premendo il bottone \"ー\" (meno)"
   _timeline:
     title: "Come funziona la Timeline"
     description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori."
@@ -1337,7 +1383,7 @@ _initialTutorial:
     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."
@@ -1388,8 +1434,10 @@ _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"
@@ -1721,6 +1769,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"
@@ -1783,7 +1836,7 @@ _ad:
 _forgotPassword:
   enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo."
   ifNoEmail: "Se il tuo indirizzo email non risulta registrato, contatta l'amministrazione dell'istanza."
-  contactAdmin: "Poiché questa istanza non permette di impostare l'indirizzo mail, contatta l'amministrazione per ripristinare la password.\n"
+  contactAdmin: "Poiché questa istanza non permette di impostare l'indirizzo mail, contatta l'amministrazione per  ripristinare la password.\n"
 _gallery:
   my: "Le mie pubblicazioni"
   liked: "Pubblicazioni che mi piacciono"
@@ -1848,7 +1901,6 @@ _serverDisconnectedBehavior:
   reload: "Ricarica automaticamente"
   dialog: "Apri avviso in finestra"
   quiet: "Visualizza avviso in modo discreto"
-  disabled: "Non visualizzare l'avviso"
 _channel:
   create: "Nuovo canale"
   edit: "Gerisci canale"
@@ -1939,7 +1991,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"
@@ -1959,6 +2010,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"
@@ -2151,7 +2203,7 @@ _widgets:
   _userList:
     chooseList: "Seleziona una lista"
   clicker: "Cliccaggio"
-  birthdayFollowings: "Chi nacque oggi"
+  birthdayFollowings: "Compleanni del giorno"
 _cw:
   hide: "Nascondere"
   show: "Continua la lettura..."
@@ -2215,6 +2267,9 @@ _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"
@@ -2307,6 +2362,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"
@@ -2352,6 +2408,8 @@ _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"
@@ -2366,6 +2424,9 @@ _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"
@@ -2405,7 +2466,7 @@ _deck:
     roleTimeline: "Timeline Ruolo"
 _dialog:
   charactersExceeded: "Hai superato il limite di {max} caratteri! ({current})"
-  charactersBelow: "Sei al di sotto del minimo di {min} caratteri! ({current})"
+  charactersBelow: "Sei al di sotto del minimo di {min} caratteri!  ({current})"
 _disabledTimeline:
   title: "Timeline disabilitata"
   description: "Il ruolo in cui sei non ti permette di leggere questa timeline"
@@ -2417,6 +2478,7 @@ _webhookSettings:
   modifyWebhook: "Modifica Webhook"
   name: "Nome"
   secret: "Segreto"
+  trigger: "Trigger"
   active: "Attivo"
   _events:
     follow: "Quando segui un profilo"
@@ -2429,7 +2491,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"
@@ -2473,6 +2539,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"
@@ -2488,6 +2556,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"
@@ -2619,3 +2691,22 @@ _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."
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b4d47a449c5b43d8dc89e8f242bb91db5156a7f7..c448d4d50a7357489e205867045a3e1a72055f76 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1,13 +1,16 @@
 _lang_: "日本語"
 
 headlineMisskey: "ノートでつながるネットワーク"
-introMisskey: "ようこそ!Sharkeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
-poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつです。"
+introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
+poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつです。"
 monthAndDay: "{month}月 {day}日"
 search: "検索"
 notifications: "通知"
 username: "ユーザー名"
 password: "パスワード"
+initialPasswordForSetup: "初期設定開始用パスワード"
+initialPasswordIsIncorrect: "初期設定開始用のパスワードが違います。"
+initialPasswordForSetupDescription: "Misskeyを自分でインストールした場合は、設定ファイルに入力したパスワードを使用してください。\nMisskeyのホスティングサービスなどを使用している場合は、提供されたパスワードを使用してください。\nパスワードを設定していない場合は、空欄にしたまま続行してください。"
 forgotPassword: "パスワードを忘れた"
 fetchingAsApObject: "連合に照会中"
 ok: "OK"
@@ -15,7 +18,7 @@ gotIt: "わかった"
 cancel: "キャンセル"
 noThankYou: "やめておく"
 enterUsername: "ユーザー名を入力"
-renotedBy: "{user}がブースト"
+renotedBy: "{user}がリノート"
 noNotes: "ノートはありません"
 noNotifications: "通知はありません"
 instance: "サーバー"
@@ -34,7 +37,6 @@ signup: "新規登録"
 uploading: "アップロード中"
 save: "保存"
 users: "ユーザー"
-approvals: "承認"
 addUser: "ユーザーを追加"
 favorite: "お気に入り"
 favorites: "お気に入り"
@@ -46,16 +48,15 @@ pin: "ピン留め"
 unpin: "ピン留め解除"
 copyContent: "内容をコピー"
 copyLink: "リンクをコピー"
-copyLinkRenote: "ブーストのリンクをコピー"
+copyLinkRenote: "リノートのリンクをコピー"
 delete: "削除"
 deleteAndEdit: "削除して編集"
-deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、ブースト、返信も全て削除されます。"
+deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。"
 addToList: "リストに追加"
 addToAntenna: "アンテナに追加"
 sendMessage: "メッセージを送信"
 copyRSS: "RSSをコピー"
 copyUsername: "ユーザー名をコピー"
-openRemoteProfile: "リモートプロフィールを開く"
 copyUserId: "ユーザーIDをコピー"
 copyNoteId: "ノートIDをコピー"
 copyFileId: "ファイルIDをコピー"
@@ -108,16 +109,14 @@ followRequests: "フォロー申請"
 unfollow: "フォロー解除"
 followRequestPending: "フォロー許可待ち"
 enterEmoji: "絵文字を入力"
-renote: "ブースト"
-unrenote: "ブースト解除"
-renoted: "ブーストしました。"
-renotedToX: "{name} にブーストしました。"
-quoted: "引用。"
-rmboost: "ブースト解除しました。"
-cantRenote: "この投稿はブーストできません。"
-cantReRenote: "ブーストをブーストすることはできません。"
+renote: "リノート"
+unrenote: "リノート解除"
+renoted: "リノートしました。"
+renotedToX: "{name} にリノートしました。"
+cantRenote: "この投稿はリノートできません。"
+cantReRenote: "リノートをリノートすることはできません。"
 quote: "引用"
-inChannelRenote: "チャンネル内ブースト"
+inChannelRenote: "チャンネル内リノート"
 inChannelQuote: "チャンネル内引用"
 renoteToChannel: "チャンネルにリノート"
 renoteToOtherChannel: "他のチャンネルにリノート"
@@ -144,19 +143,15 @@ unmarkAsSensitive: "センシティブを解除する"
 enterFileName: "ファイル名を入力"
 mute: "ミュート"
 unmute: "ミュート解除"
-renoteMute: "ブーストをミュート"
-renoteUnmute: "ブーストのミュートを解除"
+renoteMute: "リノートをミュート"
+renoteUnmute: "リノートのミュートを解除"
 block: "ブロック"
 unblock: "ブロック解除"
-markAsNSFW: "ユーザーのすべてのメディアをNSFWとしてマークする"
 suspend: "凍結"
 unsuspend: "解凍"
 blockConfirm: "ブロックしますか?"
 unblockConfirm: "ブロック解除しますか?"
-nsfwConfirm: "このアカウントからのすべてのメディアをNSFWとしてマークしてもよろしいですか?"
-unNsfwConfirm: "このアカウントのすべてのメディアをNSFWとしてマーク解除してもよろしいですか?"
 suspendConfirm: "凍結しますか?"
-approveConfirm: "このアカウントを承認してもよろしいですか?"
 unsuspendConfirm: "解凍しますか?"
 selectList: "リストを選択"
 editList: "リストを編集"
@@ -180,11 +175,9 @@ youCanCleanRemoteFilesCache: "ファイル管理の🗑️ボタンで全ての
 cacheRemoteSensitiveFiles: "リモートのセンシティブなファイルをキャッシュする"
 cacheRemoteSensitiveFilesDescription: "この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。"
 flagAsBot: "Botとして設定"
-flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Sharkeyのシステム上での扱いがBotに合ったものになります。"
+flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Misskeyのシステム上での扱いがBotに合ったものになります。"
 flagAsCat: "にゃああああああああああああああ!!!!!!!!!!!!"
 flagAsCatDescription: "にゃにゃにゃ??"
-flagSpeakAsCat: "猫語で話す"
-flagSpeakAsCatDescription: "有効にすると、あなたの投稿の 「な」を「にゃ」にします。"
 flagShowTimelineReplies: "タイムラインにノートへの返信を表示する"
 flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。"
 autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
@@ -246,6 +239,8 @@ silencedInstances: "サイレンスしたサーバー"
 silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになります。ブロックしたインスタンスには影響しません。"
 mediaSilencedInstances: "メディアサイレンスしたサーバー"
 mediaSilencedInstancesDescription: "メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。"
+federationAllowedHosts: "連合を許可するサーバー"
+federationAllowedHostsDescription: "連合を許可するサーバーのホストを改行で区切って設定します。"
 muteAndBlock: "ミュートとブロック"
 mutedUsers: "ミュートしたユーザー"
 blockedUsers: "ブロックしたユーザー"
@@ -253,7 +248,7 @@ noUsers: "ユーザーはいません"
 editProfile: "プロフィールを編集"
 noteDeleteConfirm: "このノートを削除しますか?"
 pinLimitExceeded: "これ以上ピン留めできません"
-intro: "Sharkeyのインストールが完了しました!管理者アカウントを作成しましょう。"
+intro: "Misskeyのインストールが完了しました!管理者アカウントを作成しましょう。"
 done: "完了"
 processing: "処理中"
 preview: "プレビュー"
@@ -331,7 +326,6 @@ lightThemes: "明るいテーマ"
 darkThemes: "暗いテーマ"
 syncDeviceDarkMode: "デバイスのダークモードと同期する"
 drive: "ドライブ"
-driveSearchbarPlaceholder: "検索ドライブ"
 fileName: "ファイル名"
 selectFile: "ファイルを選択"
 selectFiles: "ファイルを選択"
@@ -345,6 +339,7 @@ renameFolder: "フォルダー名を変更"
 deleteFolder: "フォルダーを削除"
 folder: "フォルダー"
 addFile: "ファイルを追加"
+showFile: "ファイルを表示"
 emptyDrive: "ドライブは空です"
 emptyFolder: "フォルダーは空です"
 unableToDelete: "削除できません"
@@ -357,7 +352,6 @@ copyUrl: "URLをコピー"
 rename: "名前を変更"
 avatar: "アイコン"
 banner: "バナー"
-background: "背景"
 displayOfSensitiveMedia: "センシティブなメディアの表示"
 whenServerDisconnected: "サーバーとの接続が失われたとき"
 disconnectedFromServer: "サーバーから切断されました"
@@ -450,7 +444,7 @@ exploreFediverse: "Fediverseを探索"
 popularTags: "人気のタグ"
 userList: "リスト"
 about: "情報"
-aboutMisskey: "Sharkeyについて"
+aboutMisskey: "Misskeyについて"
 administrator: "管理者"
 token: "確認コード"
 2fa: "二要素認証"
@@ -460,6 +454,7 @@ totpDescription: "認証アプリを使ってワンタイムパスワードを
 moderator: "モデレーター"
 moderation: "モデレーション"
 moderationNote: "モデレーションノート"
+moderationNoteDescription: "モデレーター間でだけ共有されるメモを記入することができます。"
 addModerationNote: "モデレーションノートを追加する"
 moderationLogs: "モデログ"
 nUsersMentioned: "{n}人が投稿"
@@ -492,8 +487,6 @@ enable: "有効にする"
 next: "次"
 retype: "再入力"
 noteOf: "{user}のノート"
-expandAllCws: "すべての返信の内容を表示する"
-collapseAllCws: "すべての返信の内容を隠す"
 quoteAttached: "引用付き"
 quoteQuestion: "引用として添付しますか?"
 attachAsFileQuestion: "クリップボードのテキストが長いです。テキストファイルとして添付しますか?"
@@ -523,7 +516,10 @@ uiLanguage: "UIの表示言語"
 aboutX: "{x}について"
 emojiStyle: "絵文字のスタイル"
 native: "ネイティブ"
-disableDrawer: "メニューをドロワーで表示しない"
+menuStyle: "メニューのスタイル"
+style: "スタイル"
+drawer: "ドロワー"
+popup: "ポップアップ"
 showNoteActionsOnlyHover: "ノートのアクションをホバー時のみ表示する"
 showReactionsCount: "ノートのリアクション数を表示する"
 noHistory: "履歴はありません"
@@ -538,12 +534,10 @@ createAccount: "アカウントを作成"
 existingAccount: "既存のアカウント"
 regenerate: "再生成"
 fontSize: "フォントサイズ"
-cornerRadius: "コーナーの丸み"
 mediaListWithOneImageAppearance: "画像が1枚のみのメディアリストの高さ"
 limitTo: "{x}を上限に"
 noFollowRequests: "フォロー申請はありません"
 openImageInNewTab: "画像を新しいタブで開く"
-warnForMissingAltText: "代替テキストを入れ忘れたときに警告する"
 dashboard: "ダッシュボード"
 local: "ローカル"
 remote: "リモート"
@@ -576,8 +570,6 @@ objectStorageUseProxy: "Proxyを利用する"
 objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
 objectStorageSetPublicRead: "アップロード時に'public-read'を設定する"
 s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。"
-deeplFreeMode: "DeepLX-JS を使用する (認証キー不要)"
-deeplFreeModeDescription: "DeepLX-JSの設定方法については、ドキュメントを参照してください。"
 serverLogs: "サーバーログ"
 deleteAll: "全て削除"
 showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
@@ -593,7 +585,7 @@ popout: "ポップアウト"
 volume: "音量"
 masterVolume: "マスター音量"
 notUseSound: "サウンドを出力しない"
-useSoundOnlyWhenActive: "Sharkeyがアクティブな時のみサウンドを出力する"
+useSoundOnlyWhenActive: "Misskeyがアクティブな時のみサウンドを出力する"
 details: "詳細"
 chooseEmoji: "絵文字を選択"
 unableToProcess: "操作を完了できません"
@@ -609,7 +601,9 @@ sort: "ソート"
 ascendingOrder: "昇順"
 descendingOrder: "降順"
 scratchpad: "スクラッチパッド"
-scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Sharkeyと対話するコードの記述、実行、結果の確認ができます。"
+scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Misskeyと対話するコードの記述、実行、結果の確認ができます。"
+uiInspector: "UIインスペクター"
+uiInspectorDescription: "メモリ上に存在しているUIコンポーネントのインスタンスの一覧を見ることができます。UIコンポーネントはUi:C:系関数により生成されます。"
 output: "出力"
 script: "スクリプト"
 disablePagesScript: "Pagesのスクリプトを無効にする"
@@ -707,11 +701,6 @@ channel: "チャンネル"
 create: "作成"
 notificationSetting: "通知設定"
 notificationSettingDesc: "表示する通知の種別を選択してください。"
-enableFaviconNotificationDot: "未読の通知があるときにタブのアイコンを目立たせる"
-verifyNotificationDotWorkingButton: "タブアイコン強調機能の動作確認"
-notificationDotNotWorking: "このサーバーは現時点ではタブアイコン強調機能をサポートしていません。"
-notificationDotWorking: "タブアイコン強調機能は、このサーバーで正しく機能しています。"
-notificationDotNotWorkingAdvice: "タブアイコン強調機能が機能しない場合は、管理者にドキュメントを確認するように依頼してください {link}"
 useGlobalSetting: "グローバル設定を使う"
 useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。"
 other: "その他"
@@ -724,17 +713,14 @@ behavior: "動作"
 sample: "サンプル"
 abuseReports: "通報"
 reportAbuse: "通報"
-reportAbuseRenote: "ブーストを通報"
+reportAbuseRenote: "リノートを通報"
 reportAbuseOf: "{name}を通報する"
 fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートやページなどがある場合はそのURLも記入してください。"
 abuseReported: "内容が送信されました。ご報告ありがとうございました。"
 reporter: "通報者"
 reporteeOrigin: "通報先"
 reporterOrigin: "通報元"
-forwardReport: "リモートサーバーに通報を転送する"
-forwardReportIsAnonymous: "リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。"
 send: "送信"
-abuseMarkAsResolved: "対応済みにする"
 openInNewTab: "新しいタブで開く"
 openInSideView: "サイドビューで開く"
 defaultNavigationBehaviour: "デフォルトのナビゲーション"
@@ -753,14 +739,14 @@ unclip: "クリップ解除"
 confirmToUnclipAlreadyClippedNote: "このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?"
 public: "パブリック"
 private: "非公開"
-i18nInfo: "Sharkeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
+i18nInfo: "Misskeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
 manageAccessTokens: "アクセストークンの管理"
 accountInfo: "アカウント情報"
 notesCount: "ノートの数"
 repliesCount: "返信した数"
-renotesCount: "ブーストした数"
+renotesCount: "リノートした数"
 repliedCount: "返信された数"
-renotedCount: "ブーストされた数"
+renotedCount: "リノートされた数"
 followingCount: "フォロー数"
 followersCount: "フォロワー数"
 sentReactionsCount: "リアクションした数"
@@ -776,11 +762,6 @@ noCrawleDescription: "外部の検索エンジンにあなたのユーザーペ
 lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
 alwaysMarkSensitive: "デフォルトでメディアをセンシティブ設定にする"
 loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
-showTickerOnReplies: "返信にサーバー情報を表示する"
-searchEngine: "検索MFMの検索エンジン"
-searchEngineOther: "カスタム"
-searchEngineCustomURIDescription: "カスタム検索エンジンのURIは、\"https://www.google.com/search?q=\\{query}\" や \"https://www.google.com/search?q=%s\" のような形式で入力する必要があります。"
-searchEngineCusomURI: "カスタム検索エンジン URI"
 disableShowingAnimatedImages: "アニメーション画像を再生しない"
 highlightSensitiveMedia: "メディアがセンシティブであることを分かりやすく表示"
 verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
@@ -798,15 +779,13 @@ thisIsExperimentalFeature: "これは実験的な機能です。仕様が変更
 developer: "開発者"
 makeExplorable: "アカウントを見つけやすくする"
 makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。"
-makeIndexable: "公開ノートをインデックス不可にする"
-makeIndexableDescription: "ノート検索があなたの公開ノートをインデックス化しないようにします。"
 showGapBetweenNotesInTimeline: "タイムラインのノートを離して表示"
 duplicate: "複製"
 left: "å·¦"
 center: "中央"
 wide: "広い"
 narrow: "狭い"
-reloadToApplySetting: "設定はページリロード後に反映されます。今すぐリロードしますか?"
+reloadToApplySetting: "設定はページリロード後に反映されます。"
 needReloadToApply: "反映には再起動が必要です。"
 showTitlebar: "タイトルバーを表示する"
 clearCache: "キャッシュをクリア"
@@ -814,7 +793,7 @@ onlineUsersCount: "{n}人がオンライン"
 nUsers: "{n}ユーザー"
 nNotes: "{n}ノート"
 sendErrorReports: "エラーリポートを送信"
-sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がSharkeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。"
+sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がMisskeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。"
 myTheme: "マイテーマ"
 backgroundColor: "背景"
 accentColor: "アクセント"
@@ -909,7 +888,7 @@ hashtags: "ハッシュタグ"
 troubleshooting: "トラブルシューティング"
 useBlurEffect: "UIにぼかし効果を使用"
 learnMore: "詳しく"
-misskeyUpdated: "Sharkeyが更新されました!"
+misskeyUpdated: "Misskeyが更新されました!"
 whatIsNew: "更新情報を見る"
 translate: "翻訳"
 translatedFrom: "{x}から翻訳"
@@ -929,7 +908,6 @@ itsOff: "オフになっています"
 on: "オン"
 off: "オフ"
 emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする"
-approvalRequiredForSignup: "アカウント登録を承認制にする"
 unread: "未読"
 filter: "フィルタ"
 controlPanel: "コントロールパネル"
@@ -944,8 +922,8 @@ followersVisibility: "フォロワーの公開範囲"
 continueThread: "さらにスレッドを見る"
 deleteAccountConfirm: "アカウントが削除されます。よろしいですか?"
 incorrectPassword: "パスワードが間違っています。"
+incorrectTotp: "ワンタイムパスワードが間違っているか、期限切れになっています。"
 voteConfirm: "「{choice}」に投票しますか?"
-voteConfirmMulti: "「{choice}」に投票しますか?\n 確認後、選択肢を増やすことができます。"
 hide: "隠す"
 useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
 welcomeBackWithName: "おかえりなさい、{name}さん"
@@ -981,7 +959,6 @@ recentNHours: "直近{n}時間"
 recentNDays: "ç›´è¿‘{n}æ—¥"
 noEmailServerWarning: "メールサーバーの設定がされていません。"
 thereIsUnresolvedAbuseReportWarning: "未対応の通報があります。"
-pendingUserApprovals: "承認待ちのユーザーがいます。"
 recommended: "推奨"
 check: "チェック"
 driveCapOverrideLabel: "このユーザーのドライブ容量上限を変更"
@@ -990,20 +967,9 @@ requireAdminForView: "閲覧するには管理者アカウントでログイン
 isSystemAccount: "システムにより自動で作成・管理されているアカウントです。"
 typeToConfirm: "この操作を行うには {x} と入力してください"
 deleteAccount: "アカウント削除"
-approveAccount: "承認する"
-denyAccount: "拒否と削除"
-approved: "承認済み"
-notApproved: "承認されていない"
-approvalStatus: "承認状況"
 document: "ドキュメント"
 numberOfPageCache: "ページキャッシュ数"
 numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。"
-numberOfReplies: "スレッド内の返信数"
-numberOfRepliesDescription: "この数値を大きくすると、より多くの返信が表示されます。この値を大きくしすぎると、UIが窮屈になって読みにくくなることがあります。"
-boostSettings: "ブースト設定"
-showVisibilitySelectorOnBoost: "公開範囲セレクターを表示"
-showVisibilitySelectorOnBoostDescription: "無効の場合、以下で設定したデフォルトの公開範囲が使用され、セレクターは表示されません。"
-visibilityOnBoost: "デフォルトのブースト公開範囲"
 logoutConfirm: "ログアウトしますか?"
 lastActiveDate: "最終利用日時"
 statusbar: "ステータスバー"
@@ -1048,14 +1014,12 @@ cannotLoad: "読み込めません"
 numberOfProfileView: "プロフィール表示回数"
 like: "いいね!"
 unlike: "いいねを解除"
-defaultLike: "絵文字のようなデフォルト"
 numberOfLikes: "いいね数"
 show: "表示"
 neverShow: "今後表示しない"
 remindMeLater: "また後で"
-didYouLikeMisskey: "Sharkeyを気に入っていただけましたか?"
-pleaseDonate: "Sharkeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
-pleaseDonateInstance: "インスタンス管理者への寄付によって{host}を直接サポートすることもできます。"
+didYouLikeMisskey: "Misskeyを気に入っていただけましたか?"
+pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
 correspondingSourceIsAvailable: "対応するソースコードは{anchor}から利用可能です。"
 roles: "ロール"
 role: "ロール"
@@ -1083,16 +1047,8 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります
 thisPostMayBeAnnoyingHome: "ホームに投稿"
 thisPostMayBeAnnoyingCancel: "やめる"
 thisPostMayBeAnnoyingIgnore: "このまま投稿"
-thisPostIsMissingAltTextCancel: "やめる"
-thisPostIsMissingAltTextIgnore: "このまま投稿"
-thisPostIsMissingAltText: "代替テキストがないファイルが添付されています。すべての添付ファイルに代替テキストを含むようにしてください。"
-collapseRenotes: "ブーストのスマート省略"
-collapseRenotesDescription: "リアクションやブーストをしたことがあるノートをたたんで表示します。"
-collapseNotesRepliedTo: "返信元のノートを折りたたむ"
-collapseFiles: "ファイルを折りたたむ"
-uncollapseCW: "CWを展開する"
-expandLongNote: "長い投稿を常に展開する"
-autoloadConversation: "会話スレッドを自動で読み込む"
+collapseRenotes: "リノートのスマート省略"
+collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示します。"
 internalServerError: "サーバー内部エラー"
 internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
 copyErrorInfo: "エラー情報をコピー"
@@ -1103,7 +1059,6 @@ disableFederationConfirm: "連合なしにしますか?"
 disableFederationConfirmWarn: "連合なしにしても投稿は非公開になりません。ほとんどの場合、連合なしにする必要はありません。"
 disableFederationOk: "連合なしにする"
 invitationRequiredToRegister: "現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。"
-approvalRequiredToRegister: "現在このサーバーは承認制です。参加したい理由を記入し、承認された方のみ登録できます。"
 emailNotSupported: "このサーバーではメール配信はサポートされていません"
 postToTheChannel: "チャンネルに投稿"
 cannotBeChangedLater: "後から変更できません。"
@@ -1132,6 +1087,7 @@ retryAllQueuesConfirmTitle: "今すぐ再試行しますか?"
 retryAllQueuesConfirmText: "一時的にサーバーの負荷が増大することがあります。"
 enableChartsForRemoteUser: "リモートユーザーのチャートを生成"
 enableChartsForFederatedInstances: "リモートサーバーのチャートを生成"
+enableStatsForFederatedInstances: "リモートサーバーの情報を取得"
 showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 reactionsDisplaySize: "リアクションの表示サイズ"
 limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
@@ -1146,11 +1102,10 @@ accountMoved: "このユーザーは新しいアカウントに移行しまし
 accountMovedShort: "このアカウントは移行されています"
 operationForbidden: "この操作はできません"
 forceShowAds: "常に広告を表示する"
-oneko: "にゃんこフレンド :3"
 addMemo: "メモを追加"
 editMemo: "メモを編集"
 reactionsList: "リアクション一覧"
-renotesList: "ブースト一覧"
+renotesList: "リノート一覧"
 notificationDisplay: "通知の表示"
 leftTop: "左上"
 rightTop: "右上"
@@ -1191,15 +1146,11 @@ rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロー
 cancelReactionConfirm: "リアクションを取り消しますか?"
 changeReactionConfirm: "リアクションを変更しますか?"
 later: "あとで"
-goToMisskey: "Sharkeyへ"
+goToMisskey: "Misskeyへ"
 additionalEmojiDictionary: "絵文字の追加辞書"
 installed: "インストール済み"
 branding: "ブランディング"
 enableServerMachineStats: "サーバーのマシン情報を公開する"
-enableAchievements: "実績を有効にする"
-turnOffAchievements: "オフにすると実績システムは無効になります。"
-enableBotTrending: "botのハッシュタグ追加を許可する"
-turnOffBotTrending: "オフにするとボットがハッシュタグを入力しなくなります。"
 enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
 turnOffToImprovePerformance: "オフにするとパフォーマンスが向上します。"
 createInviteCode: "招待コードを作成"
@@ -1230,20 +1181,18 @@ pastAnnouncements: "過去のお知らせ"
 youHaveUnreadAnnouncements: "未読のお知らせがあります。"
 useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
 replies: "返信"
-renotes: "ブースト"
+renotes: "リノート"
 loadReplies: "返信を見る"
 loadConversation: "会話を見る"
 pinnedList: "ピン留めされたリスト"
 keepScreenOn: "デバイスの画面を常にオンにする"
-clickToOpen: "クリックしてノートを開く"
-showBots: "ボットをタイムラインに表示"
 verifiedLink: "このリンク先の所有者であることが確認されました"
 notifyNotes: "投稿を通知"
 unnotifyNotes: "投稿の通知を解除"
 authentication: "認証"
 authenticationRequiredToContinue: "続けるには認証を行ってください"
 dateAndTime: "日時"
-showRenotes: "ブーストを表示"
+showRenotes: "リノートを表示"
 edited: "編集済み"
 notificationRecieveConfig: "通知の受信設定"
 mutualFollow: "相互フォロー"
@@ -1259,7 +1208,7 @@ externalServices: "外部サービス"
 sourceCode: "ソースコード"
 sourceCodeIsNotYetProvided: "ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。"
 repositoryUrl: "リポジトリURL"
-repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Sharkeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://activitypub.software/TransFem-org/Sharkey/ と記入します。"
+repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Misskeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://github.com/misskey-dev/misskey と記入します。"
 repositoryUrlOrTarballRequired: "リポジトリを公開していない場合、代わりにtarballを提供する必要があります。詳細は.config/example.ymlを参照してください。"
 feedback: "フィードバック"
 feedbackUrl: "フィードバックURL"
@@ -1269,8 +1218,6 @@ impressumDescription: "ドイツなどの一部の国と地域では表示が義
 privacyPolicy: "プライバシーポリシー"
 privacyPolicyUrl: "プライバシーポリシーURL"
 tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
-donation: "寄付する"
-donationUrl: "寄付URL"
 avatarDecorations: "アイコンデコレーション"
 attach: "付ける"
 detach: "外す"
@@ -1327,6 +1274,33 @@ 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: "配信状態"
@@ -1380,7 +1354,7 @@ _initialAccountSetting:
   pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。"
   initialAccountSettingCompleted: "初期設定が完了しました!"
   haveFun: "{name}をお楽しみください!"
-  youCanContinueTutorial: "このまま{name}(Sharkey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。"
+  youCanContinueTutorial: "このまま{name}(Misskey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。"
   startTutorial: "チュートリアルを開始"
   skipAreYouSure: "初期設定をスキップしますか?"
   laterAreYouSure: "初期設定をあとでやり直しますか?"
@@ -1392,10 +1366,10 @@ _initialTutorial:
   skipAreYouSure: "チュートリアルを終了しますか?"
   _landing:
     title: "チュートリアルへようこそ"
-    description: "ここでは、Sharkeyの基本的な使い方や機能を確認できます。"
+    description: "ここでは、Misskeyの基本的な使い方や機能を確認できます。"
   _note:
     title: "ノートって何?"
-    description: "Sharkeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。"
+    description: "Misskeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。"
     reply: "返信することができます。返信に対しての返信も可能で、スレッドのように会話を続けることもできます。"
     renote: "そのノートを自分のタイムラインに流して共有することができます。テキストを追加して引用することも可能です。"
     reaction: "リアクションをつけることができます。詳しくは次のページで解説します。"
@@ -1403,13 +1377,13 @@ _initialTutorial:
   _reaction:
     title: "リアクションって何?"
     description: "ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。"
-    letsTryReacting: "リアクションは、ノートの「{reaction}」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!"
+    letsTryReacting: "リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!"
     reactToContinue: "リアクションをつけると先に進めるようになります。"
     reactNotification: "あなたのノートが誰かにリアクションされると、リアルタイムで通知を受け取ります。"
-    reactDone: "「{undo}」ボタンを押すとリアクションを取り消すことができます。"
+    reactDone: "「ー」ボタンを押すとリアクションを取り消すことができます。"
   _timeline:
     title: "タイムラインのしくみ"
-    description1: "Sharkeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。"
+    description1: "Misskeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。"
     home: "あなたがフォローしているアカウントの投稿を見られます。"
     local: "このサーバーにいるユーザー全員の投稿を見られます。"
     social: "ホームタイムラインとローカルタイムラインの投稿が両方表示されます。"
@@ -1418,12 +1392,12 @@ _initialTutorial:
     description3: "その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。"
   _postNote:
     title: "ノートの投稿設定"
-    description1: "Sharkeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。"
+    description1: "Misskeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。"
     _visibility:
       description: "ノートを表示できる相手を制限できます。"
       public: "すべてのユーザーに公開。"
-      home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・ブーストから、他のユーザーも見ることができます。"
-      followers: "フォロワーにのみ公開。本人以外がブーストすることはできず、またフォロワー以外は閲覧できません。"
+      home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。"
+      followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。"
       direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。"
       doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。"
       doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。"
@@ -1446,7 +1420,7 @@ _initialTutorial:
     doItToContinue: "画像をセンシティブに設定すると先に進めるようになります。"
   _done:
     title: "チュートリアルは終了です🎉"
-    description: "ここで紹介した機能はほんの一部にすぎません。Sharkeyの使い方をより詳しく知るには、{link}をご覧ください。"
+    description: "ここで紹介した機能はほんの一部にすぎません。Misskeyの使い方をより詳しく知るには、{link}をご覧ください。"
 
 _timelineDescription:
   home: "ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。"
@@ -1469,8 +1443,10 @@ _serverSettings:
   fanoutTimelineDescription: "有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。"
   fanoutTimelineDbFallback: "データベースへのフォールバック"
   fanoutTimelineDbFallbackDescription: "有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。"
+  reactionsBufferingDescription: "有効にすると、リアクション作成時のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。"
   inquiryUrl: "問い合わせ先URL"
   inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。"
 
 _accountMigration:
   moveFrom: "別のアカウントからこのアカウントに移行"
@@ -1480,7 +1456,7 @@ _accountMigration:
   moveTo: "このアカウントを新しいアカウントへ移行"
   moveToLabel: "移行先のアカウント:"
   moveCannotBeUndone: "アカウントを移行すると、取り消すことはできません。"
-  moveAccountDescription: "新しいアカウントへ移行します。\n ・フォロワーが新しいアカウントを自動でフォローします\n ・このアカウントからのフォローは全て解除されます\n ・このアカウントではノートの作成などができなくなります\n\nフォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。\nリスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。\n\n(この説明はこのサーバー(Sharkey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。)"
+  moveAccountDescription: "新しいアカウントへ移行します。\n ・フォロワーが新しいアカウントを自動でフォローします\n ・このアカウントからのフォローは全て解除されます\n ・このアカウントではノートの作成などができなくなります\n\nフォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。\nリスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。\n\n(この説明はこのサーバー(Misskey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。)"
   moveAccountHowTo: "アカウントの移行には、まずは移行先のアカウントでこのアカウントに対しエイリアスを作成します。\nエイリアス作成後、移行先のアカウントを次のように入力してください: @username@server.example.com"
   startMigration: "移行する"
   migrationConfirm: "本当にこのアカウントを {account} に移行しますか?一度移行すると取り消せず、二度とこのアカウントを元の状態で使用できなくなります。"
@@ -1492,9 +1468,9 @@ _achievements:
   earnedAt: "獲得日時"
   _types:
     _notes1:
-      title: "just setting up my shonk"
+      title: "just setting up my msky"
       description: "初めてノートを投稿した"
-      flavor: "良いSharkeyライフを!"
+      flavor: "良いMisskeyライフを!"
     _notes10:
       title: "いくつかのノート"
       description: "ノートを10回投稿した"
@@ -1590,7 +1566,7 @@ _achievements:
     _login1000:
       title: "ノートマスターⅢ"
       description: "通算ログイン日数が1,000日"
-      flavor: "Sharkeyを使ってくれてありがとう!"
+      flavor: "Misskeyを使ってくれてありがとう!"
     _noteClipped1:
       title: "クリップせずにはいられないな"
       description: "初めてノートをクリップした"
@@ -1650,9 +1626,9 @@ _achievements:
       title: "実績好き"
       description: "実績一覧を3分以上眺め続けた"
     _iLoveMisskey:
-      title: "I Love Sharkey"
-      description: "\"I ❤ #Sharkey\"を投稿した"
-      flavor: "Sharkeyを使ってくださりありがとうございます! by 開発チーム"
+      title: "I Love Misskey"
+      description: "\"I ❤ #Misskey\"を投稿した"
+      flavor: "Misskeyを使ってくださりありがとうございます! by 開発チーム"
     _foundTreasure:
       title: "宝探し"
       description: "隠されたお宝を発見した"
@@ -1660,7 +1636,7 @@ _achievements:
       title: "ひとやすみ"
       description: "クライアントを起動してから30分以上経過した"
     _client60min:
-      title: "Sharkeyの見すぎ"
+      title: "Misskeyの見すぎ"
       description: "クライアントを起動してから60分以上経過した"
     _noteDeletedWithin1min:
       title: "いまのなし"
@@ -1731,7 +1707,7 @@ _achievements:
       title: "テスト過剰"
       description: "通知のテストをごく短時間のうちに連続して行った"
     _tutorialCompleted:
-      title: "Sharkey初心者講座 修了証"
+      title: "Misskey初心者講座 修了証"
       description: "チュートリアルを完了した"
     _bubbleGameExplodingHead:
       title: "🤯"
@@ -1779,10 +1755,8 @@ _role:
     high: "高"
   _options:
     gtlAvailable: "グローバルタイムラインの閲覧"
-    btlAvailable: "バブルタイムラインの閲覧"
     ltlAvailable: "ローカルタイムラインの閲覧"
     canPublicNote: "パブリック投稿の許可"
-    canImportNotes: "ノートのインポートが可能"
     mentionMax: "ノート内の最大メンション数"
     canInvite: "サーバー招待コードの発行"
     inviteLimit: "招待コードの作成可能数"
@@ -1807,6 +1781,11 @@ _role:
     canSearchNotes: "ノート検索の利用"
     canUseTranslator: "翻訳機能の利用"
     avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
+    canImportAntennas: "アンテナのインポートを許可"
+    canImportBlocking: "ブロックのインポートを許可"
+    canImportFollowing: "フォローのインポートを許可"
+    canImportMuting: "ミュートのインポートを許可"
+    canImportUserLists: "リストのインポートを許可"
   _condition:
     roleAssignedTo: "マニュアルロールにアサイン済み"
     isLocal: "ローカルユーザー"
@@ -1854,8 +1833,6 @@ _signup:
   almostThere: "ほとんど完了です"
   emailAddressInfo: "あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。"
   emailSent: "入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。"
-  approvalPending: "アカウントが作成され、承認待ちの状態です。"
-  reasonInfo: "インスタンスに参加したい理由を入力してください。"
 
 _accountDelete:
   accountDelete: "アカウントの削除"
@@ -1926,20 +1903,17 @@ _registry:
   createKey: "キーを作成"
 
 _aboutMisskey:
-  about: "Sharkeyは、Misskeyをベースにしたオープンソースのソフトウェアです。"
-  contributors: "主なコントリビューター"
+  about: "Misskeyはsyuiloによって2014年から開発されている、オープンソースのソフトウェアです。"
+  contributors: "コントリビューター"
   allContributors: "全てのコントリビューター"
   source: "ソースコード"
-  original: "Misskey オリジナル"
-  original_sharkey: "Sharkey オリジナル"
-  thisIsModifiedVersion: "{name}はオリジナルのSharkeyを改変したバージョンを使用しています。"
-  translation: "Sharkeyを翻訳"
+  original: "オリジナル"
+  thisIsModifiedVersion: "{name}はオリジナルのMisskeyを改変したバージョンを使用しています。"
+  translation: "Misskeyを翻訳"
   donate: "Misskeyに寄付"
-  donate_sharkey: "Sharkeyに寄付"
   morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
   patrons: "支援者"
   projectMembers: "プロジェクトメンバー"
-  testers: "テスター"
 
 _displayOfSensitiveMedia:
   respect: "センシティブ設定されたメディアを隠す"
@@ -1955,7 +1929,6 @@ _serverDisconnectedBehavior:
   reload: "自動でリロード"
   dialog: "ダイアログで警告"
   quiet: "控えめに警告"
-  disabled: "警告を無効にする"
 
 _channel:
   create: "チャンネルを作成"
@@ -1969,7 +1942,7 @@ _channel:
   notesCount: "{n}投稿があります"
   nameAndDescription: "名前と説明"
   nameOnly: "名前のみ"
-  allowRenoteToExternal: "チャンネル外へのブーストと引用ブーストを許可する"
+  allowRenoteToExternal: "チャンネル外へのリノートと引用リノートを許可する"
 
 _menuDisplay:
   sideFull: "横"
@@ -1983,7 +1956,7 @@ _wordMute:
   muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
 
 _instanceMute:
-  instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとブーストをミュートします。"
+  instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。"
   instanceMuteDescription2: "改行で区切って設定します"
   title: "設定したサーバーのノートを隠します。"
   heading: "ミュートするサーバー"
@@ -2037,7 +2010,7 @@ _theme:
     hashtag: "ハッシュタグ"
     mention: "メンション"
     mentionMe: "あなた宛てメンション"
-    renote: "Boost"
+    renote: "Renote"
     modalBg: "モーダルの背景"
     divider: "分割線"
     scrollbarHandle: "スクロールバーの取っ手"
@@ -2052,7 +2025,6 @@ _theme:
     buttonBg: "ボタンの背景"
     buttonHoverBg: "ボタンの背景 (ホバー)"
     inputBorder: "入力ボックスの縁取り"
-    listItemHoverBg: "リスト項目の背景 (ホバー)"
     driveFolderBg: "ドライブフォルダーの背景"
     wallpaperOverlay: "壁紙のオーバーレイ"
     badge: "バッジ"
@@ -2073,7 +2045,7 @@ _soundSettings:
   driveFileTypeWarn: "このファイルは対応していません"
   driveFileTypeWarnDescription: "音声ファイルを選択してください"
   driveFileDurationWarn: "音声が長すぎます"
-  driveFileDurationWarnDescription: "長い音声を使用するとSharkeyの使用に支障をきたす可能性があります。それでも続行しますか?"
+  driveFileDurationWarnDescription: "長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか?"
   driveFileError: "音声が読み込めませんでした。設定を変更してください"
 
 _ago:
@@ -2276,7 +2248,6 @@ _widgets:
   _userList:
     chooseList: "リストを選択"
   clicker: "クリッカー"
-  search: "検索"
   birthdayFollowings: "今日誕生日のユーザー"
 
 _cw:
@@ -2307,7 +2278,6 @@ _poll:
   remainingHours: "終了まであと{h}時間{m}分"
   remainingMinutes: "終了まであと{m}分{s}秒"
   remainingSeconds: "終了まであと{s}秒"
-  multiple: "複数の選択肢"
 
 _visibility:
   public: "パブリック"
@@ -2345,13 +2315,11 @@ _profile:
   metadataContent: "内容"
   changeAvatar: "アイコン画像を変更"
   changeBanner: "バナー画像を変更"
-  updateBanner: "更新バナー"
-  removeBanner: "バナーを削除"
-  changeBackground: "背景を変更する"
-  updateBackground: "背景を更新する"
-  removeBackground: "背景を削除する"
   verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
   avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
+  followedMessage: "フォローされた時のメッセージ"
+  followedMessageDescription: "フォローされた時に相手に表示する短いメッセージを設定できます。"
+  followedMessageDescriptionForLockedAccount: "フォローを承認制にしている場合、フォローリクエストを許可した時に表示されます。"
 
 _exportOrImport:
   allNotes: "全てのノート"
@@ -2398,7 +2366,6 @@ _timelines:
   local: "ローカル"
   social: "ソーシャル"
   global: "グローバル"
-  bubble: "バッッブル"
 
 _play:
   new: "Playの作成"
@@ -2481,12 +2448,11 @@ _notification:
   youGotMention: "{name}からのメンション"
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
-  youRenoted: "{name}がBoostしました"
+  youRenoted: "{name}がRenoteしました"
   youWereFollowed: "フォローされました"
   youReceivedFollowRequest: "フォローリクエストが来ました"
   yourFollowRequestAccepted: "フォローリクエストが承認されました"
   pollEnded: "アンケートの結果が出ました"
-  edited: "投稿が編集されました"
   newNote: "新しい投稿"
   unreadAntennaNote: "アンテナ {name}"
   roleAssigned: "ロールが付与されました"
@@ -2501,6 +2467,8 @@ _notification:
   renotedBySomeUsers: "{n}人がリノートしました"
   followedBySomeUsers: "{n}人にフォローされました"
   flushNotification: "通知の履歴をリセットする"
+  exportOfXCompleted: "{x}のエクスポートが完了しました"
+  login: "ログインがありました"
 
   _types:
     all: "すべて"
@@ -2508,7 +2476,7 @@ _notification:
     follow: "フォロー"
     mention: "メンション"
     reply: "リプライ"
-    renote: "Boost"
+    renote: "Renote"
     quote: "引用"
     reaction: "リアクション"
     pollEnded: "アンケートが終了"
@@ -2516,13 +2484,15 @@ _notification:
     followRequestAccepted: "フォローが受理された"
     roleAssigned: "ロールが付与された"
     achievementEarned: "実績の獲得"
+    exportCompleted: "エクスポートが完了した"
+    login: "ログイン"
+    test: "通知のテスト"
     app: "連携アプリからの通知"
-    edited: "編集済み"
 
   _actions:
     followBack: "フォローバック"
     reply: "返信"
-    renote: "ブースト"
+    renote: "Renote"
 
 _deck:
   alwaysShowMainColumn: "常にメインカラムを表示"
@@ -2582,14 +2552,17 @@ _webhookSettings:
     followed: "フォローされたとき"
     note: "ノートを投稿したとき"
     reply: "返信されたとき"
-    renote: "Boostされたとき"
+    renote: "Renoteされたとき"
     reaction: "リアクションがあったとき"
     mention: "メンションされたとき"
   _systemEvents:
     abuseReport: "ユーザーから通報があったとき"
     abuseReportResolved: "ユーザーからの通報を処理したとき"
     userCreated: "ユーザーが作成されたとき"
+    inactiveModeratorsWarning: "モデレーターが一定期間非アクティブになったとき"
+    inactiveModeratorsInvitationOnlyChanged: "モデレーターが一定期間非アクティブだったため、システムにより招待制へと変更されたとき"
   deleteConfirm: "Webhookを削除しますか?"
+  testRemarks: "スイッチの右にあるボタンをクリックするとダミーのデータを使用したテスト用Webhookを送信できます。"
 
 _abuseReport:
   _notificationRecipient:
@@ -2613,7 +2586,6 @@ _moderationLogTypes:
   updateRole: "ロールを更新"
   assignRole: "ロールへアサイン"
   unassignRole: "ロールのアサイン解除"
-  approve: "承認済み"
   suspend: "凍結"
   unsuspend: "凍結解除"
   addCustomEmoji: "カスタム絵文字追加"
@@ -2636,6 +2608,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "ファイルをセンシティブ付与"
   unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
   resolveAbuseReport: "通報を解決"
+  forwardAbuseReport: "通報を転送"
+  updateAbuseReportNote: "通報のモデレーションノート更新"
   createInvitation: "招待コードを作成"
   createAd: "広告を作成"
   deleteAd: "広告を削除"
@@ -2656,87 +2630,6 @@ _moderationLogTypes:
   deleteFlash: "Playを削除"
   deleteGalleryPost: "ギャラリーの投稿を削除"
 
-_mfm:
-  uncommonFeature: "この機能は一般的に普及していないため、他のMisskeyフォークを含めた多くのFediverseソフトウェアで表示できないことがあります。"
-  intro: "MFM はMisskey, Sharkey, Firefish, Akkomaなど、多くの場所で使用できるマークアップ言語です。ここでは、利用できるMFM構文の一覧をご覧いただけます。"
-  dummy: "SharkeyでFediverseの世界が広がります"
-  mention: "メンション"
-  mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。"
-  hashtag: "ハッシュタグ"
-  hashtagDescription: "ナンバーサイン + タグで、ハッシュタグを示すことができます。"
-  url: "URL"
-  urlDescription: "URLを示すことができます。"
-  link: "リンク"
-  linkDescription: "文章の特定の範囲を、URLに紐づけることができます。"
-  bold: "太字"
-  boldDescription: "文字を太く表示して強調することができます。"
-  small: "小文字"
-  smallDescription: "内容を小さく・薄く表示させることができます。"
-  center: "中央寄せ"
-  centerDescription: "内容を中央寄せで表示させることができます。"
-  inlineCode: "コード(インライン)"
-  inlineCodeDescription: "プログラムなどのコードをインラインでシンタックスハイライトします。"
-  blockCode: "コード(ブロック)"
-  blockCodeDescription: "複数行のプログラムなどのコードをブロックでシンタックスハイライトします。"
-  inlineMath: "数式(インライン)"
-  inlineMathDescription: "数式 (KaTeX形式)をインラインで表示します。"
-  blockMath: "数式(ブロック)"
-  blockMathDescription: "数式 (KaTeX形式)をブロックで表示します。"
-  quote: "引用"
-  quoteDescription: "内容が引用であることを示すことができます。"
-  emoji: "カスタム絵文字"
-  emojiDescription: "コロンでカスタム絵文字名を囲むと、カスタム絵文字を表示させることができます。"
-  search: "検索"
-  searchDescription: "検索ボックスを表示できます。"
-  flip: "反転"
-  flipDescription: "内容を上下または左右に反転させます。"
-  jelly: "アニメーション(びよんびよん)"
-  jellyDescription: "ゼリーが揺れるような感じのアニメーションをさせます。"
-  tada: "アニメーション(じゃーん)"
-  tadaDescription: "「じゃーん!」と強調するような感じのアニメーションをさせます。"
-  jump: "アニメーション(ジャンプ)"
-  jumpDescription: "跳ねるアニメーションをさせます。"
-  bounce: "アニメーション(バウンド)"
-  bounceDescription: "跳ねて着地するようなアニメーションをさせます。"
-  shake: "アニメーション(ぶるぶる)"
-  shakeDescription: "震えるアニメーションをさせます。"
-  twitch: "アニメーション(ガタガタ)"
-  twitchDescription: "より激しく震えるアニメーションをさせます。"
-  spin: "アニメーション(回転)"
-  spinDescription: "内容を回転させます。"
-  x2: "大"
-  x2Description: "内容を大きく表示させます。"
-  x3: "特大"
-  x3Description: "内容をより大きく表示させます。"
-  x4: "超特大"
-  x4Description: "内容をさらに大きく表示させます。"
-  blur: "ぼかし"
-  blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。"
-  font: "フォント"
-  fontDescription: "内容のフォントを指定することができます。"
-  rainbow: "レインボー"
-  rainbowDescription: "内容を虹色で表示させます。"
-  sparkle: "キラキラ"
-  sparkleDescription: "キラキラと星型のパーティクルを表示させます。"
-  rotate: "角度変更"
-  rotateDescription: "指定した角度で回転させます。"
-  position: "位置変更"
-  positionDescription: "位置をずらすことができます。"
-  crop: "切り取り"
-  cropDescription: "内容を切り抜きます。"
-  followMouse: "マウス追従"
-  followMouseDescription: "内容がマウスに追従します。スマホの場合はタップした場所に追従します。"
-  scale: "拡大"
-  scaleDescription: "内容を引き伸ばして表示します。"
-  foreground: "文字色"
-  foregroundDescription: "文字色を変更します。"
-  fade: 'フェード'
-  fadeDescription: '内容をフェードイン・フェードアウトさせます。'
-  background: "背景色"
-  backgroundDescription: "背景色を変更します。"
-  plain: "Plain"
-  plainDescription: "内側の構文を全て無効にします。"
-
 _fileViewer:
   title: "ファイルの詳細"
   type: "ファイルタイプ"
@@ -2788,19 +2681,6 @@ _externalResourceInstaller:
       title: "テーマのインストールに失敗しました"
       description: "テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。"
 
-_animatedMFM:
-  play: "MFMアニメーションを再生"
-  stop: "MFMアニメーション停止"
-  _alert:
-    text: "MFMアニメーションには、高速で点滅したり動いたりするテキスト・絵文字を含む場合があります。"
-    confirm: "再生する"
-
-_dataRequest:
-  title: "データリクエスト"
-  warn: "データリクエストは3日ごとに可能です。"
-  text: "データの保存が完了すると、このアカウントに登録されているメールアドレスにメールが送信されます。"
-  button: "データリクエスト実行"
-
 _dataSaver:
   _media:
     title: "メディアの読み込みを無効化"
@@ -2894,3 +2774,18 @@ _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/ja-KS.yml b/locales/ja-KS.yml
index 448355eb4e4668529ba8742fc7f70020cac74cad..0a8b3828f2de8d5ba80c21ca29493324caaa7700 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1,8 +1,8 @@
 ---
 _lang_: "日本語 (関西弁)"
 headlineMisskey: "ノートでつながるネットワーク"
-introMisskey: "ようお越し!Sharkeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のノートに素早く反応を追加したりもできるで✌\nほな、新しい世界を探検しよか🚀"
-poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつなんやで。"
+introMisskey: "ようお越し!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のノートに素早く反応を追加したりもできるで✌\nほな、新しい世界を探検しよか🚀"
+poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつなんやで。"
 monthAndDay: "{month}月 {day}日"
 search: "探す"
 notifications: "通知"
@@ -15,7 +15,7 @@ gotIt: "ほい"
 cancel: "やめとく"
 noThankYou: "やめとく"
 enterUsername: "ユーザー名を入れてや"
-renotedBy: "{user}がブーストしたで"
+renotedBy: "{user}がリノートしたで"
 noNotes: "ノートはあらへん"
 noNotifications: "通知はあらへん"
 instance: "サーバー"
@@ -45,10 +45,10 @@ pin: "ピン留めしとく"
 unpin: "やっぱピン留めせん"
 copyContent: "内容をコピー"
 copyLink: "リンクをコピー"
-copyLinkRenote: "ブーストのリンクをコピーするで?"
+copyLinkRenote: "リノートのリンクをコピーするで?"
 delete: "ほかす"
 deleteAndEdit: "ほかして直す"
-deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、ブースト、返信も全部消えるんやけどそれでもええん?"
+deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、リノート、返信も全部消えるんやけどそれでもええん?"
 addToList: "リストに入れたる"
 addToAntenna: "アンテナに入れる"
 sendMessage: "メッセージを送る"
@@ -113,7 +113,7 @@ renotedToX: "{name}にリノートしたで"
 cantRenote: "この投稿はリノートできへんっぽい。"
 cantReRenote: "リノート自体はリノートできへんで。"
 quote: "引用"
-inChannelRenote: "チャンネルの中でブースト"
+inChannelRenote: "チャンネルの中でリノート"
 inChannelQuote: "チャンネル内引用"
 renoteToChannel: "チャンネルにリノート"
 renoteToOtherChannel: "他のチャンネルにリノート"
@@ -140,8 +140,8 @@ unmarkAsSensitive: "そこまでアカンことないやろ"
 enterFileName: "ファイル名を入れてや"
 mute: "ミュート"
 unmute: "ミュートやめたる"
-renoteMute: "ブーストは見いひん"
-renoteUnmute: "ブーストもやっぱ見るわ"
+renoteMute: "リノートは見いひん"
+renoteUnmute: "リノートもやっぱ見るわ"
 block: "ブロック"
 unblock: "ブロックやめたる"
 suspend: "凍結"
@@ -172,11 +172,9 @@ youCanCleanRemoteFilesCache: "ファイル管理にある🗑️ボタンでキ
 cacheRemoteSensitiveFiles: "リモートのきわどいファイルをキャッシュに突っ込む"
 cacheRemoteSensitiveFilesDescription: "この設定を切ると、リモートのきわどいファイルはキャッシュせず直でリンクするようになるで。"
 flagAsBot: "Botにするで"
-flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Sharkeyのシステム上での扱いがBotに合ったもんになるからな。"
+flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Misskeyのシステム上での扱いがBotに合ったもんになるからな。"
 flagAsCat: "猫や。かわええな。"
 flagAsCatDescription: "ネコになりたいんならこれつけとき。"
-flagSpeakAsCat: "猫語で話すで"
-flagSpeakAsCatDescription: "有効にすると、あなたの投稿の 「な」を「にゃ」にするでー。"
 flagShowTimelineReplies: "タイムラインにノートへの返信を表示するで"
 flagShowTimelineRepliesDescription: "オンにしたら、タイムラインにユーザーのノートの他にもそのユーザーの他のノートへの返信を表示するで。"
 autoAcceptFollowed: "フォローしとるユーザーからのフォローリクエストを勝手に許可しとく"
@@ -245,7 +243,7 @@ noUsers: "ユーザーはおらん"
 editProfile: "プロフィールをいじる"
 noteDeleteConfirm: "このノートをほかしてええか?"
 pinLimitExceeded: "これ以上ピン留めできひん"
-intro: "Sharkeyのインストールが完了したで!管理者アカウントを作ってや。"
+intro: "Misskeyのインストールが完了したで!管理者アカウントを作ってや。"
 done: "でけた"
 processing: "処理しとる"
 preview: "プレビュー"
@@ -440,7 +438,7 @@ exploreFediverse: "Fediverseを探ってみる"
 popularTags: "人気のタグ"
 userList: "リスト"
 about: "情報"
-aboutMisskey: "Sharkeyってなんや?"
+aboutMisskey: "Misskeyってなんや?"
 administrator: "管理者"
 token: "確認コード"
 2fa: "二要素認証"
@@ -511,7 +509,6 @@ uiLanguage: "UIの表示言語"
 aboutX: "{x}について"
 emojiStyle: "絵文字のスタイル"
 native: "ネイティブ"
-disableDrawer: "メニューをドロワーで表示せえへん"
 showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで"
 showReactionsCount: "ノートのリアクション数を表示する"
 noHistory: "履歴はないわ。"
@@ -577,7 +574,7 @@ popout: "ポップアウト"
 volume: "やかましさ"
 masterVolume: "全体のやかましさ"
 notUseSound: "音出さへん"
-useSoundOnlyWhenActive: "Sharkeyがアクティブなときだけ音出す"
+useSoundOnlyWhenActive: "Misskeyがアクティブなときだけ音出す"
 details: "もっと"
 chooseEmoji: "絵文字を選ぶ"
 unableToProcess: "なんか奥の方で詰まってもうた"
@@ -593,7 +590,7 @@ sort: "仕分ける"
 ascendingOrder: "小さい順"
 descendingOrder: "大きい順"
 scratchpad: "スクラッチパッド"
-scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Sharkeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
+scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Misskeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
 output: "出力"
 script: "スクリプト"
 disablePagesScript: "Pagesのスクリプトを無効にしてや"
@@ -703,17 +700,14 @@ behavior: "動作"
 sample: "サンプル"
 abuseReports: "通報"
 reportAbuse: "通報"
-reportAbuseRenote: "ブースト苦情だすで?"
+reportAbuseRenote: "リノート苦情だすで?"
 reportAbuseOf: "{name}を通報する"
 fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ノートがある時はそのURLも書いといてなー。"
 abuseReported: "無事内容が送信されたみたいやで。おおきに〜。"
 reporter: "通報者"
 reporteeOrigin: "通報先"
 reporterOrigin: "通報元"
-forwardReport: "リモートサーバーに通報を転送するで"
-forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見えんなって、匿名のシステムアカウントとして表示されるで。"
 send: "送信"
-abuseMarkAsResolved: "対応したで"
 openInNewTab: "新しいタブで開く"
 openInSideView: "サイドビューで開く"
 defaultNavigationBehaviour: "デフォルトのナビゲーション"
@@ -732,14 +726,14 @@ unclip: "クリップやめとく"
 confirmToUnclipAlreadyClippedNote: "このノートはもう「{name}」に含まれとるで。ノート、このクリップから外そか?"
 public: "パブリック"
 private: "非公開"
-i18nInfo: "Sharkeyは有志がいろんな言語に訳しとるで。{link}で翻訳に協力したってやー。"
+i18nInfo: "Misskeyは有志がいろんな言語に訳しとるで。{link}で翻訳に協力したってやー。"
 manageAccessTokens: "アクセストークンの管理"
 accountInfo: "アカウント情報"
 notesCount: "ノートの数やで"
 repliesCount: "返信した数やで"
-renotesCount: "ブーストした数やで"
+renotesCount: "リノートした数やで"
 repliedCount: "返信された数やで"
-renotedCount: "ブーストされた数やで"
+renotedCount: "リノートされた数やで"
 followingCount: "フォロー数やで"
 followersCount: "フォロワー数やで"
 sentReactionsCount: "ツッコんだ数"
@@ -786,7 +780,7 @@ onlineUsersCount: "{n}人が起きとるで"
 nUsers: "{n}ユーザー"
 nNotes: "{n}ノート"
 sendErrorReports: "エラーリポートを送る"
-sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたとき、詳しいのが全部Sharkeyに送られて、ソフトウェアをもっと良うするで。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴なんかが含まれるな。"
+sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたとき、詳しいのが全部Misskeyに送られて、ソフトウェアをもっと良うするで。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴なんかが含まれるな。"
 myTheme: "マイテーマ"
 backgroundColor: "背景"
 accentColor: "アクセント"
@@ -881,7 +875,7 @@ hashtags: "ハッシュタグ"
 troubleshooting: "トラブルシューティング"
 useBlurEffect: "UIにぼかし効果を使うで"
 learnMore: "詳しく"
-misskeyUpdated: "Sharkeyが更新されたで!\nモデレーターの人らに感謝せなあかんで"
+misskeyUpdated: "Misskeyが更新されたで!\nモデレーターの人らに感謝せなあかんで"
 whatIsNew: "更新情報を見るで"
 translate: "翻訳"
 translatedFrom: "{x}から翻訳するで"
@@ -1010,8 +1004,8 @@ numberOfLikes: "いいね数"
 show: "表示"
 neverShow: "今後表示しない"
 remindMeLater: "また後で"
-didYouLikeMisskey: "Sharkey気に入ってくれた?"
-pleaseDonate: "Sharkeyは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
+didYouLikeMisskey: "Misskey気に入ってくれた?"
+pleaseDonate: "Misskeyは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
 correspondingSourceIsAvailable: "{anchor}"
 roles: "ロール"
 role: "ロール"
@@ -1039,8 +1033,8 @@ thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。"
 thisPostMayBeAnnoyingHome: "ホームに投稿"
 thisPostMayBeAnnoyingCancel: "やめとく"
 thisPostMayBeAnnoyingIgnore: "このまま投稿"
-collapseRenotes: "見たことあるブーストは飛ばして表示するで"
-collapseRenotesDescription: "リアクションやブーストをしたことがあるノートをたたんで表示するで。"
+collapseRenotes: "見たことあるリノートは飛ばして表示するで"
+collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示するで。"
 internalServerError: "サーバー内部エラー"
 internalServerErrorDescription: "サーバーでなんか変なこと起こっとるわ。"
 copyErrorInfo: "エラー情報をコピるで"
@@ -1096,7 +1090,7 @@ forceShowAds: "いっつも広告を映す"
 addMemo: "メモを足す"
 editMemo: "メモをいらう"
 reactionsList: "ツッコミ一覧"
-renotesList: "ブースト一覧"
+renotesList: "リノート一覧"
 notificationDisplay: "通知見せる"
 leftTop: "左上"
 rightTop: "右上"
@@ -1137,7 +1131,7 @@ rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロー
 cancelReactionConfirm: "ツッコむんをやっぱやめるか?"
 changeReactionConfirm: "ツッコミを別のに変えるか?"
 later: "あとで"
-goToMisskey: "Sharkeyへ"
+goToMisskey: "Misskeyへ"
 additionalEmojiDictionary: "絵文字の追加辞書"
 installed: "インストールしとる"
 branding: "ブランディング"
@@ -1172,7 +1166,7 @@ pastAnnouncements: "過去のお知らせやで"
 youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんやろ。"
 useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。"
 replies: "返事"
-renotes: "ブースト"
+renotes: "リノート"
 loadReplies: "返信を見るで"
 loadConversation: "会話を見るで"
 pinnedList: "ピン留めしはったリスト"
@@ -1183,7 +1177,7 @@ unnotifyNotes: "投稿の通知やめる"
 authentication: "認証"
 authenticationRequiredToContinue: "続けるんなら認証してや。"
 dateAndTime: "日時"
-showRenotes: "ブースト出す"
+showRenotes: "リノート出す"
 edited: "いじったやつ"
 notificationRecieveConfig: "通知もらうかの設定"
 mutualFollow: "お互いフォローしてんで"
@@ -1314,7 +1308,7 @@ _initialAccountSetting:
   pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。"
   initialAccountSettingCompleted: "初期設定終わりや!"
   haveFun: "{name}、楽しんでな~"
-  youCanContinueTutorial: "こんまま{name}(Sharkey)の使い方のチュートリアルにも行けるけど、ここでやめてすぐに使い始めてもええで。"
+  youCanContinueTutorial: "こんまま{name}(Misskey)の使い方のチュートリアルにも行けるけど、ここでやめてすぐに使い始めてもええで。"
   startTutorial: "チュートリアルはじめる"
   skipAreYouSure: "初期設定飛ばすか?"
   laterAreYouSure: "初期設定あとでやり直すん?"
@@ -1325,10 +1319,10 @@ _initialTutorial:
   skipAreYouSure: "チュートリアルやめるか?"
   _landing:
     title: "チュートリアルによう来たな"
-    description: "ここでは、Sharkeyのカンタンな使い方とか機能を確かめれんで。"
+    description: "ここでは、Misskeyのカンタンな使い方とか機能を確かめれんで。"
   _note:
     title: "ノートってなんや?"
-    description: "Sharkeyでの投稿は「ノート」って呼ばれてんで。ノートは順々にタイムラインに載ってて、リアルタイムで新しくなってってんで。"
+    description: "Misskeyでの投稿は「ノート」って呼ばれてんで。ノートは順々にタイムラインに載ってて、リアルタイムで新しくなってってんで。"
     reply: "返信もできるで。返信の返信もできるから、スレッドっぽく会話をそのまま続けれもするで。"
     renote: "そのノートを自分のタイムラインに流して共有できるで。テキスト入れて引用してもええな。"
     reaction: "ツッコミをつけることもできるで。細かいことは次のページや。"
@@ -1336,13 +1330,13 @@ _initialTutorial:
   _reaction:
     title: "ツッコミってなんや?"
     description: "ノートには「ツッコミ」できんねん。「いいね」とか何言っとるかわからんし、簡単に表現できるのはええことやん?"
-    letsTryReacting: "ノートの「{reaction}」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。"
+    letsTryReacting: "ノートの「+」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。"
     reactToContinue: "ツッコんだら進めるようになるで。"
     reactNotification: "あんたのノートが誰かにツッコまれたら、すぐ通知するで。"
-    reactDone: "「{undo}」ボタンでツッコミやめれるで。"
+    reactDone: "「ー」ボタンでツッコミやめれるで。"
   _timeline:
     title: "タイムラインのしくみ"
-    description1: "Sharkeyには、いろいろタイムラインがあんで(ただ、サーバーによっては無効化されてるところもあるな)。"
+    description1: "Misskeyには、いろいろタイムラインがあんで(ただ、サーバーによっては無効化されてるところもあるな)。"
     home: "あんたがフォローしてるアカウントの投稿が見れんねん。"
     local: "このサーバーの中におる全員の投稿が見れるで。"
     social: "ホームタイムラインの投稿もローカルタイムラインのも一緒に見れるで。"
@@ -1351,12 +1345,12 @@ _initialTutorial:
     description3: "その他にも、リストタイムラインとかチャンネルタイムラインとかがあんねん。詳しいのは{link}を見とき。"
   _postNote:
     title: "ノートの投稿設定"
-    description1: "Sharkeyにノートを投稿するとき、いろんなオプションが付けれるで。投稿画面はこんな感じや。"
+    description1: "Misskeyにノートを投稿するとき、いろんなオプションが付けれるで。投稿画面はこんな感じや。"
     _visibility:
       description: "ノートを見れる相手を制限できるわ。"
       public: "みんなに見せるで。"
-      home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、ブーストからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。"
-      followers: "フォロワーにだけ見せるで。自分以外はブーストできへんし、フォロワー以外は絶対に見れへん。"
+      home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、リノートからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。"
+      followers: "フォロワーにだけ見せるで。自分以外はリノートできへんし、フォロワー以外は絶対に見れへん。"
       direct: "指定した人にだけ公開されて、ついでに通知も送るで。ダイレクトメールの代わりとして使ってな。"
       doNotSendConfidencialOnDirect1: "機密情報を送るときは十分注意せえよ。"
       doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容が見れるから、信用できへんサーバーのひとにダイレクト投稿するときには、めっちゃ用心しとくんやで。"
@@ -1422,7 +1416,7 @@ _achievements:
     _notes1:
       title: "まいど!"
       description: "初めてノート投稿したった"
-      flavor: "Sharkeyを楽しんでな~"
+      flavor: "Misskeyを楽しんでな~"
     _notes10:
       title: "ノートの天保山"
       description: "ノートを10回投稿した"
@@ -1518,7 +1512,7 @@ _achievements:
     _login1000:
       title: "ノートマイスターⅢ"
       description: "通算1,000日ログインした"
-      flavor: "Sharkeyようさん使てもろておおきにな!"
+      flavor: "Misskeyようさん使てもろておおきにな!"
     _noteClipped1:
       title: "アカンどれもクリップしたいわ"
       description: "初めてノートをクリップした"
@@ -1578,9 +1572,9 @@ _achievements:
       title: "実績好き"
       description: "実績一覧を3分以上眺め続けた"
     _iLoveMisskey:
-      title: "Sharkey好きやねん"
-      description: "\"I ❤ #Sharkey\"を投稿した"
-      flavor: "Sharkeyを使ってくれておおきにな~ by 開発チーム"
+      title: "Misskey好きやねん"
+      description: "\"I ❤ #Misskey\"を投稿した"
+      flavor: "Misskeyを使ってくれておおきにな~ by 開発チーム"
     _foundTreasure:
       title: "なんでも鑑定団"
       description: "隠されたお宝を発見した"
@@ -1588,7 +1582,7 @@ _achievements:
       title: "ねんね"
       description: "クライアントを起動してから30分以上経過した"
     _client60min:
-      title: "Sharkeyの見過ぎや!"
+      title: "Misskeyの見過ぎや!"
       description: "クライアント付けてから1時間経ってもうたで。"
     _noteDeletedWithin1min:
       title: "*おおっと*"
@@ -1659,7 +1653,7 @@ _achievements:
       title: "心配性"
       description: "通知のテストしすぎやって"
     _tutorialCompleted:
-      title: "Sharkeyひよっこ講座 修了証"
+      title: "Misskeyひよっこ講座 修了証"
       description: "チュートリアル全部やった"
     _bubbleGameExplodingHead:
       title: "🤯"
@@ -1836,14 +1830,14 @@ _registry:
   domain: "ドメイン"
   createKey: "キーを作る"
 _aboutMisskey:
-  about: "Sharkeyは、Misskeyをベースにしたオープンソースなソフトウェアや。"
+  about: "Misskeyはsyuiloが2014年からずっと作ってはる、オープンソースなソフトウェアや。"
   contributors: "主な貢献者"
   allContributors: "全ての貢献者"
   source: "ソースコード"
   original: "オリジナル"
-  thisIsModifiedVersion: "{name}はオリジナルのSharkeyをいじったバージョンをつこうてるで。"
-  translation: "Sharkeyを翻訳"
-  donate: "Sharkeyに寄付"
+  thisIsModifiedVersion: "{name}はオリジナルのMisskeyをいじったバージョンをつこうてるで。"
+  translation: "Misskeyを翻訳"
+  donate: "Misskeyに寄付"
   morePatrons: "他にもぎょうさんの人からサポートしてもろてんねん。ほんまおおきに🥰"
   patrons: "支援者"
   projectMembers: ""
@@ -1871,7 +1865,7 @@ _channel:
   notesCount: "{n}こ投稿があるで"
   nameAndDescription: "名前と説明"
   nameOnly: "名前だけ"
-  allowRenoteToExternal: "チャンネルの外にブーストできるようにする"
+  allowRenoteToExternal: "チャンネルの外にリノートできるようにする"
 _menuDisplay:
   sideFull: "横"
   sideIcon: "横(アイコン)"
@@ -1949,7 +1943,6 @@ _theme:
     buttonBg: "ボタンの背景"
     buttonHoverBg: "ボタンの背景 (ホバー)"
     inputBorder: "入力ボックスの縁取り"
-    listItemHoverBg: "リスト項目の背景 (ホバー)"
     driveFolderBg: "ドライブフォルダーの背景"
     wallpaperOverlay: "壁紙のオーバーレイ"
     badge: "バッジ"
@@ -1968,7 +1961,7 @@ _soundSettings:
   driveFileTypeWarn: "このファイルは対応しとらへん"
   driveFileTypeWarnDescription: "音声ファイルを選びや"
   driveFileDurationWarn: "音が長すぎるわ"
-  driveFileDurationWarnDescription: "長い音使うたらSharkey使うのに良うないかもしれへんで。それでもええか?"
+  driveFileDurationWarnDescription: "長い音使うたらMisskey使うのに良うないかもしれへんで。それでもええか?"
   driveFileError: "音声が読み込めへんかったで。設定を変更せえや"
 _ago:
   future: "未来"
@@ -2344,7 +2337,7 @@ _notification:
   youGotMention: "{name}からのメンション"
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
-  youRenoted: "{name}がブーストしたみたいやで"
+  youRenoted: "{name}がリノートしたみたいやで"
   youWereFollowed: "フォローされたで"
   youReceivedFollowRequest: "フォロー許可してほしいみたいやな"
   yourFollowRequestAccepted: "フォローさせてもろたで"
@@ -2360,7 +2353,7 @@ _notification:
   notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで"
   reactedBySomeUsers: "{n}人がツッコんだで"
   likedBySomeUsers: "{n}人がいいねしたで"
-  renotedBySomeUsers: "{n}人がブーストしたで"
+  renotedBySomeUsers: "{n}人がリノートしたで"
   followedBySomeUsers: "{n}人にフォローされたで"
   flushNotification: "通知の履歴をリセットする"
   _types:
@@ -2377,6 +2370,7 @@ _notification:
     followRequestAccepted: "フォローが受理されたで"
     roleAssigned: "ロールが付与された"
     achievementEarned: "実績の獲得"
+    login: "ログイン"
     app: "連携アプリからの通知や"
   _actions:
     followBack: "フォローバック"
@@ -2435,7 +2429,7 @@ _webhookSettings:
     followed: "フォローもらったとき~!"
     note: "ノートを投稿したとき~!"
     reply: "返信があるとき~!"
-    renote: "ブーストされるとき~!"
+    renote: "リノートされるとき~!"
     reaction: "ツッコまれたとき~!"
     mention: "メンションがあるとき~!"
   _systemEvents:
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index b3ad46f2b16a9b964c56420a50d93f290940f358..222599572a61287000c0b9531106f2f6c82e9b63 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 9323ed2a26ae54927368c6e10d3477006f35e4b4..6c667b48da4f5d676dad01f7d58704f96c12ec15 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -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: "좋네예한 걸"
@@ -794,6 +811,7 @@ _notification:
     mention: "멘션"
     quote: "따오기"
     reaction: "반엉"
+    login: "로그인"
   _actions:
     reply: "답하기"
 _deck:
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 64bae5a9d7b27db0bbc6bb579b575dfb03960fbb..414202adab50fb0e2ae339e7115d8b902b7e7644 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: "정지됨"
@@ -1320,10 +1371,10 @@ _initialTutorial:
   _reaction:
     title: "'리액션'이 무엇인가요?"
     description: "노트에 '리액션'을 보낼 수 있습니다. '좋아요'만으로는 충분히 전해지지 않는 감정을, 이모지에 실어서 가볍게 보낼 수 있습니다."
-    letsTryReacting: "리액션은 노트의 '{reaction}' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!"
+    letsTryReacting: "리액션은 노트의 '+' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!"
     reactToContinue: "다음으로 진행하려면 리액션을 보내세요."
     reactNotification: "누군가가 나의 노트에 리액션을 보내면 실시간으로 알림을 받게 됩니다."
-    reactDone: "'{undo}' 버튼을 눌러서 리액션을 취소할 수 있습니다."
+    reactDone: "'-' 버튼을 눌러서 리액션을 취소할 수 있습니다."
   _timeline:
     title: "타임라인에 대하여"
     description1: "Misskey에는 종류에 따라 여러 가지의 타임라인으로 구성되어 있습니다.(서버에 따라서는 일부 타임라인을 사용할 수 없는 경우가 있습니다)"
@@ -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 1bead5635dfa1500ac0dadc1063a6d25fc59c5a2..b100d0300f269bcafbc5b0d4ada334b8245cc73f 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 686d532c4c30ee24ae129dced23ce126fb1e23f9..dde3035357dd686e5d3ad6999f1132b8788168ba 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -251,7 +251,7 @@ upload: "Uploaden"
 keepOriginalUploading: "Origineel beeld behouden."
 keepOriginalUploadingDescription: "Bewaar de originele versie bij het uploaden van afbeeldingen. Indien uitgeschakeld, wordt bij het uploaden een alternatieve versie voor webpublicatie genereert."
 fromDrive: "Van schijf"
-fromUrl: "Van URL"
+fromUrl: "Van  URL"
 uploadFromUrl: "Uploaden vanaf een URL"
 uploadFromUrlDescription: "URL van het bestand dat je wil uploaden"
 uploadFromUrlRequested: "Uploadverzoek"
@@ -427,7 +427,7 @@ windowMaximize: "Maximaliseren"
 windowRestore: "Herstellen"
 loggedInAsBot: "Momenteel als bot ingelogd"
 icon: "Avatar"
-replies: "Antwoorden"
+replies: "Antwoord"
 renotes: "Herdelen"
 _delivery:
   stop: "Opgeschort"
@@ -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 cd00ecf9abe6eaa494d8fd7bb4d188acf6321fda..c5f61db7450694108098ad5ac8021613e3e68aed 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 b20eabf7f22e7d346ce159405cebff6d32d81320..d7afd577607e0843a7994041a74dcecbac2c09d6 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -81,7 +81,7 @@ exportRequested: "Zażądałeś eksportu. Może to zająć trochę czasu. Po zak
 importRequested: "Zażądano importu. Może to zająć chwilę."
 lists: "Listy"
 noLists: "Nie masz żadnych list"
-note: "Wpis"
+note: "Utwórz wpis"
 notes: "Wpisy"
 following: "Obserwowani"
 followers: "ObserwujÄ…cy"
@@ -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"
@@ -865,7 +861,7 @@ whatIsNew: "Pokaż zmiany"
 translate: "Przetłumacz"
 translatedFrom: "Przetłumaczone z {x}"
 accountDeletionInProgress: "Trwa usuwanie konta"
-usernameInfo: "Nazwa, która identyfikuje Twoje konto spośród innych na tym serwerze. Możesz użyć alfabetu (a~z, A~Z), cyfr (0~9) lub podkreślników (_). Nazwy użytkownika nie mogą być później zmieniane."
+usernameInfo: "Nazwa, która identyfikuje Twoje konto spośród innych na tym serwerze.  Możesz użyć alfabetu (a~z, A~Z), cyfr (0~9) lub podkreślników (_). Nazwy użytkownika nie mogą być później zmieniane."
 aiChanMode: "Tryb Ai"
 devMode: "Tryb programisty"
 keepCw: "Zostaw ostrzeżenia o zawartości"
@@ -1016,8 +1012,8 @@ emailNotSupported: "Wysyłanie wiadomości E-mail nie jest obsługiwane na tym s
 postToTheChannel: "Publikuj na kanale"
 youFollowing: "Åšledzeni"
 icon: "Awatar"
-replies: "Odpowiedzi"
-renotes: "Udostępnień"
+replies: "Odpowiedz"
+renotes: "Udostępnij"
 sourceCode: "Kod źródłowy"
 flip: "Odwróć"
 lastNDays: "W ciÄ…gu ostatnich {n} dni"
@@ -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 71ba7c4371a9bbb9c724188a6f29ab6168b63809..9039fd21414bd1c7144a14edb7c6a556d947c537 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -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"
@@ -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"
@@ -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"
@@ -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"
@@ -1420,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"
@@ -1948,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"
@@ -2377,6 +2372,7 @@ _notification:
     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"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 95c1e1650827a7bc6b0e76024c94a703b123206c..3cc09aa5c2d67568f59442b4e745ac3ef0826686 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"
@@ -649,7 +645,7 @@ searchByGoogle: "Caută"
 file: "Fișiere"
 show: "Arată"
 icon: "Avatar"
-replies: "Răspunsuri"
+replies: "Răspunde"
 renotes: "Re-notează"
 _delivery:
   stop: "Suspendat"
@@ -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 88f59155d656da1e3f2fb7e24a9aa8ebac14e79a..70178ec2fd63a1cc5c0cc26257aff77959cdfcb3 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -2,7 +2,7 @@
 _lang_: "Русский"
 headlineMisskey: "Сеть, сплетённая из заметок"
 introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀"
-poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый инстансом Misskey."
+poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый экземпляром Misskey."
 monthAndDay: "{day}.{month}"
 search: "Поиск"
 notifications: "Уведомления"
@@ -10,15 +10,15 @@ username: "Имя пользователя"
 password: "Пароль"
 forgotPassword: "Забыли пароль?"
 fetchingAsApObject: "Приём с других сайтов"
-ok: "Окей"
+ok: "Подтвердить"
 gotIt: "Ясно!"
 cancel: "Отмена"
 noThankYou: "Нет, спасибо"
 enterUsername: "Введите имя пользователя"
-renotedBy: "{user} делится"
+renotedBy: "{user} репостнул(а)"
 noNotes: "Нет ни одной заметки"
 noNotifications: "Нет уведомлений"
-instance: "Инстанс"
+instance: "Экземпляр"
 settings: "Настройки"
 notificationSettings: "Настройки уведомлений"
 basicSettings: "Основные настройки"
@@ -45,22 +45,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 +109,14 @@ enterEmoji: "Введите эмодзи"
 renote: "Репост"
 unrenote: "Отмена репоста"
 renoted: "Репост совершён."
+renotedToX: "Репостнуть в {name}."
 cantRenote: "Это нельзя репостить."
 cantReRenote: "Невозможно репостить репост."
 quote: "Цитата"
 inChannelRenote: "В канале"
 inChannelQuote: "Заметки в канале"
+renoteToChannel: "Репостнуть в канал"
+renoteToOtherChannel: "Репостнуть в другой канал"
 pinnedNote: "Закреплённая заметка"
 pinned: "Закрепить в профиле"
 you: "Ð’Ñ‹"
@@ -150,6 +155,7 @@ editList: "Редактировать список"
 selectChannel: "Выберите канал"
 selectAntenna: "Выберите антенну"
 editAntenna: "Редактировать антенну"
+createAntenna: "Создать антенну"
 selectWidget: "Выберите виджет"
 editWidgets: "Редактировать виджеты"
 editWidgetsExit: "Готово"
@@ -157,11 +163,12 @@ customEmojis: "Собственные эмодзи"
 emoji: "Эмодзи"
 emojis: "Эмодзи"
 emojiName: "Название эмодзи"
-emojiUrl: "URL эмодзи"
+emojiUrl: "Ссылка на эмодзи"
 addEmoji: "Добавить эмодзи"
 settingGuide: "Рекомендуемые настройки"
 cacheRemoteFiles: "Кешировать внешние файлы"
 cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы."
+youCanCleanRemoteFilesCache: "Вы можете очистить кэш, нажав на кнопку 🗑️ в меню управления файлами."
 cacheRemoteSensitiveFiles: "Кэшировать внешние файлы «не для всех»"
 cacheRemoteSensitiveFilesDescription: "Если отключено, файлы «не для всех» загружаются непосредственно с удалённых серверов, не кэшируясь."
 flagAsBot: "Аккаунт бота"
@@ -175,6 +182,10 @@ addAccount: "Добавить учётную запись"
 reloadAccountsList: "Обновить список учётных записей"
 loginFailed: "Неудачная попытка входа"
 showOnRemote: "Перейти к оригиналу на сайт"
+continueOnRemote: "Продолжить на удалённом сервере"
+chooseServerOnMisskeyHub: "Выбрать сервер с Misskey Hub"
+specifyServerHost: "Укажите сервер напрямую"
+inputHostName: "Введите домен"
 general: "Общее"
 wallpaper: "Обои"
 setWallpaper: "Установить обои"
@@ -185,6 +196,7 @@ followConfirm: "Подписаться на {name}?"
 proxyAccount: "Учётная запись прокси"
 proxyAccountDescription: "Учетная запись прокси предназначена служить подписчиком на пользователей с других сайтов. Например, если пользователь добавит кого-то с другого сайта а список, деятельность того не отобразится, пока никто с этого же сайта не подписан на него. Чтобы это стало возможным, на него подписывается прокси."
 host: "Хост"
+selectSelf: "Выбрать себя"
 selectUser: "Выберите пользователя"
 recipient: "Кому"
 annotation: "Описание"
@@ -199,6 +211,7 @@ perHour: "По часам"
 perDay: "По дням"
 stopActivityDelivery: "Остановить отправку обновлений активности"
 blockThisInstance: "Блокировать этот инстанс"
+silenceThisInstance: "Заглушить этот инстанс"
 operations: "Операции"
 software: "Программы"
 version: "Версия"
@@ -218,6 +231,7 @@ clearCachedFiles: "Очистить кэш"
 clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?"
 blockedInstances: "Заблокированные инстансы"
 blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом."
+silencedInstances: "Заглушённые инстансы"
 muteAndBlock: "Скрытие и блокировка"
 mutedUsers: "Скрытые пользователи"
 blockedUsers: "Заблокированные пользователи"
@@ -236,7 +250,7 @@ noJobs: "Нет заданий"
 federating: "Федерируется"
 blocked: "Заблокировано"
 suspended: "Заморожено"
-all: "Всё"
+all: "Все"
 subscribing: "Подписка"
 publishing: "Публикация"
 notResponding: "Нет ответа"
@@ -268,7 +282,7 @@ messaging: "Сообщения"
 upload: "Загрузить"
 keepOriginalUploading: "Сохранить исходное изображение"
 keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации."
-fromDrive: "С «диска»"
+fromDrive: "С Диска"
 fromUrl: "По ссылке"
 uploadFromUrl: "Загрузить по ссылке"
 uploadFromUrlDescription: "Ссылка на файл, который хотите загрузить"
@@ -308,6 +322,7 @@ selectFile: "Выберите файл"
 selectFiles: "Выберите файлы"
 selectFolder: "Выберите папку"
 selectFolders: "Выберите папки"
+fileNotSelected: "Файл не выбран"
 renameFile: "Переименовать файл"
 folderName: "Имя папки"
 createFolder: "Создать папку"
@@ -359,8 +374,8 @@ disablingTimelinesInfo: "У администраторов и модератор
 registration: "Регистрация"
 enableRegistration: "Разрешить регистрацию"
 invite: "Пригласить"
-driveCapacityPerLocalAccount: "Объём диска на одного локального пользователя"
-driveCapacityPerRemoteAccount: "Объём диска на одного пользователя с другого сайта"
+driveCapacityPerLocalAccount: "Объём Диска на одного локального пользователя"
+driveCapacityPerRemoteAccount: "Объём Диска на одного пользователя с другого экземпляра"
 inMb: "В мегабайтах"
 bannerUrl: "Ссылка на изображение в шапке"
 backgroundImageUrl: "Ссылка на фоновое изображение"
@@ -379,6 +394,7 @@ mcaptcha: "mCaptcha"
 enableMcaptcha: "Включить mCaptcha"
 mcaptchaSiteKey: "Ключ сайта"
 mcaptchaSecretKey: "Секретный ключ"
+mcaptchaInstanceUrl: "Ссылка на сервер mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Включить reCAPTCHA"
 recaptchaSiteKey: "Ключ сайта"
@@ -393,7 +409,8 @@ manageAntennas: "Настройки антенн"
 name: "Название"
 antennaSource: "Источник антенны"
 antennaKeywords: "Ключевые слова"
-antennaExcludeKeywords: "Исключения"
+antennaExcludeKeywords: "Чёрный список слов"
+antennaExcludeBots: "Исключать ботов"
 antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них."
 notifyAntenna: "Уведомлять о новых заметках"
 withFileAntenna: "Только заметки с вложениями"
@@ -426,6 +443,7 @@ totp: "Приложение-аутентификатор"
 totpDescription: "Описание приложения-аутентификатора"
 moderator: "Модератор"
 moderation: "Модерация"
+moderationLogs: "Журнал модерации"
 nUsersMentioned: "Упомянуло пользователей: {n}"
 securityKeyAndPasskey: "Ключ безопасности и парольная фраза"
 securityKey: "Ключ безопасности"
@@ -458,10 +476,12 @@ retype: "Введите ещё раз"
 noteOf: "Что пишет {user}"
 quoteAttached: "Цитата"
 quoteQuestion: "Хотите добавить цитату?"
+attachAsFileQuestion: "Текста в буфере обмена слишком много. Прикрепить как текстовый файл?"
 noMessagesYet: "Пока ни одного сообщения"
 newMessageExists: "Новое сообщение"
 onlyOneFileCanBeAttached: "К сообщению можно прикрепить только один файл"
 signinRequired: "Пожалуйста, войдите"
+signinOrContinueOnRemote: "Чтобы продолжить, вам необходимо войти в аккаунт на своём сервере или зарегистрироваться / войти в аккаунт на этом."
 invitations: "Приглашения"
 invitationCode: "Код приглашения"
 checking: "Проверка"
@@ -471,7 +491,7 @@ usernameInvalidFormat: "Можно использовать только лат
 tooShort: "Слишком короткий"
 tooLong: "Слишком длинный"
 weakPassword: "Слабый пароль"
-normalPassword: "Годный пароль"
+normalPassword: "Хороший пароль"
 strongPassword: "Надёжный пароль"
 passwordMatched: "Совпали"
 passwordNotMatched: "Не совпадают"
@@ -483,8 +503,8 @@ uiLanguage: "Язык интерфейса"
 aboutX: "Описание {x}"
 emojiStyle: "Стиль эмодзи"
 native: "Системные"
-disableDrawer: "Не использовать выдвижные меню"
 showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении"
+showReactionsCount: "Видеть количество реакций на заметках"
 noHistory: "История пока пуста"
 signinHistory: "Журнал посещений"
 enableAdvancedMfm: "Включить расширенный MFM"
@@ -547,7 +567,7 @@ popout: "Развернуть"
 volume: "Громкость"
 masterVolume: "Основная регулировка громкости"
 notUseSound: "Выключить звук"
-useSoundOnlyWhenActive: "Использовать звук, когда Misskey активен."
+useSoundOnlyWhenActive: "Воспроизводить звук только когда Misskey активен."
 details: "Подробнее"
 chooseEmoji: "Выберите эмодзи"
 unableToProcess: "Не удаётся завершить операцию"
@@ -601,7 +621,7 @@ poll: "Опрос"
 useCw: "Скрывать содержимое под предупреждением"
 enablePlayer: "Включить проигрыватель"
 disablePlayer: "Выключить проигрыватель"
-expandTweet: "Развернуть твит"
+expandTweet: "Развернуть заметку"
 themeEditor: "Редактор темы оформления"
 description: "Описание"
 describeFile: "Добавить подпись"
@@ -613,7 +633,7 @@ plugins: "Расширения"
 preferencesBackups: "Резервная копия"
 deck: "Пульт"
 undeck: "Покинуть пульт"
-useBlurEffectForModal: "Размывка под формой поверх всего"
+useBlurEffectForModal: "Размытие за формой ввода заметки"
 useFullReactionPicker: "Полнофункциональный выбор реакций"
 width: "Ширина"
 height: "Высота"
@@ -644,7 +664,7 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений"
 smtpSecureInfo: "Выключите при использовании STARTTLS."
 testEmail: "Проверка доставки электронной почты"
 wordMute: "Скрытие слов"
-hardWordMute: ""
+hardWordMute: "Строгое скрытие слов"
 regexpError: "Ошибка в регулярном выражении"
 regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:"
 instanceMute: "Глушение инстансов"
@@ -680,10 +700,7 @@ abuseReported: "Жалоба отправлена. Большое спасибо
 reporter: "Сообщивший"
 reporteeOrigin: "О ком сообщено"
 reporterOrigin: "Кто сообщил"
-forwardReport: "Отправить жалобу на инстанс автора."
-forwardReportIsAnonymous: "Жалоба на удалённый инстанс будет отправлена анонимно. Вместо ваших данных у получателя будет отображена системная учётная запись."
 send: "Отправить"
-abuseMarkAsResolved: "Отметить жалобу как решённую"
 openInNewTab: "Открыть в новой вкладке"
 openInSideView: "Открывать в боковой колонке"
 defaultNavigationBehaviour: "Поведение навигации по умолчанию"
@@ -726,6 +743,7 @@ lockedAccountInfo: "Даже если вы вручную подтверждае
 alwaysMarkSensitive: "Отмечать файлы как «содержимое не для всех» по умолчанию"
 loadRawImages: "Сразу показывать изображения в полном размере"
 disableShowingAnimatedImages: "Не проигрывать анимацию"
+highlightSensitiveMedia: "Выделять содержимое не для всех"
 verificationEmailSent: "Вам отправлено письмо для подтверждения. Пройдите, пожалуйста, по ссылке из письма, чтобы завершить проверку."
 notSet: "Не настроено"
 emailVerified: "Адрес электронной почты подтверждён."
@@ -743,7 +761,7 @@ makeExplorable: "Опубликовать профиль в «Обзоре»."
 makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»."
 showGapBetweenNotesInTimeline: "Показывать разделитель между заметками в ленте"
 duplicate: "Дубликат"
-left: "Влево"
+left: "Слева"
 center: "По центру"
 wide: "Толстый"
 narrow: "Тонкий"
@@ -822,7 +840,7 @@ noMaintainerInformationWarning: "Не заполнены сведения об 
 noBotProtectionWarning: "Ботозащита не настроена"
 configure: "Настроить"
 postToGallery: "Опубликовать в галерею"
-postToHashtag: "Написать заметку с этим хэштегом"
+postToHashtag: "Написать заметку с этим хештегом"
 gallery: "Галерея"
 recentPosts: "Недавние публикации"
 popularPosts: "Популярные публикации"
@@ -839,13 +857,13 @@ emailNotConfiguredWarning: "Не указан адрес электронной
 ratio: "Соотношение"
 previewNoteText: "Предварительный просмотр"
 customCss: "Индивидуальный CSS"
-customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что сайт перестанет нормально работать у вас."
+customCssWarn: "Используйте эту настройку только если знаете, что делаете. Ошибки здесь чреваты тем, что у вас перестанет нормально работать сайт."
 global: "Всеобщая"
 squareAvatars: "Квадратные аватарки"
 sent: "Отправить"
 received: "Получено"
 searchResult: "Результаты поиска"
-hashtags: "Хэштег"
+hashtags: "Хештеги"
 troubleshooting: "Разрешение проблем"
 useBlurEffect: "Размытие в интерфейсе"
 learnMore: "Подробнее"
@@ -857,7 +875,7 @@ accountDeletionInProgress: "В настоящее время выполняет
 usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже."
 aiChanMode: "Режим Ай"
 devMode: "Режим разработчика"
-keepCw: "Сохраняйте Предупреждения о содержимом"
+keepCw: "Сохраняйте предупреждения о содержимом"
 pubSub: "Учётные записи Pub/Sub"
 lastCommunication: "Последнее сообщение"
 resolved: "Решено"
@@ -878,6 +896,8 @@ makeReactionsPublicDescription: "Список сделанных вами реа
 classic: "Классика"
 muteThread: "Скрыть цепочку"
 unmuteThread: "Отменить сокрытие цепочки"
+followingVisibility: "Видимость подписок"
+followersVisibility: "Видимость подписчиков"
 continueThread: "Показать следующие ответы"
 deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?"
 incorrectPassword: "Пароль неверен."
@@ -987,6 +1007,7 @@ assign: "Назначить"
 unassign: "Отменить назначение"
 color: "Цвет"
 manageCustomEmojis: "Управлять пользовательскими эмодзи"
+manageAvatarDecorations: "Управление украшениями аватара"
 youCannotCreateAnymore: "Вы достигли лимита создания."
 cannotPerformTemporary: "Временно недоступен"
 cannotPerformTemporaryDescription: "Это действие временно невозможно выполнить из-за превышения лимита выполнения."
@@ -1003,7 +1024,8 @@ thisPostMayBeAnnoying: "Это сообщение может быть непри
 thisPostMayBeAnnoyingHome: "Этот пост может быть отправлен на главную"
 thisPostMayBeAnnoyingCancel: "Этот пост не может быть отменен."
 thisPostMayBeAnnoyingIgnore: "Этот пост может быть проигнорирован "
-collapseRenotes: "Свернуть репосты"
+collapseRenotes: "Сворачивать увиденные репосты"
+collapseRenotesDescription: "Сворачивать посты с которыми вы взаимодействовали."
 internalServerError: "Внутренняя ошибка сервера"
 internalServerErrorDescription: "Внутри сервера произошла непредвиденная ошибка."
 copyErrorInfo: "Скопировать код ошибки"
@@ -1027,7 +1049,10 @@ resetPasswordConfirm: "Сбросить пароль?"
 sensitiveWords: "Чувствительные слова"
 sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
 sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+prohibitedWords: "Запрещённые слова"
+prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать пост, содержащий указанное слово/набор слов.\nМножество слов может быть указано, разделяемые новой строкой."
 prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+hiddenTags: "Скрытые хештеги"
 notesSearchNotAvailable: "Поиск заметок недоступен"
 license: "Лицензия"
 unfavoriteConfirm: "Удалить избранное?"
@@ -1038,9 +1063,14 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
 retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться"
 enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей"
 enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов"
+showClipButtonInNoteFooter: "Показать кнопку добавления в подборку в меню действий с заметкой"
+reactionsDisplaySize: "Размер реакций"
+limitWidthOfReaction: "Ограничить максимальную ширину реакций и отображать их в уменьшенном размере."
 noteIdOrUrl: "ID или ссылка на заметку"
 video: "Видео"
 videos: "Видео"
+audio: "Звук"
+audioFiles: "Звуковые файлы"
 dataSaver: "Экономия трафика"
 accountMigration: "Перенос учётной записи"
 accountMoved: "Учётная запись перенесена"
@@ -1052,12 +1082,13 @@ editMemo: "Изменить памятку"
 reactionsList: "Список реакций"
 renotesList: "Репосты"
 notificationDisplay: "Отображение уведомлений"
-leftTop: "Влево вверх"
-rightTop: "Вправо вверх"
-leftBottom: "Влево вниз"
-rightBottom: "Вправо вниз"
-vertical: "Вертикальная"
-horizontal: "Сбоку"
+leftTop: "Слева вверху"
+rightTop: "Справа сверху"
+leftBottom: "Слева внизу"
+rightBottom: "Справа внизу"
+stackAxis: "Положение уведомлений"
+vertical: "Вертикально"
+horizontal: "Горизонтально"
 position: "Позиция"
 serverRules: "Правила сервера"
 pleaseConfirmBelowBeforeSignup: "Для регистрации на данном сервере, необходимо согласится с нижеследующими положениями."
@@ -1069,57 +1100,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 +1483,7 @@ _role:
     canPublicNote: "Может публиковать общедоступные заметки"
     canInvite: "Может создавать пригласительные коды"
     canManageCustomEmojis: "Управлять пользовательскими эмодзи"
+    canManageAvatarDecorations: "Управление украшениями аватара"
     driveCapacity: "Доступное пространство на «диске»"
     alwaysMarkNsfw: "Всегда отмечать файлы как «не для всех»"
     pinMax: "Доступное количество закреплённых заметок"
@@ -1505,6 +1594,11 @@ _aboutMisskey:
   donate: "Пожертвование на Misskey"
   morePatrons: "Большое спасибо и многим другим, кто принял участие в этом проекте! 🥰"
   patrons: "Материальная поддержка"
+  projectMembers: "Участники проекта"
+_displayOfSensitiveMedia:
+  respect: "Скрывать содержимое не для всех"
+  ignore: "Показывать содержимое не для всех"
+  force: "Скрывать всё содержимое"
 _instanceTicker:
   none: "Не показывать"
   remote: "Только для других сайтов"
@@ -1533,7 +1627,7 @@ _wordMute:
   muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках."
   muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)."
 _instanceMute:
-  instanceMuteDescription: "Заметки и репосты с указанных здесь инстансов, а также ответы пользователям оттуда же не будут отображаться."
+  instanceMuteDescription: "Любые активности, затрагивающие инстансы из данного списка, будут скрыты."
   instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке"
   title: "Скрывает заметки с заданных инстансов."
   heading: "Список скрытых инстансов"
@@ -1582,7 +1676,7 @@ _theme:
     navActive: "Текст на боковой панели (активирован)"
     navIndicator: "Индикатор на боковой панели"
     link: "Ссылка"
-    hashtag: "Хэштег"
+    hashtag: "Хештег"
     mention: "Упоминание"
     mentionMe: "Упоминания вас"
     renote: "Репост"
@@ -1600,7 +1694,6 @@ _theme:
     buttonBg: "Фон кнопки"
     buttonHoverBg: "Текст кнопки"
     inputBorder: "Рамка поля ввода"
-    listItemHoverBg: "Фон пункта списка (под указателем)"
     driveFolderBg: "Фон папки «Диска»"
     wallpaperOverlay: "Слой обоев"
     badge: "Значок"
@@ -1612,6 +1705,10 @@ _sfx:
   note: "Заметки"
   noteMy: "Собственные заметки"
   notification: "Уведомления"
+  reaction: "При выборе реакции"
+_soundSettings:
+  driveFile: "Использовать аудиофайл с Диска."
+  driveFileWarn: "Выбрать аудиофайл с Диска."
 _ago:
   future: "Из будущего"
   justNow: "Только что"
@@ -1690,6 +1787,7 @@ _permissions:
   "write:gallery": "Редактирование галереи"
   "read:gallery-likes": "Просмотр списка понравившегося в галерее"
   "write:gallery-likes": "Изменение списка понравившегося в галерее"
+  "write:admin:reset-password": "Сбросить пароль пользователю"
 _auth:
   shareAccessTitle: "Разрешения для приложений"
   shareAccess: "Дать доступ для «{name}» к вашей учётной записи?"
@@ -1743,6 +1841,7 @@ _widgets:
   _userList:
     chooseList: "Выберите список"
   clicker: "Счётчик щелчков"
+  birthdayFollowings: "Пользователи, у которых сегодня день рождения"
 _cw:
   hide: "Спрятать"
   show: "Показать"
@@ -1796,7 +1895,7 @@ _profile:
   name: "Имя"
   username: "Имя пользователя"
   description: "О себе"
-  youCanIncludeHashtags: "Можете использовать здесь хэштеги"
+  youCanIncludeHashtags: "Можете использовать здесь хештеги."
   metadata: "Дополнительные сведения"
   metadataEdit: "Редактировать дополнительные сведения"
   metadataDescription: "Можно добавить до четырёх дополнительных граф в профиль."
@@ -1804,6 +1903,8 @@ _profile:
   metadataContent: "Содержимое"
   changeAvatar: "Поменять аватар"
   changeBanner: "Поменять изображение в шапке"
+  verifiedLinkDescription: "Указывая здесь URL, содержащий ссылку на профиль, иконка владения ресурсом может быть отображена рядом с полем"
+  avatarDecorationMax: "Вы можете добавить до {max} украшений."
 _exportOrImport:
   allNotes: "Все заметки\n"
   favoritedNotes: "Избранное"
@@ -1926,6 +2027,9 @@ _notification:
   unreadAntennaNote: "Антенна {name}"
   emptyPushNotificationMessage: "Обновлены push-уведомления"
   achievementEarned: "Получено достижение"
+  checkNotificationBehavior: "Проверить внешний вид уведомления"
+  sendTestNotification: "Отправить тестовое уведомление"
+  flushNotification: "Очистить уведомления"
   _types:
     all: "Все"
     follow: "Подписки"
@@ -1938,6 +2042,7 @@ _notification:
     receiveFollowRequest: "Получен запрос на подписку"
     followRequestAccepted: "Запрос на подписку одобрен"
     achievementEarned: "Получение достижений"
+    login: "Войти"
     app: "Уведомления из приложений"
   _actions:
     followBack: "отвечает взаимной подпиской"
@@ -1977,19 +2082,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 e130d68ed8501d67b26a587d399d6e811411c702..c43f3d860d87559378ff2682e861e4a47cef569f 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 409d682af7a4587b16fe0148b861a717f62798b7..60ce45a6b926d0782ac744acf974f3363597555f 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"
@@ -917,7 +913,7 @@ color: "Farba"
 horizontal: "Strana"
 youFollowing: "Sledované"
 icon: "Avatar"
-replies: "Odpovede"
+replies: "Odpovedať"
 renotes: "Preposlať"
 sourceCode: "Zdrojový kód"
 flip: "Preklopiť"
@@ -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 c98782450f163d0b8ad9d2d88f382dcdb84bc91c..5a0de660e8963b78ca4f6ce9f95a425d7578a708 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -486,7 +486,7 @@ pleaseDonate: "Misskey är en gratis programvara som används på {host}. Donera
 resetPasswordConfirm: "Återställ verkligen ditt lösenord?"
 dataSaver: "Databesparing"
 icon: "Profilbild"
-replies: "Svar"
+replies: "Svara"
 renotes: "Omnotera"
 _delivery:
   stop: "Suspenderad"
@@ -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 508ca38949a456a881773c138bca7edddc769047..c70d448e2b8b3e8706b18c314a4da0776801eb55 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -509,7 +509,6 @@ uiLanguage: "ภาษาอินเทอร์เฟซผู้ใช้ง
 aboutX: "เกี่ยวกับ {x}"
 emojiStyle: "สไตล์ของเอโมจิ"
 native: "ภาษาแม่"
-disableDrawer: "ไม่แสดงเมนูในรูปแบบลิ้นชัก"
 showNoteActionsOnlyHover: "แสดงการดำเนินการโน้ตเมื่อโฮเวอร์(วางเมาส์เหนือ)เท่านั้น"
 showReactionsCount: "แสดงจำนวนรีแอกชั่นในโน้ต"
 noHistory: "ไม่มีประวัติ"
@@ -708,10 +707,7 @@ abuseReported: "เราได้ส่งรายงานของคุณ
 reporter: "ผู้รายงาน"
 reporteeOrigin: "ปลายทางรายงาน"
 reporterOrigin: "แหล่งผู้รายงาน"
-forwardReport: "ส่งต่อรายงานไปยังเซิร์ฟเวอร์ระยะไกล"
-forwardReportIsAnonymous: "ข้อมูลของคุณจะไม่ปรากฏบนเซิร์ฟเวอร์ระยะไกลและปรากฏเป็นบัญชีระบบที่ไม่ระบุชื่อ"
 send: "ส่ง"
-abuseMarkAsResolved: "ทำเครื่องหมายรายงานว่าแก้ไขแล้ว"
 openInNewTab: "เปิดในแท็บใหม่"
 openInSideView: "เปิดในมุมมองด้านข้าง"
 defaultNavigationBehaviour: "พฤติกรรมการนำทางที่เป็นค่าเริ่มต้น"
@@ -1334,10 +1330,10 @@ _initialTutorial:
   _reaction:
     title: "รีแอคชั่นคืออะไร?"
     description: "โน้ตสามารถ“รีแอคชั่น”ด้วยเอโมจิต่างๆ ซึ่งทำให้สามารถแสดงความแตกต่างเล็กๆ น้อยๆ ที่อาจไม่สามารถสื่อออกมาได้ด้วยการแค่การกด “ถูกใจ”"
-    letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “{reaction}” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!"
+    letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “+” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!"
     reactToContinue: "เพิ่มรีแอคชั่นเพื่อดำเนินการต่อ"
     reactNotification: "คุณจะได้รับการแจ้งเตือนแบบเรียลไทม์เมื่อมีคนตอบรีแอคชั่นโน้ตของคุณ"
-    reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “{undo}”"
+    reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “-”"
   _timeline:
     title: "แนวคิดเรื่องของไทม์ไลน์"
     description1: "Misskey มีหลายไทม์ไลน์ขึ้นอยู่กับวิธีการใช้งานของคุณ (บางไทม์ไลน์อาจไม่สามารถใช้ได้ขึ้นอยู่กับนโยบายของเซิร์ฟเวอร์)"
@@ -1418,7 +1414,7 @@ _achievements:
   earnedAt: "ได้รับเมื่อ"
   _types:
     _notes1:
-      title: "just setting up my shonk"
+      title: "just setting up my msky"
       description: "โพสต์โน้ตเป็นครั้งแรก"
       flavor: "ขอให้มีช่วงเวลาที่ดีกับ Misskey นะคะ!"
     _notes10:
@@ -1947,7 +1943,6 @@ _theme:
     buttonBg: "ปุ่มพื้นหลัง"
     buttonHoverBg: "ปุ่มพื้นหลัง (โฮเวอร์)"
     inputBorder: "เส้นขอบของช่องป้อนข้อมูล"
-    listItemHoverBg: "รายการไอเทมพื้นหลัง (โฮเวอร์)"
     driveFolderBg: "พื้นหลังโฟลเดอร์ไดรฟ์"
     wallpaperOverlay: "วอลล์เปเปอร์ซ้อนทับ"
     badge: "ตรา"
@@ -2375,6 +2370,7 @@ _notification:
     followRequestAccepted: "อนุมัติให้ติดตามแล้ว"
     roleAssigned: "ให้บทบาท"
     achievementEarned: "ปลดล็อกความสำเร็จแล้ว"
+    login: "เข้าสู่ระบบ"
     app: "การแจ้งเตือนจากแอปที่มีลิงก์"
   _actions:
     followBack: "ติดตามกลับด้วย"
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 266fb3161c606ee4de0a7d6daef14815419d45b0..fe2f158ff687bd876d6e0e165edfd4c4b2023f81 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -359,7 +359,7 @@ smtpUser: "Kullanıcı Adı"
 smtpPass: "Åžifre"
 notificationSetting: "Bildirim ayarları"
 instanceTicker: "Notların sunucu bilgileri"
-noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında vb. dolaşılmamasını ve dizine eklememesini talep et."
+noCrawleDescription: "Arama motorlarından profilinde, notlarında, sayfalarında  vb. dolaşılmamasını ve dizine eklememesini talep et."
 clearCache: "Ön belleği temizle"
 onlineUsersCount: "{n} kullanıcı çevrim içi"
 user: "Kullanıcı"
@@ -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 e48f64511cf51651e6aa54f789986ef6f6dcf198..fef26040a5b4debe46f3a64e4bec61590e9f0623 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 36d741d30e4a92e97fb1c25ae2992baf7a6545a3..f2262cd71f097fed8a416669387d8e44696b27fe 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 2023771104bfc30eebb6afeeb99b31069dd8abe1..37a550008ab955a7ac1bf26ddc988e6eeb3a0b2b 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -443,7 +443,7 @@ text: "Matn"
 enable: "Yoqish"
 next: "Keyingisi"
 retype: "Qayta kiriting"
-noteOf: "{user} tomonidan joylandi\n"
+noteOf: "{user} tomonidan  joylandi\n"
 quoteAttached: "Iqtibos"
 quoteQuestion: "Iqtibos sifatida qo'shilsinmi?"
 noMessagesYet: "Bu yerda xabarlar yo'q"
@@ -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"
@@ -843,7 +839,7 @@ rolesAssignedToMe: "Mening rollarim"
 resetPasswordConfirm: "Qayta parol o'rnatmoqchimisiz?"
 sensitiveWords: "Ta'sirchan so'zlar"
 icon: "Avatar"
-replies: "Javoblar"
+replies: "Javob berish"
 renotes: "Qayta qayd etish"
 flip: "Teskari"
 _delivery:
@@ -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/version.d.ts b/locales/version.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..54ceec744371f00ddaa5a89cc91d2dc68ce5d8f1
--- /dev/null
+++ b/locales/version.d.ts
@@ -0,0 +1 @@
+export const localesVersion: string;
diff --git a/locales/version.js b/locales/version.js
new file mode 100644
index 0000000000000000000000000000000000000000..e84414b74d85a06e717657fedae272cab7bed77e
--- /dev/null
+++ b/locales/version.js
@@ -0,0 +1,14 @@
+import { createHash } from 'crypto';
+import locales from './index.js';
+
+// MD5 is acceptable because we don't need cryptographic security.
+const hash = createHash('md5');
+
+// Derive the version hash from locale content exclusively.
+// This avoids the problem of "stuck" translations after modifying locale files.
+const localesText = JSON.stringify(locales);
+hash.update(localesText, 'utf8');
+
+// We can't use regular base64 since this becomes part of a filename.
+// Base64URL avoids special characters that would cause an issue.
+export const localesVersion = hash.digest().toString('base64url');
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 87b4403c27aa33e29b8c0d0bf08bf8c4c0fc9b03..235497d844289569a7c7b0fe0d09e9ce03322760 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"
@@ -1163,7 +1159,7 @@ _achievements:
   earnedAt: "Ngày thu nhận"
   _types:
     _notes1:
-      title: "just setting up my shonk"
+      title: "just setting up my msky"
       description: "Lần đầu tiên đăng bài"
       flavor: "Chúc bạn trên Miskey vui vẻ nha!!"
     _notes10:
@@ -1288,7 +1284,7 @@ _achievements:
     _iLoveMisskey:
       title: "Tôi Yêu Misskey"
       description: "Đăng lời nói \"I ❤ #Misskey\""
-      flavor: "Xin chân thành cảm ơn bạn đã sử dụng Misskey!! by Đội ngũ phát triển"
+      flavor: "Xin chân thành cảm ơn bạn đã sử dụng Misskey!!  by Đội ngũ phát triển"
     _foundTreasure:
       title: "Tìm kiếm kho báu"
       description: "Tìm thấy được những kho báu cất giấu"
@@ -1329,7 +1325,7 @@ _achievements:
       description: "Bấm chỗ này"
     _justPlainLucky:
       title: "Chỉ là một cuộc máy mắn"
-      description: "Mỗi 10 giây thu nhận được với tỷ lệ 0.005%"
+      description: "Mỗi 10 giây thu nhận được với tỷ lệ  0.005%"
     _setNameToSyuilo:
       title: "Ngưỡng mộ với vị thần"
       description: "Đạt tên là syuilo"
@@ -1483,7 +1479,7 @@ _wordMute:
   muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
   muteWordsDescription2: "Bao quanh các từ khóa bằng dấu gạch chéo để sử dụng cụm từ thông dụng."
 _instanceMute:
-  instanceMuteDescription: "Thao tác này sẽ ẩn mọi tút/lượt đăng lại từ các máy chủ được liệt kê, bao gồm cả những tút dạng trả lời từ máy chủ bị ẩn."
+  instanceMuteDescription: "Thao tác này sẽ ẩn mọi tút/lượt đăng lại từ các máy chủ được liệt kê, bao gồm cả những tút  dạng trả lời từ máy chủ bị ẩn."
   instanceMuteDescription2: "Tách bằng cách xuống dòng"
   title: "Ẩn tút từ những máy chủ đã liệt kê."
   heading: "Danh sách những máy chủ bị ẩn"
@@ -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 1bc1df7930d9910c4395d7ee05ef1a6d87a319dc..b81018cc1f10fc60277cde6aa82bc60708f83875 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: "如果禁用这项设定,远程服务器的敏感媒体将不会被缓存,而是直接链接。"
@@ -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: "AiScript 控制台"
 scratchpadDescription: "AiScript 控制台为 AiScript 提供了实验环境。您可以编写代码与 Misskey 交互,运行并查看结果。"
+uiInspector: "UI 检查器"
+uiInspectorDescription: "查看所有内存中由 UI 组件生成出的实例。UI 组件由 UI:C 系列函数所生成。"
 output: "输出"
 script: "脚本"
 disablePagesScript: "禁用页面脚本"
@@ -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: "限制回应的最大宽度,并将其缩小显示"
@@ -1189,10 +1200,10 @@ followingOrFollower: "关注中或关注者"
 fileAttachedOnly: "仅限媒体"
 showRepliesToOthersInTimeline: "在时间线中包含给别人的回复"
 hideRepliesToOthersInTimeline: "在时间线中隐藏给别人的回复"
-showRepliesToOthersInTimelineAll: "在时间线中包含现在关注的所有人的回复"
-hideRepliesToOthersInTimelineAll: "在时间线中隐藏现在关注的所有人的回复"
-confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中包含现在关注的所有人的回复吗?"
-confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏现在关注的所有人的回复吗?"
+showRepliesToOthersInTimelineAll: "在时间线中显示所有现在关注的人的回复"
+hideRepliesToOthersInTimelineAll: "在时间线中隐藏所有现在关注的人的回复"
+confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中显示所有现在关注的人的回复吗?"
+confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏所有现在关注的人的回复吗?"
 externalServices: "外部服务"
 sourceCode: "源代码"
 sourceCodeIsNotYetProvided: "还未提供源代码。要解决此问题请联系管理员。"
@@ -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: "停止投递"
@@ -1334,10 +1371,10 @@ _initialTutorial:
   _reaction:
     title: "什么是回应?"
     description: "您可以在帖子中添加“回应”。 您可以使用反应轻松地表达点“赞”所无法传达的细微差别。"
-    letsTryReacting: "回应可以通过点击帖子中的「{reaction}」按钮来添加。试着给这个示例帖子添加一个回应!"
+    letsTryReacting: "回应可以通过点击帖子中的「+」按钮来添加。试着给这个示例帖子添加一个回应!"
     reactToContinue: "添加一个回应来继续"
     reactNotification: "当您的帖子被某人添加了回应时,将实时收到通知。"
-    reactDone: "通过按下「{undo}」按钮,可以取消已经添加的回应"
+    reactDone: "通过按下「ー」按钮,可以取消已经添加的回应"
   _timeline:
     title: "时间线的运作方式"
     description1: "Misskey 根据使用方式提供了多个时间线(根据服务器的设定,可能有一些被禁用)。"
@@ -1397,8 +1434,10 @@ _serverSettings:
   fanoutTimelineDescription: "当启用时,可显著提高获取各种时间线时的性能,并减轻数据库的负荷。但是相对的 Redis 的内存使用量将会增加。如果服务器的内存不是很大,又或者运行不稳定的话可以把它关掉。"
   fanoutTimelineDbFallback: "回退到数据库"
   fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。"
+  reactionsBufferingDescription: "开启时可显著提高发送回应时的性能,及减轻数据库负荷。但 Redis 的内存用量会相应增加。"
   inquiryUrl: "联络地址"
   inquiryUrlDescription: "用来指定诸如向服务运营商咨询的论坛地址,或记载了运营商联系方式之类的网页地址。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "若在一段时间内没有检测到管理活动,为防止垃圾信息,此设定将自动关闭。"
 _accountMigration:
   moveFrom: "从别的账号迁移到此账户"
   moveFromSub: "为另一个账户建立别名"
@@ -1598,7 +1637,7 @@ _achievements:
     _postedAt0min0sec:
       title: "报时"
       description: "在 0 点发布一篇帖子"
-      flavor: "嘣 嘣 嘣 Biu——!"
+      flavor: "嘟 · 嘟 · 嘟 · 哔——"
     _selfQuote:
       title: "自我引用"
       description: "引用了自己的帖子"
@@ -1647,8 +1686,8 @@ _achievements:
       flavor: "今年也请对本服务器多多指教!"
     _cookieClicked:
       title: "点击饼干小游戏"
-      description: "点击了可疑的饼干"
-      flavor: "是不是软件有问题?"
+      description: "点击了饼干"
+      flavor: "用错软件了?"
     _brainDiver:
       title: "Brain Diver"
       description: "发布了包含 Brain Diver 链接的帖子"
@@ -1665,7 +1704,7 @@ _achievements:
     _bubbleGameDoubleExplodingHead:
       title: "两个🤯"
       description: "你合成出了2个游戏里最大的Emoji"
-      flavor: ""
+      flavor: "大约能 装满 这些便当盒 🤯 🤯 (比划)"
 _role:
   new: "创建角色"
   edit: "编辑角色"
@@ -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: "收藏的帖子"
@@ -2362,6 +2408,8 @@ _notification:
   renotedBySomeUsers: "{n} 人转发了"
   followedBySomeUsers: "被 {n} 人关注"
   flushNotification: "重置通知历史"
+  exportOfXCompleted: "已完成 {x} 个导出"
+  login: "有新的登录"
   _types:
     all: "全部"
     note: "用户的新帖子"
@@ -2376,6 +2424,9 @@ _notification:
     followRequestAccepted: "关注请求已通过"
     roleAssigned: "授予的角色"
     achievementEarned: "取得的成就"
+    exportCompleted: "已完成导出"
+    login: "登录"
+    test: "测试通知"
     app: "关联应用的通知"
   _actions:
     followBack: "回关"
@@ -2441,7 +2492,10 @@ _webhookSettings:
     abuseReport: "当收到举报时"
     abuseReportResolved: "当举报被处理时"
     userCreated: "当用户被创建时"
+    inactiveModeratorsWarning: "当管理员在一段时间内不活跃时"
+    inactiveModeratorsInvitationOnlyChanged: "当因为管理员在一段时间内不活跃,导致服务器变为邀请制时"
   deleteConfirm: "要删除 webhook 吗?"
+  testRemarks: "点击开关右侧的按钮,可以发送使用假数据的测试 Webhook。"
 _abuseReport:
   _notificationRecipient:
     createRecipient: "新建举报通知"
@@ -2485,6 +2539,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "标记网盘文件为敏感媒体"
   unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
   resolveAbuseReport: "处理举报"
+  forwardAbuseReport: "转发举报"
+  updateAbuseReportNote: "更新举报用管理笔记"
   createInvitation: "生成邀请码"
   createAd: "创建了广告"
   deleteAd: "删除了广告"
@@ -2640,3 +2696,17 @@ _contextMenu:
   app: "应用"
   appWithShift: "Shift 键应用"
   native: "浏览器的用户界面"
+_embedCodeGen:
+  title: "自定义嵌入代码"
+  header: "显示标题"
+  autoload: "连续加载(不推荐)"
+  maxHeight: "最大高度"
+  maxHeightDescription: "若将最大值设为 0 则不限制最大高度。为防止小工具无限增高,建议设置一下。"
+  maxHeightWarn: "最大高度限制已禁用(0)。若这不是您想要的效果,请将最大高度设一个值。"
+  previewIsNotActual: "由于超出了预览画面可显示的范围,因此显示内容会与实际嵌入时有所不同。"
+  rounded: "圆角"
+  border: "外边框"
+  applyToPreview: "应用预览"
+  generateCode: "生成嵌入代码"
+  codeGenerated: "已生成代码"
+  codeGeneratedDescription: "将生成的代码贴到网站上来使用。"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 80ad2eaf027c31a3589a028bb82d5d89ff990582..de18342bbf6354aa915209d2b65fee58aa578627 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: "在移動設備上使用抽屜顯示"
@@ -1009,7 +1019,7 @@ show: "檢視"
 neverShow: "不再顯示"
 remindMeLater: "以後再說"
 didYouLikeMisskey: "您喜歡 Misskey 嗎?"
-pleaseDonate: "Misskey 是由 {host} 使用的免費軟體。請贊助我們,讓開發得以持續!"
+pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發的工作能夠持續!"
 correspondingSourceIsAvailable: "對應的原始碼可以在 {anchor} 處找到。"
 roles: "角色"
 role: "角色"
@@ -1077,6 +1087,7 @@ retryAllQueuesConfirmTitle: "要現在重試嗎?"
 retryAllQueuesConfirmText: "伺服器的負荷可能會暫時增加。"
 enableChartsForRemoteUser: "生成遠端使用者的圖表"
 enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
+enableStatsForFederatedInstances: "取得遠端伺服器資訊"
 showClipButtonInNoteFooter: "新增摘錄按鈕至貼文"
 reactionsDisplaySize: "反應的顯示尺寸"
 limitWidthOfReaction: "限制反應的最大寬度,並縮小顯示尺寸。"
@@ -1185,8 +1196,8 @@ showRenotes: "顯示其他人的轉發貼文"
 edited: "已編輯"
 notificationRecieveConfig: "接受通知的設定"
 mutualFollow: "互相追隨"
-followingOrFollower: "追隨中或追隨者"
-fileAttachedOnly: "顯示包含附件的貼文"
+followingOrFollower: "追隨中或者追隨者"
+fileAttachedOnly: "只顯示包含附件的貼文"
 showRepliesToOthersInTimeline: "顯示給其他人的回覆"
 hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
 showRepliesToOthersInTimelineAll: "在時間軸包含追隨中所有人的回覆"
@@ -1256,13 +1267,39 @@ 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: "名稱中包含禁止使用的字串。 如果您想使用此名稱,請聯絡您的伺服器管理員。"
+_abuseUserReport:
+  forward: "轉發"
+  forwardDescription: "以匿名系統帳戶將檢舉轉發至遠端伺服器。"
+  resolve: "解決"
+  accept: "接受"
+  reject: "拒絕"
+  resolveTutorial: "如果您已回覆正當的檢舉,請選擇「接受」以將案件標記為已解決。\n 如果檢舉的內容不正當,請選擇「拒絕」將案件標記為已解決。"
 _delivery:
   status: "傳送狀態"
   stop: "停止發送"
@@ -1334,10 +1371,10 @@ _initialTutorial:
   _reaction:
     title: "什麼是反應?"
     description: "您可以在貼文中添加「反應」。您可以使用反應輕鬆隨意地表達「最愛/大心」所無法傳達的細微差別。"
-    letsTryReacting: "可以透過點擊貼文上的「{reaction}」按鈕來添加反應。請嘗試在此範例貼文添加反應!"
+    letsTryReacting: "可以透過點擊貼文上的「+」按鈕來添加反應。請嘗試在此範例貼文添加反應!"
     reactToContinue: "添加反應以繼續教學課程。"
     reactNotification: "當有人對您的貼文做出反應時會即時接收到通知。"
-    reactDone: "按下「{undo}」按鈕可以取消反應。"
+    reactDone: "按下「-」按鈕可以取消反應。"
   _timeline:
     title: "時間軸如何運作"
     description1: "Misskey根據使用方式提供了多個時間軸(伺服器可能會將部份時間軸停用)。"
@@ -1397,8 +1434,10 @@ _serverSettings:
   fanoutTimelineDescription: "如果啟用的話,檢索各個時間軸的性能會顯著提昇,資料庫的負荷也會減少。不過,Redis 的記憶體使用量會增加。如果伺服器的記憶體容量比較少或者運行不穩定,可以停用。"
   fanoutTimelineDbFallback: "資料庫的回退"
   fanoutTimelineDbFallbackDescription: "若啟用,在時間軸沒有快取的情況下將執行回退處理以額外查詢資料庫。若停用,可以透過不執行回退處理來進一步減少伺服器的負荷,但會限制可取得的時間軸範圍。"
+  reactionsBufferingDescription: "啟用時,可以顯著提高建立反應時的效能並減少資料庫的負載。 但是,Redis 記憶體使用量會增加。"
   inquiryUrl: "聯絡表單網址"
   inquiryUrlDescription: "指定伺服器運營者的聯絡表單網址,或包含運營者聯絡資訊網頁的網址。"
+  thisSettingWillAutomaticallyOffWhenModeratorsInactive: "為了防止 spam,如果一段期間內沒有偵測到審查員的活動,此設定將自動關閉。"
 _accountMigration:
   moveFrom: "從其他帳戶遷移到這個帳戶"
   moveFromSub: "為另一個帳戶建立別名"
@@ -1412,7 +1451,7 @@ _accountMigration:
   startMigration: "遷移"
   migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。"
   movedAndCannotBeUndone: "帳戶已遷移。\n遷移無法撤消。"
-  postMigrationNote: "在完成遷移的 24 小時後解除此帳戶的追隨。此帳戶的追隨中、追隨者數量變為 0。由於不會解除追隨者,你的追隨者仍然可以繼續檢視這個帳戶發布給追隨者的貼文。"
+  postMigrationNote: "取消追蹤此帳戶將在遷移操作後 24 小時執行。\n 此帳戶有 0 個關注者/關注者。 您的關注者仍然可以看到此帳戶的關注者帖子,因為您不會被取消關注。"
   movedTo: "要遷移到的帳戶:"
 _achievements:
   earnedAt: "獲得日期"
@@ -1532,7 +1571,7 @@ _achievements:
     _markedAsCat:
       title: "我是貓"
       description: "已將帳戶設定為貓"
-      flavor: "還沒有名字。"
+      flavor: "沒有名字。"
     _following1:
       title: "首次追隨"
       description: "首次追隨了"
@@ -1546,7 +1585,7 @@ _achievements:
       title: "一百位朋友"
       description: "追隨超過100人了"
     _following300:
-      title: "朋友過多"
+      title: "朋友太多"
       description: "追隨超過300人了"
     _followers1:
       title: "第一個追隨者"
@@ -1730,6 +1769,11 @@ _role:
     canSearchNotes: "可否搜尋貼文"
     canUseTranslator: "使用翻譯功能"
     avatarDecorationLimit: "頭像裝飾的最大設置量"
+    canImportAntennas: "允許匯入天線"
+    canImportBlocking: "允許匯入封鎖名單"
+    canImportFollowing: "允許匯入跟隨名單"
+    canImportMuting: "允許匯入靜音名單"
+    canImportUserLists: "允許匯入清單"
   _condition:
     roleAssignedTo: "手動指派角色完成"
     isLocal: "本地使用者"
@@ -1867,7 +1911,7 @@ _channel:
   following: "追隨中"
   usersCount: "有 {n} 人參與"
   notesCount: "有 {n} 篇貼文"
-  nameAndDescription: "名稱與說明"
+  nameAndDescription: "名稱"
   nameOnly: "僅名稱"
   allowRenoteToExternal: "允許在頻道外轉發和引用"
 _menuDisplay:
@@ -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: "「我的最愛」貼文"
@@ -2362,6 +2408,8 @@ _notification:
   renotedBySomeUsers: "{n}人做了轉發"
   followedBySomeUsers: "被{n}人追隨了"
   flushNotification: "重置通知歷史紀錄"
+  exportOfXCompleted: "{x} 的匯出已完成。"
+  login: "已登入"
   _types:
     all: "全部 "
     note: "使用者的最新貼文"
@@ -2376,6 +2424,9 @@ _notification:
     followRequestAccepted: "追隨請求已接受"
     roleAssigned: "已授予角色"
     achievementEarned: "獲得成就"
+    exportCompleted: "已完成匯出。"
+    login: "登入"
+    test: "通知測試"
     app: "應用程式通知"
   _actions:
     followBack: "追隨回去"
@@ -2441,7 +2492,10 @@ _webhookSettings:
     abuseReport: "當使用者檢舉時"
     abuseReportResolved: "當處理了使用者的檢舉時"
     userCreated: "使用者被新增時"
+    inactiveModeratorsWarning: "當審查員在一段時間內沒有活動時"
+    inactiveModeratorsInvitationOnlyChanged: "當審查員在一段時間內不活動時,系統會將模式變更為邀請制"
   deleteConfirm: "請問是否要刪除 Webhook?"
+  testRemarks: "按下切換開關右側的按鈕,就會將假資料發送至 Webhook。"
 _abuseReport:
   _notificationRecipient:
     createRecipient: "新增接收檢舉的通知對象"
@@ -2454,7 +2508,7 @@ _abuseReport:
         mail: "寄送到擁有監察員權限的使用者電子郵件地址(僅在收到檢舉時)"
         webhook: "向指定的 SystemWebhook 發送通知(在收到檢舉和解決檢舉時發送)"
     keywords: "關鍵字"
-    notifiedUser: "被通知的使用者"
+    notifiedUser: "通知的使用者"
     notifiedWebhook: "使用的 Webhook"
     deleteConfirm: "確定要刪除通知對象嗎?"
 _moderationLogTypes:
@@ -2485,6 +2539,8 @@ _moderationLogTypes:
   markSensitiveDriveFile: "標記為敏感檔案"
   unmarkSensitiveDriveFile: "撤銷標記為敏感檔案"
   resolveAbuseReport: "解決檢舉"
+  forwardAbuseReport: "轉發檢舉"
+  updateAbuseReportNote: "更新檢舉的審查備註"
   createInvitation: "建立邀請碼"
   createAd: "建立廣告"
   deleteAd: "刪除廣告"
@@ -2640,3 +2696,17 @@ _contextMenu:
   app: "應用程式"
   appWithShift: "Shift 鍵應用程式"
   native: "瀏覽器的使用者介面"
+_embedCodeGen:
+  title: "自訂嵌入程式碼"
+  header: "檢視標頭 "
+  autoload: "自動繼續載入(不建議)"
+  maxHeight: "最大高度"
+  maxHeightDescription: "設定為 0 時代表沒有最大值。請指定某個值以避免小工具持續在縱向延伸。"
+  maxHeightWarn: "最大高度限制已停用(0)。如果這個變更不是您想要的,請將最大高度設定為某個值。"
+  previewIsNotActual: "由於超出了預覽畫面可顯示的範圍,因此顯示內容會與實際嵌入時有所不同。"
+  rounded: "圓角"
+  border: "給外框加上邊框"
+  applyToPreview: "反映在預覽中"
+  generateCode: "建立嵌入程式碼"
+  codeGenerated: "已產生程式碼"
+  codeGeneratedDescription: "請將產生的程式碼貼到您的網站上。"
diff --git a/package.json b/package.json
index 2877204540d67741e54e98424815154b3b3f3105..b312044921972cb04bd522b29bfd6c0eb911807a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "sharkey",
-	"version": "2024.8.2",
+	"version": "2024.9.1",
 	"codename": "shonk",
 	"repository": {
 		"type": "git",
@@ -8,7 +8,9 @@
 	},
 	"packageManager": "pnpm@9.6.0",
 	"workspaces": [
+		"packages/frontend-shared",
 		"packages/frontend",
+		"packages/frontend-embed",
 		"packages/backend",
 		"packages/sw",
 		"packages/misskey-js",
@@ -32,9 +34,13 @@
 		"watch": "pnpm dev",
 		"dev": "node scripts/dev.mjs",
 		"lint": "pnpm -r lint",
+		"lint-all": "pnpm -r --no-bail lint",
+		"eslint": "pnpm -r eslint",
+		"eslint-all": "pnpm -r --no-bail eslint",
 		"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
 		"cy:run": "pnpm cypress run",
 		"e2e": "pnpm start-server-and-test start:test http://localhost:61812 cy:run",
+		"e2e-dev-container": "cp ./.config/cypress-devcontainer.yml ./.config/test.yml && pnpm start-server-and-test start:test http://localhost:61812 cy:run",
 		"jest": "cd packages/backend && pnpm jest",
 		"jest-and-coverage": "cd packages/backend && pnpm jest-and-coverage",
 		"test": "pnpm -r test",
@@ -53,11 +59,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": {
@@ -66,10 +72,10 @@
 		"@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"
 	}
 }
diff --git a/packages/backend/assets/embed.js b/packages/backend/assets/embed.js
new file mode 100644
index 0000000000000000000000000000000000000000..24fccc1b6c1f4554e3c25e7327a7ee49e50d16db
--- /dev/null
+++ b/packages/backend/assets/embed.js
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: MIT
+ */
+//@ts-check
+(() => {
+	/** @type {NodeListOf<HTMLIFrameElement>} */
+	const els = document.querySelectorAll('iframe[data-misskey-embed-id]');
+
+	window.addEventListener('message', function (event) {
+		els.forEach((el) => {
+			if (event.source !== el.contentWindow) {
+				return;
+			}
+
+			const id = el.dataset.misskeyEmbedId;
+
+			if (event.data.type === 'misskey:embed:ready') {
+				el.contentWindow?.postMessage({
+					type: 'misskey:embedParent:registerIframeId',
+					payload: {
+						iframeId: id,
+					}
+				}, '*');
+			}
+			if (event.data.type === 'misskey:embed:changeHeight' && event.data.iframeId === id) {
+				el.style.height = event.data.payload.height + 'px';
+			}
+		});
+	});
+})();
diff --git a/packages/backend/eslint.config.js b/packages/backend/eslint.config.js
index 4fd9f0cd51b6819c6f0a9bb17c7b068027bf3626..452045bc3ecb155d6ba6ee65f5501385fd5c122a 100644
--- a/packages/backend/eslint.config.js
+++ b/packages/backend/eslint.config.js
@@ -1,5 +1,6 @@
 import tsParser from '@typescript-eslint/parser';
 import sharedConfig from '../shared/eslint.config.js';
+import globals from 'globals';
 
 export default [
 	...sharedConfig,
@@ -43,4 +44,25 @@ export default [
 			}],
 		},
 	},
+	{
+		files: ['src/server/web/**/*.js', 'src/server/web/**/*.ts'],
+		languageOptions: {
+			globals: {
+				...globals.browser,
+				LANGS: true,
+				CLIENT_ENTRY: true,
+				LANGS_VERSION: true,
+			},
+		},
+	},
+	{
+		ignores: [
+			"**/lib/",
+			"**/temp/",
+			"**/built/",
+			"**/coverage/",
+			"**/node_modules/",
+			"**/migration/",
+		]
+	},
 ];
diff --git a/packages/backend/migration/1711008460816-external-website-warn.js b/packages/backend/migration/1711008460816-external-website-warn.js
new file mode 100644
index 0000000000000000000000000000000000000000..d36639459b8f68c4299d07ec3d4f199b8020f7d9
--- /dev/null
+++ b/packages/backend/migration/1711008460816-external-website-warn.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class ExternalWebsiteWarn1711008460816 {
+    name = 'ExternalWebsiteWarn1711008460816'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "trustedLinkUrlPatterns" character varying(3072) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "trustedLinkUrlPatterns"`);
+    }
+}
\ No newline at end of file
diff --git a/packages/backend/migration/1723944246767-followedMessage.js b/packages/backend/migration/1723944246767-followedMessage.js
new file mode 100644
index 0000000000000000000000000000000000000000..fc9ad1cb8538a25f30258d172737af1d7b88cd78
--- /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 0000000000000000000000000000000000000000..bc19e9cc8aa67d9444496c5e8f1591809693ac03
--- /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/1727027985575-SidebarLogo.js b/packages/backend/migration/1727027985575-SidebarLogo.js
new file mode 100644
index 0000000000000000000000000000000000000000..03344a367fb66af15b138caadae28c3ce81adbbc
--- /dev/null
+++ b/packages/backend/migration/1727027985575-SidebarLogo.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: piuvas and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SidebarLogo1727027985575 {
+    name = 'SidebarLogo1727027985575';
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "sidebarLogoUrl" character varying(1024)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "sidebarLogoUrl"`);
+    }
+}
diff --git a/packages/backend/migration/1727491883993-user-score.js b/packages/backend/migration/1727491883993-user-score.js
new file mode 100644
index 0000000000000000000000000000000000000000..7292d5363c53c1f5808fd067bcf07486c31a9cd8
--- /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 0000000000000000000000000000000000000000..52c24df4f76a03f2e2f0a5ded7012d86755338f1
--- /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/1727659258948-add_latest_note.js b/packages/backend/migration/1727659258948-add_latest_note.js
new file mode 100644
index 0000000000000000000000000000000000000000..0ab2b3f0d1cea7801e02e2ee461c455b01e8752d
--- /dev/null
+++ b/packages/backend/migration/1727659258948-add_latest_note.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AddLatestNote1727659258948 {
+	name = 'AddLatestNote1727659258948';
+
+	async up(queryRunner) {
+		await queryRunner.query('CREATE TABLE "latest_note" ("user_id" character varying(32) NOT NULL, "note_id" character varying(32) NOT NULL, CONSTRAINT "PK_f619b62bfaafabe68f52fb50c9a" PRIMARY KEY ("user_id"))');
+		await queryRunner.query('ALTER TABLE "latest_note" ADD CONSTRAINT "FK_20e346fffe4a2174585005d6d80" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION');
+		await queryRunner.query('ALTER TABLE "latest_note" ADD CONSTRAINT "FK_47a38b1c13de6ce4e5090fb1acd" FOREIGN KEY ("note_id") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION');
+	}
+
+	async down(queryRunner) {
+		await queryRunner.query('ALTER TABLE "latest_note" DROP CONSTRAINT "FK_47a38b1c13de6ce4e5090fb1acd"');
+		await queryRunner.query('ALTER TABLE "latest_note" DROP CONSTRAINT "FK_20e346fffe4a2174585005d6d80"');
+		await queryRunner.query('DROP TABLE "latest_note"');
+	}
+}
diff --git a/packages/backend/migration/1727998351561-increase_character_limits.js b/packages/backend/migration/1727998351561-increase_character_limits.js
new file mode 100644
index 0000000000000000000000000000000000000000..87541638460c96de11cfee9b1cc5d81a5678f1ff
--- /dev/null
+++ b/packages/backend/migration/1727998351561-increase_character_limits.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class IncreaseCharacterLimits1727998351561 {
+	name = 'IncreaseCharacterLimits1727998351561'
+
+	async up(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(100000)`);
+		await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE text`);
+	}
+
+	async down(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "note" ALTER COLUMN "cw" TYPE varchar(512)`);
+		await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(8192)`);
+	}
+}
diff --git a/packages/backend/migration/1728177700920-add-reject-reports.js b/packages/backend/migration/1728177700920-add-reject-reports.js
new file mode 100644
index 0000000000000000000000000000000000000000..ed5f6bc559ef5c998678d0110947e2771ae9380a
--- /dev/null
+++ b/packages/backend/migration/1728177700920-add-reject-reports.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AddRejectReports1728177700920 {
+    name = 'AddRejectReports1728177700920'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" ADD "rejectReports" boolean NOT NULL DEFAULT false`);
+		}
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "rejectReports"`);
+		}
+}
diff --git a/packages/backend/migration/1728348353115-soft-limit-drive-comment.js b/packages/backend/migration/1728348353115-soft-limit-drive-comment.js
new file mode 100644
index 0000000000000000000000000000000000000000..4eb04432c268faf67268e4a0280e08e8b0cab5ac
--- /dev/null
+++ b/packages/backend/migration/1728348353115-soft-limit-drive-comment.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SoftLimitDriveComment1728348353115 {
+    name = 'SoftLimitDriveComment1728348353115'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE text`);
+		}
+
+    async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "drive_file" ALTER COLUMN "comment" TYPE varchar(100000)`);
+		}
+}
diff --git a/packages/backend/migration/1728420772835-track-latest-note-type.js b/packages/backend/migration/1728420772835-track-latest-note-type.js
new file mode 100644
index 0000000000000000000000000000000000000000..4c9b4ca5949e8708eb5342cdff014145dc7c9adc
--- /dev/null
+++ b/packages/backend/migration/1728420772835-track-latest-note-type.js
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class TrackLatestNoteType1728420772835 {
+    name = 'TrackLatestNoteType1728420772835'
+
+    async up(queryRunner) {
+				await queryRunner.query(`ALTER TABLE "latest_note" DROP CONSTRAINT "PK_f619b62bfaafabe68f52fb50c9a"`);
+				await queryRunner.query(`ALTER TABLE "latest_note" ADD "is_public" boolean NOT NULL DEFAULT false`);
+				await queryRunner.query(`ALTER TABLE "latest_note" ADD "is_reply" boolean NOT NULL DEFAULT false`);
+				await queryRunner.query(`ALTER TABLE "latest_note" ADD "is_quote" boolean NOT NULL DEFAULT false`);
+				await queryRunner.query(`ALTER TABLE "latest_note" ADD CONSTRAINT "PK_a44ac8ca9cb916faeefc0912abd" PRIMARY KEY ("user_id", is_public, is_reply, is_quote)`);
+		}
+
+    async down(queryRunner) {
+				await queryRunner.query(`ALTER TABLE "latest_note" DROP CONSTRAINT "PK_a44ac8ca9cb916faeefc0912abd"`);
+				await queryRunner.query(`ALTER TABLE "latest_note" DROP COLUMN is_quote`);
+				await queryRunner.query(`ALTER TABLE "latest_note" DROP COLUMN is_reply`);
+				await queryRunner.query(`ALTER TABLE "latest_note" DROP COLUMN is_public`);
+				await queryRunner.query(`ALTER TABLE "latest_note" ADD CONSTRAINT "PK_f619b62bfaafabe68f52fb50c9a" PRIMARY KEY ("user_id")`);
+		}
+}
diff --git a/packages/backend/migration/1729414690009-defaultSensitive.js b/packages/backend/migration/1729414690009-defaultSensitive.js
new file mode 100644
index 0000000000000000000000000000000000000000..47debf05a70a3c4ca9f154dfe10db4a063ca9a78
--- /dev/null
+++ b/packages/backend/migration/1729414690009-defaultSensitive.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: marie and sharkey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class DefaultSensitive1729414690009 {
+    name = 'DefaultSensitive1729414690009'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_profile" ADD "defaultSensitive" boolean NOT NULL DEFAULT false`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "defaultSensitive"`);
+    }
+}
diff --git a/packages/backend/migration/1730505338000-friendlyCaptcha.js b/packages/backend/migration/1730505338000-friendlyCaptcha.js
new file mode 100644
index 0000000000000000000000000000000000000000..94a4f22dfa4f726788bef0723b18619188eef266
--- /dev/null
+++ b/packages/backend/migration/1730505338000-friendlyCaptcha.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: marie and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class friendlyCaptcha1730505338000 {
+	name = 'friendlyCaptcha1730505338000';
+
+	async up(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "meta" ADD "enableFC" boolean NOT NULL DEFAULT false`, undefined);
+		await queryRunner.query(`ALTER TABLE "meta" ADD "fcSiteKey" character varying(1024)`, undefined);
+		await queryRunner.query(`ALTER TABLE "meta" ADD "fcSecretKey" character varying(1024)`, undefined);
+	}
+
+	async down(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "fcSecretKey"`, undefined);
+		await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "fcSiteKey"`, undefined);
+		await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableFC"`, undefined);
+	}
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 353613bb575d49a9033210c4f8a5ecbd87337416..19547c503358d1de42b12871df89493aae32256c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -20,7 +20,7 @@
 		"restart": "pnpm build && pnpm start",
 		"dev": "node ./scripts/dev.mjs",
 		"typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit",
-		"eslint": "eslint --quiet \"src/**/*.ts\" --cache",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
 		"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",
@@ -65,24 +65,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.0",
+		"@fastify/cookie": "10.0.0",
+		"@fastify/cors": "10.0.0",
+		"@fastify/express": "4.0.0",
+		"@fastify/http-proxy": "10.0.0",
+		"@fastify/multipart": "9.0.0",
+		"@fastify/static": "8.0.0",
+		"@fastify/view": "10.0.0",
 		"@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.3",
+		"@nestjs/core": "10.4.3",
+		"@nestjs/testing": "10.4.3",
 		"@peertube/http-signature": "1.7.0",
 		"@sentry/node": "8.20.0",
 		"@sentry/profiling-node": "8.20.0",
@@ -100,8 +100,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.13.2",
 		"cacheable-lookup": "7.0.0",
 		"cbor": "9.0.2",
 		"chalk": "5.3.0",
@@ -112,12 +112,12 @@
 		"content-disposition": "0.5.4",
 		"date-fns": "2.30.0",
 		"deep-email-validator": "0.1.21",
-		"fast-xml-parser": "^4.4.0",
-		"fastify": "4.28.1",
+		"fast-xml-parser": "4.4.1",
+		"fastify": "5.0.0",
 		"fastify-multer": "^2.0.3",
-		"fastify-raw-body": "4.3.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",
 		"glob": "11.0.0",
@@ -127,16 +127,17 @@
 		"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",
 		"megalodon": "workspace:*",
-		"meilisearch": "0.41.0",
+		"meilisearch": "0.42.0",
+		"juice": "11.0.0",
 		"microformats-parser": "2.0.2",
 		"mime-types": "2.1.35",
 		"misskey-js": "workspace:*",
@@ -145,24 +146,24 @@
 		"nanoid": "5.0.7",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.2",
-		"nodemailer": "6.9.14",
+		"nodemailer": "6.9.15",
 		"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.2",
 		"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",
 		"proxy-addr": "^2.0.7",
 		"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",
@@ -170,17 +171,17 @@
 		"rxjs": "7.8.1",
 		"sanitize-html": "2.13.0",
 		"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",
 		"uuid": "^9.0.1",
 		"vary": "1.1.2",
@@ -190,7 +191,7 @@
 	},
 	"devDependencies": {
 		"@jest/globals": "29.7.0",
-		"@nestjs/platform-express": "10.3.10",
+		"@nestjs/platform-express": "10.4.3",
 		"@simplewebauthn/types": "10.0.0",
 		"@swc/jest": "0.2.36",
 		"@types/accepts": "1.3.7",
@@ -199,10 +200,10 @@
 		"@types/body-parser": "1.19.5",
 		"@types/color-convert": "2.0.3",
 		"@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",
@@ -210,11 +211,11 @@
 		"@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/proxy-addr": "^2.0.3",
 		"@types/pug": "2.0.10",
 		"@types/punycode": "2.1.4",
@@ -222,7 +223,7 @@
 		"@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",
@@ -231,17 +232,17 @@
 		"@types/uuid": "^9.0.4",
 		"@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 d4bf4baf43cbf2048eda4db33ece2f82921718ee..f33a450325a644cfe8681820cb51daaf4325c3a6 100644
--- a/packages/backend/scripts/check_connect.js
+++ b/packages/backend/scripts/check_connect.js
@@ -12,26 +12,49 @@ const config = loadConfig();
 // createPostgresDataSource handels primaries and replicas automatically.
 // usually, it only opens connections first use, so we force it using
 // .initialize()
-createPostgresDataSource(config)
-	.initialize()
-	.then(c => { c.destroy() })
-	.catch(e => { throw e });
-
+async function connectToPostgres(){
+	const source = createPostgresDataSource(config);
+	await source.initialize();
+	await source.destroy();
+}
 
 // Connect to all redis servers
-function connectToRedis(redisOptions) {
-	const redis = new Redis(redisOptions);
-	redis.on('connect', () => redis.disconnect());
-	redis.on('error', (e) => {
-		throw e;
+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.
-(new Set([
-	config.redis,
-	config.redisForPubsub,
-	config.redisForJobQueue,
-	config.redisForTimelines,
-])).forEach(connectToRedis);
+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 09971e8ca022c2a20fa4fd08ed0f3d730cea753e..6ae8ccfbb32ca49338e608c5bd16acdd0baf5965 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 3f9967ad19ea6d1972aa2cf6e8bbde2d1b1cf98a..3dc49c7eb6c41f14f30b4fe30b0e13bf446c8bf5 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -51,6 +51,7 @@ type Source = {
 	redisForPubsub?: RedisOptionsSource;
 	redisForJobQueue?: RedisOptionsSource;
 	redisForTimelines?: RedisOptionsSource;
+	redisForReactions?: RedisOptionsSource;
 	meilisearch?: {
 		host: string;
 		port: string;
@@ -72,6 +73,11 @@ type Source = {
 
 	maxFileSize?: number;
 	maxNoteLength?: number;
+	maxCwLength?: number;
+	maxRemoteCwLength?: number;
+	maxRemoteNoteLength?: number;
+	maxAltTextLength?: number;
+	maxRemoteAltTextLength?: number;
 
 	clusterLimit?: number;
 
@@ -146,8 +152,13 @@ export type Config = {
 	proxySmtp: string | undefined;
 	proxyBypassHosts: string[] | undefined;
 	allowedPrivateNetworks: string[] | undefined;
-	maxFileSize: number | undefined;
+	maxFileSize: number;
 	maxNoteLength: number;
+	maxRemoteNoteLength: number;
+	maxCwLength: number;
+	maxRemoteCwLength: number;
+	maxAltTextLength: number;
+	maxRemoteAltTextLength: number;
 	clusterLimit: number | undefined;
 	id: string;
 	outgoingAddress: string | undefined;
@@ -177,8 +188,10 @@ export type Config = {
 	authUrl: string;
 	driveUrl: string;
 	userAgent: string;
-	clientEntry: string;
-	clientManifestExists: boolean;
+	frontendEntry: string;
+	frontendManifestExists: boolean;
+	frontendEmbedEntry: string;
+	frontendEmbedManifestExists: boolean;
 	mediaProxy: string;
 	externalMediaProxyEnabled: boolean;
 	videoThumbnailGenerator: string | null;
@@ -186,6 +199,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;
@@ -219,10 +233,15 @@ const path = process.env.MISSKEY_CONFIG_YML
 
 export function loadConfig(): Config {
 	const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
-	const clientManifestExists = fs.existsSync(`${_dirname}/../../../built/_vite_/manifest.json`);
-	const clientManifest = clientManifestExists ?
-		JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
+
+	const frontendManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_vite_/manifest.json');
+	const frontendEmbedManifestExists = fs.existsSync(_dirname + '/../../../built/_frontend_embed_vite_/manifest.json');
+	const frontendManifest = frontendManifestExists ?
+		JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_vite_/manifest.json`, 'utf-8'))
 		: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
+	const frontendEmbedManifest = frontendEmbedManifestExists ?
+		JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_frontend_embed_vite_/manifest.json`, 'utf-8'))
+		: { 'src/boot.ts': { file: 'src/boot.ts' } };
 
 	const configFiles = globSync(path).sort();
 
@@ -282,6 +301,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,
@@ -289,8 +309,13 @@ export function loadConfig(): Config {
 		proxySmtp: config.proxySmtp,
 		proxyBypassHosts: config.proxyBypassHosts,
 		allowedPrivateNetworks: config.allowedPrivateNetworks,
-		maxFileSize: config.maxFileSize,
+		maxFileSize: config.maxFileSize ?? 262144000,
 		maxNoteLength: config.maxNoteLength ?? 3000,
+		maxRemoteNoteLength: config.maxRemoteNoteLength ?? 100000,
+		maxCwLength: config.maxCwLength ?? 500,
+		maxRemoteCwLength: config.maxRemoteCwLength ?? 5000,
+		maxAltTextLength: config.maxAltTextLength ?? 20000,
+		maxRemoteAltTextLength: config.maxRemoteAltTextLength ?? 100000,
 		clusterLimit: config.clusterLimit,
 		outgoingAddress: config.outgoingAddress,
 		outgoingAddressFamily: config.outgoingAddressFamily,
@@ -313,8 +338,10 @@ export function loadConfig(): Config {
 			config.videoThumbnailGenerator.endsWith('/') ? config.videoThumbnailGenerator.substring(0, config.videoThumbnailGenerator.length - 1) : config.videoThumbnailGenerator
 			: null,
 		userAgent: `Misskey/${version} (${config.url})`,
-		clientEntry: clientManifest['src/_boot_.ts'],
-		clientManifestExists: clientManifestExists,
+		frontendEntry: frontendManifest['src/_boot_.ts'],
+		frontendManifestExists: frontendManifestExists,
+		frontendEmbedEntry: frontendEmbedManifest['src/boot.ts'],
+		frontendEmbedManifestExists: frontendEmbedManifestExists,
 		perChannelMaxNoteCacheCount: config.perChannelMaxNoteCacheCount ?? 1000,
 		perUserNotificationsMaxCount: config.perUserNotificationsMaxCount ?? 500,
 		deactivateAntennaThreshold: config.deactivateAntennaThreshold ?? (1000 * 60 * 60 * 24 * 7),
@@ -455,7 +482,7 @@ function applyEnvOverrides(config: Source) {
 	_apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]);
 	_apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]);
 	_apply_top([
-		['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines'],
+		['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines', 'redisForReactions'],
 		['host', 'port', 'username', 'pass', 'db', 'prefix'],
 	]);
 	_apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]);
@@ -463,7 +490,7 @@ function applyEnvOverrides(config: Source) {
 	_apply_top(['sentryForBackend', 'enableNodeProfiling']);
 	_apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]);
 	_apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]);
-	_apply_top([['maxFileSize', 'maxNoteLength', 'pidFile']]);
+	_apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile']]);
 	_apply_top(['import', ['downloadTimeout', 'maxFileSize']]);
 	_apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature']]);
 }
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index 02c27779cab436a594488a631d193970b30b46e3..adb0a63ad7a8f11a130474db014b4427241c1d71 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -3,26 +3,10 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-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
 
-//#region hard limits
-// If you change DB_* values, you must also change the DB schema.
-
-/**
- * Maximum note text length that can be stored in DB.
- * Surrogate pairs count as one
- */
-export const DB_MAX_NOTE_TEXT_LENGTH = 8192;
-
-/**
- * Maximum image description length that can be stored in DB.
- * Surrogate pairs count as one
- */
-export const DB_MAX_IMAGE_COMMENT_LENGTH = 8192;
-//#endregion
+export const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
 
 // ブラウザで直接表示することを許可するファイルの種類のリスト
 // ここに含まれないものは application/octet-stream としてレスポンスされる
diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts
index 7be53358856454c3bacd81b45cc9a09bd4d99020..fe2c63e7d6467d81b24e76e09243e8723820900b 100644
--- a/packages/backend/src/core/AbuseReportNotificationService.ts
+++ b/packages/backend/src/core/AbuseReportNotificationService.ts
@@ -14,10 +14,10 @@ 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';
@@ -27,15 +27,19 @@ 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,
 	) {
@@ -93,10 +97,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) {
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index b6b591d24036e7106134f1612a400cf815318872..6e3125044c6845b693344530c09e80c7a513acaf 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,
 	) {
@@ -276,7 +277,7 @@ export class AccountMoveService {
 		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) {
+				if (this.meta.enableChartsForFederatedInstances) {
 					this.instanceChart.updateFollowers(i.host, false);
 				}
 			});
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 89e475b5f1d3d428942529cb5fa8e664e787412a..ec9ace417e858e7c03dd74bc64ac7fbf8bb872e9 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 f6b7955cd20778afd69b8e7e4015a4deb2f4ff45..4be45dabb8f6f74e646a97b6f2805972de6bd5bd 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -10,6 +10,7 @@ import { bindThis } from '@/decorators.js';
 type CaptchaResponse = {
 	success: boolean;
 	'error-codes'?: string[];
+	'errors'?: string[];
 };
 
 @Injectable()
@@ -73,6 +74,35 @@ export class CaptchaService {
 		}
 	}
 
+	@bindThis
+	public async verifyFriendlyCaptcha(secret: string, response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw new Error('frc-failed: no response provided');
+		}
+
+		const result = await this.httpRequestService.send('https://api.friendlycaptcha.com/api/v1/siteverify', {
+			method: 'POST',
+			body: JSON.stringify({
+				secret: secret,
+				solution: response,
+			}),
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		});
+
+		if (result.status !== 200) {
+			throw new Error('frc-failed: frc didn\'t return 200 OK');
+		}
+
+		const resp = await result.json() as CaptchaResponse;
+
+		if (resp.success !== true) {
+			const errorCodes = resp['errors'] ? resp['errors'].join(', ') : '';
+			throw new Error(`frc-failed: ${errorCodes}`);
+		}
+	}
+
 	// https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
 	@bindThis
 	public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 7e51d3afa468093455f38edf4db0de285491d2ba..c083068392a98ae76ec0bdc0b096b1c56963ffbd 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -13,6 +13,7 @@ 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 { AccountMoveService } from './AccountMoveService.js';
 import { AccountUpdateService } from './AccountUpdateService.js';
 import { AnnouncementService } from './AnnouncementService.js';
@@ -42,6 +43,7 @@ import { ModerationLogService } from './ModerationLogService.js';
 import { NoteCreateService } from './NoteCreateService.js';
 import { NoteEditService } from './NoteEditService.js';
 import { NoteDeleteService } from './NoteDeleteService.js';
+import { LatestNoteService } from './LatestNoteService.js';
 import { NotePiningService } from './NotePiningService.js';
 import { NoteReadService } from './NoteReadService.js';
 import { NotificationService } from './NotificationService.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';
@@ -149,6 +152,7 @@ import { ApQuestionService } from './activitypub/models/ApQuestionService.js';
 import { QueueModule } from './QueueModule.js';
 import { QueueService } from './QueueService.js';
 import { LoggerService } from './LoggerService.js';
+import { SponsorsService } from './SponsorsService.js';
 import type { Provider } from '@nestjs/common';
 
 //#region 文字列ベースでのinjection用(循環参照対応のため)
@@ -184,6 +188,7 @@ const $ModerationLogService: Provider = { provide: 'ModerationLogService', useEx
 const $NoteCreateService: Provider = { provide: 'NoteCreateService', useExisting: NoteCreateService };
 const $NoteEditService: Provider = { provide: 'NoteEditService', useExisting: NoteEditService };
 const $NoteDeleteService: Provider = { provide: 'NoteDeleteService', useExisting: NoteDeleteService };
+const $LatestNoteService: Provider = { provide: 'LatestNoteService', useExisting: LatestNoteService };
 const $NotePiningService: Provider = { provide: 'NotePiningService', useExisting: NotePiningService };
 const $NoteReadService: Provider = { provide: 'NoteReadService', useExisting: NoteReadService };
 const $NotificationService: Provider = { provide: 'NotificationService', useExisting: NotificationService };
@@ -192,6 +197,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,6 +217,7 @@ 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 $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
@@ -295,6 +302,8 @@ const $ApPersonService: Provider = { provide: 'ApPersonService', useExisting: Ap
 const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting: ApQuestionService };
 //#endregion
 
+const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: SponsorsService };
+
 @Module({
 	imports: [
 		QueueModule,
@@ -332,6 +341,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		NoteCreateService,
 		NoteEditService,
 		NoteDeleteService,
+		LatestNoteService,
 		NotePiningService,
 		NoteReadService,
 		NotificationService,
@@ -340,6 +350,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PushNotificationService,
 		QueryService,
 		ReactionService,
+		ReactionsBufferingService,
 		RelayService,
 		RoleService,
 		S3Service,
@@ -359,6 +370,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		VideoProcessingService,
 		UserWebhookService,
 		SystemWebhookService,
+		WebhookTestService,
 		UtilityService,
 		FileInfoService,
 		SearchService,
@@ -443,6 +455,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		ApQuestionService,
 		QueueService,
 
+		SponsorsService,
+
 		//#region 文字列ベースでのinjection用(循環参照対応のため)
 		$LoggerService,
 		$AbuseReportService,
@@ -476,6 +490,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$NoteCreateService,
 		$NoteEditService,
 		$NoteDeleteService,
+		$LatestNoteService,
 		$NotePiningService,
 		$NoteReadService,
 		$NotificationService,
@@ -484,6 +499,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PushNotificationService,
 		$QueryService,
 		$ReactionService,
+		$ReactionsBufferingService,
 		$RelayService,
 		$RoleService,
 		$S3Service,
@@ -503,6 +519,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$VideoProcessingService,
 		$UserWebhookService,
 		$SystemWebhookService,
+		$WebhookTestService,
 		$UtilityService,
 		$FileInfoService,
 		$SearchService,
@@ -586,6 +603,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$ApPersonService,
 		$ApQuestionService,
 		//#endregion
+
+		$SponsorsService,
 	],
 	exports: [
 		QueueModule,
@@ -621,6 +640,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		NoteCreateService,
 		NoteEditService,
 		NoteDeleteService,
+		LatestNoteService,
 		NotePiningService,
 		NoteReadService,
 		NotificationService,
@@ -629,6 +649,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PushNotificationService,
 		QueryService,
 		ReactionService,
+		ReactionsBufferingService,
 		RelayService,
 		RoleService,
 		S3Service,
@@ -648,6 +669,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		VideoProcessingService,
 		UserWebhookService,
 		SystemWebhookService,
+		WebhookTestService,
 		UtilityService,
 		FileInfoService,
 		SearchService,
@@ -731,6 +753,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		ApQuestionService,
 		QueueService,
 
+		SponsorsService,
+
 		//#region 文字列ベースでのinjection用(循環参照対応のため)
 		$LoggerService,
 		$AbuseReportService,
@@ -764,6 +788,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$NoteCreateService,
 		$NoteEditService,
 		$NoteDeleteService,
+		$LatestNoteService,
 		$NotePiningService,
 		$NoteReadService,
 		$NotificationService,
@@ -772,6 +797,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PushNotificationService,
 		$QueryService,
 		$ReactionService,
+		$ReactionsBufferingService,
 		$RelayService,
 		$RoleService,
 		$S3Service,
@@ -791,6 +817,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$VideoProcessingService,
 		$UserWebhookService,
 		$SystemWebhookService,
+		$WebhookTestService,
 		$UtilityService,
 		$FileInfoService,
 		$SearchService,
@@ -873,6 +900,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$ApPersonService,
 		$ApQuestionService,
 		//#endregion
+
+		$SponsorsService,
 	],
 })
 export class CoreModule { }
diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts
index 7f1b8f3efb0feb917919d0f4b3346fb25a9d2e19..8408e958636ca6ea3149b821b1588385ed264d25 100644
--- a/packages/backend/src/core/DeleteAccountService.ts
+++ b/packages/backend/src/core/DeleteAccountService.ts
@@ -13,6 +13,7 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
 
 @Injectable()
 export class DeleteAccountService {
@@ -38,6 +39,7 @@ export class DeleteAccountService {
 	}, moderator?: MiUser): Promise<void> {
 		const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
 		if (_user.isRoot) throw new Error('cannot delete a root account');
+		if (isSystemAccount(_user)) throw new Error('cannot delete a system account');
 
 		if (moderator != null) {
 			this.moderationLogService.log(moderator, 'deleteAccount', {
diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts
index 83452845d46348ca8aa33d202482c3738f029794..0e992f05ded5618f325a0dd6695a5f617c8caf05 100644
--- a/packages/backend/src/core/DownloadService.ts
+++ b/packages/backend/src/core/DownloadService.ts
@@ -42,7 +42,7 @@ export class DownloadService {
 
 		const timeout = options.timeout ?? 30 * 1000;
 		const operationTimeout = options.operationTimeout ?? 60 * 1000;
-		const maxSize = options.maxSize ?? this.config.maxFileSize ?? 262144000;
+		const maxSize = options.maxSize ?? this.config.maxFileSize;
 
 		const urlObj = new URL(url);
 		let filename = urlObj.pathname.split('/').pop() ?? 'untitled';
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index 46fa4243a7d518aa613d1c04f718f39916023c71..086f2f94d54ba0b93364c1f88ade5a5599940870 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}`);
@@ -228,25 +227,33 @@ export class DriveService {
 			const thumbnailAccessKey = 'thumbnail-' + randomUUID();
 			const webpublicAccessKey = 'webpublic-' + randomUUID();
 
-			const url = this.internalStorageService.saveFromPath(accessKey, path);
-
-			let thumbnailUrl: string | null = null;
-			let webpublicUrl: string | null = null;
+			// Ugly type is just to help TS figure out that 2nd / 3rd promises are optional.
+			const promises: [Promise<string>, ...(Promise<string> | undefined)[]] = [
+				this.internalStorageService.saveFromPath(accessKey, path),
+			];
 
 			if (alts.thumbnail) {
-				thumbnailUrl = this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data);
-				this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`);
+				promises.push(this.internalStorageService.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data));
 			}
 
 			if (alts.webpublic) {
-				webpublicUrl = this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data);
+				promises.push(this.internalStorageService.saveFromBuffer(webpublicAccessKey, alts.webpublic.data));
+			}
+
+			const [url, thumbnailUrl, webpublicUrl] = await Promise.all(promises);
+
+			if (thumbnailUrl) {
+				this.registerLogger.info(`thumbnail stored: ${thumbnailAccessKey}`);
+			}
+
+			if (webpublicUrl) {
 				this.registerLogger.info(`web stored: ${webpublicAccessKey}`);
 			}
 
 			file.storedInternal = true;
 			file.url = url;
-			file.thumbnailUrl = thumbnailUrl;
-			file.webpublicUrl = webpublicUrl;
+			file.thumbnailUrl = thumbnailUrl ?? null;
+			file.webpublicUrl = webpublicUrl ?? null;
 			file.accessKey = accessKey;
 			file.thumbnailAccessKey = thumbnailAccessKey;
 			file.webpublicAccessKey = webpublicAccessKey;
@@ -376,10 +383,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 +397,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
@@ -463,9 +468,7 @@ export class DriveService {
 		requestHeaders = null,
 		ext = null,
 	}: AddFileArgs): Promise<MiDriveFile> {
-		const instance = await this.metaService.fetch();
 		const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
-
 		const info = await this.fileInfoService.getFileInfo(path);
 		this.registerLogger.info(`${JSON.stringify(info)}`);
 
@@ -565,11 +568,11 @@ export class DriveService {
 		file.maybeSensitive = info.sensitive;
 		file.maybePorn = info.porn;
 		file.isSensitive = user
-			? this.userEntityService.isLocalUser(user) && profile!.alwaysMarkNsfw ? true :
+			? this.userEntityService.isLocalUser(user) && (profile!.alwaysMarkNsfw || profile!.defaultSensitive) ? true :
 			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 (userRoleNSFW) file.isSensitive = true;
 
@@ -631,7 +634,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);
 			}
 		}
@@ -725,19 +728,19 @@ export class DriveService {
 
 	@bindThis
 	public async deleteFileSync(file: MiDriveFile, isExpired = false, deleter?: MiUser) {
+		const promises = [];
+
 		if (file.storedInternal) {
-			this.internalStorageService.del(file.accessKey!);
+			promises.push(this.internalStorageService.del(file.accessKey!));
 
 			if (file.thumbnailUrl) {
-				this.internalStorageService.del(file.thumbnailAccessKey!);
+				promises.push(this.internalStorageService.del(file.thumbnailAccessKey!));
 			}
 
 			if (file.webpublicUrl) {
-				this.internalStorageService.del(file.webpublicAccessKey!);
+				promises.push(this.internalStorageService.del(file.webpublicAccessKey!));
 			}
 		} else if (!file.isLink) {
-			const promises = [];
-
 			promises.push(this.deleteObjectStorageFile(file.accessKey!));
 
 			if (file.thumbnailUrl) {
@@ -747,10 +750,10 @@ export class DriveService {
 			if (file.webpublicUrl) {
 				promises.push(this.deleteObjectStorageFile(file.webpublicAccessKey!));
 			}
-
-			await Promise.all(promises);
 		}
 
+		await Promise.all(promises);
+
 		this.deletePostProcess(file, isExpired, deleter);
 	}
 
@@ -778,7 +781,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);
 			}
 		}
@@ -800,14 +803,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 435dbbae28a95f62e645aac738605e2eaea45d1c..a176474b953f6a14ecdd7a68645be8c4cee5aba6 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 7aeeb781786c8b60d7d00b091eebcf0902ee73cd..7ec565557c5dd1bc702f4086b1d121893be9e281 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -12,6 +12,8 @@ import { IdService } from '@/core/IdService.js';
 import { DI } from '@/di-symbols.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
+import { QueryFailedError } from 'typeorm';
+import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
 
 @Injectable()
 export class FederatedInstanceService implements OnApplicationShutdown {
@@ -56,11 +58,24 @@ export class FederatedInstanceService implements OnApplicationShutdown {
 		const index = await this.instancesRepository.findOneBy({ host });
 
 		if (index == null) {
-			const i = await this.instancesRepository.insertOne({
-				id: this.idService.gen(),
-				host,
-				firstRetrievedAt: new Date(),
-			});
+			let i;
+			try {
+				i = await this.instancesRepository.insertOne({
+					id: this.idService.gen(),
+					host,
+					firstRetrievedAt: new Date(),
+				});
+			} catch (e: unknown) {
+				if (e instanceof QueryFailedError) {
+					if (isDuplicateKeyValueError(e)) {
+						i = await this.instancesRepository.findOneBy({ host });
+					}
+				}
+
+				if (i == null) {
+					throw e;
+				}
+			}
 
 			this.federatedInstanceCache.set(host, i);
 			return i;
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 753011cded8983ada947cb70c5d469a157dab18d..211c22bfaf6fbd5335ac617da3a69f266a1a4f4d 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -246,7 +246,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 eb192ee6dafca28d2ce0945975daf520c1657778..793bbeecb16242a765e27e316e608051d6f5d1b6 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/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts
index 4fb8a93e4916400bdf153493c69b0c1505562fd8..f7371f8e79defbcd165af4df8cecb4eda2008459 100644
--- a/packages/backend/src/core/InternalStorageService.ts
+++ b/packages/backend/src/core/InternalStorageService.ts
@@ -4,6 +4,7 @@
  */
 
 import * as fs from 'node:fs';
+import { copyFile, mkdir, unlink, writeFile } from 'node:fs/promises';
 import * as Path from 'node:path';
 import { fileURLToPath } from 'node:url';
 import { dirname } from 'node:path';
@@ -23,6 +24,8 @@ export class InternalStorageService {
 		@Inject(DI.config)
 		private config: Config,
 	) {
+		// No one should erase the working directory *while the server is running*.
+		fs.mkdirSync(path, { recursive: true });
 	}
 
 	@bindThis
@@ -36,21 +39,19 @@ export class InternalStorageService {
 	}
 
 	@bindThis
-	public saveFromPath(key: string, srcPath: string) {
-		fs.mkdirSync(path, { recursive: true });
-		fs.copyFileSync(srcPath, this.resolvePath(key));
+	public async saveFromPath(key: string, srcPath: string): Promise<string> {
+		await copyFile(srcPath, this.resolvePath(key));
 		return `${this.config.url}/files/${key}`;
 	}
 
 	@bindThis
-	public saveFromBuffer(key: string, data: Buffer) {
-		fs.mkdirSync(path, { recursive: true });
-		fs.writeFileSync(this.resolvePath(key), data);
+	public async saveFromBuffer(key: string, data: Buffer): Promise<string> {
+		await writeFile(this.resolvePath(key), data);
 		return `${this.config.url}/files/${key}`;
 	}
 
 	@bindThis
-	public del(key: string) {
-		fs.unlink(this.resolvePath(key), () => {});
+	public async del(key: string): Promise<void> {
+		await unlink(this.resolvePath(key));
 	}
 }
diff --git a/packages/backend/src/core/LatestNoteService.ts b/packages/backend/src/core/LatestNoteService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c3798055066fd14ae5245eb85c1d27c8f0fd025c
--- /dev/null
+++ b/packages/backend/src/core/LatestNoteService.ts
@@ -0,0 +1,139 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Not } from 'typeorm';
+import { MiNote } from '@/models/Note.js';
+import { isPureRenote } from '@/misc/is-renote.js';
+import { SkLatestNote } from '@/models/LatestNote.js';
+import { DI } from '@/di-symbols.js';
+import type { LatestNotesRepository, NotesRepository } from '@/models/_.js';
+import { LoggerService } from '@/core/LoggerService.js';
+import Logger from '@/logger.js';
+
+@Injectable()
+export class LatestNoteService {
+	private readonly logger: Logger;
+
+	constructor(
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
+		@Inject(DI.latestNotesRepository)
+		private latestNotesRepository: LatestNotesRepository,
+
+		loggerService: LoggerService,
+	) {
+		this.logger = loggerService.getLogger('LatestNoteService');
+	}
+
+	handleUpdatedNoteBG(before: MiNote, after: MiNote): void {
+		this
+			.handleUpdatedNote(before, after)
+			.catch(err => this.logger.error('Unhandled exception while updating latest_note (after update):', err));
+	}
+
+	async handleUpdatedNote(before: MiNote, after: MiNote): Promise<void> {
+		// If the key didn't change, then there's nothing to update
+		if (SkLatestNote.areEquivalent(before, after)) return;
+
+		// Simulate update as delete + create
+		await this.handleDeletedNote(before);
+		await this.handleCreatedNote(after);
+	}
+
+	handleCreatedNoteBG(note: MiNote): void {
+		this
+			.handleCreatedNote(note)
+			.catch(err => this.logger.error('Unhandled exception while updating latest_note (after create):', err));
+	}
+
+	async handleCreatedNote(note: MiNote): Promise<void> {
+		// Ignore DMs.
+		// Followers-only posts are *included*, as this table is used to back the "following" feed.
+		if (note.visibility === 'specified') return;
+
+		// Ignore pure renotes
+		if (isPureRenote(note)) return;
+
+		// Compute the compound key of the entry to check
+		const key = SkLatestNote.keyFor(note);
+
+		// Make sure that this isn't an *older* post.
+		// We can get older posts through replies, lookups, updates, etc.
+		const currentLatest = await this.latestNotesRepository.findOneBy(key);
+		if (currentLatest != null && currentLatest.noteId >= note.id) return;
+
+		// Record this as the latest note for the given user
+		const latestNote = new SkLatestNote({
+			...key,
+			noteId: note.id,
+		});
+		await this.latestNotesRepository.upsert(latestNote, ['userId', 'isPublic', 'isReply', 'isQuote']);
+	}
+
+	handleDeletedNoteBG(note: MiNote): void {
+		this
+			.handleDeletedNote(note)
+			.catch(err => this.logger.error('Unhandled exception while updating latest_note (after delete):', err));
+	}
+
+	async handleDeletedNote(note: MiNote): Promise<void> {
+		// If it's a DM, then it can't possibly be the latest note so we can safely skip this.
+		if (note.visibility === 'specified') return;
+
+		// If it's a pure renote, then it can't possibly be the latest note so we can safely skip this.
+		if (isPureRenote(note)) return;
+
+		// Compute the compound key of the entry to check
+		const key = SkLatestNote.keyFor(note);
+
+		// Check if the deleted note was possibly the latest for the user
+		const existingLatest = await this.latestNotesRepository.findOneBy(key);
+		if (existingLatest == null || existingLatest.noteId !== note.id) return;
+
+		// Find the newest remaining note for the user.
+		// We exclude DMs and pure renotes.
+		const nextLatest = await this.notesRepository
+			.createQueryBuilder('note')
+			.select()
+			.where({
+				userId: key.userId,
+				visibility: key.isPublic
+					? 'public'
+					: Not('specified'),
+				replyId: key.isReply
+					? Not(null)
+					: null,
+				renoteId: key.isQuote
+					? Not(null)
+					: null,
+			})
+			.andWhere(`
+				(
+					note."renoteId" IS NULL
+					OR note.text IS NOT NULL
+					OR note.cw IS NOT NULL
+					OR note."replyId" IS NOT NULL
+					OR note."hasPoll"
+					OR note."fileIds" != '{}'
+				)
+			`)
+			.orderBy({ id: 'DESC' })
+			.getOne();
+		if (!nextLatest) return;
+
+		// Record it as the latest
+		const latestNote = new SkLatestNote({
+			...key,
+			noteId: nextLatest.id,
+		});
+
+		// When inserting the latest note, it's possible that another worker has "raced" the insert and already added a newer note.
+		// We must use orIgnore() to ensure that the query ignores conflicts, otherwise an exception may be thrown.
+		await this.latestNotesRepository
+			.createQueryBuilder('latest')
+			.insert()
+			.into(SkLatestNote)
+			.values(latestNote)
+			.orIgnore()
+			.execute();
+	}
+}
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index ec630f804e7cf3406fde4e9a314560f32745ad95..3d88d0aefe97a23e999ac6d6afe43404cc279f54 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 94bb5af6b5402a845340df691d623193c203e2ee..42676d6f981bf425abf441700d32dc988fb11b87 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -6,7 +6,7 @@
 import { URL } from 'node:url';
 import { Inject, Injectable } from '@nestjs/common';
 import * as parse5 from 'parse5';
-import { Window, DocumentFragment, XMLSerializer } from 'happy-dom';
+import { Window, XMLSerializer } from 'happy-dom';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { intersperse } from '@/misc/prelude/array.js';
@@ -412,8 +412,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;
@@ -465,7 +467,7 @@ export class MfmService {
 
 		const serialized = new XMLSerializer().serializeToString(body);
 
-		happyDOM.close().catch(e => {});
+		happyDOM.close().catch(err => {});
 
 		return serialized;
 	}
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index c252336f995c7dde574c1b64e1e85092c8b321bb..1bc4599a602200a1bede36d9381aed4e04400188 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -8,13 +8,12 @@ import * as mfm from '@transfem-org/sfm-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';
@@ -49,9 +45,7 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
 import { NoteReadService } from '@/core/NoteReadService.js';
 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';
@@ -62,6 +56,8 @@ import { isReply } from '@/misc/is-reply.js';
 import { trackPromise } from '@/misc/promise-tracker.js';
 import { isUserRelated } from '@/misc/is-user-related.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { LatestNoteService } from '@/core/LatestNoteService.js';
+import { CollapsedQueue } from '@/misc/collapsed-queue.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -153,11 +149,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,
 
@@ -212,7 +212,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,
@@ -221,7 +220,10 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private utilityService: UtilityService,
 		private userBlockingService: UserBlockingService,
 		private cacheService: CacheService,
-	) { }
+		private latestNoteService: LatestNoteService,
+	) {
+		this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount);
+	}
 
 	@bindThis
 	public async create(user: {
@@ -254,10 +256,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) {
@@ -265,17 +265,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';
@@ -334,9 +334,13 @@ export class NoteCreateService implements OnApplicationShutdown {
 			data.localOnly = true;
 		}
 
+		const maxTextLength = user.host == null
+			? this.config.maxNoteLength
+			: this.config.maxRemoteNoteLength;
+
 		if (data.text) {
-			if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
-				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
+			if (data.text.length > maxTextLength) {
+				data.text = data.text.slice(0, maxTextLength);
 			}
 			data.text = data.text.trim();
 			if (data.text === '') {
@@ -346,6 +350,22 @@ export class NoteCreateService implements OnApplicationShutdown {
 			data.text = null;
 		}
 
+		const maxCwLength = user.host == null
+			? this.config.maxCwLength
+			: this.config.maxRemoteCwLength;
+
+		if (data.cw) {
+			if (data.cw.length > maxCwLength) {
+				data.cw = data.cw.slice(0, maxCwLength);
+			}
+			data.cw = data.cw.trim();
+			if (data.cw === '') {
+				data.cw = null;
+			}
+		} else {
+			data.cw = null;
+		}
+
 		let tags = data.apHashtags;
 		let emojis = data.apEmojis;
 		let mentionedUsers = data.apMentions;
@@ -368,7 +388,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);
 
@@ -537,10 +557,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 		isBot: MiUser['isBot'];
 		noindex: MiUser['noindex'];
 	}, 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);
 		}
 
@@ -548,11 +566,11 @@ export class NoteCreateService implements OnApplicationShutdown {
 		if (this.userEntityService.isRemoteUser(user)) {
 			this.federatedInstanceService.fetch(user.host).then(async i => {
 				if (note.renote && note.text) {
-					this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
+					this.updateNotesCountQueue.enqueue(i.id, 1);
 				} else if (!note.renote) {
-					this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
+					this.updateNotesCountQueue.enqueue(i.id, 1);
 				}
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+				if (this.meta.enableChartsForFederatedInstances) {
 					this.instanceChart.updateNote(i.host, note, true);
 				}
 			});
@@ -560,7 +578,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		// ハッシュタグ更新
 		if (data.visibility === 'public' || data.visibility === 'home') {
-			if (user.isBot && meta.enableBotTrending) {
+			if (user.isBot && this.meta.enableBotTrending) {
 				this.hashtagService.updateHashtags(user, tags);
 			} else if (!user.isBot) {
 				this.hashtagService.updateHashtags(user, tags);
@@ -796,6 +814,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 			});
 		}
 
+		// Update the Latest Note index / following feed
+		this.latestNoteService.handleCreatedNoteBG(note);
+
 		// Register to search database
 		if (!user.noindex) this.index(note);
 	}
@@ -934,15 +955,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: {
@@ -952,9 +972,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 {
@@ -992,9 +1012,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);
 				}
 			}
 
@@ -1011,25 +1031,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);
@@ -1038,9 +1058,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) {
@@ -1099,9 +1119,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 (
@@ -1117,12 +1137,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 7ce6d7c605a73da210d37f9e5999812ddb81e219..285db9f152d33bd0be57a84e342c864365bf6f2c 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -6,8 +6,8 @@
 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 { MiNote, IMentionedRemoteUsers } from '@/models/Note.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,12 +19,11 @@ 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';
+import { LatestNoteService } from '@/core/LatestNoteService.js';
 
 @Injectable()
 export class NoteDeleteService {
@@ -32,6 +31,9 @@ export class NoteDeleteService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -42,18 +44,17 @@ 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,
 		private perUserNotesChart: PerUserNotesChart,
 		private instanceChart: InstanceChart,
+		private latestNoteService: LatestNoteService,
 	) {}
 
 	/**
@@ -109,10 +110,8 @@ 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);
 			}
 
@@ -131,7 +130,7 @@ export class NoteDeleteService {
 					} else if (!note.renoteId) {
 						this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
 					}
-					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					if (this.meta.enableChartsForFederatedInstances) {
 						this.instanceChart.updateNote(i.host, note, false);
 					}
 				});
@@ -148,6 +147,8 @@ export class NoteDeleteService {
 			userId: user.id,
 		});
 
+		this.latestNoteService.handleDeletedNoteBG(note);
+
 		if (deleter && (note.userId !== deleter.id)) {
 			const user = await this.usersRepository.findOneByOrFail({ id: note.userId });
 			this.moderationLogService.log(deleter, 'deleteNote', {
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index 5ff0f26e2bac1fdaa965159130f25a4121a2febc..d31958e5d4abd3ac2ce5757d2e5b94377b94d009 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -8,13 +8,12 @@ import * as mfm from '@transfem-org/sfm-js';
 import { DataSource, In, 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 { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js';
+import type { NoteEditRepository, ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MiMeta, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository, PollsRepository } from '@/models/_.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiApp } from '@/models/App.js';
 import { concat } from '@/misc/prelude/array.js';
@@ -40,9 +39,7 @@ import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerServ
 import { NoteReadService } from '@/core/NoteReadService.js';
 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 { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -52,6 +49,9 @@ import { isReply } from '@/misc/is-reply.js';
 import { trackPromise } from '@/misc/promise-tracker.js';
 import { isUserRelated } from '@/misc/is-user-related.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { LatestNoteService } from '@/core/LatestNoteService.js';
+import { CollapsedQueue } from '@/misc/collapsed-queue.js';
+import { NoteCreateService } from '@/core/NoteCreateService.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
 
@@ -145,11 +145,15 @@ type Option = {
 @Injectable()
 export class NoteEditService 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,
 
@@ -207,14 +211,17 @@ export class NoteEditService implements OnApplicationShutdown {
 		private apDeliverManagerService: ApDeliverManagerService,
 		private apRendererService: ApRendererService,
 		private roleService: RoleService,
-		private metaService: MetaService,
 		private searchService: SearchService,
 		private activeUsersChart: ActiveUsersChart,
 		private instanceChart: InstanceChart,
 		private utilityService: UtilityService,
 		private userBlockingService: UserBlockingService,
 		private cacheService: CacheService,
-	) { }
+		private latestNoteService: LatestNoteService,
+		private noteCreateService: NoteCreateService,
+	) {
+		this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount);
+	}
 
 	@bindThis
 	public async edit(user: {
@@ -247,6 +254,11 @@ export class NoteEditService implements OnApplicationShutdown {
 			data.reply = undefined;
 		}
 
+		// changing visibility on an edit is ill-defined, let's try to
+		// keep the same visibility as the original note
+		data.visibility = oldnote.visibility;
+		data.localOnly = oldnote.localOnly;
+
 		// チャンネル外にリプライしたら対象のスコープに合わせる
 		// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
 		if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
@@ -270,10 +282,8 @@ export class NoteEditService implements OnApplicationShutdown {
 		if (data.channel != null) data.localOnly = true;
 		if (data.updatedAt == null) data.updatedAt = new Date();
 
-		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) {
@@ -281,17 +291,17 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 		}
 
-		const hasProhibitedWords = await this.checkProhibitedWordsContain({
+		const hasProhibitedWords = this.noteCreateService.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';
@@ -354,9 +364,13 @@ export class NoteEditService implements OnApplicationShutdown {
 			data.localOnly = true;
 		}
 
+		const maxTextLength = user.host == null
+			? this.config.maxNoteLength
+			: this.config.maxRemoteNoteLength;
+
 		if (data.text) {
-			if (data.text.length > DB_MAX_NOTE_TEXT_LENGTH) {
-				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
+			if (data.text.length > maxTextLength) {
+				data.text = data.text.slice(0, maxTextLength);
 			}
 			data.text = data.text.trim();
 			if (data.text === '') {
@@ -366,6 +380,22 @@ export class NoteEditService implements OnApplicationShutdown {
 			data.text = null;
 		}
 
+		const maxCwLength = user.host == null
+			? this.config.maxCwLength
+			: this.config.maxRemoteCwLength;
+
+		if (data.cw) {
+			if (data.cw.length > maxCwLength) {
+				data.cw = data.cw.slice(0, maxCwLength);
+			}
+			data.cw = data.cw.trim();
+			if (data.cw === '') {
+				data.cw = null;
+			}
+		} else {
+			data.cw = null;
+		}
+
 		let tags = data.apHashtags;
 		let emojis = data.apEmojis;
 		let mentionedUsers = data.apMentions;
@@ -388,7 +418,7 @@ export class NoteEditService 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);
 
@@ -429,9 +459,6 @@ export class NoteEditService implements OnApplicationShutdown {
 		if (data.cw !== oldnote.cw) {
 			update.cw = data.cw;
 		}
-		if (data.localOnly !== oldnote.localOnly) {
-			update.localOnly = data.localOnly;
-		}
 		if (oldnote.hasPoll !== !!data.poll) {
 			update.hasPoll = !!data.poll;
 		}
@@ -496,6 +523,7 @@ export class NoteEditService implements OnApplicationShutdown {
 				renoteUserId: data.renote ? data.renote.userId : null,
 				renoteUserHost: data.renote ? data.renote.userHost : null,
 				userHost: user.host,
+				reactionAndUserPairCache: oldnote.reactionAndUserPairCache,
 			});
 
 			if (data.uri != null) note.uri = data.uri;
@@ -544,7 +572,7 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 
 			setImmediate('post edited', { signal: this.#shutdownController.signal }).then(
-				() => this.postNoteEdited(note, user, data, silent, tags!, mentionedUsers!),
+				() => this.postNoteEdited(note, oldnote, user, data, silent, tags!, mentionedUsers!),
 				() => { /* aborted, ignore this */ },
 			);
 
@@ -555,7 +583,7 @@ export class NoteEditService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	private async postNoteEdited(note: MiNote, user: {
+	private async postNoteEdited(note: MiNote, oldNote: MiNote, user: {
 		id: MiUser['id'];
 		username: MiUser['username'];
 		host: MiUser['host'];
@@ -565,8 +593,8 @@ export class NoteEditService implements OnApplicationShutdown {
 		// 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.updateNotesCountQueue.enqueue(i.id, 1);
+				if (this.meta.enableChartsForFederatedInstances) {
 					this.instanceChart.updateNote(i.host, note, true);
 				}
 			});
@@ -752,6 +780,9 @@ export class NoteEditService implements OnApplicationShutdown {
 			});
 		}
 
+		// Update the Latest Note index / following feed
+		this.latestNoteService.handleUpdatedNoteBG(oldNote, note);
+
 		// Register to search database
 		if (!user.noindex) this.index(note);
 	}
@@ -852,15 +883,14 @@ export class NoteEditService 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: {
@@ -870,9 +900,9 @@ export class NoteEditService 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 {
@@ -910,9 +940,9 @@ export class NoteEditService 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);
 				}
 			}
 
@@ -929,25 +959,25 @@ export class NoteEditService 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);
@@ -956,9 +986,9 @@ export class NoteEditService 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) {
@@ -1017,30 +1047,24 @@ export class NoteEditService implements OnApplicationShutdown {
 		}
 	}
 
-	public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
-		if (prohibitedWords == null) {
-			prohibitedWords = (await this.metaService.fetch()).prohibitedWords;
-		}
-
-		if (
-			this.utilityService.isKeyWordIncluded(
-				this.utilityService.concatNoteContentsForKeyWordCheck(content),
-				prohibitedWords,
-			)
-		) {
-			return true;
-		}
+	@bindThis
+	private collapseNotesCount(oldValue: number, newValue: number) {
+		return oldValue + newValue;
+	}
 
-		return false;
+	@bindThis
+	private async performUpdateNotesCount(id: MiNote['id'], incrBy: number) {
+		await this.instancesRepository.increment({ id: id }, 'notesCount', incrBy);
 	}
 
 	@bindThis
-	public dispose(): void {
+	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/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts
index 71d663bf90607b225166505636ee61b8206cea52..c3ff2a68d3357fe13f5b168a4333bed2d892b5d6 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 6a845b951de65335336f953c9f4a5b346f1fa9f0..1479bb00d9b814848dff916e5ff13ea486183b84 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 be5f10771a9f4e89c6a4e13c64041b5097a5a1dd..dc13aa21bfd948eae043d5e363168cf666cd710f 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -88,6 +88,12 @@ export class QueueService {
 			repeat: { pattern: '*/5 * * * *' },
 			removeOnComplete: true,
 		});
+
+		this.systemQueue.add('bakeBufferedReactions', {
+		}, {
+			repeat: { pattern: '0 0 * * *' },
+			removeOnComplete: true,
+		});
 	}
 
 	@bindThis
@@ -511,10 +517,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,
@@ -527,7 +538,7 @@ export class QueueService {
 		};
 
 		return this.userWebhookDeliverQueue.add(webhook.id, data, {
-			attempts: 4,
+			attempts: opts?.attempts ?? 4,
 			backoff: {
 				type: 'custom',
 			},
@@ -538,10 +549,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,
@@ -553,7 +569,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 17ff1687868ff17e6fc018d68ed5550146660679..0179b0680faa4463ddd1272b8d4edac5649586c0 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, NoteThreadMutingsRepository } from '@/models/_.js';
+import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository, NoteThreadMutingsRepository, 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 = /^:([\p{Letter}\p{Number}\p{Mark}_+-]+)(?:@([\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,
@@ -90,12 +89,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,
@@ -108,8 +107,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);
@@ -155,7 +152,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 {
@@ -177,7 +174,6 @@ export class ReactionService {
 			reaction,
 		};
 
-		// Create reaction
 		try {
 			await this.noteReactionsRepository.insert(record);
 		} catch (e) {
@@ -201,16 +197,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 (
@@ -230,7 +230,7 @@ export class ReactionService {
 			}
 		}
 
-		if (meta.enableChartsForRemoteUser || (user.host == null)) {
+		if (this.meta.enableChartsForRemoteUser || (user.host == null)) {
 			this.perUserReactionsChart.update(user, note);
 		}
 
@@ -317,14 +317,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,
@@ -346,8 +350,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'] {
@@ -360,10 +377,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;
 			})
@@ -418,11 +432,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 0000000000000000000000000000000000000000..b4207c51062c6eec25c5f7938810095d3d6a2ada
--- /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 7984dc56279ed58314f0c2560618475673c82f85..64f75390311fa1425f8d95193dd0619df98033d5 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';
@@ -60,6 +60,11 @@ export type RolePolicies = {
 	rateLimitFactor: number;
 	canImportNotes: boolean;
 	avatarDecorationLimit: number;
+	canImportAntennas: boolean;
+	canImportBlocking: boolean;
+	canImportFollowing: boolean;
+	canImportMuting: boolean;
+	canImportUserLists: boolean;
 };
 
 export const DEFAULT_POLICIES: RolePolicies = {
@@ -91,6 +96,11 @@ export const DEFAULT_POLICIES: RolePolicies = {
 	rateLimitFactor: 1,
 	canImportNotes: true,
 	avatarDecorationLimit: 1,
+	canImportAntennas: true,
+	canImportBlocking: true,
+	canImportFollowing: true,
+	canImportMuting: true,
+	canImportUserLists: true,
 };
 
 @Injectable()
@@ -105,8 +115,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,
@@ -123,7 +133,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,
@@ -343,8 +352,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;
 
@@ -393,6 +401,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 			rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
 			canImportNotes: calc('canImportNotes', vs => vs.some(v => v === true)),
 			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)),
 		};
 	}
 
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 80907a89214a8afed443cd68009abbf0ebf9e876..1b0b1e5bbd2a779a9ae0f40ef42412f587be0972 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -9,7 +9,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import * as argon2 from 'argon2';
 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';
@@ -21,7 +21,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()
@@ -30,6 +29,9 @@ export class SignupService {
 		@Inject(DI.db)
 		private db: DataSource,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -40,7 +42,6 @@ export class SignupService {
 		private userService: UserService,
 		private userEntityService: UserEntityService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private instanceActorService: InstanceActorService,
 		private usersChart: UsersChart,
 	) {
@@ -54,10 +55,10 @@ export class SignupService {
 		host?: string | null;
 		reason?: string | null;
 		ignorePreservedUsernames?: boolean;
+		approved?: boolean;
 	}) {
 		const { username, password, passwordHash, host, reason } = opts;
 		let hash = passwordHash;
-		const instance = await this.metaService.fetch(true);
 
 		// Validate username
 		if (!this.userEntityService.validateLocalUsername(username)) {
@@ -91,7 +92,7 @@ export class SignupService {
 		const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
 
 		if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
-			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');
 			}
@@ -115,9 +116,6 @@ export class SignupService {
 			));
 
 		let account!: MiUser;
-		let defaultApproval = false;
-
-		if (!instance.approvalRequiredForSignup) defaultApproval = true;
 
 		// Start transaction
 		await this.db.transaction(async transactionalEntityManager => {
@@ -135,7 +133,7 @@ export class SignupService {
 				host: this.utilityService.toPunyNullable(host),
 				token: secret,
 				isRoot: isTheFirstUser,
-				approved: defaultApproval,
+				approved: isTheFirstUser || (opts.approved ?? !this.meta.approvalRequiredForSignup),
 				signupReason: reason,
 			}));
 
@@ -163,4 +161,3 @@ export class SignupService {
 		return { account, secret };
 	}
 }
-
diff --git a/packages/backend/src/core/SponsorsService.ts b/packages/backend/src/core/SponsorsService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..77dd6f81a44d05d0158452dbc9d7271834a7a0ea
--- /dev/null
+++ b/packages/backend/src/core/SponsorsService.ts
@@ -0,0 +1,87 @@
+/*
+ * SPDX-FileCopyrightText: marie and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
+import type { MiMeta } from '@/models/_.js';
+import * as Redis from 'ioredis';
+import { DI } from '@/di-symbols.js';
+import { RedisKVCache } from '@/misc/cache.js';
+import { bindThis } from '@/decorators.js';
+
+@Injectable()
+export class SponsorsService implements OnApplicationShutdown {
+	private cache: RedisKVCache<void[]>;
+
+	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+	) {
+		this.cache = new RedisKVCache<void[]>(this.redisClient, 'sponsors', {
+			lifetime: 1000 * 60 * 60,
+			memoryCacheLifetime: 1000 * 60,
+			fetcher: (key) => {
+				if (key === 'instance') return this.fetchInstanceSponsors();
+				return this.fetchSharkeySponsors();
+			},
+			toRedisConverter: (value) => JSON.stringify(value),
+			fromRedisConverter: (value) => JSON.parse(value),
+		});
+	}
+
+	@bindThis
+	private async fetchInstanceSponsors() {
+		if (!(this.meta.donationUrl && this.meta.donationUrl.includes('opencollective.com'))) {
+			return [];
+		}
+
+		try {
+			const backers = await fetch(`${this.meta.donationUrl}/members/users.json`).then((response) => response.json());
+
+			// Merge both together into one array and make sure it only has Active subscriptions
+			const allSponsors = [...backers].filter(sponsor => sponsor.isActive === true && sponsor.role === 'BACKER' && sponsor.tier);
+
+			// Remove possible duplicates
+			return [...new Map(allSponsors.map(v => [v.profile, v])).values()];
+		} catch (error) {
+			return [];
+		}
+	}
+
+	@bindThis
+	private async fetchSharkeySponsors() {
+		try {
+			const backers = await fetch('https://opencollective.com/sharkey/tiers/backer/all.json').then((response) => response.json());
+			const sponsorsOC = await fetch('https://opencollective.com/sharkey/tiers/sponsor/all.json').then((response) => response.json());
+
+			// Merge both together into one array and make sure it only has Active subscriptions
+			const allSponsors = [...sponsorsOC, ...backers].filter(sponsor => sponsor.isActive === true);
+
+			// Remove possible duplicates
+			return [...new Map(allSponsors.map(v => [v.profile, v])).values()];
+		} catch (error) {
+			return [];
+		}
+	}
+
+	@bindThis
+	public async instanceSponsors(forceUpdate: boolean) {
+		if (forceUpdate) this.cache.refresh('instance');
+		return this.cache.fetch('instance');
+	}
+
+	@bindThis
+	public async sharkeySponsors(forceUpdate: boolean) {
+		if (forceUpdate) this.cache.refresh('sharkey');
+		return this.cache.fetch('sharkey');
+	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.cache.dispose();
+	}
+}
diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts
index bc6851f788da65298a47095a79c2a4e5239fe811..bb7c6b8c0e027a11919b712b0759b4546262ec98 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'];
@@ -165,19 +165,24 @@ export class SystemWebhookService implements OnApplicationShutdown {
 	/**
 	 * 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 6aab8fde70c2c39387e8a4b7e7ab9cb49ceb381f..77e7b60beac647005a6446caacde149a7d5696f2 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([
@@ -307,14 +308,14 @@ export class UserFollowingService implements OnModuleInit {
 			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) {
+					if (this.meta.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) {
+					if (this.meta.enableChartsForFederatedInstances) {
 						this.instanceChart.updateFollowers(i.host, true);
 					}
 				});
@@ -439,14 +440,14 @@ export class UserFollowingService implements OnModuleInit {
 			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) {
+					if (this.meta.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) {
+					if (this.meta.enableChartsForFederatedInstances) {
 						this.instanceChart.updateFollowers(i.host, false);
 					}
 				});
diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts
index 7920e58e367856d702d040cc3663babbbd64c630..30dcaa6f7dd9bf480eab1622caabffa3c598f0be 100644
--- a/packages/backend/src/core/UserSuspendService.ts
+++ b/packages/backend/src/core/UserSuspendService.ts
@@ -15,6 +15,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
 import { RelationshipJobData } from '@/queue/types.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
 
 @Injectable()
 export class UserSuspendService {
@@ -38,6 +39,8 @@ export class UserSuspendService {
 
 	@bindThis
 	public async suspend(user: MiUser, moderator: MiUser): Promise<void> {
+		if (isSystemAccount(user)) throw new Error('cannot suspend a system account');
+
 		await this.usersRepository.update(user.id, {
 			isSuspended: true,
 		});
diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts
index e96bfeea9581ab900067379e8e1e227724f835bd..8a40a5368804e2f4538dc2068c42c4e5a0e17020 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 22871e44f8fe142cf7002ad0a3893c4cf5a38cf5..009dd4665fff2f4b0b9cdb933e9fd06a3ef1ea59 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,
 	) {
 	}
 
@@ -112,4 +116,18 @@ export class UtilityService {
 		const host = `${this.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
 		return host;
 	}
+
+	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 ec9f4484a4c0fc870099e99d6c09a19b5ff51445..75ab0a207c4518ae3738a53e1c449667b598e1ea 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 0000000000000000000000000000000000000000..4a31c1d17ad0117c5ba157ee0e375a04dd3ba96b
--- /dev/null
+++ b/packages/backend/src/core/WebhookTestService.ts
@@ -0,0 +1,454 @@
+/*
+ * 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';
+
+const oneDayMillis = 24 * 60 * 60 * 1000;
+
+function generateAbuseReport(override?: Partial<MiAbuseUserReport>): MiAbuseUserReport {
+	return {
+		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,
+		...override,
+	};
+}
+
+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,
+		backgroundId: null,
+		background: null,
+		avatarUrl: null,
+		bannerUrl: null,
+		backgroundUrl: null,
+		avatarBlurhash: null,
+		bannerBlurhash: null,
+		backgroundBlurhash: null,
+		avatarDecorations: [],
+		tags: [],
+		isSuspended: false,
+		isLocked: false,
+		isSilenced: false,
+		isBot: false,
+		isCat: true,
+		speakAsCat: true,
+		isRoot: false,
+		isExplorable: true,
+		isHibernated: false,
+		isDeleted: false,
+		emojis: [],
+		score: 0,
+		host: null,
+		inbox: null,
+		sharedInbox: null,
+		featured: null,
+		uri: null,
+		followersUri: null,
+		token: null,
+		approved: true,
+		signupReason: null,
+		noindex: false,
+		...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,
+		updatedAt: 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,
+		speakAsCat: user.speakAsCat,
+		emojis: user.emojis,
+		onlineStatus: 'active',
+		badgeRoles: [],
+		noindex: user.noindex,
+		isModerator: false,
+		isAdmin: false,
+		isSystem: false,
+		isSilenced: user.isSilenced,
+		...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,
+		backgroundUrl: user.backgroundUrl,
+		backgroundBlurhash: user.backgroundBlurhash,
+		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,
+		listenbrainz: null,
+		...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;
+			}
+		}
+	}
+}
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index 062af397321e55e5eafd389250165627f087991d..8c97cc8ce838bb6dd81f3d81ee3270285f2c2920 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -16,6 +16,7 @@ import type { MiLocalUser, MiRemoteUser } from '@/models/User.js';
 import { getApId } from './type.js';
 import { ApPersonService } from './models/ApPersonService.js';
 import type { IObject } from './type.js';
+import { ApLoggerService } from '@/core/activitypub/ApLoggerService.js';
 
 export type UriParseResult = {
 	/** wether the URI was generated by us */
@@ -53,13 +54,14 @@ export class ApDbResolverService implements OnApplicationShutdown {
 
 		private cacheService: CacheService,
 		private apPersonService: ApPersonService,
+		private apLoggerService: ApLoggerService,
 	) {
 		this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
 		this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
 	}
 
 	@bindThis
-	public parseUri(value: string | IObject): UriParseResult {
+	public parseUri(value: string | IObject | [string | IObject]): UriParseResult {
 		const separator = '/';
 
 		const uri = new URL(getApId(value));
@@ -78,7 +80,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
 	 * AP Note => Misskey Note in DB
 	 */
 	@bindThis
-	public async getNoteFromApId(value: string | IObject): Promise<MiNote | null> {
+	public async getNoteFromApId(value: string | IObject | [string | IObject]): Promise<MiNote | null> {
 		const parsed = this.parseUri(value);
 
 		if (parsed.local) {
@@ -98,7 +100,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
 	 * AP Person => Misskey User in DB
 	 */
 	@bindThis
-	public async getUserFromApId(value: string | IObject): Promise<MiLocalUser | MiRemoteUser | null> {
+	public async getUserFromApId(value: string | IObject | [string | IObject]): Promise<MiLocalUser | MiRemoteUser | null> {
 		const parsed = this.parseUri(value);
 
 		if (parsed.local) {
@@ -174,10 +176,16 @@ export class ApDbResolverService implements OnApplicationShutdown {
 	 */
 	@bindThis
 	public async refetchPublicKeyForApId(user: MiRemoteUser): Promise<MiUserPublickey | null> {
+		this.apLoggerService.logger.debug('Re-fetching public key for user', { userId: user.id, uri: user.uri });
 		await this.apPersonService.updatePerson(user.uri);
+
 		const key = await this.userPublickeysRepository.findOneBy({ userId: user.id });
-		if (key != null) {
-			await this.publicKeyByUserIdCache.set(user.id, key);
+		this.publicKeyByUserIdCache.set(user.id, key);
+
+		if (key) {
+			this.apLoggerService.logger.info('Re-fetched public key for user', { userId: user.id, uri: user.uri });
+		} else {
+			this.apLoggerService.logger.warn('Failed to re-fetch key for user', { userId: user.id, uri: user.uri });
 		}
 		return key;
 	}
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 6a28cbad15083d3dd42ac11a52207390024a08be..d54c9544c34a7f4297298bd71d7847eda41d615b 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -5,6 +5,7 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { In } from 'typeorm';
+import * as Bull from 'bullmq';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
@@ -17,18 +18,18 @@ 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';
 import { AbuseReportService } from '@/core/AbuseReportService.js';
+import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
 import { ApNoteService } from './models/ApNoteService.js';
 import { ApLoggerService } from './ApLoggerService.js';
@@ -39,6 +40,7 @@ import { ApPersonService } from './models/ApPersonService.js';
 import { ApQuestionService } from './models/ApQuestionService.js';
 import type { Resolver } from './ApResolverService.js';
 import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove, IPost } from './type.js';
+import { fromTuple } from '@/misc/from-tuple.js';
 
 @Injectable()
 export class ApInboxService {
@@ -48,6 +50,9 @@ export class ApInboxService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -64,7 +69,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,
@@ -83,6 +87,7 @@ export class ApInboxService {
 		private apQuestionService: ApQuestionService,
 		private queueService: QueueService,
 		private globalEventService: GlobalEventService,
+		private federatedInstanceService: FederatedInstanceService,
 	) {
 		this.logger = this.apLoggerService.logger;
 	}
@@ -250,7 +255,8 @@ export class ApInboxService {
 		}
 
 		if (activity.target === actor.featured) {
-			const note = await this.apNoteService.resolveNote(activity.object);
+			const object = fromTuple(activity.object);
+			const note = await this.apNoteService.resolveNote(object);
 			if (note == null) return 'note not found';
 			await this.notePiningService.addPinned(actor, note.id);
 			return;
@@ -267,11 +273,12 @@ export class ApInboxService {
 
 		const resolver = this.apResolverService.createResolver();
 
-		if (!activity.object) return 'skip: activity has no object property';
-		const targetUri = getApId(activity.object);
+		const activityObject = fromTuple(activity.object);
+		if (!activityObject) return 'skip: activity has no object property';
+		const targetUri = getApId(activityObject);
 		if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
 
-		const target = await resolver.resolve(activity.object).catch(e => {
+		const target = await resolver.resolve(activityObject).catch(e => {
 			this.logger.error(`Resolution failed: ${e}`);
 			return e;
 		});
@@ -289,9 +296,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);
 
@@ -367,29 +373,30 @@ export class ApInboxService {
 
 		this.logger.info(`Create: ${uri}`);
 
-		if (!activity.object) return 'skip: activity has no object property';
-		const targetUri = getApId(activity.object);
+		const activityObject = fromTuple(activity.object);
+		if (!activityObject) return 'skip: activity has no object property';
+		const targetUri = getApId(activityObject);
 		if (targetUri.startsWith('bear:')) return 'skip: bearcaps url not supported.';
 
 		// copy audiences between activity <=> object.
-		if (typeof activity.object === 'object') {
-			const to = unique(concat([toArray(activity.to), toArray(activity.object.to)]));
-			const cc = unique(concat([toArray(activity.cc), toArray(activity.object.cc)]));
+		if (typeof activityObject === 'object') {
+			const to = unique(concat([toArray(activity.to), toArray(activityObject.to)]));
+			const cc = unique(concat([toArray(activity.cc), toArray(activityObject.cc)]));
 
 			activity.to = to;
 			activity.cc = cc;
-			activity.object.to = to;
-			activity.object.cc = cc;
+			activityObject.to = to;
+			activityObject.cc = cc;
 		}
 
 		// If there is no attributedTo, use Activity actor.
-		if (typeof activity.object === 'object' && !activity.object.attributedTo) {
-			activity.object.attributedTo = activity.actor;
+		if (typeof activityObject === 'object' && !activityObject.attributedTo) {
+			activityObject.attributedTo = activity.actor;
 		}
 
 		const resolver = this.apResolverService.createResolver();
 
-		const object = await resolver.resolve(activity.object).catch(e => {
+		const object = await resolver.resolve(activityObject).catch(e => {
 			this.logger.error(`Resolution failed: ${e}`);
 			throw e;
 		});
@@ -445,15 +452,15 @@ export class ApInboxService {
 		// 削除対象objectのtype
 		let formerType: string | undefined;
 
-		if (typeof activity.object === 'string') {
+		const activityObject = fromTuple(activity.object);
+		if (typeof activityObject === 'string') {
 			// typeが不明だけど、どうせ消えてるのでremote resolveしない
 			formerType = undefined;
 		} else {
-			const object = activity.object;
-			if (isTombstone(object)) {
-				formerType = toSingle(object.formerType);
+			if (isTombstone(activityObject)) {
+				formerType = toSingle(activityObject.formerType);
 			} else {
-				formerType = toSingle(object.type);
+				formerType = toSingle(activityObject.type);
 			}
 		}
 
@@ -530,6 +537,12 @@ export class ApInboxService {
 
 	@bindThis
 	private async flag(actor: MiRemoteUser, activity: IFlag): Promise<string> {
+		// Make sure the source instance is allowed to send reports.
+		const instance = await this.federatedInstanceService.fetch(actor.host);
+		if (instance.rejectReports) {
+			throw new Bull.UnrecoverableError(`Rejecting report from instance: ${actor.host}`);
+		}
+
 		// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
 		// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
 		const uris = getApIds(activity.object);
@@ -607,7 +620,8 @@ export class ApInboxService {
 		}
 
 		if (activity.target === actor.featured) {
-			const note = await this.apNoteService.resolveNote(activity.object);
+			const activityObject = fromTuple(activity.object);
+			const note = await this.apNoteService.resolveNote(activityObject);
 			if (note == null) return 'note not found';
 			await this.notePiningService.removePinned(actor, note.id);
 			return;
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 55d1054de91190ab0a484a93160090f52ebda659..42ee5bc58ade2d3389b55f5850db2f9f3e522fa2 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -23,11 +23,10 @@ import { MfmService } from '@/core/MfmService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import type { MiUserKeypair } from '@/models/UserKeypair.js';
-import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository } from '@/models/_.js';
+import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFilesRepository, PollsRepository, InstancesRepository, MiMeta } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { IdService } from '@/core/IdService.js';
-import { MetaService } from '../MetaService.js';
 import { JsonLdService } from './JsonLdService.js';
 import { ApMfmService } from './ApMfmService.js';
 import { CONTEXT } from './misc/contexts.js';
@@ -39,6 +38,9 @@ export class ApRendererService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -65,7 +67,6 @@ export class ApRendererService {
 		private apMfmService: ApMfmService,
 		private mfmService: MfmService,
 		private idService: IdService,
-		private metaService: MetaService,
 	) {
 	}
 
@@ -199,7 +200,8 @@ export class ApRendererService {
 			type: 'Flag',
 			actor: this.userEntityService.genLocalUserUri(user.id),
 			content,
-			object,
+			// This MUST be an array for Pleroma compatibility: https://activitypub.software/TransFem-org/Sharkey/-/issues/641#note_7301
+			object: [object],
 		};
 	}
 
@@ -272,10 +274,9 @@ export class ApRendererService {
 	@bindThis
 	public async renderLike(noteReaction: MiNoteReaction, note: { uri: string | null }): Promise<ILike> {
 		const reaction = noteReaction.reaction;
-		const meta = await this.metaService.fetch(true);
 		let isMastodon = false;
 
-		if (meta.defaultLike && reaction.replaceAll(':', '') === meta.defaultLike.replaceAll(':', '')) {
+		if (this.meta.defaultLike && reaction.replaceAll(':', '') === this.meta.defaultLike.replaceAll(':', '')) {
 			const note = await this.notesRepository.findOneBy({ id: noteReaction.noteId });
 
 			if (note && note.userHost) {
@@ -419,7 +420,7 @@ export class ApRendererService {
 
 		const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
 
-		const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
+		const { content } = this.apMfmService.getNoteHtml(note, apAppend);
 
 		const emojis = await this.getEmojis(note.emojis);
 		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
@@ -449,13 +450,11 @@ export class ApRendererService {
 			attributedTo,
 			summary: summary ?? undefined,
 			content: content ?? undefined,
-			...(noMisskeyContent ? {} : {
-				_misskey_content: text,
-				source: {
-					content: text,
-					mediaType: 'text/x.misskeymarkdown',
-				},
-			}),
+			_misskey_content: text,
+			source: {
+				content: text,
+				mediaType: 'text/x.misskeymarkdown',
+			},
 			_misskey_quote: quote,
 			quoteUrl: quote,
 			quoteUri: quote,
@@ -517,6 +516,7 @@ 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,
 			icon: avatar ? this.renderImage(avatar) : null,
 			image: banner ? this.renderImage(banner) : null,
 			backgroundUrl: background ? this.renderImage(background) : null,
@@ -526,6 +526,7 @@ export class ApRendererService {
 			publicKey: this.renderKey(user, keypair, '#main-key'),
 			isCat: user.isCat,
 			noindex: user.noindex,
+			indexable: !user.noindex,
 			speakAsCat: user.speakAsCat,
 			attachment: attachment.length ? attachment : undefined,
 		};
@@ -710,7 +711,7 @@ export class ApRendererService {
 
 		const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
 
-		const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
+		const { content } = this.apMfmService.getNoteHtml(note, apAppend);
 
 		const emojis = await this.getEmojis(note.emojis);
 		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
@@ -741,13 +742,11 @@ export class ApRendererService {
 			summary: summary ?? undefined,
 			content: content ?? undefined,
 			updated: note.updatedAt?.toISOString(),
-			...(noMisskeyContent ? {} : {
-				_misskey_content: text,
-				source: {
-					content: text,
-					mediaType: 'text/x.misskeymarkdown',
-				},
-			}),
+			_misskey_content: text,
+			source: {
+				content: text,
+				mediaType: 'text/x.misskeymarkdown',
+			},
 			_misskey_quote: quote,
 			quoteUrl: quote,
 			quoteUri: quote,
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index 0b9139db902a25b1c0dac8d75f5ea5dd30ba462c..38c78cf900d7d9cf2b3dabe272856fba31f64511 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -208,12 +208,12 @@ export class ApRequestService {
 		const contentType = res.headers.get('content-type');
 
 		if (
-			res.ok
-			&& (contentType ?? '').split(';')[0].trimEnd().toLowerCase() === 'text/html'
-			&& _followAlternate === true
+			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,
@@ -247,7 +247,7 @@ export class ApRequestService {
 			} catch (e) {
 				// something went wrong parsing the HTML, ignore the whole thing
 			} finally {
-				await window.happyDOM.close();
+				happyDOM.close().catch(err => {});
 			}
 		}
 		//#endregion
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index b047a6c59ba581cc22e528f7ac0f6755de2f1e83..5d5c61ce2cfe73d32b07bd5e6272d801ff89095f 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';
@@ -21,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
 import { ApRendererService } from './ApRendererService.js';
 import { ApRequestService } from './ApRequestService.js';
 import type { IObject, ICollection, IOrderedCollection } from './type.js';
+import { fromTuple } from '@/misc/from-tuple.js';
 
 export class Resolver {
 	private history: Set<string>;
@@ -29,6 +29,7 @@ export class Resolver {
 
 	constructor(
 		private config: Config,
+		private meta: MiMeta,
 		private usersRepository: UsersRepository,
 		private notesRepository: NotesRepository,
 		private pollsRepository: PollsRepository,
@@ -36,7 +37,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,
@@ -67,7 +67,10 @@ export class Resolver {
 	}
 
 	@bindThis
-	public async resolve(value: string | IObject): Promise<IObject> {
+	public async resolve(value: string | IObject | [string | IObject]): Promise<IObject> {
+		// eslint-disable-next-line no-param-reassign
+		value = fromTuple(value);
+
 		if (typeof value !== 'string') {
 			return value;
 		}
@@ -94,8 +97,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');
 		}
 
@@ -186,6 +188,9 @@ export class ApResolverService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -203,7 +208,6 @@ export class ApResolverService {
 
 		private utilityService: UtilityService,
 		private instanceActorService: InstanceActorService,
-		private metaService: MetaService,
 		private apRequestService: ApRequestService,
 		private httpRequestService: HttpRequestService,
 		private apRendererService: ApRendererService,
@@ -216,6 +220,7 @@ export class ApResolverService {
 	public createResolver(): Resolver {
 		return new Resolver(
 			this.config,
+			this.meta,
 			this.usersRepository,
 			this.notesRepository,
 			this.pollsRepository,
@@ -223,7 +228,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 815b20b910d89b977da73a47249d4048da471e9f..da75fc1d4214eb98235c4e0a6860d46c6783d09b 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -545,6 +545,7 @@ const extension_context_definition = {
 	Emoji: 'toot:Emoji',
 	featured: 'toot:featured',
 	discoverable: 'toot:discoverable',
+	indexable: 'toot:indexable',
 	// schema
 	schema: 'http://schema.org#',
 	PropertyValue: 'schema:PropertyValue',
@@ -556,6 +557,7 @@ const extension_context_definition = {
 	'_misskey_reaction': 'misskey:_misskey_reaction',
 	'_misskey_votes': 'misskey:_misskey_votes',
 	'_misskey_summary': 'misskey:_misskey_summary',
+	'_misskey_followedMessage': 'misskey:_misskey_followedMessage',
 	'isCat': 'misskey:isCat',
 	// Firefish
 	firefish: 'https://joinfirefish.org/ns#',
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index b281ac9728fe6e96daff02cb026cc9bc5f021e00..259889d94526a7882e64bedaf2862eb01e75799a 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -5,17 +5,16 @@
 
 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';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { checkHttps } from '@/misc/check-https.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
+import type { Config } from '@/config.js';
 import { ApResolverService } from '../ApResolverService.js';
 import { ApLoggerService } from '../ApLoggerService.js';
 import { isDocument, type IObject } from '../type.js';
@@ -25,10 +24,14 @@ export class ApImageService {
 	private logger: Logger;
 
 	constructor(
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
+		@Inject(DI.config)
+		private config: Config,
 
-		private metaService: MetaService,
 		private apResolverService: ApResolverService,
 		private driveService: DriveService,
 		private apLoggerService: ApLoggerService,
@@ -65,12 +68,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);
 
 		await this.federatedInstanceService.fetch(actor.host).then(async i => {
 			if (i.isNSFW) {
@@ -84,7 +85,7 @@ export class ApImageService {
 			uri: image.url,
 			sensitive: !!(image.sensitive),
 			isLink: !shouldBeCached,
-			comment: truncate(image.name ?? undefined, DB_MAX_IMAGE_COMMENT_LENGTH),
+			comment: truncate(image.name ?? undefined, this.config.maxRemoteAltTextLength),
 		});
 		if (!file.isLink || file.url === image.url) return file;
 
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 7b7a7921fbca40a1692b86a9c46db189ed552f6d..f404a77fbb11d5e42c60105887c2665a278f7095 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, NotesRepository } from '@/models/_.js';
+import type { PollsRepository, EmojisRepository, NotesRepository, 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';
@@ -47,6 +46,9 @@ export class ApNoteService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.pollsRepository)
 		private pollsRepository: PollsRepository,
 
@@ -69,7 +71,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,
@@ -187,7 +188,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');
 		}
@@ -565,9 +566,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 10b1fc0bf4b9e70bb11072f45e4195f6533b4cc1..2046dad0995a76187a12f5ce22d6416b53415fcd 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');
@@ -319,8 +319,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}/);
@@ -395,6 +395,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,
@@ -433,10 +434,10 @@ export class ApPersonService implements OnModuleInit {
 		this.cacheService.uriPersonCache.set(user.uri, user);
 
 		// Register host
-		this.federatedInstanceService.fetch(host).then(async i => {
+		this.federatedInstanceService.fetch(host).then(i => {
 			this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
-			if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+			if (this.meta.enableChartsForFederatedInstances) {
 				this.instanceChart.newUser(i.host);
 			}
 		});
@@ -520,8 +521,8 @@ export class ApPersonService implements OnModuleInit {
 						return undefined;
 					}
 					return 'private';
-				})
-			)
+				}),
+			),
 		);
 
 		const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@@ -595,6 +596,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/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts
index 73004d10b0c5eedb59bb865cd6e4d72f012c53e3..9246398fdebf3aefa98af61506c1fa17d5dc9e91 100644
--- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts
+++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts
@@ -98,7 +98,7 @@ export class ApQuestionService {
 			const newCount = apChoices.filter(ap => ap.name === choice).at(0)?.replies?.totalItems;
 			if (newCount == null) throw new Error('invalid newCount: ' + newCount);
 
-			if (oldCount !== newCount) {
+			if (oldCount <= newCount) {
 				changed = true;
 				poll.votes[poll.choices.indexOf(choice)] = newCount;
 			}
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index 2f58825de1a36819a7de1c3e6e6b4acf40651e08..af5aba9c162817180c05af4a999c93747e73f8a2 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -3,6 +3,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { fromTuple } from '@/misc/from-tuple.js';
+
 export type Obj = { [x: string]: any };
 export type ApObject = IObject | string | (IObject | string)[];
 
@@ -13,6 +15,7 @@ export interface IObject {
 	name?: string | null;
 	summary?: string | null;
 	_misskey_summary?: string;
+	_misskey_followedMessage?: string | null;
 	published?: string;
 	cc?: ApObject;
 	to?: ApObject;
@@ -52,10 +55,13 @@ export function getOneApId(value: ApObject): string {
 /**
  * Get ActivityStreams Object id
  */
-export function getApId(value: string | IObject): string {
+export function getApId(value: string | IObject | [string | IObject]): string {
+	// eslint-disable-next-line no-param-reassign
+	value = fromTuple(value);
+
 	if (typeof value === 'string') return value;
 	if (typeof value.id === 'string') return value.id;
-	throw new Error('cannot detemine id');
+	throw new Error('cannot determine id');
 }
 
 /**
@@ -84,7 +90,9 @@ export function getApHrefNullable(value: string | IObject | undefined): string |
 export interface IActivity extends IObject {
 	//type: 'Activity';
 	actor: IObject | string;
-	object: IObject | string;
+	// ActivityPub spec allows for arrays: https://www.w3.org/TR/activitystreams-vocabulary/#properties
+	// Misskey can only handle one value, so we use a tuple for that case.
+	object: IObject | string | [IObject | string] ;
 	target?: IObject | string;
 	/** LD-Signature */
 	signature?: {
diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts
index c2329a2f7357be5b095a1d99d119db41f694016e..c9b43cc66d7c3e72ecc1f10342a0122910a4f74d 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 ANY(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 ANY(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 ANY(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 ANY(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 ANY(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/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts
index d2dbaf227030a108f879f99523fb3bfd0772840b..d54c954bf29ec8c46f98d38445d2d8d60291a332 100644
--- a/packages/backend/src/core/entities/FollowingEntityService.ts
+++ b/packages/backend/src/core/entities/FollowingEntityService.ts
@@ -8,11 +8,14 @@ import { DI } from '@/di-symbols.js';
 import type { FollowingsRepository } from '@/models/_.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import type { Packed } from '@/misc/json-schema.js';
-import type { } from '@/models/Blocking.js';
-import type { MiUser } from '@/models/User.js';
-import type { MiFollowing } from '@/models/Following.js';
+import { MiBlocking } from '@/models/Blocking.js';
+import { MiUserProfile } from '@/models/UserProfile.js';
+import type { MiLocalUser, MiUser } from '@/models/User.js';
+import { MiFollowing } from '@/models/Following.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
+import { QueryService } from '@/core/QueryService.js';
+import { RoleService } from '@/core/RoleService.js';
 import { UserEntityService } from './UserEntityService.js';
 
 type LocalFollowerFollowing = MiFollowing & {
@@ -47,6 +50,8 @@ export class FollowingEntityService {
 
 		private userEntityService: UserEntityService,
 		private idService: IdService,
+		private queryService: QueryService,
+		private roleService: RoleService,
 	) {
 	}
 
@@ -70,6 +75,53 @@ export class FollowingEntityService {
 		return following.followeeHost != null;
 	}
 
+	@bindThis
+	public async getFollowing(me: MiLocalUser, params: FollowsQueryParams) {
+		return await this.getFollows(me, params, 'following.followerHost = :host');
+	}
+
+	@bindThis
+	public async getFollowers(me: MiLocalUser, params: FollowsQueryParams) {
+		return await this.getFollows(me, params, 'following.followeeHost = :host');
+	}
+
+	private async getFollows(me: MiLocalUser, params: FollowsQueryParams, condition: string) {
+		const builder = this.followingsRepository.createQueryBuilder('following');
+		const query = this.queryService
+			.makePaginationQuery(builder, params.sinceId, params.untilId)
+			.andWhere(condition, { host: params.host })
+			.limit(params.limit);
+
+		if (!await this.roleService.isModerator(me)) {
+			query.setParameter('me', me.id);
+
+			// Make sure that the followee doesn't block us, if their profile will be included.
+			if (params.includeFollowee) {
+				query.leftJoin(MiBlocking, 'followee_blocking', 'followee_blocking."blockerId" = following."followeeId" AND followee_blocking."blockeeId" = :me');
+				query.andWhere('followee_blocking.id IS NULL');
+			}
+
+			// Make sure that the follower doesn't block us, if their profile will be included.
+			if (params.includeFollower) {
+				query.leftJoin(MiBlocking, 'follower_blocking', 'follower_blocking."blockerId" = following."followerId" AND follower_blocking."blockeeId" = :me');
+				query.andWhere('follower_blocking.id IS NULL');
+			}
+
+			// Make sure that the followee hasn't hidden this connection.
+			query.leftJoin(MiUserProfile, 'followee', 'followee."userId" = following."followeeId"');
+			query.leftJoin(MiFollowing, 'me_following_followee', 'me_following_followee."followerId" = :me AND me_following_followee."followeeId" = following."followerId"');
+			query.andWhere('(followee."userId" = :me OR followee."followersVisibility" = \'public\' OR (followee."followersVisibility" = \'followers\' AND me_following_followee.id IS NOT NULL))');
+
+			// Make sure that the follower hasn't hidden this connection.
+			query.leftJoin(MiUserProfile, 'follower', 'follower."userId" = following."followerId"');
+			query.leftJoin(MiFollowing, 'me_following_follower', 'me_following_follower."followerId" = :me AND me_following_follower."followeeId" = following."followerId"');
+			query.andWhere('(follower."userId" = :me OR follower."followingVisibility" = \'public\' OR (follower."followingVisibility" = \'followers\' AND me_following_follower.id IS NOT NULL))');
+		}
+
+		const followings = await query.getMany();
+		return await this.packMany(followings, me, { populateFollowee: params.includeFollowee, populateFollower: params.includeFollower });
+	}
+
 	@bindThis
 	public async pack(
 		src: MiFollowing['id'] | MiFollowing,
@@ -124,3 +176,12 @@ export class FollowingEntityService {
 	}
 }
 
+interface FollowsQueryParams {
+	readonly host: string;
+	readonly limit: number;
+	readonly includeFollower: boolean;
+	readonly includeFollowee: boolean;
+
+	readonly sinceId?: string;
+	readonly untilId?: string;
+}
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index 7695e6dfa73e1f4cd5286fc5ddda7d9d401220ed..63e5923255b6f64a3c5cb39850826c08fc99fa76 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,14 +51,15 @@ 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,
 			infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
 			latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
 			isNSFW: instance.isNSFW,
+			rejectReports: instance.rejectReports,
 			moderationNote: iAmModerator ? instance.moderationNote : null,
 		};
 	}
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
index 3128b762f4f56feeb579a1c8eedddf316efa46bf..b2b9aebb79154ff70ba36318448ec8c52873a708 100644
--- a/packages/backend/src/core/entities/MetaEntityService.ts
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -9,7 +9,6 @@ import JSON5 from 'json5';
 import type { Packed } from '@/misc/json-schema.js';
 import type { MiMeta } from '@/models/Meta.js';
 import type { AdsRepository } from '@/models/_.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';
@@ -23,11 +22,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,
 	) { }
 
@@ -36,7 +37,7 @@ export class MetaEntityService {
 		let instance = meta;
 
 		if (!instance) {
-			instance = await this.metaService.fetch();
+			instance = this.meta;
 		}
 
 		const ads = await this.adsRepository.createQueryBuilder('ads')
@@ -97,6 +98,8 @@ export class MetaEntityService {
 			recaptchaSiteKey: instance.recaptchaSiteKey,
 			enableTurnstile: instance.enableTurnstile,
 			turnstileSiteKey: instance.turnstileSiteKey,
+			enableFC: instance.enableFC,
+			fcSiteKey: instance.fcSiteKey,
 			swPublickey: instance.swPublicKey,
 			themeColor: instance.themeColor,
 			mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
@@ -105,9 +108,15 @@ export class MetaEntityService {
 			serverErrorImageUrl: instance.serverErrorImageUrl,
 			notFoundImageUrl: instance.notFoundImageUrl,
 			iconUrl: instance.iconUrl,
+			sidebarLogoUrl: instance.sidebarLogoUrl,
 			backgroundImageUrl: instance.backgroundImageUrl,
 			logoImageUrl: instance.logoImageUrl,
 			maxNoteTextLength: this.config.maxNoteLength,
+			maxRemoteNoteTextLength: this.config.maxRemoteNoteLength,
+			maxCwLength: this.config.maxCwLength,
+			maxRemoteCwLength: this.config.maxRemoteCwLength,
+			maxAltTextLength: this.config.maxAltTextLength,
+			maxRemoteAltTextLength: this.config.maxRemoteAltTextLength,
 			defaultLightTheme,
 			defaultDarkTheme,
 			defaultLike: instance.defaultLike,
@@ -119,6 +128,7 @@ export class MetaEntityService {
 				imageUrl: ad.imageUrl,
 				dayOfWeek: ad.dayOfWeek,
 			})),
+			trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns,
 			notesPerOneAd: instance.notesPerOneAd,
 			enableEmail: instance.enableEmail,
 			enableServiceWorker: instance.enableServiceWorker,
@@ -132,6 +142,7 @@ export class MetaEntityService {
 			mediaProxy: this.config.mediaProxy,
 			enableUrlPreview: instance.urlPreviewEnabled,
 			noteSearchableScope: (this.config.meilisearch == null || this.config.meilisearch.scope !== 'local') ? 'global' : 'local',
+			maxFileSize: this.config.maxFileSize,
 		};
 
 		return packed;
@@ -142,7 +153,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 493723ac453261bb0bb66e5254bcf113d0fdbda3..4dd17c5af3900c89298c268357a583e4351b2fa7 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -11,11 +11,11 @@ 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';
@@ -29,12 +29,16 @@ export class NoteEntityService implements OnModuleInit {
 	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,
 
@@ -63,6 +67,8 @@ export class NoteEntityService implements OnModuleInit {
 		//private driveFileEntityService: DriveFileEntityService,
 		//private customEmojiService: CustomEmojiService,
 		//private reactionService: ReactionService,
+		//private reactionsBufferingService: ReactionsBufferingService,
+		//private idService: IdService,
 	) {
 	}
 
@@ -71,6 +77,7 @@ 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');
 	}
 
@@ -119,7 +126,7 @@ export class NoteEntityService implements OnModuleInit {
 							followerId: meId,
 						},
 					});
-					
+
 					hide = !isFollowing;
 				} else {
 					// フォロワーかどうか
@@ -304,6 +311,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'>>
@@ -320,6 +328,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)) {
@@ -332,7 +349,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;
@@ -352,10 +369,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,
@@ -375,8 +392,12 @@ export class NoteEntityService implements OnModuleInit {
 			uri: note.uri ?? undefined,
 			url: note.url ?? undefined,
 			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_),
 			} : {}),
 
 			...(opts.detail ? {
@@ -416,6 +437,8 @@ export class NoteEntityService implements OnModuleInit {
 	) {
 		if (notes.length === 0) return [];
 
+		const bufferedReactions = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : null;
+
 		const meId = me ? me.id : null;
 		const myReactionsMap = new Map<MiNote['id'], string | null>();
 		if (meId) {
@@ -426,23 +449,33 @@ export class NoteEntityService implements OnModuleInit {
 
 			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);
+					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);
 						}
@@ -477,6 +510,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 e2de450756c5325213637ac931eacb5a48943a7c..bbaf0cb7c846da23049e1c9079a379d4b81bce41 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 2b1c4d5c63b71841cb07565075fdce72f2ef9b6e..b1832ca0f51ed5042e83d3acc01f40a36149acf0 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -53,6 +53,7 @@ import type { OnModuleInit } from '@nestjs/common';
 import type { NoteEntityService } from './NoteEntityService.js';
 import type { DriveFileEntityService } from './DriveFileEntityService.js';
 import type { PageEntityService } from './PageEntityService.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
 
 const Ajv = _Ajv.default;
 const ajv = new Ajv();
@@ -373,6 +374,13 @@ export class UserEntityService implements OnModuleInit {
 		return count > 0;
 	}
 
+	@bindThis
+	public async getHasPendingSentFollowRequest(userId: MiUser['id']): Promise<boolean> {
+		return this.followRequestsRepository.existsBy({
+			followerId: userId,
+		});
+	}
+
 	@bindThis
 	public getOnlineStatus(user: MiUser): 'unknown' | 'online' | 'active' | 'offline' {
 		if (user.hideOnlineStatus) return 'unknown';
@@ -525,6 +533,7 @@ export class UserEntityService implements OnModuleInit {
 				flipH: ud.flipH || undefined,
 				offsetX: ud.offsetX || undefined,
 				offsetY: ud.offsetY || undefined,
+				showBelow: ud.showBelow || undefined,
 				url: decorations.find(d => d.id === ud.id)!.url,
 			}))) : [],
 			isBot: user.isBot,
@@ -554,7 +563,7 @@ export class UserEntityService implements OnModuleInit {
 					name: r.name,
 					iconUrl: r.iconUrl,
 					displayOrder: r.displayOrder,
-				}))
+				})),
 			) : undefined,
 
 			...(isDetailed ? {
@@ -611,11 +620,14 @@ export class UserEntityService implements OnModuleInit {
 				avatarId: user.avatarId,
 				bannerId: user.bannerId,
 				backgroundId: user.backgroundId,
+				followedMessage: profile!.followedMessage,
 				isModerator: isModerator,
 				isAdmin: isAdmin,
+				isSystem: isSystemAccount(user),
 				injectFeaturedNote: profile!.injectFeaturedNote,
 				receiveAnnouncementEmail: profile!.receiveAnnouncementEmail,
 				alwaysMarkNsfw: profile!.alwaysMarkNsfw,
+				defaultSensitive: profile!.defaultSensitive,
 				autoSensitive: profile!.autoSensitive,
 				carefulBot: profile!.carefulBot,
 				autoAcceptFollowed: profile!.autoAcceptFollowed,
@@ -639,6 +651,7 @@ export class UserEntityService implements OnModuleInit {
 				hasUnreadChannel: false, // 後方互換性のため
 				hasUnreadNotification: notificationsInfo?.hasUnread, // 後方互換性のため
 				hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
+				hasPendingSentFollowRequest: this.getHasPendingSentFollowRequest(user.id),
 				unreadNotificationsCount: notificationsInfo?.unreadCount,
 				mutedWords: profile!.mutedWords,
 				hardMutedWords: profile!.hardMutedWords,
@@ -680,6 +693,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 0be2149a0a18a33b43e7a1a6834dcd837160f499..6e9d29dcbd26e8e7abbc02394cb511e5cb8cdf99 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 21777657d185ad450048f654b5df5d11459814b0..42f925e1251cf8d5eb48caeb96d5a72fb203c017 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 d4a21ab625a7f7dff67762f76d21e5b90db15fe5..5ea500ac775d499bb04d85286196040c20506252 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'),
@@ -19,6 +21,7 @@ export const DI = {
 	announcementReadsRepository: Symbol('announcementReadsRepository'),
 	appsRepository: Symbol('appsRepository'),
 	avatarDecorationsRepository: Symbol('avatarDecorationsRepository'),
+	latestNotesRepository: Symbol('latestNotesRepository'),
 	noteFavoritesRepository: Symbol('noteFavoritesRepository'),
 	noteThreadMutingsRepository: Symbol('noteThreadMutingsRepository'),
 	noteReactionsRepository: Symbol('noteReactionsRepository'),
diff --git a/packages/backend/src/misc/collapsed-queue.ts b/packages/backend/src/misc/collapsed-queue.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5bc20a78ae29a0ddeb22e949599b8ff66250fb7c
--- /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 3e1c099e000c3c33a1aeb9221c7bd7b1386251bd..fa3ef0a267d6f36025fab2801f353e8ee04c36df 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/from-tuple.ts b/packages/backend/src/misc/from-tuple.ts
new file mode 100644
index 0000000000000000000000000000000000000000..366b1e310fa45f1288a5a4cce5979940af456a1e
--- /dev/null
+++ b/packages/backend/src/misc/from-tuple.ts
@@ -0,0 +1,7 @@
+export function fromTuple<T>(value: T | [T]): T {
+	if (Array.isArray(value)) {
+		return value[0];
+	}
+
+	return value;
+}
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 1a07139a50e5a8d210450d6956f77c67657ac753..60dddee9a2887a3e3cb975d5c331457e03f4fd9f 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -22,14 +22,14 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
 
 	// 本文
 	if (note.cw != null) {
-		summary += note.cw;
-	} else {
-		summary += note.text ? note.text : '';
+		summary += `CW: ${note.cw}`;
+	} else if (note.text) {
+		summary += note.text;
 	}
 
 	// ファイルが添付されているとき
-	if ((note.files ?? []).length !== 0) {
-		summary += ` (📎${note.files!.length})`;
+	if (note.files && note.files.length !== 0) {
+		summary += ` (📎${note.files.length})`;
 	}
 
 	// 投票が添付されているとき
@@ -39,7 +39,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
 
 	// 返信のとき
 	if (note.replyId) {
-		if (note.reply) {
+		if (note.reply && !note.cw) {
 			summary += `\n\nRE: ${getNoteSummary(note.reply)}`;
 		} else {
 			summary += '\n\nRE: ...';
@@ -48,7 +48,7 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
 
 	// Renoteのとき
 	if (note.renoteId) {
-		if (note.renote) {
+		if (note.renote && !note.cw) {
 			summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
 		} else {
 			summary += '\n\nRN: ...';
diff --git a/packages/backend/src/misc/is-renote.ts b/packages/backend/src/misc/is-renote.ts
index 48f821806c1a55cf4870fbfc2802cfa3d90b1841..c128fded143efeeb703c0c6d75641c68087ec141 100644
--- a/packages/backend/src/misc/is-renote.ts
+++ b/packages/backend/src/misc/is-renote.ts
@@ -23,6 +23,17 @@ type Quote =
 		hasPoll: true
 	});
 
+type PureRenote =
+	Renote & {
+		text: null,
+		cw: null,
+		replyId: null,
+		hasPoll: false,
+		fileIds: {
+			length: 0,
+		},
+	};
+
 export function isRenote(note: MiNote): note is Renote {
 	return note.renoteId != null;
 }
@@ -36,6 +47,10 @@ export function isQuote(note: Renote): note is Quote {
 		note.fileIds.length > 0;
 }
 
+export function isPureRenote(note: MiNote): note is PureRenote {
+	return isRenote(note) && !isQuote(note);
+}
+
 type PackedRenote =
 	Packed<'Note'> & {
 		renoteId: NonNullable<Packed<'Note'>['renoteId']>
diff --git a/packages/backend/src/misc/is-system-account.ts b/packages/backend/src/misc/is-system-account.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a34882f4944afaf8101af8f1575101b878c6f959
--- /dev/null
+++ b/packages/backend/src/misc/is-system-account.ts
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+interface UserLike {
+	readonly username: string;
+	readonly host: string | null;
+}
+
+/**
+ * Checks if the given user represents a system account, such as instance.actor.
+ */
+export function isSystemAccount(user: UserLike): boolean {
+	return user.host == null && user.username.includes('.');
+}
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index a721b8663c3c32328047f3d7150b8eab8b0d2ae0..040e36228ccfca52a3c940155f07811b0b4c7434 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -144,7 +144,9 @@ export interface Schema extends OfSchema {
 	readonly type?: TypeStringef;
 	readonly nullable?: boolean;
 	readonly optional?: boolean;
+	readonly prefixItems?: ReadonlyArray<Schema>;
 	readonly items?: Schema;
+	readonly unevaluatedItems?: Schema | boolean;
 	readonly properties?: Obj;
 	readonly required?: ReadonlyArray<Extract<keyof NonNullable<this['properties']>, string>>;
 	readonly description?: string;
@@ -198,6 +200,7 @@ type UnionSchemaType<a extends readonly any[], X extends Schema = a[number]> = X
 //type UnionObjectSchemaType<a extends readonly any[], X extends Schema = a[number]> = X extends any ? ObjectSchemaType<X> : never;
 type UnionObjType<s extends Obj, a extends readonly any[], X extends ReadonlyArray<keyof s> = a[number]> = X extends any ? ObjType<s, X> : never;
 type ArrayUnion<T> = T extends any ? Array<T> : never;
+type ArrayToTuple<X extends ReadonlyArray<Schema>> = { [K in keyof X]: SchemaType<X[K]> };
 
 type ObjectSchemaTypeDef<p extends Schema> =
 	p['ref'] extends keyof typeof refs ? Packed<p['ref']> :
@@ -232,6 +235,12 @@ export type SchemaTypeDef<p extends Schema> =
 			p['items']['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<NonNullable<p['items']['allOf']>>>[] :
 			never
 		) :
+		p['prefixItems'] extends ReadonlyArray<Schema> ? (
+			p['items'] extends NonNullable<Schema> ? [...ArrayToTuple<p['prefixItems']>, ...SchemaType<p['items']>[]] :
+			p['items'] extends false ? ArrayToTuple<p['prefixItems']> :
+			p['unevaluatedItems'] extends false ? ArrayToTuple<p['prefixItems']> :
+			[...ArrayToTuple<p['prefixItems']>, ...unknown[]]
+		) :
 		p['items'] extends NonNullable<Schema> ? SchemaType<p['items']>[] :
 		any[]
 	) :
diff --git a/packages/backend/src/models/DriveFile.ts b/packages/backend/src/models/DriveFile.ts
index dd810681c5334bd5800d42aeb679fd6d058e43c9..12d7b31e005c8f70cfc48886ee8762ba75eb39fc 100644
--- a/packages/backend/src/models/DriveFile.ts
+++ b/packages/backend/src/models/DriveFile.ts
@@ -60,8 +60,7 @@ export class MiDriveFile {
 	})
 	public size: number;
 
-	@Column('varchar', {
-		length: 8192,
+	@Column('text', {
 		nullable: true,
 		comment: 'The comment of the DriveFile.',
 	})
diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts
index dd625f95d31d02c749245a1a621eb47b6db86516..ba93190c570ce4751aa30b2c629aee48e5667bbf 100644
--- a/packages/backend/src/models/Instance.ts
+++ b/packages/backend/src/models/Instance.ts
@@ -158,7 +158,12 @@ export class MiInstance {
 		default: false,
 	})
 	public isNSFW: boolean;
-	
+
+	@Column('boolean', {
+		default: false,
+	})
+	public rejectReports: boolean;
+
 	@Column('varchar', {
 		length: 16384, default: '',
 	})
diff --git a/packages/backend/src/models/LatestNote.ts b/packages/backend/src/models/LatestNote.ts
new file mode 100644
index 0000000000000000000000000000000000000000..064fcccc0a469ae3d0ac7099b1a0cb5e2626ef2f
--- /dev/null
+++ b/packages/backend/src/models/LatestNote.ts
@@ -0,0 +1,100 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { MiUser } from '@/models/User.js';
+import { MiNote } from '@/models/Note.js';
+import { isQuote, isRenote } from '@/misc/is-renote.js';
+
+/**
+ * Maps a user to the most recent post by that user.
+ * Public, home-only, and followers-only posts are included.
+ * DMs are not counted.
+ */
+@Entity('latest_note')
+export class SkLatestNote {
+	@PrimaryColumn({
+		name: 'user_id',
+		type: 'varchar' as const,
+		length: 32,
+	})
+	public userId: string;
+
+	@PrimaryColumn('boolean', {
+		name: 'is_public',
+		default: false,
+	})
+	public isPublic: boolean;
+
+	@PrimaryColumn('boolean', {
+		name: 'is_reply',
+		default: false,
+	})
+	public isReply: boolean;
+
+	@PrimaryColumn('boolean', {
+		name: 'is_quote',
+		default: false,
+	})
+	public isQuote: boolean;
+
+	@ManyToOne(() => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn({
+		name: 'user_id',
+	})
+	public user: MiUser | null;
+
+	@Column({
+		name: 'note_id',
+		type: 'varchar' as const,
+		length: 32,
+	})
+	public noteId: string;
+
+	@ManyToOne(() => MiNote, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn({
+		name: 'note_id',
+	})
+	public note: MiNote | null;
+
+	constructor(data?: Partial<SkLatestNote>) {
+		if (!data) return;
+
+		for (const [k, v] of Object.entries(data)) {
+			(this as Record<string, unknown>)[k] = v;
+		}
+	}
+
+	/**
+	 * Generates a compound key matching a provided note.
+	 */
+	static keyFor(note: MiNote) {
+		return {
+			userId: note.userId,
+			isPublic: note.visibility === 'public',
+			isReply: note.replyId != null,
+			isQuote: isRenote(note) && isQuote(note),
+		};
+	}
+
+	/**
+	 * Checks if two notes would produce equivalent compound keys.
+	 */
+	static areEquivalent(first: MiNote, second: MiNote): boolean {
+		const firstKey = SkLatestNote.keyFor(first);
+		const secondKey = SkLatestNote.keyFor(second);
+
+		return (
+			firstKey.userId === secondKey.userId &&
+			firstKey.isPublic === secondKey.isPublic &&
+			firstKey.isReply === secondKey.isReply &&
+			firstKey.isQuote === secondKey.isQuote
+		);
+	}
+}
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 07c4e28b3a6d30d1346993a371de41aadc9aee24..0ea6765d6ac78950a721b7afe033f3efc33afa68 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -139,6 +139,12 @@ export class MiMeta {
 	})
 	public app512IconUrl: string | null;
 
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public sidebarLogoUrl: string | null;
+
 	@Column('varchar', {
 		length: 1024,
 		nullable: true,
@@ -263,6 +269,23 @@ export class MiMeta {
 	})
 	public turnstileSecretKey: string | null;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableFC: boolean;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public fcSiteKey: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public fcSecretKey: string | null;
+
 	// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること
 
 	@Column('enum', {
@@ -621,6 +644,11 @@ export class MiMeta {
 	})
 	public perUserListTimelineCacheMax: number;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableReactionsBuffering: boolean;
+
 	@Column('integer', {
 		default: 0,
 	})
@@ -668,4 +696,25 @@ export class MiMeta {
 		nullable: true,
 	})
 	public urlPreviewUserAgent: string | null;
+
+	@Column('varchar', {
+		length: 3072,
+		array: true,
+		default: '{}',
+		comment: 'An array of URL strings or regex that can be used to omit warnings about redirects to external sites. Separate them with spaces to specify AND, and enclose them with slashes to specify regular expressions. Each item is regarded as an OR.',
+	})
+	public trustedLinkUrlPatterns: string[];
+
+	@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/Note.ts b/packages/backend/src/models/Note.ts
index b11e2ec62b76817cbea0a7e0d90ca006642240f9..408e023ff70375f74794615274c9456dc700be7d 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -66,8 +66,8 @@ export class MiNote {
 	})
 	public name: string | null;
 
-	@Column('varchar', {
-		length: 512, nullable: true,
+	@Column('text', {
+		nullable: true,
 	})
 	public cw: string | null;
 
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 4ed71a106cb1b2d6eaf7ee776fb837bc801c0139..c4f046c5657203812d5ffc40142be369f282196b 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -7,6 +7,8 @@ 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';
+import { userExportableEntities } from '@/types.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,12 @@ export type MiNotification = {
 	id: string;
 	createdAt: string;
 	achievement: string;
+} | {
+	type: 'exportCompleted';
+	id: string;
+	createdAt: string;
+	exportedEntity: typeof userExportableEntities[number];
+	fileId: MiDriveFile['id'];
 } | {
 	type: 'app';
 	id: string;
@@ -85,7 +94,7 @@ export type MiNotification = {
 	/**
 	 * アプリ通知のbody
 	 */
-	customBody: string | null;
+	customBody: string;
 
 	/**
 	 * アプリ通知のheader
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 1eaeb86df686bd8b9cba4a902db6c9c436ada4f2..eb45b9a6311e9c1565b724143c79a30384b92df7 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -7,6 +7,7 @@ import type { Provider } from '@nestjs/common';
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
 import {
+	SkLatestNote,
 	MiAbuseReportNotificationRecipient,
 	MiAbuseUserReport,
 	MiAccessToken,
@@ -118,6 +119,12 @@ const $avatarDecorationsRepository: Provider = {
 	inject: [DI.db],
 };
 
+const $latestNotesRepository: Provider = {
+	provide: DI.latestNotesRepository,
+	useFactory: (db: DataSource) => db.getRepository(SkLatestNote).extend(miRepository as MiRepository<SkLatestNote>),
+	inject: [DI.db],
+};
+
 const $noteFavoritesRepository: Provider = {
 	provide: DI.noteFavoritesRepository,
 	useFactory: (db: DataSource) => db.getRepository(MiNoteFavorite).extend(miRepository as MiRepository<MiNoteFavorite>),
@@ -511,6 +518,7 @@ const $reversiGamesRepository: Provider = {
 		$announcementReadsRepository,
 		$appsRepository,
 		$avatarDecorationsRepository,
+		$latestNotesRepository,
 		$noteFavoritesRepository,
 		$noteThreadMutingsRepository,
 		$noteReactionsRepository,
@@ -583,6 +591,7 @@ const $reversiGamesRepository: Provider = {
 		$announcementReadsRepository,
 		$appsRepository,
 		$avatarDecorationsRepository,
+		$latestNotesRepository,
 		$noteFavoritesRepository,
 		$noteThreadMutingsRepository,
 		$noteReactionsRepository,
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index b0910133c9bbe6e1c35904dc67c9de638bc31eba..c7ecccf1cf62d876b1b73476f29341aca72bfc1d 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -170,6 +170,7 @@ export class MiUser {
 		flipH?: boolean;
 		offsetX?: number;
 		offsetY?: number;
+		showBelow?: boolean;
 	}[];
 
 	@Index()
@@ -178,6 +179,11 @@ export class MiUser {
 	})
 	public tags: string[];
 
+	@Column('integer', {
+		default: 0,
+	})
+	public score: number;
+
 	@Column('boolean', {
 		default: false,
 		comment: 'Whether the User is suspended.',
@@ -340,6 +346,7 @@ 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 listenbrainzSchema = { type: "string", minLength: 1, maxLength: 128 } 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 40ea26f6107d90021e186ac95e8746039dfe86a1..751b1aff08a33297744d2533fcfee6db34130f33 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -49,6 +49,14 @@ export class MiUserProfile {
 	})
 	public description: string | null;
 
+	// フォローされた際のメッセージ
+	@Column('varchar', {
+		length: 256, nullable: true,
+	})
+	public followedMessage: string | null;
+
+	// TODO: 鍵アカウントの場合の、フォローリクエスト受信時のメッセージも設定できるようにする
+
 	@Column('jsonb', {
 		default: [],
 	})
@@ -188,6 +196,11 @@ export class MiUserProfile {
 	})
 	public alwaysMarkNsfw: boolean;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public defaultSensitive: boolean;
+
 	@Column('boolean', {
 		default: false,
 	})
diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts
index 2a727f86fd93e1a764382da41332177de6f2d895..8ef73fa143da5b67aeca2e5d1c3906d60285f44b 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', 'edited'] as const;
+export type WebhookEventTypes = typeof webhookEventTypes[number];
 
 @Entity('webhook')
 export class MiWebhook {
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index f7646dce2ae0b928117e1d1c773863f2545573d4..ac2dd62aa29ae233bb44b18cf1e4b058f5a2a064 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -10,6 +10,7 @@ import { RelationIdLoader } from 'typeorm/query-builder/relation-id/RelationIdLo
 import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer.js';
 import { ObjectUtils } from 'typeorm/util/ObjectUtils.js';
 import { OrmUtils } from 'typeorm/util/OrmUtils.js';
+import { SkLatestNote } from '@/models/LatestNote.js';
 import { MiAbuseUserReport } from '@/models/AbuseUserReport.js';
 import { MiAbuseReportNotificationRecipient } from '@/models/AbuseReportNotificationRecipient.js';
 import { MiAccessToken } from '@/models/AccessToken.js';
@@ -126,6 +127,7 @@ export const miRepository = {
 } satisfies MiRepository<ObjectLiteral>;
 
 export {
+	SkLatestNote,
 	MiAbuseUserReport,
 	MiAbuseReportNotificationRecipient,
 	MiAccessToken,
@@ -224,6 +226,7 @@ export type GalleryPostsRepository = Repository<MiGalleryPost> & MiRepository<Mi
 export type HashtagsRepository = Repository<MiHashtag> & MiRepository<MiHashtag>;
 export type InstancesRepository = Repository<MiInstance> & MiRepository<MiInstance>;
 export type MetasRepository = Repository<MiMeta> & MiRepository<MiMeta>;
+export type LatestNotesRepository = Repository<SkLatestNote> & MiRepository<SkLatestNote>;
 export type ModerationLogsRepository = Repository<MiModerationLog> & MiRepository<MiModerationLog>;
 export type MutingsRepository = Repository<MiMuting> & MiRepository<MiMuting>;
 export type RenoteMutingsRepository = Repository<MiRenoteMuting> & MiRepository<MiRenoteMuting>;
diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts
index 062dba9bad6449427749e733fdaf31f043a36e1f..7960e748e9a5e63b4669c78552c130544ed40b93 100644
--- a/packages/backend/src/models/json-schema/federation-instance.ts
+++ b/packages/backend/src/models/json-schema/federation-instance.ts
@@ -121,6 +121,11 @@ export const packedFederationInstanceSchema = {
 			optional: false,
 			nullable: false,
 		},
+		rejectReports: {
+			type: 'boolean',
+			optional: false,
+			nullable: false,
+		},
 		moderationNote: {
 			type: 'string',
 			optional: true, nullable: true,
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index 1d620f16fdcfd37f9fe3fd413a7c553df7280eeb..decdbd56506c04512f16ef204d3e7499a2c4a96b 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -127,6 +127,14 @@ export const packedMetaLiteSchema = {
 			type: 'string',
 			optional: false, nullable: true,
 		},
+		enableFC: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		fcSiteKey: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
 		enableAchievements: {
 			type: 'boolean',
 			optional: false, nullable: true,
@@ -160,10 +168,34 @@ export const packedMetaLiteSchema = {
 			type: 'string',
 			optional: false, nullable: true,
 		},
+		sidebarLogoUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
 		maxNoteTextLength: {
 			type: 'number',
 			optional: false, nullable: false,
 		},
+		maxRemoteNoteTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxCwLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxRemoteCwLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxAltTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		maxRemoteAltTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
 		ads: {
 			type: 'array',
 			optional: false, nullable: false,
@@ -269,6 +301,18 @@ export const packedMetaLiteSchema = {
 			optional: false, nullable: false,
 			default: 'local',
 		},
+		trustedLinkUrlPatterns: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+		maxFileSize: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
 	},
 } as const;
 
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index 3f31cc47ee13120ad734ab1108e9b73f1b59bd28..990e8957cfb32360f76e226837d774de1a7b8d6a 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -3,7 +3,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { notificationTypes } from '@/types.js';
+import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
+import { notificationTypes, userExportableEntities } from '@/types.js';
 
 const baseSchema = {
 	type: 'object',
@@ -266,6 +267,10 @@ export const packedNotificationSchema = {
 				optional: false, nullable: false,
 				format: 'id',
 			},
+			message: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 		},
 	}, {
 		type: 'object',
@@ -294,6 +299,27 @@ export const packedNotificationSchema = {
 			achievement: {
 				type: 'string',
 				optional: false, nullable: false,
+				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',
 			},
 		},
 	}, {
@@ -311,11 +337,11 @@ export const packedNotificationSchema = {
 			},
 			header: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 			},
 			icon: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 			},
 		},
 	}, {
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index 504b9b122fb0dbdb8d995a8ff630b3923b6640f6..19ea6263c9c63c5f8d35558161a8d82fe62f8e56 100644
--- a/packages/backend/src/models/json-schema/role.ts
+++ b/packages/backend/src/models/json-schema/role.ts
@@ -276,6 +276,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 33a3efd453016a0488dbcfdd0643304b30932db4..d5e847cc40493d0e985629fb407cd657bf82b488 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -104,6 +104,10 @@ export const packedUserLiteSchema = {
 						type: 'number',
 						nullable: false, optional: true,
 					},
+					showBelow: {
+						type: 'boolean',
+						nullable: false, optional: true,
+					},
 				},
 			},
 		},
@@ -117,9 +121,10 @@ export const packedUserLiteSchema = {
 			nullable: false, optional: true,
 			default: false,
 		},
-		isSilenced: {
+		isSystem: {
 			type: 'boolean',
-			nullable: false, optional: false,
+			nullable: false, optional: true,
+			default: false,
 		},
 		noindex: {
 			type: 'boolean',
@@ -137,6 +142,10 @@ export const packedUserLiteSchema = {
 			type: 'boolean',
 			nullable: false, optional: true,
 		},
+		isSilenced: {
+			type: 'boolean',
+			nullable: false, optional: false,
+		},
 		instance: {
 			type: 'object',
 			nullable: false, optional: true,
@@ -268,6 +277,10 @@ export const packedUserDetailedNotMeOnlySchema = {
 			type: 'boolean',
 			nullable: false, optional: false,
 		},
+		isSilenced: {
+			type: 'boolean',
+			nullable: false, optional: false,
+		},
 		isSuspended: {
 			type: 'boolean',
 			nullable: false, optional: false,
@@ -287,7 +300,7 @@ export const packedUserDetailedNotMeOnlySchema = {
 			nullable: true, optional: false,
 			example: '2018-03-12',
 		},
-		ListenBrainz: {
+		listenbrainz: {
 			type: 'string',
 			nullable: true,
 			optional: false,
@@ -403,6 +416,10 @@ export const packedUserDetailedNotMeOnlySchema = {
 				ref: 'RoleLite',
 			},
 		},
+		followedMessage: {
+			type: 'string',
+			nullable: true, optional: true,
+		},
 		memo: {
 			type: 'string',
 			nullable: true, optional: false,
@@ -475,6 +492,10 @@ export const packedMeDetailedOnlySchema = {
 			nullable: true, optional: false,
 			format: 'id',
 		},
+		followedMessage: {
+			type: 'string',
+			nullable: true, optional: false,
+		},
 		isModerator: {
 			type: 'boolean',
 			nullable: true, optional: false,
@@ -495,6 +516,10 @@ export const packedMeDetailedOnlySchema = {
 			type: 'boolean',
 			nullable: false, optional: false,
 		},
+		defaultSensitive: {
+			type: 'boolean',
+			nullable: false, optional: false,
+		},
 		autoSensitive: {
 			type: 'boolean',
 			nullable: false, optional: false,
@@ -569,6 +594,10 @@ export const packedMeDetailedOnlySchema = {
 			type: 'boolean',
 			nullable: false, optional: false,
 		},
+		hasPendingSentFollowRequest: {
+			type: 'boolean',
+			nullable: false, optional: false,
+		},
 		unreadNotificationsCount: {
 			type: 'number',
 			nullable: false, optional: false,
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 047b7f8ae9ff43b8a96b1db83dd97f30767958a7..2d66e6e4454cdef2debc54d9138dbb9260893b26 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -83,6 +83,7 @@ import { MiReversiGame } from '@/models/ReversiGame.js';
 import { Config } from '@/config.js';
 import MisskeyLogger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
+import { SkLatestNote } from '@/models/LatestNote.js';
 
 pg.types.setTypeParser(20, Number);
 
@@ -130,6 +131,7 @@ class MyCustomLogger implements Logger {
 }
 
 export const entities = [
+	SkLatestNote,
 	MiAnnouncement,
 	MiAnnouncementRead,
 	MiMeta,
diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts
index 7daca687a1a903a9af3697c8979d3ccad4e6a935..7c6675b15d8a9e781848b4ca43ec4ead87cf4625 100644
--- a/packages/backend/src/queue/QueueProcessorModule.ts
+++ b/packages/backend/src/queue/QueueProcessorModule.ts
@@ -14,6 +14,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';
@@ -53,6 +54,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
 		ResyncChartsProcessorService,
 		CleanChartsProcessorService,
 		CheckExpiredMutingsProcessorService,
+		BakeBufferedReactionsProcessorService,
 		CleanProcessorService,
 		DeleteDriveFilesProcessorService,
 		ExportAccountDataProcessorService,
diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts
index 7a6169bf9cfaffe27efd1d2ec0d0b3a09ea7d469..eaeb6d58dfe9777a37931b0c0a9e1849a2b8d83a 100644
--- a/packages/backend/src/queue/QueueProcessorService.ts
+++ b/packages/backend/src/queue/QueueProcessorService.ts
@@ -40,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';
@@ -122,24 +123,35 @@ export class QueueProcessorService implements OnApplicationShutdown {
 		private cleanChartsProcessorService: CleanChartsProcessorService,
 		private aggregateRetentionProcessorService: AggregateRetentionProcessorService,
 		private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService,
+		private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService,
 		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
@@ -151,6 +163,7 @@ 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 'clean': return this.cleanProcessorService.process();
 					default: throw new Error(`unrecognized job type ${job.name} for system`);
 				}
@@ -173,15 +186,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
@@ -238,15 +251,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
@@ -278,15 +291,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
@@ -318,15 +331,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
@@ -358,15 +371,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
@@ -398,15 +411,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
@@ -445,15 +458,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
@@ -486,15 +499,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 0000000000000000000000000000000000000000..d49c99f694af950f10ee9d9f3d94062f690f7bdd
--- /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/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 4076e9da9038f8ca7f5d5c66b882745bc6127dfd..9590a4fe71c5ad5c2865a32a5a88fa23938f5126 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)';
 		}
 
@@ -88,7 +87,7 @@ export class DeliverProcessorService {
 				this.apRequestChart.deliverSucc();
 				this.federationChart.deliverd(i.host, true);
 
-				if (meta.enableChartsForFederatedInstances) {
+				if (this.meta.enableChartsForFederatedInstances) {
 					this.instanceChart.requestSent(i.host, true);
 				}
 			});
@@ -120,7 +119,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);
 				}
 			});
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index 88c4ea29c0e630fab61b78a4912713ca068b3a7d..b3111865adc6e238cb8191a46967cc10a3c615fd 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 6ec3c18786fee8e5d6d51947e7932b62f7d76f80..ecc439db69ce5040906180178c6ce785030467bf 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 01eab26e96abc55ee65242262750efacc275a6ce..583ddbb745a47e9654da5d13e3a678f07d5b39cf 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 45087927a55629e09fc920fb0864dda8ae174c44..14d32e78b3fca95e854b3fc0b286aa21128b0a35 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 7bb626dd31bf43cce4cce1867bdf4331bd4f263a..b81feece018e31acda879f24596cc6a93abede4e 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 1cc80e66d7189f552431ffd30dabb958bc1c0185..903f96251587f6943746f1ea5033c8e043ac100c 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 243b74f2c2be99c63736800f058df92ff6f4f158..f9867ade292a30d4d0e5f81809992415df9ec163 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 c7611012d74b8aacc838bd5a0004d769a1a04b9f..9e2b678219cfd4ceb16976fcf78d978fe13adcc6 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 ee87cff5d360e37aa945dd113e04761a17c33417..c483d79854d0f028cb26833172b25b560fc41a33 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 04ad74ee01a68594b150cd15c649ca0da6f0578a..17ba71df3dad690b434df53e992403a68eafb5a2 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -88,23 +88,30 @@ export class ImportCustomEmojisProcessorService {
 				await this.emojisRepository.delete({
 					name: nameNfc,
 				});
-				const driveFile = await this.driveService.addFile({
-					user: null,
-					path: emojiPath,
-					name: record.fileName,
-					force: true,
-				});
-				await this.customEmojiService.add({
-					name: nameNfc,
-					category: emojiInfo.category?.normalize('NFC'),
-					host: null,
-					aliases: emojiInfo.aliases?.map((a: string) => a.normalize('NFC')),
-					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: nameNfc,
+						category: emojiInfo.category?.normalize('NFC'),
+						host: null,
+						aliases: emojiInfo.aliases?.map((a: string) => a.normalize('NFC')),
+						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 641b8b860717b7c736f54a01365e97513e040f17..11b00bb6832472a20d759b09b4f651b3af39917e 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(60 * 1000 * 5, 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}`;
 		}
 
@@ -108,19 +118,16 @@ export class InboxProcessorService {
 		// HTTP-Signatureの検証
 		let httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
 
-		// また、signatureのsignerは、activity.actorと一致する必要がある
-		if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
-			let renewKeyFailed = true;
-
-			if (!httpSignatureValidated) {
-				authUser.key = await this.apDbResolverService.refetchPublicKeyForApId(authUser.user);
-
-				if (authUser.key != null) {
-					httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
-					renewKeyFailed = false;
-				}
+		// maybe they changed their key? refetch it
+		if (!httpSignatureValidated) {
+			authUser.key = await this.apDbResolverService.refetchPublicKeyForApId(authUser.user);
+			if (authUser.key != null) {
+				httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
 			}
+		}
 
+		// また、signatureのsignerは、activity.actorと一致する必要がある
+		if (!httpSignatureValidated || authUser.user.uri !== getApId(activity.actor)) {
 			// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
 			const ldSignature = activity.signature;
 			if (ldSignature) {
@@ -169,9 +176,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 {
@@ -190,11 +196,9 @@ export class InboxProcessorService {
 
 		// Update stats
 		this.federatedInstanceService.fetch(authUser.user.host).then(i => {
-			this.federatedInstanceService.update(i.id, {
+			this.updateInstanceQueue.enqueue(i.id, {
 				latestRequestReceivedAt: new Date(),
-				isNotResponding: false,
-				// もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる
-				suspensionState: i.suspensionState === 'autoSuspendedForNotResponding' ? 'none' : undefined,
+				shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding',
 			});
 
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
@@ -202,7 +206,7 @@ export class InboxProcessorService {
 			this.apRequestChart.inbox();
 			this.federationChart.inbox(i.host);
 
-			if (meta.enableChartsForFederatedInstances) {
+			if (this.meta.enableChartsForFederatedInstances) {
 				this.instanceChart.requestReceived(i.host);
 			}
 		});
@@ -230,4 +234,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 398ad54f6af6bc0821af3a1f400ad95834df0d42..52592c47c64912f5bc5afd54ef15f48c7699e6e8 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -21,7 +21,6 @@ import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
 import { QueueService } from '@/core/QueueService.js';
 import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
-import { MetaService } from '@/core/MetaService.js';
 import { UserKeypairService } from '@/core/UserKeypairService.js';
 import { InstanceActorService } from '@/core/InstanceActorService.js';
 import type { MiUserPublickey } from '@/models/UserPublickey.js';
@@ -75,7 +74,6 @@ export class ActivityPubServerService {
 		@Inject(DI.followRequestsRepository)
 		private followRequestsRepository: FollowRequestsRepository,
 
-		private metaService: MetaService,
 		private utilityService: UtilityService,
 		private userEntityService: UserEntityService,
 		private instanceActorService: InstanceActorService,
@@ -175,8 +173,7 @@ export class ActivityPubServerService {
 			return true;
 		}
 
-		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, keyHost)) {
+		if (!this.utilityService.isFederationAllowedHost(keyHost)) {
 			/* blocked instance: refuse (we don't care if the signature is
 				 good, if they even pretend to be from a blocked instance,
 				 they're out) */
@@ -208,15 +205,11 @@ export class ActivityPubServerService {
 
 		let httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
 
+		// maybe they changed their key? refetch it
 		if (!httpSignatureValidated) {
-			this.authlogger.info(`${logPrefix} failed to validate signature, re-fetching the key for ${authUser.user.uri}`);
-			// maybe they changed their key? refetch it
 			authUser.key = await this.apDbResolverService.refetchPublicKeyForApId(authUser.user);
-
 			if (authUser.key != null) {
 				httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
-			} else {
-				this.authlogger.warn(`${logPrefix} failed to re-fetch key for ${authUser.user}`);
 			}
 		}
 
@@ -795,7 +788,7 @@ export class ActivityPubServerService {
 
 		fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return;
-			
+
 			vary(reply.raw, 'Accept');
 
 			const userId = request.params.user;
@@ -811,7 +804,7 @@ export class ActivityPubServerService {
 
 		fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return;
-			
+
 			vary(reply.raw, 'Accept');
 
 			const user = await this.usersRepository.findOneBy({
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index 65a82181740999d72da472184857035a2a757aaf..1a4d0cb48f29136577cef3a9d0bc890d90ca0138 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -80,7 +80,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();
 		});
@@ -145,12 +145,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);
@@ -165,7 +165,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);
 					}
 				}
 
@@ -312,11 +312,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 2c3ed85925c93781df739b033cdeebfd816b604d..5980609f02bd94e19178f5d4d17d679847845625 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/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index bc8d3c0411d00418958ebb96c776daa286db6706..6dee6ecd780c2abac0432f7485e224bcb6b3b53f 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -121,7 +121,13 @@ export class NodeinfoServerService {
 					enableRecaptcha: meta.enableRecaptcha,
 					enableMcaptcha: meta.enableMcaptcha,
 					enableTurnstile: meta.enableTurnstile,
+					enableFC: meta.enableFC,
 					maxNoteTextLength: this.config.maxNoteLength,
+					maxRemoteNoteTextLength: this.config.maxRemoteNoteLength,
+					maxCwLength: this.config.maxCwLength,
+					maxRemoteCwLength: this.config.maxRemoteCwLength,
+					maxAltTextLength: this.config.maxAltTextLength,
+					maxRemoteAltTextLength: this.config.maxRemoteAltTextLength,
 					enableEmail: meta.enableEmail,
 					enableServiceWorker: meta.enableServiceWorker,
 					proxyAccountName: proxyAccount ? proxyAccount.username : null,
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index 39c8f67b8e9f0b7fe01c6d408823ab692fa71eff..216e6b4fb81d217f2d8440d745266508ad2e813f 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -49,6 +49,7 @@ import { MastodonApiServerService } from './api/mastodon/MastodonApiServerServic
 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: [
@@ -74,6 +75,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 30c133d9ece5dc59f252c7909e2c535e5b7a5393..43a2a3a2b03b3a5af6bf34655c129dd89d416b46 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';
@@ -45,6 +44,9 @@ export class ServerService implements OnApplicationShutdown {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -54,7 +56,6 @@ export class ServerService implements OnApplicationShutdown {
 		@Inject(DI.emojisRepository)
 		private emojisRepository: EmojisRepository,
 
-		private metaService: MetaService,
 		private userEntityService: UserEntityService,
 		private apiServerService: ApiServerService,
 		private openApiServerService: OpenApiServerService,
@@ -167,8 +168,8 @@ export class ServerService implements OnApplicationShutdown {
 			}
 
 			return await reply.redirect(
-				301,
 				url.toString(),
+				301,
 			);
 		});
 
@@ -195,7 +196,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 808795fdac300b9dd22f36bf759ea1e41332cf62..016db6ac194bfbc1535f7c3748fe16978b521583 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,
@@ -199,9 +200,18 @@ export class ApiCallService implements OnApplicationShutdown {
 			return;
 		}
 
-		const [path] = await createTemp();
+		const [path, cleanup] = await createTemp();
 		await stream.pipeline(multipartData.file, fs.createWriteStream(path));
 
+		// ファイルサイズが制限を超えていた場合
+		// なお truncated はストリームを読み切ってからでないと機能しないため、stream.pipeline より後にある必要がある
+		if (multipartData.file.truncated) {
+			cleanup();
+			reply.code(413);
+			reply.send();
+			return;
+		}
+
 		const fields = {} as Record<string, unknown>;
 		for (const [k, v] of Object.entries(multipartData.fields)) {
 			fields[k] = typeof v === 'object' && 'value' in v ? v.value : undefined;
@@ -256,10 +266,14 @@ 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;
+		if (!ip) {
+			this.logger.warn(`user ${user.id} has a null IP address; please check your network configuration.`);
+			return;
+		}
+
 		const ips = this.userIpHistories.get(user.id);
 		if (ips == null || !ips.has(ip)) {
 			if (ips == null) {
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 4a5935f93092dd91c631c50c0eeb498cc80defe1..ac3b982742e6d8fe6f1c868cf0ad2d097dcc2fd4 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);
 	}
@@ -49,7 +52,7 @@ export class ApiServerService {
 
 		fastify.register(multipart, {
 			limits: {
-				fileSize: this.config.maxFileSize ?? 262144000,
+				fileSize: this.config.maxFileSize,
 				files: 1,
 			},
 		});
@@ -115,6 +118,7 @@ export class ApiServerService {
 				'hcaptcha-response'?: string;
 				'g-recaptcha-response'?: string;
 				'turnstile-response'?: string;
+				'frc-captcha-solution'?: string;
 			}
 		}>('/signup', (request, reply) => this.signupApiService.signup(request, reply));
 
@@ -131,6 +135,12 @@ export class ApiServerService {
 			};
 		}>('/signin', (request, reply) => this.signinApiService.signin(request, reply));
 
+		fastify.post<{
+			Body: {
+				credential?: AuthenticationResponseJSON;
+			};
+		}>('/signin-with-passkey', (request, reply) => this.signinWithPasskeyApiService.signin(request, reply));
+
 		fastify.post<{ Body: { code: string; } }>('/signup-pending', (request, reply) => this.signupApiService.signupPending(request, reply));
 
 		fastify.get('/v1/instance/peers', async (request, reply) => {
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 4a08410cebe57f1b556d2fb3df7cdeccf3f56664..5bdd7cf65088496816b95f4d06bd79b07152696d 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -79,6 +79,7 @@ import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
 import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
 import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
 import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
+import * as ep___admin_declineUser from './endpoints/admin/decline-user.js';
 import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
 import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
 import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
@@ -97,6 +98,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';
@@ -189,6 +191,7 @@ import * as ep___following_invalidate from './endpoints/following/invalidate.js'
 import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
 import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
 import * as ep___following_requests_list from './endpoints/following/requests/list.js';
+import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
 import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
 import * as ep___gallery_featured from './endpoints/gallery/featured.js';
 import * as ep___gallery_popular from './endpoints/gallery/popular.js';
@@ -266,6 +269,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';
@@ -290,6 +294,7 @@ import * as ep___notes_delete from './endpoints/notes/delete.js';
 import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
 import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
 import * as ep___notes_featured from './endpoints/notes/featured.js';
+import * as ep___notes_following from './endpoints/notes/following.js';
 import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
 import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
 import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
@@ -297,6 +302,7 @@ import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
 import * as ep___notes_mentions from './endpoints/notes/mentions.js';
 import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
 import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
+import * as ep___notes_polls_refresh from './endpoints/notes/polls/refresh.js';
 import * as ep___notes_reactions from './endpoints/notes/reactions.js';
 import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
 import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
@@ -475,6 +481,7 @@ const $admin_silenceUser: Provider = { provide: 'ep:admin/silence-user', useClas
 const $admin_unsilenceUser: Provider = { provide: 'ep:admin/unsilence-user', useClass: ep___admin_unsilenceUser.default };
 const $admin_suspendUser: Provider = { provide: 'ep:admin/suspend-user', useClass: ep___admin_suspendUser.default };
 const $admin_approveUser: Provider = { provide: 'ep:admin/approve-user', useClass: ep___admin_approveUser.default };
+const $admin_declineUser: Provider = { provide: 'ep:admin/decline-user', useClass: ep___admin_declineUser.default };
 const $admin_unsuspendUser: Provider = { provide: 'ep:admin/unsuspend-user', useClass: ep___admin_unsuspendUser.default };
 const $admin_updateMeta: Provider = { provide: 'ep:admin/update-meta', useClass: ep___admin_updateMeta.default };
 const $admin_deleteAccount: Provider = { provide: 'ep:admin/delete-account', useClass: ep___admin_deleteAccount.default };
@@ -493,6 +500,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 };
@@ -585,6 +593,7 @@ const $following_invalidate: Provider = { provide: 'ep:following/invalidate', us
 const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default };
 const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default };
 const $following_requests_list: Provider = { provide: 'ep:following/requests/list', useClass: ep___following_requests_list.default };
+const $following_requests_sent: Provider = { provide: 'ep:following/requests/sent', useClass: ep___following_requests_sent.default };
 const $following_requests_reject: Provider = { provide: 'ep:following/requests/reject', useClass: ep___following_requests_reject.default };
 const $gallery_featured: Provider = { provide: 'ep:gallery/featured', useClass: ep___gallery_featured.default };
 const $gallery_popular: Provider = { provide: 'ep:gallery/popular', useClass: ep___gallery_popular.default };
@@ -662,6 +671,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 };
@@ -686,6 +696,7 @@ const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___not
 const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
 const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
 const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
+const $notes_following: Provider = { provide: 'ep:notes/following', useClass: ep___notes_following.default };
 const $notes_globalTimeline: Provider = { provide: 'ep:notes/global-timeline', useClass: ep___notes_globalTimeline.default };
 const $notes_bubbleTimeline: Provider = { provide: 'ep:notes/bubble-timeline', useClass: ep___notes_bubbleTimeline.default };
 const $notes_hybridTimeline: Provider = { provide: 'ep:notes/hybrid-timeline', useClass: ep___notes_hybridTimeline.default };
@@ -693,6 +704,7 @@ const $notes_localTimeline: Provider = { provide: 'ep:notes/local-timeline', use
 const $notes_mentions: Provider = { provide: 'ep:notes/mentions', useClass: ep___notes_mentions.default };
 const $notes_polls_recommendation: Provider = { provide: 'ep:notes/polls/recommendation', useClass: ep___notes_polls_recommendation.default };
 const $notes_polls_vote: Provider = { provide: 'ep:notes/polls/vote', useClass: ep___notes_polls_vote.default };
+const $notes_polls_refresh: Provider = { provide: 'ep:notes/polls/refresh', useClass: ep___notes_polls_refresh.default };
 const $notes_reactions: Provider = { provide: 'ep:notes/reactions', useClass: ep___notes_reactions.default };
 const $notes_reactions_create: Provider = { provide: 'ep:notes/reactions/create', useClass: ep___notes_reactions_create.default };
 const $notes_reactions_delete: Provider = { provide: 'ep:notes/reactions/delete', useClass: ep___notes_reactions_delete.default };
@@ -875,6 +887,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_unsilenceUser,
 		$admin_suspendUser,
 		$admin_approveUser,
+		$admin_declineUser,
 		$admin_unsuspendUser,
 		$admin_updateMeta,
 		$admin_deleteAccount,
@@ -893,6 +906,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,
@@ -985,6 +999,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$following_requests_accept,
 		$following_requests_cancel,
 		$following_requests_list,
+		$following_requests_sent,
 		$following_requests_reject,
 		$gallery_featured,
 		$gallery_popular,
@@ -1062,6 +1077,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,
@@ -1086,6 +1102,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$notes_favorites_create,
 		$notes_favorites_delete,
 		$notes_featured,
+		$notes_following,
 		$notes_globalTimeline,
 		$notes_bubbleTimeline,
 		$notes_hybridTimeline,
@@ -1093,6 +1110,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$notes_mentions,
 		$notes_polls_recommendation,
 		$notes_polls_vote,
+		$notes_polls_refresh,
 		$notes_reactions,
 		$notes_reactions_create,
 		$notes_reactions_delete,
@@ -1269,6 +1287,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$admin_unsilenceUser,
 		$admin_suspendUser,
 		$admin_approveUser,
+		$admin_declineUser,
 		$admin_unsuspendUser,
 		$admin_updateMeta,
 		$admin_deleteAccount,
@@ -1287,6 +1306,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,
@@ -1456,6 +1476,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,
@@ -1480,6 +1501,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$notes_favorites_create,
 		$notes_favorites_delete,
 		$notes_featured,
+		$notes_following,
 		$notes_globalTimeline,
 		$notes_bubbleTimeline,
 		$notes_hybridTimeline,
@@ -1487,6 +1509,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
 		$notes_mentions,
 		$notes_polls_recommendation,
 		$notes_polls_vote,
+		$notes_polls_refresh,
 		$notes_reactions,
 		$notes_reactions_create,
 		$notes_reactions_delete,
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 6fbcacbc1172f7ecf683b92b12f8b463e9250934..64af7da7a61b18ce9d49d1f26aa6bd302f300c02 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -21,11 +21,12 @@ import { IdService } from '@/core/IdService.js';
 import { bindThis } from '@/decorators.js';
 import { WebAuthnService } from '@/core/WebAuthnService.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
-import { MetaService } from '@/core/MetaService.js';
 import { RateLimiterService } from './RateLimiterService.js';
 import { SigninService } from './SigninService.js';
 import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
 import type { FastifyReply, FastifyRequest } from 'fastify';
+import { isSystemAccount } from '@/misc/is-system-account.js';
+import type { MiMeta } from '@/models/_.js';
 
 @Injectable()
 export class SigninApiService {
@@ -33,6 +34,9 @@ export class SigninApiService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -47,7 +51,6 @@ export class SigninApiService {
 		private signinService: SigninService,
 		private userAuthService: UserAuthService,
 		private webAuthnService: WebAuthnService,
-		private metaService: MetaService,
 	) {
 	}
 
@@ -66,8 +69,6 @@ export class SigninApiService {
 		reply.header('Access-Control-Allow-Origin', this.config.url);
 		reply.header('Access-Control-Allow-Credentials', 'true');
 
-		const instance = await this.metaService.fetch(true);
-
 		const body = request.body;
 		const username = body['username'];
 		const password = body['password'];
@@ -125,9 +126,15 @@ export class SigninApiService {
 			});
 		}
 
+		if (isSystemAccount(user)) {
+			return error(403, {
+				id: 's8dhsj9s-a93j-493j-ja9k-kas9sj20aml2',
+			});
+		}
+
 		const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
 
-		if (!user.approved && instance.approvalRequiredForSignup) {
+		if (!user.approved && this.meta.approvalRequiredForSignup) {
 			reply.code(403);
 			return {
 				error: {
@@ -162,7 +169,7 @@ export class SigninApiService {
 						password: newHash
 					});
 				}
-				if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
+				if (!this.meta.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
 
 				return this.signinService.signin(request, reply, user);
 			} else {
@@ -193,7 +200,7 @@ export class SigninApiService {
 				});
 			}
 
-			if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
+			if (!this.meta.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
 
 			return this.signinService.signin(request, reply, user);
 		} else if (body.credential) {
@@ -206,7 +213,7 @@ export class SigninApiService {
 			const authorized = await this.webAuthnService.verifyAuthentication(user.id, body.credential);
 
 			if (authorized) {
-				if (!instance.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
+				if (!this.meta.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
 				return this.signinService.signin(request, reply, user);
 			} else {
 				return await fail(403, {
diff --git a/packages/backend/src/server/api/SigninWithPasskeyApiService.ts b/packages/backend/src/server/api/SigninWithPasskeyApiService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9ba23c54e22656c80f5d848eb3073eb29b959b8e
--- /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 f89c3954f80e8a4d6c8320a143a9052115707285..db860d710a8f93bbb7ffeb06e53ee04dcc9518a2 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -8,9 +8,8 @@ import { Inject, Injectable } from '@nestjs/common';
 import * as argon2 from 'argon2';
 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';
@@ -31,6 +30,9 @@ export class SignupApiService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -48,7 +50,6 @@ export class SignupApiService {
 
 		private userEntityService: UserEntityService,
 		private idService: IdService,
-		private metaService: MetaService,
 		private captchaService: CaptchaService,
 		private signupService: SignupService,
 		private signinService: SigninService,
@@ -71,37 +72,42 @@ export class SignupApiService {
 				'g-recaptcha-response'?: string;
 				'turnstile-response'?: string;
 				'm-captcha-response'?: string;
+				'frc-captcha-solution'?: 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 (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.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.enableRecaptcha && instance.recaptchaSecretKey) {
-				await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-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.enableTurnstile && instance.turnstileSecretKey) {
-				await this.captchaService.verifyTurnstile(instance.turnstileSecretKey, body['turnstile-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 (this.meta.enableFC && this.meta.fcSecretKey) {
+				await this.captchaService.verifyFriendlyCaptcha(this.meta.fcSecretKey, body['frc-captcha-solution']).catch(err => {
 					throw new FastifyReplyError(400, err);
 				});
 			}
@@ -114,7 +120,7 @@ export class SignupApiService {
 		const reason = body['reason'];
 		const emailAddress = body['emailAddress'];
 
-		if (instance.emailRequiredForSignup) {
+		if (this.meta.emailRequiredForSignup) {
 			if (emailAddress == null || typeof emailAddress !== 'string') {
 				reply.code(400);
 				return;
@@ -127,7 +133,7 @@ export class SignupApiService {
 			}
 		}
 
-		if (instance.approvalRequiredForSignup) {
+		if (this.meta.approvalRequiredForSignup) {
 			if (reason == null || typeof reason !== 'string') {
 				reply.code(400);
 				return;
@@ -136,7 +142,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;
@@ -157,7 +163,7 @@ export class SignupApiService {
 			}
 
 			// メアド認証が有効の場合
-			if (instance.emailRequiredForSignup) {
+			if (this.meta.emailRequiredForSignup) {
 				// メアド認証済みならエラー
 				if (ticket.usedBy) {
 					reply.code(400);
@@ -175,7 +181,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');
 			}
@@ -185,7 +191,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');
 			}
@@ -220,7 +226,7 @@ export class SignupApiService {
 
 			reply.code(204);
 			return;
-		} else if (instance.approvalRequiredForSignup) {
+		} else if (this.meta.approvalRequiredForSignup) {
 			const { account } = await this.signupService.signup({
 				username, password, host, reason,
 			});
@@ -288,8 +294,6 @@ export class SignupApiService {
 
 		const code = body['code'];
 
-		const instance = await this.metaService.fetch(true);
-
 		try {
 			const pendingUser = await this.userPendingsRepository.findOneByOrFail({ code });
 
@@ -324,7 +328,7 @@ export class SignupApiService {
 				});
 			}
 
-			if (instance.approvalRequiredForSignup) {
+			if (this.meta.approvalRequiredForSignup) {
 				if (pendingUser.email) {
 					this.emailService.sendEmail(pendingUser.email, 'Approval pending',
 						'Congratulations! Your account is now pending approval. You will get notified when you have been accepted.',
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index e2fcd1a9d03c0cf12e6922ae7575a1c09ba6c741..14e002929a41cc287f9f53849e354e9517508af9 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -85,6 +85,7 @@ import * as ep___admin_silenceUser from './endpoints/admin/silence-user.js';
 import * as ep___admin_unsilenceUser from './endpoints/admin/unsilence-user.js';
 import * as ep___admin_suspendUser from './endpoints/admin/suspend-user.js';
 import * as ep___admin_approveUser from './endpoints/admin/approve-user.js';
+import * as ep___admin_declineUser from './endpoints/admin/decline-user.js';
 import * as ep___admin_unsuspendUser from './endpoints/admin/unsuspend-user.js';
 import * as ep___admin_updateMeta from './endpoints/admin/update-meta.js';
 import * as ep___admin_deleteAccount from './endpoints/admin/delete-account.js';
@@ -103,6 +104,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';
@@ -195,6 +197,7 @@ import * as ep___following_invalidate from './endpoints/following/invalidate.js'
 import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
 import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
 import * as ep___following_requests_list from './endpoints/following/requests/list.js';
+import * as ep___following_requests_sent from './endpoints/following/requests/sent.js';
 import * as ep___following_requests_reject from './endpoints/following/requests/reject.js';
 import * as ep___gallery_featured from './endpoints/gallery/featured.js';
 import * as ep___gallery_popular from './endpoints/gallery/popular.js';
@@ -272,6 +275,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';
@@ -296,6 +300,7 @@ import * as ep___notes_delete from './endpoints/notes/delete.js';
 import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
 import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
 import * as ep___notes_featured from './endpoints/notes/featured.js';
+import * as ep___notes_following from './endpoints/notes/following.js';
 import * as ep___notes_globalTimeline from './endpoints/notes/global-timeline.js';
 import * as ep___notes_bubbleTimeline from './endpoints/notes/bubble-timeline.js';
 import * as ep___notes_hybridTimeline from './endpoints/notes/hybrid-timeline.js';
@@ -303,6 +308,7 @@ import * as ep___notes_localTimeline from './endpoints/notes/local-timeline.js';
 import * as ep___notes_mentions from './endpoints/notes/mentions.js';
 import * as ep___notes_polls_recommendation from './endpoints/notes/polls/recommendation.js';
 import * as ep___notes_polls_vote from './endpoints/notes/polls/vote.js';
+import * as ep___notes_polls_refresh from './endpoints/notes/polls/refresh.js';
 import * as ep___notes_reactions from './endpoints/notes/reactions.js';
 import * as ep___notes_reactions_create from './endpoints/notes/reactions/create.js';
 import * as ep___notes_reactions_delete from './endpoints/notes/reactions/delete.js';
@@ -479,6 +485,7 @@ const eps = [
 	['admin/unsilence-user', ep___admin_unsilenceUser],
 	['admin/suspend-user', ep___admin_suspendUser],
 	['admin/approve-user', ep___admin_approveUser],
+	['admin/decline-user', ep___admin_declineUser],
 	['admin/unsuspend-user', ep___admin_unsuspendUser],
 	['admin/update-meta', ep___admin_updateMeta],
 	['admin/delete-account', ep___admin_deleteAccount],
@@ -497,6 +504,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],
@@ -589,6 +597,7 @@ const eps = [
 	['following/requests/accept', ep___following_requests_accept],
 	['following/requests/cancel', ep___following_requests_cancel],
 	['following/requests/list', ep___following_requests_list],
+	['following/requests/sent', ep___following_requests_sent],
 	['following/requests/reject', ep___following_requests_reject],
 	['gallery/featured', ep___gallery_featured],
 	['gallery/popular', ep___gallery_popular],
@@ -666,6 +675,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],
@@ -690,6 +700,7 @@ const eps = [
 	['notes/favorites/create', ep___notes_favorites_create],
 	['notes/favorites/delete', ep___notes_favorites_delete],
 	['notes/featured', ep___notes_featured],
+	['notes/following', ep___notes_following],
 	['notes/global-timeline', ep___notes_globalTimeline],
 	['notes/bubble-timeline', ep___notes_bubbleTimeline],
 	['notes/hybrid-timeline', ep___notes_hybridTimeline],
@@ -697,6 +708,7 @@ const eps = [
 	['notes/mentions', ep___notes_mentions],
 	['notes/polls/recommendation', ep___notes_polls_recommendation],
 	['notes/polls/vote', ep___notes_polls_vote],
+	['notes/polls/refresh', ep___notes_polls_refresh],
 	['notes/reactions', ep___notes_reactions],
 	['notes/reactions/create', ep___notes_reactions_create],
 	['notes/reactions/delete', ep___notes_reactions_delete],
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 a7e8a3b018cd30cd45dece1d17abcb407a5a68e7..7754899b95ba11a85d7ea6852c18258dcd0af351 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -3,16 +3,16 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
+import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { UsersRepository } from '@/models/_.js';
+import { MiAccessToken, MiUser } from '@/models/_.js';
 import { SignupService } from '@/core/SignupService.js';
 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 { Packed } from '@/misc/json-schema.js';
+import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '@/server/api/error.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -28,6 +28,35 @@ export const meta = {
 			},
 		},
 	},
+
+	errors: {
+		// From ApiCallService.ts
+		noCredential: {
+			message: 'Credential required.',
+			code: 'CREDENTIAL_REQUIRED',
+			id: '1384574d-a912-4b81-8601-c7b1c4085df1',
+			httpStatusCode: 401,
+		},
+		noAdmin: {
+			message: 'You are not assigned to an administrator role.',
+			code: 'ROLE_PERMISSION_DENIED',
+			kind: 'permission',
+			id: 'c3d38592-54c0-429d-be96-5636b0431a61',
+		},
+		noPermission: {
+			message: 'Your app does not have the necessary permissions to use this endpoint.',
+			code: 'PERMISSION_DENIED',
+			kind: 'permission',
+			id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838',
+		},
+	},
+
+	// Required token permissions, but we need to check them manually.
+	// ApiCallService checks access in a way that would prevent creating the first account.
+	softPermissions: [
+		'write:admin:account',
+		'write:admin:approve-user',
+	],
 } as const;
 
 export const paramDef = {
@@ -42,22 +71,19 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.usersRepository)
-		private usersRepository: UsersRepository,
-
+		private roleService: RoleService,
 		private userEntityService: UserEntityService,
 		private signupService: SignupService,
 		private instanceActorService: InstanceActorService,
 	) {
 		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');
+			await this.ensurePermissions(_me, token);
 
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
 				password: ps.password,
 				ignorePreservedUsernames: true,
+				approved: true,
 			});
 
 			const res = await this.userEntityService.pack(account, account, {
@@ -70,4 +96,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			return res;
 		});
 	}
+
+	private async ensurePermissions(me: MiUser | null, token: MiAccessToken | null): Promise<void> {
+		// Tokens have scoped permissions which may be *less* than the user's official role, so we need to check.
+		if (token && !meta.softPermissions.every(p => token.permission.includes(p))) {
+			throw new ApiError(meta.errors.noPermission);
+		}
+
+		// Only administrators (including root) can create users.
+		if (me && !await this.roleService.isAdministrator(me)) {
+			throw new ApiError(meta.errors.noAdmin);
+		}
+
+		// Anonymous access is only allowed for initial instance setup.
+		if (!me && await this.instanceActorService.realLocalUsersPresent()) {
+			throw new ApiError(meta.errors.noCredential);
+		}
+	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/decline-user.ts b/packages/backend/src/server/api/endpoints/admin/decline-user.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a75dd977dc723d62f403f768fbb8c4c13f03d60
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/decline-user.ts
@@ -0,0 +1,75 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UsedUsernamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { DI } from '@/di-symbols.js';
+import { EmailService } from '@/core/EmailService.js';
+import { DeleteAccountService } from '@/core/DeleteAccountService.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireModerator: true,
+	kind: 'write:admin:decline-user',
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.userProfilesRepository)
+		private userProfilesRepository: UserProfilesRepository,
+
+		@Inject(DI.usedUsernamesRepository)
+		private usedUsernamesRepository: UsedUsernamesRepository,
+
+		private moderationLogService: ModerationLogService,
+		private emailService: EmailService,
+		private deleteAccountService: DeleteAccountService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const user = await this.usersRepository.findOneBy({ id: ps.userId });
+
+			if (user == null || user.isDeleted) {
+				throw new Error('user not found or already deleted');
+			}
+
+			if (user.approved) {
+				throw new Error('user is already approved');
+			}
+
+			if (user.host) {
+				throw new Error('user is not local');
+			}
+
+			const profile = await this.userProfilesRepository.findOneBy({ userId: ps.userId });
+
+			if (profile?.email) {
+				this.emailService.sendEmail(profile.email, 'Account Declined',
+					'Your Account has been declined!',
+					'Your Account has been declined!');
+			}
+
+			await this.usedUsernamesRepository.delete({ username: user.username });
+
+			await this.deleteAccountService.deleteAccount(user);
+
+			this.moderationLogService.log(me, 'decline', {
+				userId: user.id,
+				userUsername: user.username,
+				userHost: user.host,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index 9e93310746c95900fdc1f7640dd603a726041963..601c898f52ab46f5c254871d950e18f08df5475b 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -31,15 +31,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
-		@Inject(DI.notesRepository)
+		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
 		private queueService: QueueService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const followings = await this.followingsRepository.findBy({
-				followerHost: ps.host,
-			});
+			const followings = await this.followingsRepository.findBy([
+				{
+					followeeHost: ps.host,
+				},
+				{
+					followerHost: ps.host,
+				},
+			]);
 
 			const pairs = await Promise.all(followings.map(f => Promise.all([
 				this.usersRepository.findOneByOrFail({ id: f.followerId }),
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
index 8b142910a69b0a2c832ce0af599b1c3913d2e4a4..daf19c44353c08d75ce7e9c5504a72789d1512ef 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -25,6 +25,7 @@ export const paramDef = {
 		host: { type: 'string' },
 		isSuspended: { type: 'boolean' },
 		isNSFW: { type: 'boolean' },
+		rejectReports: { type: 'boolean' },
 		moderationNote: { type: 'string' },
 	},
 	required: ['host'],
@@ -57,6 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			await this.federatedInstanceService.update(instance.id, {
 				suspensionState,
 				isNSFW: ps.isNSFW,
+				rejectReports: ps.rejectReports,
 				moderationNote: ps.moderationNote,
 			});
 
@@ -74,6 +76,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
+			if (ps.isNSFW != null && instance.isNSFW !== ps.isNSFW) {
+				const message = ps.rejectReports ? 'setRemoteInstanceNSFW' : 'unsetRemoteInstanceNSFW';
+				this.moderationLogService.log(me, message, {
+					id: instance.id,
+					host: instance.host,
+				});
+			}
+
+			if (ps.rejectReports != null && instance.rejectReports !== ps.rejectReports) {
+				const message = ps.rejectReports ? 'rejectRemoteInstanceReports' : 'acceptRemoteInstanceReports';
+				this.moderationLogService.log(me, message, {
+					id: instance.id,
+					host: instance.host,
+				});
+			}
+
 			if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
 				this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
 					id: instance.id,
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 063bb6751b290f3e955ffd6ba60f581f4916375e..6e368eff43f8a8deb31fca9faa7ee2f14fb3efa0 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -73,6 +73,14 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			enableFC: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			fcSiteKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			swPublickey: {
 				type: 'string',
 				optional: false, nullable: true,
@@ -110,6 +118,10 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			sidebarLogoUrl: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			enableEmail: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -124,7 +136,7 @@ export const meta = {
 			},
 			silencedHosts: {
 				type: 'array',
-				optional: true,
+				optional: false,
 				nullable: false,
 				items: {
 					type: 'string',
@@ -215,6 +227,10 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			fcSecretKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			sensitiveMediaDetection: {
 				type: 'string',
 				optional: false, nullable: false,
@@ -396,6 +412,10 @@ export const meta = {
 				type: 'number',
 				optional: false, nullable: false,
 			},
+			enableReactionsBuffering: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
 			notesPerOneAd: {
 				type: 'number',
 				optional: false, nullable: false,
@@ -522,6 +542,26 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			trustedLinkUrlPatterns: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+					optional: false, nullable: false,
+				},
+			},
+			federation: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			federationHosts: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+					optional: false, nullable: false,
+				},
+			},
 		},
 	},
 } as const;
@@ -572,6 +612,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				recaptchaSiteKey: instance.recaptchaSiteKey,
 				enableTurnstile: instance.enableTurnstile,
 				turnstileSiteKey: instance.turnstileSiteKey,
+				enableFC: instance.enableFC,
+				fcSiteKey: instance.fcSiteKey,
 				swPublickey: instance.swPublicKey,
 				themeColor: instance.themeColor,
 				mascotImageUrl: instance.mascotImageUrl,
@@ -582,6 +624,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				iconUrl: instance.iconUrl,
 				app192IconUrl: instance.app192IconUrl,
 				app512IconUrl: instance.app512IconUrl,
+				sidebarLogoUrl: instance.sidebarLogoUrl,
 				backgroundImageUrl: instance.backgroundImageUrl,
 				logoImageUrl: instance.logoImageUrl,
 				defaultLightTheme: instance.defaultLightTheme,
@@ -605,6 +648,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				mcaptchaSecretKey: instance.mcaptchaSecretKey,
 				recaptchaSecretKey: instance.recaptchaSecretKey,
 				turnstileSecretKey: instance.turnstileSecretKey,
+				fcSecretKey: instance.fcSecretKey,
 				sensitiveMediaDetection: instance.sensitiveMediaDetection,
 				sensitiveMediaDetectionSensitivity: instance.sensitiveMediaDetectionSensitivity,
 				setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
@@ -656,6 +700,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,
@@ -664,6 +709,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				urlPreviewRequireContentLength: instance.urlPreviewRequireContentLength,
 				urlPreviewUserAgent: instance.urlPreviewUserAgent,
 				urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
+				trustedLinkUrlPatterns: instance.trustedLinkUrlPatterns,
+				federation: instance.federation,
+				federationHosts: instance.federationHosts,
 			};
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
index 7a3410ffa7519ca0af75458abe896da12c1a091f..f3e440b4cb516d2d4ec5ce9785f7bf78a25507f4 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -21,16 +21,15 @@ export const meta = {
 		items: {
 			type: 'array',
 			optional: false, nullable: false,
-			items: {
-				anyOf: [
-					{
-						type: 'string',
-					},
-					{
-						type: 'number',
-					},
-				],
-			},
+			prefixItems: [
+				{
+					type: 'string',
+				},
+				{
+					type: 'number',
+				},
+			],
+			unevaluatedItems: false,
 		},
 		example: [[
 			'example.com',
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
index 305ae1af1da5d404ca9b745b6291e645c274806e..e7589cba81128dc205cceaa3ee27be3207e4a3ff 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -21,16 +21,15 @@ export const meta = {
 		items: {
 			type: 'array',
 			optional: false, nullable: false,
-			items: {
-				anyOf: [
-					{
-						type: 'string',
-					},
-					{
-						type: 'number',
-					},
-				],
-			},
+			prefixItems: [
+				{
+					type: 'string',
+				},
+				{
+					type: 'number',
+				},
+			],
+			unevaluatedItems: false,
 		},
 		example: [[
 			'example.com',
diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
index 828dbae7126d1a72b17ba38a3624803cc3356045..e4bb545f5d69b8b9b47b2f63c1612763c25b683c 100644
--- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
@@ -11,6 +11,7 @@ import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { secureRndstr } from '@/misc/secure-rndstr.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -63,6 +64,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new Error('cannot reset password of root');
 			}
 
+			if (isSystemAccount(user)) {
+				throw new Error('cannot reset password of system account');
+			}
+
 			const passwd = secureRndstr(8);
 
 			// Generate hash of password
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 a7ca7f9547fa8665d82466e2a54cc827763aa9e3..669bffe2dcdddea1d9d03558c0630101e0532ffc 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -11,6 +11,7 @@ import { RoleService } from '@/core/RoleService.js';
 import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
 import { IdService } from '@/core/IdService.js';
 import { notificationRecieveConfig } from '@/models/json-schema/user.js';
+import { isSystemAccount } from '@/misc/is-system-account.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -31,6 +32,14 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
+			approved: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			followedMessage: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			autoAcceptFollowed: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -111,6 +120,10 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
+			isSystem: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
 			isSilenced: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -228,6 +241,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				emailVerified: profile.emailVerified,
 				approved: user.approved,
 				signupReason: user.signupReason,
+				followedMessage: profile.followedMessage,
 				autoAcceptFollowed: profile.autoAcceptFollowed,
 				noCrawle: profile.noCrawle,
 				preventAiLearning: profile.preventAiLearning,
@@ -240,6 +254,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				mutedInstances: profile.mutedInstances,
 				notificationRecieveConfig: profile.notificationRecieveConfig,
 				isModerator: isModerator,
+				isSystem: isSystemAccount(user),
 				isSilenced: isSilenced,
 				isSuspended: user.isSuspended,
 				isHibernated: user.isHibernated,
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 0000000000000000000000000000000000000000..fb2ddf4b446d76e476603c539e83fc3786b25c42
--- /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-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 6bda1ae6adb08e3f344df40e1bc2041b09080d36..98760bbcc3fbd72ea06886263cbc9a71e21fcf57 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -55,6 +55,7 @@ export const paramDef = {
 		iconUrl: { type: 'string', nullable: true },
 		app192IconUrl: { type: 'string', nullable: true },
 		app512IconUrl: { type: 'string', nullable: true },
+		sidebarLogoUrl: { type: 'string', nullable: true },
 		backgroundImageUrl: { type: 'string', nullable: true },
 		logoImageUrl: { type: 'string', nullable: true },
 		name: { type: 'string', nullable: true },
@@ -80,6 +81,9 @@ export const paramDef = {
 		enableTurnstile: { type: 'boolean' },
 		turnstileSiteKey: { type: 'string', nullable: true },
 		turnstileSecretKey: { type: 'string', nullable: true },
+		enableFC: { type: 'boolean' },
+		fcSiteKey: { type: 'string', nullable: true },
+		fcSecretKey: { type: 'string', nullable: true },
 		sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
 		sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
 		setSensitiveFlagAutomatically: { type: 'boolean' },
@@ -150,6 +154,7 @@ export const paramDef = {
 		perRemoteUserUserTimelineCacheMax: { type: 'integer' },
 		perUserHomeTimelineCacheMax: { type: 'integer' },
 		perUserListTimelineCacheMax: { type: 'integer' },
+		enableReactionsBuffering: { type: 'boolean' },
 		notesPerOneAd: { type: 'integer' },
 		silencedHosts: {
 			type: 'array',
@@ -175,6 +180,21 @@ export const paramDef = {
 		urlPreviewRequireContentLength: { type: 'boolean' },
 		urlPreviewUserAgent: { type: 'string', nullable: true },
 		urlPreviewSummaryProxyUrl: { type: 'string', nullable: true },
+		trustedLinkUrlPatterns: {
+			type: 'array', nullable: true, items: {
+				type: 'string',
+			},
+		},
+		federation: {
+			type: 'string',
+			enum: ['all', 'none', 'specified'],
+		},
+		federationHosts: {
+			type: 'array',
+			items: {
+				type: 'string',
+			},
+		},
 	},
 	required: [],
 } as const;
@@ -250,6 +270,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.app512IconUrl = ps.app512IconUrl;
 			}
 
+			if (ps.sidebarLogoUrl !== undefined) {
+				set.sidebarLogoUrl = ps.sidebarLogoUrl;
+			}
+
 			if (ps.serverErrorImageUrl !== undefined) {
 				set.serverErrorImageUrl = ps.serverErrorImageUrl;
 			}
@@ -362,6 +386,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.turnstileSecretKey = ps.turnstileSecretKey;
 			}
 
+			if (ps.enableFC !== undefined) {
+				set.enableFC = ps.enableFC;
+			}
+
+			if (ps.fcSiteKey !== undefined) {
+				set.fcSiteKey = ps.fcSiteKey;
+			}
+
+			if (ps.fcSecretKey !== undefined) {
+				set.fcSecretKey = ps.fcSecretKey;
+			}
+
 			if (ps.enableBotTrending !== undefined) {
 				set.enableBotTrending = ps.enableBotTrending;
 			}
@@ -626,6 +662,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;
 			}
@@ -660,6 +700,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.urlPreviewSummaryProxyUrl = value === '' ? null : value;
 			}
 
+			if (Array.isArray(ps.trustedLinkUrlPatterns)) {
+				set.trustedLinkUrlPatterns = ps.trustedLinkUrlPatterns.filter(Boolean);
+			}
+
+			if (ps.federation !== undefined) {
+				set.federation = ps.federation;
+			}
+
+			if (Array.isArray(ps.federationHosts)) {
+				set.blockedHosts = 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 577b9e1b1f8c347d342671c708b1b9869686f429..e0c8ddcc8478e8851089b4e222082fd2b03f26cb 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 0c30bca9e0bf17b52d6825c80f8511644bc58485..10f26b19126ed0a8301504e042c5bd81449dd0fa 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 ca6789a4642351caf70cd095b18e85152944dda1..a877d1ce0d034277902d5afb03195803ef43a9ad 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,10 +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 host = this.utilityService.extractDbHost(uri);
-		const fetchedMeta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(fetchedMeta.blockedHosts, host)) return null;
+		if (!this.utilityService.isFederationAllowedUri(uri)) return null;
 
 		let local = await this.mergePack(me, ...await Promise.all([
 			this.apDbResolverService.getUserFromApId(uri),
@@ -123,6 +118,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		]));
 		if (local != null) return local;
 
+		const host = this.utilityService.extractDbHost(uri);
+
 		// local object, not found in db? fail
 		if (this.utilityService.isSelfHost(host)) return null;
 
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 936948164990cdc8ee12906b3e7e496f45d7c7a7..06130464a97f271fa2df6ae5152f75b462b72281 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';
@@ -65,6 +63,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,16 +76,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,
 			});
@@ -95,7 +92,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, withFiles: ps.withFiles, withRenotes: ps.withRenotes }, me), me);
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts
index 7e9b0fa0e1e430f3831bbd0d691c128ebb0026a6..eb45e29f9e123bed218043a0de1842b9edfd4bf8 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 4670392025242aec0958949c870715bfa969ac28..b86059b5e7ac988e9d4d928d7706150d9669e159 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 9c17f93ab26bfbf280a97b89f25fd22752bab9a4..f67ff6ddc4b03b65ad1e304ea4c554143a757618 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 { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
+import { Inject, Injectable } from '@nestjs/common';
 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 type { Config } from '@/config.js';
 import { ApiError } from '../../../error.js';
+import { MiMeta } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -55,6 +56,12 @@ export const meta = {
 			code: 'NO_FREE_SPACE',
 			id: 'd08dbc37-a6a9-463a-8c47-96c32ab5f064',
 		},
+
+		commentTooLong: {
+			message: 'Cannot upload the file because the comment exceeds the instance limit.',
+			code: 'COMMENT_TOO_LONG',
+			id: '333652d9-0826-40f5-a2c3-e2bedcbb9fe5',
+		},
 	},
 } as const;
 
@@ -63,7 +70,7 @@ export const paramDef = {
 	properties: {
 		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		name: { type: 'string', nullable: true, default: null },
-		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, default: null },
+		comment: { type: 'string', nullable: true, default: null },
 		isSensitive: { type: 'boolean', default: false },
 		force: { type: 'boolean', default: false },
 	},
@@ -73,8 +80,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.config)
+		private config: Config,
+
 		private driveFileEntityService: DriveFileEntityService,
-		private metaService: MetaService,
 		private driveService: DriveService,
 	) {
 		super(meta, paramDef, async (ps, me, _, file, cleanup, ip, headers) => {
@@ -91,7 +103,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			const instance = await this.metaService.fetch();
+			if (ps.comment && ps.comment.length > this.config.maxAltTextLength) {
+				throw new ApiError(meta.errors.commentTooLong);
+			}
 
 			try {
 				// Create file
@@ -103,8 +117,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/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index 554101812694c8016bf5c67017c3916364b89fdd..1501abf9ced35d61fb5192e636045361f7c2ad51 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -9,8 +9,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { DriveService } from '@/core/DriveService.js';
+import type { Config } from '@/config.js';
 import { ApiError } from '../../../error.js';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -51,6 +51,12 @@ export const meta = {
 			code: 'RESTRICTED_BY_ROLE',
 			id: '7f59dccb-f465-75ab-5cf4-3ce44e3282f7',
 		},
+
+		commentTooLong: {
+			message: 'Cannot upload the file because the comment exceeds the instance limit.',
+			code: 'COMMENT_TOO_LONG',
+			id: '333652d9-0826-40f5-a2c3-e2bedcbb9fe5',
+		},
 	},
 	res: {
 		type: 'object',
@@ -66,7 +72,7 @@ export const paramDef = {
 		folderId: { type: 'string', format: 'misskey:id', nullable: true },
 		name: { type: 'string' },
 		isSensitive: { type: 'boolean' },
-		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH },
+		comment: { type: 'string', nullable: true },
 	},
 	required: ['fileId'],
 } as const;
@@ -76,6 +82,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	constructor(
 		@Inject(DI.driveFilesRepository)
 		private driveFilesRepository: DriveFilesRepository,
+		@Inject(DI.config)
+		private config: Config,
 
 		private driveService: DriveService,
 		private roleService: RoleService,
@@ -90,6 +98,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.accessDenied);
 			}
 
+			if (ps.comment && ps.comment.length > this.config.maxAltTextLength) {
+				throw new ApiError(meta.errors.commentTooLong);
+			}
+
 			let packedFile;
 
 			try {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index 49d2e78d08bc24a36635ac35d00ed27aaf36dc5e..e20d482e7046243cf1da6de923c653083581054a 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -4,12 +4,14 @@
  */
 
 import ms from 'ms';
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { DriveService } from '@/core/DriveService.js';
-import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/const.js';
+import { ApiError } from '@/server/api/error.js';
+import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
 
 export const meta = {
 	tags: ['drive'],
@@ -26,6 +28,14 @@ export const meta = {
 	prohibitMoved: true,
 
 	kind: 'write:drive',
+
+	errors: {
+		commentTooLong: {
+			message: 'Cannot upload the file because the comment exceeds the instance limit.',
+			code: 'COMMENT_TOO_LONG',
+			id: '333652d9-0826-40f5-a2c3-e2bedcbb9fe5',
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -34,7 +44,7 @@ export const paramDef = {
 		url: { type: 'string' },
 		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		isSensitive: { type: 'boolean', default: false },
-		comment: { type: 'string', nullable: true, maxLength: DB_MAX_IMAGE_COMMENT_LENGTH, default: null },
+		comment: { type: 'string', nullable: true, default: null },
 		marker: { type: 'string', nullable: true, default: null },
 		force: { type: 'boolean', default: false },
 	},
@@ -44,11 +54,18 @@ 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,
+
 		private driveFileEntityService: DriveFileEntityService,
 		private driveService: DriveService,
 		private globalEventService: GlobalEventService,
 	) {
 		super(meta, paramDef, async (ps, user, _1, _2, _3, ip, headers) => {
+			if (ps.comment && ps.comment.length > this.config.maxAltTextLength) {
+				throw new ApiError(meta.errors.commentTooLong);
+			}
+
 			this.driveService.uploadFromUrl({ url: ps.url, user, folderId: ps.folderId, sensitive: ps.isSensitive, force: ps.force, comment: ps.comment, requestIp: ip, requestHeaders: headers }).then(file => {
 				this.driveFileEntityService.pack(file, { self: true }).then(packedFile => {
 					this.globalEventService.publishMainStream(user.id, 'urlUploadFinished', {
diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts
index ce4dd130670c99fda1842bbee6c3f568e717bfe8..d5b80035df8f3e04e0bd42e4e51ab21674f9ab33 100644
--- a/packages/backend/src/server/api/endpoints/federation/followers.ts
+++ b/packages/backend/src/server/api/endpoints/federation/followers.ts
@@ -3,17 +3,15 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { FollowingsRepository } from '@/models/_.js';
-import { QueryService } from '@/core/QueryService.js';
 import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
-import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	tags: ['federation'],
 
-	requireCredential: false,
+	requireCredential: true,
+	kind: 'read:account',
 
 	res: {
 		type: 'array',
@@ -33,6 +31,8 @@ export const paramDef = {
 		sinceId: { type: 'string', format: 'misskey:id' },
 		untilId: { type: 'string', format: 'misskey:id' },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		includeFollower: { type: 'boolean', default: false },
+		includeFollowee: { type: 'boolean', default: true },
 	},
 	required: ['host'],
 } as const;
@@ -40,21 +40,10 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-
 		private followingEntityService: FollowingEntityService,
-		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
-				.andWhere('following.followeeHost = :host', { host: ps.host });
-
-			const followings = await query
-				.limit(ps.limit)
-				.getMany();
-
-			return await this.followingEntityService.packMany(followings, me, { populateFollowee: true });
+			return this.followingEntityService.getFollowers(me, ps);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts
index 1a793889c7e9d478fec2de168b44bb3db96f3ec9..215f94fbcc4975e427f5c2d3aeca7440541058bc 100644
--- a/packages/backend/src/server/api/endpoints/federation/following.ts
+++ b/packages/backend/src/server/api/endpoints/federation/following.ts
@@ -3,17 +3,15 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Inject, Injectable } from '@nestjs/common';
+import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { FollowingsRepository } from '@/models/_.js';
-import { QueryService } from '@/core/QueryService.js';
 import { FollowingEntityService } from '@/core/entities/FollowingEntityService.js';
-import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	tags: ['federation'],
 
-	requireCredential: false,
+	requireCredential: true,
+	kind: 'read:account',
 
 	res: {
 		type: 'array',
@@ -33,6 +31,8 @@ export const paramDef = {
 		sinceId: { type: 'string', format: 'misskey:id' },
 		untilId: { type: 'string', format: 'misskey:id' },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		includeFollower: { type: 'boolean', default: false },
+		includeFollowee: { type: 'boolean', default: true },
 	},
 	required: ['host'],
 } as const;
@@ -40,21 +40,10 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-
 		private followingEntityService: FollowingEntityService,
-		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const query = this.queryService.makePaginationQuery(this.followingsRepository.createQueryBuilder('following'), ps.sinceId, ps.untilId)
-				.andWhere('following.followerHost = :host', { host: ps.host });
-
-			const followings = await query
-				.limit(ps.limit)
-				.getMany();
-
-			return await this.followingEntityService.packMany(followings, me, { populateFollowee: true });
+			return this.followingEntityService.getFollowing(me, ps);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/following/requests/sent.ts b/packages/backend/src/server/api/endpoints/following/requests/sent.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6325f01bb8c83b38b8b1130cf6be532fd5730e03
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/following/requests/sent.ts
@@ -0,0 +1,77 @@
+/*
+ * 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 { QueryService } from '@/core/QueryService.js';
+import type { FollowRequestsRepository } from '@/models/_.js';
+import { FollowRequestEntityService } from '@/core/entities/FollowRequestEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+	tags: ['following', 'account'],
+
+	requireCredential: true,
+
+	kind: 'read:following',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			properties: {
+				id: {
+					type: 'string',
+					optional: false, nullable: false,
+					format: 'id',
+				},
+				follower: {
+					type: 'object',
+					optional: false, nullable: false,
+					ref: 'UserLite',
+				},
+				followee: {
+					type: 'object',
+					optional: false, nullable: false,
+					ref: 'UserLite',
+				},
+			},
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		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.followRequestsRepository)
+		private followRequestsRepository: FollowRequestsRepository,
+
+		private followRequestEntityService: FollowRequestEntityService,
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const query = this.queryService.makePaginationQuery(this.followRequestsRepository.createQueryBuilder('request'), ps.sinceId, ps.untilId)
+				.andWhere('request.followerId = :meId', { meId: me.id });
+
+			const requests = await query
+				.limit(ps.limit)
+				.getMany();
+
+			return await this.followRequestEntityService.packMany(requests, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
index 73231e8e09ad14f03f2712ce44787dd76199600a..603df44733fd7ab57115ec339f9375cd013e67e1 100644
--- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
+++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
@@ -3,10 +3,11 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js';
-import { MetaService } from '@/core/MetaService.js';
+import type { MiMeta } from '@/models/_.js';
 
 export const meta = {
 	requireCredential: true,
@@ -25,12 +26,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,
+
 		private achievementService: AchievementService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const meta = await this.metaService.fetch();
-			if (!meta.enableAchievements) return;
+			if (!this.serverSettings.enableAchievements) return;
 
 			await this.achievementService.create(me.id, ps.name);
 		});
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 bc46163e3d27f6198fa91e4959eb58ce53f0a01a..bdf6c065e89c1d1c96a3061518819049b5237876 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 260610853930d7ba0a62bfe4c13758831ca11728..d7bb6bcd22562a8b2ec4c6961bf819aa54810154 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 d5e824df274077c32521c319e57034fb5cf7ea76..e03192d8c67b7b6a35c0aca0d29d4a01f45179f5 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 0f5800404eaf688d67d9340921c6815ba47eaa3b..76b285bb7e3f9e76ba9b4c08df8ad136216660f5 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 bacdd5c88f285432445763abc6c17b023ee09685..76ecfd082ca3ca7e6d42489250de4f85233bfac7 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 7332026d84efaa390c865ca2a1d897219b558096..0be8bfb6952cb660ae7cead83d020e14578bb038 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -8,7 +8,7 @@ import ms from 'ms';
 //import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 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';
@@ -16,7 +16,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 = {
@@ -71,10 +70,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,
@@ -106,7 +107,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 6cc22e79948da1c2daf0dd8ecac664a901e6d99b..8994c3fff6e8826c816664a6dfd719086661e181 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -13,9 +13,8 @@ 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 { MiLocalUser, MiUser } from '@/models/User.js';
-import { birthdaySchema, listenbrainzSchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
+import { birthdaySchema, listenbrainzSchema, 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';
@@ -146,6 +145,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 },
 		listenbrainz: { ...listenbrainzSchema, nullable: true },
@@ -159,6 +159,7 @@ export const paramDef = {
 				flipH: { type: 'boolean', nullable: true },
 				offsetX: { type: 'number', nullable: true, maximum: 0.25, minimum: -0.25 },
 				offsetY: { type: 'number', nullable: true, maximum: 0.25, minimum: -0.25 },
+				showBelow: { type: 'boolean', nullable: true },
 			},
 			required: ['id'],
 		} },
@@ -192,6 +193,7 @@ export const paramDef = {
 		injectFeaturedNote: { type: 'boolean' },
 		receiveAnnouncementEmail: { type: 'boolean' },
 		alwaysMarkNsfw: { type: 'boolean' },
+		defaultSensitive: { type: 'boolean' },
 		autoSensitive: { type: 'boolean' },
 		followingVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
 		followersVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
@@ -283,6 +285,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;
@@ -347,6 +350,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (policies.alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
 				profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
 			}
+			if (typeof ps.defaultSensitive === 'boolean') profileUpdates.defaultSensitive = ps.defaultSensitive;
 			if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
 
 			if (ps.avatarId) {
@@ -399,7 +403,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				updates.backgroundUrl = null;
 				updates.backgroundBlurhash = null;
 			}
-			
+
 			if (ps.avatarDecorations) {
 				policies ??= await this.roleService.getUserPolicies(user.id);
 				const decorations = await this.avatarDecorationService.getAll(true);
@@ -417,6 +421,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					flipH: d.flipH ?? false,
 					offsetX: d.offsetX ?? 0,
 					offsetY: d.offsetY ?? 0,
+					showBelow: d.showBelow ?? false,
 				}));
 			}
 
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 9eb7f5b3a0333f1eab499d8e64fa38d5c5aba40b..6e84603f7a454e5641627e7e5ede8b0a64466b4b 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 fe07afb2d08965bdb690696b5ce52cdcb0c676d1..394c178f2adf1628a18de72ea96e43c56c447452 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 5ddb79caf283c31b8378550a188be670f406b0f7..4a0c09ff0ccbf8850d4fd029eb937f7bbd8dbf3c 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 0000000000000000000000000000000000000000..2bf6df9ce2f9b5bc7aa2dffc8c1d5186505fca2e
--- /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 2786bd98d5bf6a0c42e0b293c42c7667517c85f9..2ffd41ae28a3650f79c5f818fdea8106b6d6163e 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/bubble-timeline.ts b/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts
index c5e3a5a5f74fcc49fd01813ec419a0ab4af42baf..94ec8c37ec589a2e2e8b3b026791ce0782f27a15 100644
--- a/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/bubble-timeline.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Brackets } from 'typeorm';
-import type { NotesRepository } from '@/models/_.js';
+import type { NotesRepository, MiMeta } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
@@ -9,7 +9,6 @@ import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '../../error.js';
 import { CacheService } from '@/core/CacheService.js';
-import { MetaService } from '@/core/MetaService.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -51,6 +50,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,
 
@@ -59,11 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private roleService: RoleService,
 		private activeUsersChart: ActiveUsersChart,
 		private cacheService: CacheService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const policies = await this.roleService.getUserPolicies(me ? me.id : null);
-			const instance = await this.metaService.fetch();
 			if (!policies.btlAvailable) {
 				throw new ApiError(meta.errors.btlDisabled);
 			}
@@ -79,7 +79,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
 				.andWhere('note.visibility = \'public\'')
 				.andWhere('note.channelId IS NULL')
-				.andWhere('note.userHost IN (:...hosts)', { hosts: instance.bubbleInstances })
+				.andWhere('note.userHost IN (:...hosts)', { hosts: this.serverSettings.bubbleInstances })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
@@ -97,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			if (!ps.withBots) query.andWhere('user.isBot = FALSE');
-			
+
 			if (ps.withRenotes === false) {
 				query.andWhere(new Brackets(qb => {
 					qb.where('note.renoteId IS NULL');
diff --git a/packages/backend/src/server/api/endpoints/notes/create.test.ts b/packages/backend/src/server/api/endpoints/notes/create.test.ts
index f3d887bb200af9f3c8d00272c280d53b0b63fd0a..18d80e867ba222ddfaf806c6b8b30920136e5219 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.test.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.test.ts
@@ -5,15 +5,12 @@
 
 process.env.NODE_ENV = 'test';
 
-import { readFile } from 'node:fs/promises';
-import { fileURLToPath } from 'node:url';
-import { dirname } from 'node:path';
 import { describe, test, expect } from '@jest/globals';
+import { loadConfig } from '@/config.js';
 import { getValidator } from '../../../../../test/prelude/get-api-validator.js';
 import { paramDef } from './create.js';
 
-const _filename = fileURLToPath(import.meta.url);
-const _dirname = dirname(_filename);
+const config = loadConfig();
 
 const VALID = true;
 const INVALID = false;
@@ -21,7 +18,12 @@ const INVALID = false;
 describe('api:notes/create', () => {
 	describe('validation', () => {
 		const v = getValidator(paramDef);
-		const tooLong = readFile(_dirname + '/../../../../../test/resources/misskey.svg', 'utf-8');
+		const tooLong = (limit: number) => {
+			const arr: string[] = [''];
+			arr.length = limit + 1;
+			arr.fill('a');
+			return arr.join('');
+		};
 
 		test('reject empty', () => {
 			const valid = v({ });
@@ -71,8 +73,8 @@ describe('api:notes/create', () => {
 					.toBe(INVALID);
 			});
 
-			test('over 500 characters cw', async () => {
-				expect(v({ text: 'Body', cw: await tooLong }))
+			test('over max characters cw', async () => {
+				expect(v({ text: '', cw: tooLong(config.maxNoteLength) }))
 					.toBe(INVALID);
 			});
 		});
@@ -220,7 +222,7 @@ describe('api:notes/create', () => {
 			});
 
 			test('reject poll with too long choice', async () => {
-				expect(v({ poll: { choices: [await tooLong, '2'] } }))
+				expect(v({ poll: { choices: [tooLong(config.maxNoteLength), '2'] } }))
 					.toBe(INVALID);
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 626f03b758690542d1301aa6adaaf467c0b55179..d1cf0123dc306950678468a644f2294ee7188fa7 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';
 
@@ -92,6 +90,12 @@ export const meta = {
 			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
 		},
 
+		maxCwLength: {
+			message: 'You tried posting a content warning which is too long.',
+			code: 'MAX_CW_LENGTH',
+			id: '7004c478-bda3-4b4f-acb2-4316398c9d52',
+		},
+
 		cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
 			message: 'You cannot reply to a specified visibility note with extended visibility.',
 			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
@@ -149,7 +153,7 @@ export const paramDef = {
 		visibleUserIds: { type: 'array', uniqueItems: true, items: {
 			type: 'string', format: 'misskey:id',
 		} },
-		cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
+		cw: { type: 'string', nullable: true, minLength: 1 },
 		localOnly: { type: 'boolean', default: false },
 		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
 		noExtractMentions: { type: 'boolean', default: false },
@@ -252,9 +256,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteCreateService: NoteCreateService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			if (ps.text && (ps.text.length > this.config.maxNoteLength)) {
+			if (ps.text && ps.text.length > this.config.maxNoteLength) {
 				throw new ApiError(meta.errors.maxLength);
 			}
+			if (ps.cw && ps.cw.length > this.config.maxCwLength) {
+				throw new ApiError(meta.errors.maxCwLength);
+			}
 
 			let visibleUsers: MiUser[] = [];
 			if (ps.visibleUserIds) {
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 835cbc14facfea71eb2cbb9bda5d6871b4f1fcea..dc94c78e7570cd01cc11c4a5b59e55eb2c8d9476 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -86,6 +86,12 @@ export const meta = {
 			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
 		},
 
+		maxCwLength: {
+			message: 'You tried posting a content warning which is too long.',
+			code: 'MAX_CW_LENGTH',
+			id: '7004c478-bda3-4b4f-acb2-4316398c9d52',
+		},
+
 		cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
 			message: 'You cannot reply to a specified visibility note with extended visibility.',
 			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
@@ -197,7 +203,7 @@ export const paramDef = {
 				format: 'misskey:id',
 			},
 		},
-		cw: { type: 'string', nullable: true, minLength: 1, maxLength: 500 },
+		cw: { type: 'string', nullable: true, minLength: 1 },
 		localOnly: { type: 'boolean', default: false },
 		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
 		noExtractMentions: { type: 'boolean', default: false },
@@ -297,9 +303,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteEditService: NoteEditService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			if (ps.text && (ps.text.length > this.config.maxNoteLength)) {
+			if (ps.text && ps.text.length > this.config.maxNoteLength) {
 				throw new ApiError(meta.errors.maxLength);
 			}
+			if (ps.cw && ps.cw.length > this.config.maxCwLength) {
+				throw new ApiError(meta.errors.maxCwLength);
+			}
+
 			let visibleUsers: MiUser[] = [];
 			if (ps.visibleUserIds) {
 				visibleUsers = await this.usersRepository.findBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/following.ts b/packages/backend/src/server/api/endpoints/notes/following.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b6604b9798b958214c76fa2544faa17aa33de7d1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/notes/following.ts
@@ -0,0 +1,178 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
+import { SkLatestNote, MiFollowing } from '@/models/_.js';
+import type { 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 { QueryService } from '@/core/QueryService.js';
+import { ApiError } from '@/server/api/error.js';
+
+export const meta = {
+	tags: ['notes'],
+
+	requireCredential: true,
+	kind: 'read:account',
+	allowGet: true,
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'Note',
+		},
+	},
+
+	errors: {
+		bothWithRepliesAndWithFiles: {
+			message: 'Specifying both includeReplies and filesOnly is not supported',
+			code: 'BOTH_INCLUDE_REPLIES_AND_FILES_ONLY',
+			id: '91c8cb9f-36ed-46e7-9ca2-7df96ed6e222',
+		},
+		bothWithFollowersAndIncludeNonPublic: {
+			message: 'Specifying both list:followers and includeNonPublic is not supported',
+			code: 'BOTH_LIST_FOLLOWERS_AND_INCLUDE_NON_PUBLIC',
+			id: '7a1b9cb6-235b-4e58-9c00-32c1796f502c',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		list: { type: 'string', enum: ['following', 'followers', 'mutuals'], default: 'following' },
+
+		filesOnly: { type: 'boolean', default: false },
+		includeNonPublic: { type: 'boolean', default: false },
+		includeReplies: { type: 'boolean', default: false },
+		includeQuotes: { type: 'boolean', default: false },
+		includeBots: { type: 'boolean', default: true },
+
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		sinceDate: { type: 'integer' },
+		untilDate: { type: 'integer' },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
+		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			if (ps.includeReplies && ps.filesOnly) throw new ApiError(meta.errors.bothWithRepliesAndWithFiles);
+			if (ps.list === 'followers' && ps.includeNonPublic) throw new ApiError(meta.errors.bothWithFollowersAndIncludeNonPublic);
+
+			const query = this.notesRepository
+				.createQueryBuilder('note')
+				.setParameter('me', me.id)
+
+				// Limit to latest notes
+				.innerJoin(
+					(sub: SelectQueryBuilder<SkLatestNote>) => {
+						sub
+							.from(SkLatestNote, 'latest')
+
+							// Return only one note per user
+							.addSelect('latest.user_id', 'user_id')
+							.addSelect('MAX(latest.note_id)', 'note_id')
+							.groupBy('latest.user_id');
+
+						// Match selected note types.
+						if (!ps.includeNonPublic) {
+							sub.andWhere('latest.is_public = true');
+						}
+						if (!ps.includeReplies) {
+							sub.andWhere('latest.is_reply = false');
+						}
+						if (!ps.includeQuotes) {
+							sub.andWhere('latest.is_quote = false');
+						}
+
+						return sub;
+					},
+					'latest',
+					'note.id = latest.note_id',
+				)
+
+				// Avoid N+1 queries from the "pack" method
+				.innerJoinAndSelect('note.user', 'user')
+				.leftJoinAndSelect('note.reply', 'reply')
+				.leftJoinAndSelect('note.renote', 'renote')
+				.leftJoinAndSelect('reply.user', 'replyUser')
+				.leftJoinAndSelect('renote.user', 'renoteUser')
+				.leftJoinAndSelect('note.channel', 'channel')
+			;
+
+			// Select the appropriate collection of users
+			if (ps.list === 'followers') {
+				addFollower(query);
+			} else if (ps.list === 'following') {
+				addFollowee(query);
+			} else {
+				addMutual(query);
+			}
+
+			// Limit to files, if requested
+			if (ps.filesOnly) {
+				query.andWhere('note."fileIds" != \'{}\'');
+			}
+
+			// Match selected user types.
+			if (!ps.includeBots) {
+				query.andWhere('"user"."isBot" = false');
+			}
+
+			// Respect blocks and mutes
+			this.queryService.generateBlockedUserQuery(query, me);
+			this.queryService.generateMutedUserQuery(query, me);
+
+			// Support pagination
+			this.queryService
+				.makePaginationQuery(query, ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				.orderBy('note.id', 'DESC')
+				.take(ps.limit);
+
+			// Query and return the next page
+			const notes = await query.getMany();
+			return await this.noteEntityService.packMany(notes, me);
+		});
+	}
+}
+
+/**
+ * Limit to followers (they follow us)
+ */
+function addFollower<T extends SelectQueryBuilder<ObjectLiteral>>(query: T): T {
+	return query.innerJoin(MiFollowing, 'follower', 'follower."followerId" = latest.user_id AND follower."followeeId" = :me');
+}
+
+/**
+ * Limit to followees (we follow them)
+ */
+function addFollowee<T extends SelectQueryBuilder<ObjectLiteral>>(query: T): T {
+	return query.innerJoin(MiFollowing, 'followee', 'followee."followerId" = :me AND followee."followeeId" = latest.user_id');
+}
+
+/**
+ * Limit to mutuals (they follow us AND we follow them)
+ */
+function addMutual<T extends SelectQueryBuilder<ObjectLiteral>>(query: T): T {
+	addFollower(query);
+	addFollowee(query);
+	return query;
+}
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 fdc9a779564ba67ac5fd47e41d23aee91b1ffd6f..75be7b988887f8f923164ded3e9435e471a456ed 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';
@@ -75,6 +74,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,
 
@@ -88,7 +90,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) => {
@@ -102,9 +103,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,
@@ -158,7 +157,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,
 				excludeBots: !ps.withBots,
diff --git a/packages/backend/src/server/api/endpoints/notes/like.ts b/packages/backend/src/server/api/endpoints/notes/like.ts
index 17ee93736070a79ac0e8eb1c8606797e54d836c2..593463aea0ac919a101a884e348e171a3a1e64e0 100644
--- a/packages/backend/src/server/api/endpoints/notes/like.ts
+++ b/packages/backend/src/server/api/endpoints/notes/like.ts
@@ -1,8 +1,9 @@
-import { Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { ReactionService } from '@/core/ReactionService.js';
-import { MetaService } from '@/core/MetaService.js';
+import type { MiMeta } from '@/models/_.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -26,6 +27,12 @@ export const meta = {
 			code: 'YOU_HAVE_BEEN_BLOCKED',
 			id: '20ef5475-9f38-4e4c-bd33-de6d979498ec',
 		},
+
+		cannotReactToRenote: {
+			message: 'You cannot like a Renote.',
+			code: 'CANNOT_REACT_TO_RENOTE',
+			id: 'eaccdc08-ddef-43fe-908f-d108faad57f5',
+		},
 	},
 } as const;
 
@@ -41,13 +48,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,
+
 		private getterService: GetterService,
 		private reactionService: ReactionService,
-		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const instance = await this.metaService.fetch();
-			const like = ps.override ?? instance.defaultLike;
+			const like = ps.override ?? this.serverSettings.defaultLike;
 			const note = await this.getterService.getNote(ps.noteId).catch(err => {
 				if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 				throw err;
@@ -58,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					return;
 				}
 				if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked);
+				if (err.id === '12c35529-3c79-4327-b1cc-e2cf63a71925') throw new ApiError(meta.errors.cannotReactToRenote);
 				throw err;
 			});
 			return;
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 5c3c7ae7d0c457f38c138d99889ba780f3804978..d4c806d7e2af430131fce6df7a6fe8f7d90bb86c 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';
@@ -67,6 +65,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,
 
@@ -74,10 +75,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);
@@ -90,9 +89,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,
@@ -117,7 +114,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/polls/refresh.ts b/packages/backend/src/server/api/endpoints/notes/polls/refresh.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b96691f894b906dfb7d4077eee923648bae6ac27
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/notes/polls/refresh.ts
@@ -0,0 +1,98 @@
+/*
+ * SPDX-FileCopyrightText: marie and sharkey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { UserBlockingService } from '@/core/UserBlockingService.js';
+import { ApQuestionService } from '@/core/activitypub/models/ApQuestionService.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { ApiError } from '../../../error.js';
+
+export const meta = {
+	tags: ['notes'],
+
+	requireCredential: true,
+
+	prohibitMoved: true,
+
+	kind: 'read:federation',
+
+	errors: {
+		noSuchNote: {
+			message: 'No such note.',
+			code: 'NO_SUCH_NOTE',
+			id: 'ecafbd2e-c283-4d6d-aecb-1a0a33b75396',
+		},
+
+		noPoll: {
+			message: 'The note does not attach a poll.',
+			code: 'NO_POLL',
+			id: '5f979967-52d9-4314-a911-1c673727f92f',
+		},
+
+		noUri: {
+			message: 'The note has no URI defined.',
+			code: 'INVALID_URI',
+			id: 'e0cc9a04-f2e8-41e4-a5f1-4127293260ca',
+		},
+
+		youHaveBeenBlocked: {
+			message: 'You cannot refresh this poll because you have been blocked by this user.',
+			code: 'YOU_HAVE_BEEN_BLOCKED',
+			id: '85a5377e-b1e9-4617-b0b9-5bea73331e49',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		noteId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['noteId'],
+} as const;
+
+// TODO: ロジックをサービスに切り出す
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private getterService: GetterService,
+		private userBlockingService: UserBlockingService,
+		private apQuestionService: ApQuestionService,
+		private noteEntityService: NoteEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			// Get note
+			const note = await this.getterService.getNote(ps.noteId).catch(err => {
+				if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+				throw err;
+			});
+
+			if (!note.hasPoll) {
+				throw new ApiError(meta.errors.noPoll);
+			}
+
+			// Check blocking
+			if (note.userId !== me.id) {
+				const blocked = await this.userBlockingService.checkBlocked(note.userId, me.id);
+				if (blocked) {
+					throw new ApiError(meta.errors.youHaveBeenBlocked);
+				}
+			}
+
+			if (!note.uri) {
+				throw new ApiError(meta.errors.noUri);
+			}
+
+			await this.apQuestionService.updateQuestion(note.uri);
+
+			return await this.noteEntityService.pack(note, me, {
+				detail: true,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index 55ff6771b1a2313f843fba3099b141a75c330ec5..2b4885a194f2123a167203f930e0d5538c05e0ab 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -5,14 +5,13 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository } from '@/models/_.js';
+import type { NotesRepository, MiMeta } from '@/models/_.js';
 import { safeForSql } from '@/misc/safe-for-sql.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
-import { MetaService } from '@/core/MetaService.js';
 import { CacheService } from '@/core/CacheService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 
@@ -69,18 +68,18 @@ 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,
 
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
-		private metaService: MetaService,
 		private cacheService: CacheService,
 		private utilityService: UtilityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const meta = await this.metaService.fetch(true);
-
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 				.andWhere('note.visibility = \'public\'')
 				.innerJoinAndSelect('note.user', 'user')
@@ -89,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser');
 
-			if (!meta.enableBotTrending) query.andWhere('user.isBot = FALSE');
+			if (!this.serverSettings.enableBotTrending) query.andWhere('user.isBot = FALSE');
 
 			this.queryService.generateVisibilityQuery(query, me);
 			if (me) this.queryService.generateMutedUserQuery(query, me);
@@ -156,8 +155,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			notes = notes.filter(note => {
 				if (note.user?.isSilenced && me && followings && note.userId !== me.id && !followings[note.userId]) return false;
 				if (note.user?.isSuspended) return false;
-				if (this.utilityService.isBlockedHost(meta.blockedHosts, note.userHost)) return false;
-				if (this.utilityService.isSilencedHost(meta.silencedHosts, note.userHost)) return false;
+				if (note.userHost) {
+					if (!this.utilityService.isFederationAllowedHost(note.userHost)) return false;
+					if (this.utilityService.isSilencedHost(this.serverSettings.silencedHosts, note.userHost)) return false;
+				}
 				return true;
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index 1a14703e6eb2647d54e568b20465b87830012454..d40a04c1b1f22006d042659960d4b36920645bde 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 = {
@@ -57,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,
 
@@ -70,15 +72,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,
@@ -110,7 +109,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 d6ef655291f4a2382ff6eb67a049ece4b22f6866..234248db5c0740390ba252bf2906ec9a63606a11 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,13 +87,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				return;
 			}
 
-			const instance = await this.metaService.fetch();
-
-			if (instance.deeplAuthKey == null && !instance.deeplFreeMode) {
+			if (this.serverSettings.deeplAuthKey == null && !this.serverSettings.deeplFreeMode) {
 				throw new ApiError(meta.errors.unavailable);
 			}
 
-			if (instance.deeplFreeMode && !instance.deeplFreeInstance) {
+			if (this.serverSettings.deeplFreeMode && !this.serverSettings.deeplFreeInstance) {
 				throw new ApiError(meta.errors.unavailable);
 			}
 
@@ -98,11 +99,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
 
 			const params = new URLSearchParams();
-			if (instance.deeplAuthKey) params.append('auth_key', instance.deeplAuthKey);
+			if (this.serverSettings.deeplAuthKey) params.append('auth_key', this.serverSettings.deeplAuthKey);
 			params.append('text', note.text);
 			params.append('target_lang', targetLang);
 
-			const endpoint = instance.deeplFreeMode && instance.deeplFreeInstance ? instance.deeplFreeInstance : instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
+			const endpoint = this.serverSettings.deeplFreeMode && this.serverSettings.deeplFreeInstance ? this.serverSettings.deeplFreeInstance : 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',
@@ -112,7 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				},
 				body: params.toString(),
 			});
-			if (instance.deeplAuthKey) {
+			if (this.serverSettings.deeplAuthKey) {
 				const json = (await res.json()) as {
 					translations: {
 						detected_source_language: string;
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 43877e61efaf4fc0bcf5f6cc3a27b0c1415049ec..87f9b322a60289adebfbfdf706d8a276e8f14281 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 15832ef7f8d7b3b120ea8b57f8288efad44fa838..5b0b656c63fe069bb9392f20a9d32b6163bdfa7e 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 c13802eb0689d51da2936cbab5a143f964af9e30..8301c85f2eb6e61127a0f0aef328d6b4f6eea752 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/sponsors.ts b/packages/backend/src/server/api/endpoints/sponsors.ts
index 99414e739abd381853ce4c76e552bc9ae47ae7b4..2a8a461a8f2c7d607329273db5208c669d9df4c6 100644
--- a/packages/backend/src/server/api/endpoints/sponsors.ts
+++ b/packages/backend/src/server/api/endpoints/sponsors.ts
@@ -3,14 +3,13 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Inject, Injectable } from '@nestjs/common';
-import * as Redis from 'ioredis';
+import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { DI } from '@/di-symbols.js';
+import { SponsorsService } from '@/core/SponsorsService.js';
 
 export const meta = {
 	tags: ['meta'],
-	description: 'Get Sharkey Sponsors',
+	description: 'Get Sharkey Sponsors or Instance Sponsors',
 
 	requireCredential: false,
 	requireCredentialPrivateMode: false,
@@ -20,6 +19,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		forceUpdate: { type: 'boolean', default: false },
+		instance: { type: 'boolean', default: false },
 	},
 	required: [],
 } as const;
@@ -27,31 +27,14 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-        @Inject(DI.redis) private redisClient: Redis.Redis,
+		private sponsorsService: SponsorsService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			let totalSponsors;
-			const cachedSponsors = await this.redisClient.get('sponsors');	
-
-			if (!ps.forceUpdate && cachedSponsors) {
-				totalSponsors = JSON.parse(cachedSponsors);
+			if (ps.instance) {
+				return { sponsor_data: await this.sponsorsService.instanceSponsors(ps.forceUpdate) };
 			} else {
-				try {
-					const backers = await fetch('https://opencollective.com/sharkey/tiers/backer/all.json').then((response) => response.json());
-					const sponsorsOC = await fetch('https://opencollective.com/sharkey/tiers/sponsor/all.json').then((response) => response.json());
-
-					// Merge both together into one array and make sure it only has Active subscriptions
-					const allSponsors = [...sponsorsOC, ...backers].filter(sponsor => sponsor.isActive === true);
-
-					// Remove possible duplicates
-					totalSponsors = [...new Map(allSponsors.map(v => [v.profile, v])).values()];
-
-					await this.redisClient.set('sponsors', JSON.stringify(totalSponsors), 'EX', 3600);
-				} catch (error) {
-					totalSponsors = [];
-				}
+				return { sponsor_data: await this.sponsorsService.sharkeySponsors(ps.forceUpdate) };
 			}
-			return { sponsor_data: totalSponsors };
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index a9a33149f95ac7aada8a087acbcd7b7ea5aaa22b..fd76df2d3c0b1adb19e54aac2b1781adf1154a5a 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 affb0996f182a477ebc02c4ac3f592b064951e97..4944be9b05417615afddb40466b1093739e307ca 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 cc76c12f1d897e3eb3ab3aa7188f6aee2af90474..263d0629617116b6f6e09fa5ca2432c1a58896c5 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -5,18 +5,18 @@
 
 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';
 import { ApiError } from '@/server/api/error.js';
+import { isQuote, isRenote } from '@/misc/is-renote.js';
 
 export const meta = {
 	tags: ['users', 'notes'],
@@ -51,7 +51,11 @@ export const paramDef = {
 	properties: {
 		userId: { type: 'string', format: 'misskey:id' },
 		withReplies: { type: 'boolean', default: false },
+		withRepliesToSelf: { type: 'boolean', default: true },
+		withQuotes: { type: 'boolean', default: true },
 		withRenotes: { type: 'boolean', default: true },
+		withBots: { type: 'boolean', default: true },
+		withNonPublic: { type: 'boolean', default: true },
 		withChannelNotes: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -67,6 +71,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 +82,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 +98,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,
@@ -103,6 +107,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					withChannelNotes: ps.withChannelNotes,
 					withFiles: ps.withFiles,
 					withRenotes: ps.withRenotes,
+					withQuotes: ps.withQuotes,
+					withBots: ps.withBots,
+					withNonPublic: ps.withNonPublic,
+					withRepliesToOthers: ps.withReplies,
+					withRepliesToSelf: ps.withRepliesToSelf,
 				}, me);
 
 				return await this.noteEntityService.packMany(timeline, me);
@@ -127,11 +136,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				excludeReplies: ps.withChannelNotes && !ps.withReplies, // userTimelineWithChannel may include replies
 				excludeNoFiles: ps.withChannelNotes && ps.withFiles, // userTimelineWithChannel may include notes without files
 				excludePureRenotes: !ps.withRenotes,
+				excludeBots: !ps.withBots,
 				noteFilter: note => {
 					if (note.channel?.isSensitive && !isSelf) return false;
 					if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
 					if (note.visibility === 'followers' && !isFollowing && !isSelf) return false;
 
+					// These are handled by DB fallback, but we duplicate them here in case a timeline was already populated with notes
+					if (!ps.withRepliesToSelf && note.reply?.userId === note.userId) return false;
+					if (!ps.withQuotes && isRenote(note) && isQuote(note)) return false;
+					if (!ps.withNonPublic && note.visibility !== 'public') return false;
+
 					return true;
 				},
 				dbFallback: async (untilId, sinceId, limit) => await this.getFromDb({
@@ -142,6 +157,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					withChannelNotes: ps.withChannelNotes,
 					withFiles: ps.withFiles,
 					withRenotes: ps.withRenotes,
+					withQuotes: ps.withQuotes,
+					withBots: ps.withBots,
+					withNonPublic: ps.withNonPublic,
+					withRepliesToOthers: ps.withReplies,
+					withRepliesToSelf: ps.withRepliesToSelf,
 				}, me),
 			});
 
@@ -157,6 +177,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		withChannelNotes: boolean,
 		withFiles: boolean,
 		withRenotes: boolean,
+		withQuotes: boolean,
+		withBots: boolean,
+		withNonPublic: boolean,
+		withRepliesToOthers: boolean,
+		withRepliesToSelf: boolean,
 	}, me: MiLocalUser | null) {
 		const isSelf = me && (me.id === ps.userId);
 
@@ -188,7 +213,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			query.andWhere('note.fileIds != \'{}\'');
 		}
 
-		if (ps.withRenotes === false) {
+		if (!ps.withRenotes && !ps.withQuotes) {
+			query.andWhere('note.renoteId IS NULL');
+		} else if (!ps.withRenotes) {
 			query.andWhere(new Brackets(qb => {
 				qb.orWhere('note.userId != :userId', { userId: ps.userId });
 				qb.orWhere('note.renoteId IS NULL');
@@ -196,6 +223,35 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				qb.orWhere('note.fileIds != \'{}\'');
 				qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
 			}));
+		} else if (!ps.withQuotes) {
+			query.andWhere(`
+				(
+					note."renoteId" IS NULL
+					OR (
+						note.text IS NULL
+						AND note.cw IS NULL
+						AND note."replyId" IS NULL
+						AND note."hasPoll" IS FALSE
+						AND note."fileIds" = '{}'
+					)
+				)
+			`);
+		}
+
+		if (!ps.withRepliesToOthers && !ps.withRepliesToSelf) {
+			query.andWhere('reply.id IS NULL');
+		} else if (!ps.withRepliesToOthers) {
+			query.andWhere('(reply.id IS NULL OR reply."userId" = note."userId")');
+		} else if (!ps.withRepliesToSelf) {
+			query.andWhere('(reply.id IS NULL OR reply."userId" != note."userId")');
+		}
+
+		if (!ps.withNonPublic) {
+			query.andWhere('note.visibility = \'public\'');
+		}
+
+		if (!ps.withBots) {
+			query.andWhere('"user"."isBot" = false');
 		}
 
 		return await query.limit(ps.limit).getMany();
diff --git a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts
index 8c9cca173066b8fbac1af36bf551a17794b5ef94..b40e4cdaa4590369e7c637cb81afbe79b2fdc0be 100644
--- a/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts
+++ b/packages/backend/src/server/api/mastodon/MastodonApiServerService.ts
@@ -8,11 +8,10 @@ import megalodon, { Entity, MegalodonInterface } from 'megalodon';
 import querystring from 'querystring';
 import { IsNull } from 'typeorm';
 import multer from 'fastify-multer';
-import type { AccessTokensRepository, NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { AccessTokensRepository, NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository, MiMeta } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 import type { Config } from '@/config.js';
-import { MetaService } from '@/core/MetaService.js';
 import { convertAnnouncement, convertFilter, convertAttachment, convertFeaturedTag, convertList, MastoConverters } from './converters.js';
 import { getInstance } from './endpoints/meta.js';
 import { ApiAuthMastodon, ApiAccountMastodon, ApiFilterMastodon, ApiNotifyMastodon, ApiSearchMastodon, ApiTimelineMastodon, ApiStatusMastodon } from './endpoints.js';
@@ -31,6 +30,8 @@ export function getClient(BASE_URL: string, authorization: string | undefined):
 @Injectable()
 export class MastodonApiServerService {
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
         @Inject(DI.usersRepository)
         private usersRepository: UsersRepository,
 		@Inject(DI.notesRepository)
@@ -43,7 +44,6 @@ export class MastodonApiServerService {
 		private accessTokensRepository: AccessTokensRepository,
         @Inject(DI.config)
         private config: Config,
-        private metaService: MetaService,
 		private userEntityService: UserEntityService,
 		private driveService: DriveService,
 		private mastoConverter: MastoConverters,
@@ -112,7 +112,7 @@ export class MastodonApiServerService {
 					order: { id: 'ASC' },
 				});
 				const contact = admin == null ? null : await this.mastoConverter.convertAccount((await client.getAccount(admin.id)).data);
-				reply.send(await getInstance(data.data, contact as Entity.Account, this.config, await this.metaService.fetch()));
+				reply.send(await getInstance(data.data, contact as Entity.Account, this.config, this.serverSettings));
 			} catch (e: any) {
 				/* console.error(e); */
 				reply.code(401).send(e.response.data);
diff --git a/packages/backend/src/server/api/mastodon/endpoints/meta.ts b/packages/backend/src/server/api/mastodon/endpoints/meta.ts
index 79d2c62a241fb0b826b20c188ab877bb78a12c56..c9833b85d7c07d733c27f94751001a655b54d645 100644
--- a/packages/backend/src/server/api/mastodon/endpoints/meta.ts
+++ b/packages/backend/src/server/api/mastodon/endpoints/meta.ts
@@ -54,7 +54,7 @@ export async function getInstance(
 			},
 			polls: {
 				max_options: 10,
-				max_characters_per_option: 50,
+				max_characters_per_option: 150,
 				min_expiration: 50,
 				max_expiration: 2629746,
 			},
diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
index 647e9cab8114787cef22d2b8ead602f1ca8a9051..8693f0c6ac0dc8aa97a53445d9adc68df043c6ad 100644
--- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
@@ -3,7 +3,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { MetaService } from '@/core/MetaService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 226e161122fa92ae701e5e40d262f4d86440ff0f..9939aa49ee4f2e3f79831e5895db76d97ee2989b 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -30,7 +30,7 @@ class ChannelChannel extends Channel {
 	}
 
 	@bindThis
-	public async init(params: any) {
+	public async init(params: JsonObject) {
 		if (typeof params.channelId !== 'string') return;
 		this.channelId = params.channelId;
 		this.withFiles = !!(params.withFiles ?? false);
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 0af90a844b0c9fe0569c1b864f4c38aee2cf2fb9..c14f6c9123bb98b065e2eb724b288e770ab50bf0 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,7 +24,6 @@ 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,
@@ -61,7 +60,8 @@ const staticAssets = `${_dirname}/../../../assets/`;
 const clientAssets = `${_dirname}/../../../../frontend/assets/`;
 const assets = `${_dirname}/../../../../../built/_frontend_dist_/`;
 const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
-const viteOut = `${_dirname}/../../../../../built/_vite_/`;
+const frontendViteOut = `${_dirname}/../../../../../built/_frontend_vite_/`;
+const frontendEmbedViteOut = `${_dirname}/../../../../../built/_frontend_embed_vite_/`;
 const tarball = `${_dirname}/../../../../../built/tarball/`;
 
 @Injectable()
@@ -72,6 +72,9 @@ export class ClientServerService {
 		@Inject(DI.config)
 		private config: Config,
 
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -108,7 +111,6 @@ export class ClientServerService {
 		private clipEntityService: ClipEntityService,
 		private channelEntityService: ChannelEntityService,
 		private reversiGameEntityService: ReversiGameEntityService,
-		private metaService: MetaService,
 		private urlPreviewService: UrlPreviewService,
 		private feedService: FeedService,
 		private roleService: RoleService,
@@ -128,39 +130,37 @@ 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',
 			}, {
 				// 空文字列の場合右辺を使いたいため
 				// 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': '300x300',
 				'type': 'image/png',
 				'purpose': 'any',
@@ -179,7 +179,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');
@@ -193,9 +193,9 @@ export class ClientServerService {
 			icon: meta.iconUrl,
 			appleTouchIcon: meta.app512IconUrl,
 			themeColor: meta.themeColor,
-			serverErrorImageUrl: meta.serverErrorImageUrl ?? 'https://launcher.moe/error.png',
-			infoImageUrl: meta.infoImageUrl ?? 'https://launcher.moe/nothinghere.png',
-			notFoundImageUrl: meta.notFoundImageUrl ?? 'https://launcher.moe/missingpage.webp',
+			serverErrorImageUrl: meta.serverErrorImageUrl ?? '/client-assets/status/error.png',
+			infoImageUrl: meta.infoImageUrl ?? '/client-assets/status/nothinghere.png',
+			notFoundImageUrl: meta.notFoundImageUrl ?? '/client-assets/status/missingpage.webp',
 			instanceUrl: this.config.url,
 			randomMOTD: this.config.customMOTD ? this.config.customMOTD[Math.floor(Math.random() * this.config.customMOTD.length)] : undefined,
 			metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)),
@@ -242,7 +242,7 @@ export class ClientServerService {
 			}
 		});
 
-		const serverAdapter = new FastifyAdapter();
+		const bullBoardServerAdapter = new BullBoardFastifyAdapter();
 
 		createBullBoard({
 			queues: [
@@ -255,11 +255,11 @@ export class ClientServerService {
 				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, {
@@ -280,15 +280,22 @@ export class ClientServerService {
 		});
 
 		//#region vite assets
-		if (this.config.clientManifestExists) {
+		if (this.config.frontendEmbedManifestExists) {
 			fastify.register((fastify, options, done) => {
 				fastify.register(fastifyStatic, {
-					root: viteOut,
+					root: frontendViteOut,
 					prefix: '/vite/',
 					maxAge: ms('30 days'),
 					immutable: true,
 					decorateReply: false,
 				});
+				fastify.register(fastifyStatic, {
+					root: frontendEmbedViteOut,
+					prefix: '/embed_vite/',
+					maxAge: ms('30 days'),
+					immutable: true,
+					decorateReply: false,
+				});
 				fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
 				done();
 			});
@@ -299,6 +306,13 @@ export class ClientServerService {
 				prefix: '/vite',
 				rewritePrefix: '/vite',
 			});
+
+			const embedPort = (process.env.EMBED_VITE_PORT ?? '5174');
+			fastify.register(fastifyProxy, {
+				upstream: 'http://localhost:' + embedPort,
+				prefix: '/embed_vite',
+				rewritePrefix: '/embed_vite',
+			});
 		}
 		//#endregion
 
@@ -443,15 +457,20 @@ export class ClientServerService {
 		// Manifest
 		fastify.get('/manifest.json', async (request, reply) => await this.manifestHandler(reply));
 
+		// Embed Javascript
+		fastify.get('/embed.js', async (request, reply) => {
+			return await reply.sendFile('/embed.js', staticAssets, {
+				maxAge: ms('1 day'),
+			});
+		});
+
 		fastify.get('/robots.txt', async (request, reply) => {
 			return await reply.sendFile('/robots.txt', staticAssets);
 		});
 
 		// OpenSearch XML
 		fastify.get('/opensearch.xml', async (request, reply) => {
-			const meta = await this.metaService.fetch();
-
-			const name = meta.name ?? 'Sharkey';
+			const name = this.meta.name ?? 'Sharkey';
 			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>`;
@@ -468,14 +487,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 ?? 'Sharkey',
+				desc: this.meta.description,
+				...await this.generateCommonPugData(this.meta),
 				...data,
 			});
 		};
@@ -553,7 +571,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?:/))
@@ -569,7 +586,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 {
 				// リモートユーザーなので
@@ -607,7 +624,6 @@ export class ClientServerService {
 			if (note) {
 				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');
@@ -619,7 +635,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);
@@ -644,7 +660,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 {
@@ -658,7 +673,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);
@@ -674,7 +689,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');
@@ -684,7 +698,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);
@@ -700,7 +714,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');
@@ -710,7 +723,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);
@@ -724,7 +737,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');
@@ -734,7 +746,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);
@@ -749,11 +761,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);
@@ -768,11 +779,10 @@ 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);
@@ -780,7 +790,7 @@ export class ClientServerService {
 		});
 		//#endregion
 
-		//region noindex pages
+		//#region noindex pages
 		// Tags
 		fastify.get<{ Params: { clip: string; } }>('/tags/:tag', async (request, reply) => {
 			return await renderBase(reply, { noindex: true });
@@ -790,21 +800,97 @@ export class ClientServerService {
 		fastify.get<{ Params: { clip: string; } }>('/user-tags/:tag', async (request, reply) => {
 			return await renderBase(reply, { noindex: true });
 		});
-		//endregion
+		//#endregion
 
-		fastify.get('/_info_card_', async (request, reply) => {
-			const meta = await this.metaService.fetch(true);
+		//#region embed pages
+		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 ?? 'Sharkey',
+				...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: this.meta.name ?? 'Sharkey',
+				...await this.generateCommonPugData(this.meta),
+				embedCtx: htmlSafeJsonStringify({
+					note: _note,
+				}),
+			});
+		});
+
+		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 ?? 'Sharkey',
+				...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 ?? 'Sharkey',
+				...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() }),
 			});
 		});
+		//#endregion
 
 		fastify.get('/bios', async (request, reply) => {
 			return await reply.view('bios', {
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index ef804b5bfdbc613814ae3e0e2b325b4c583cf6c9..981fbb435326c90541a2d4588a808779975f1dba 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';
@@ -32,7 +31,9 @@ export class UrlPreviewService {
 		@Inject(DI.redis)
 		private redisClient: Redis.Redis,
 
-		private metaService: MetaService,
+		@Inject(DI.meta)
+		private meta: MiMeta,
+
 		private httpRequestService: HttpRequestService,
 		private loggerService: LoggerService,
 	) {
@@ -75,9 +76,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({
@@ -98,14 +97,14 @@ export class UrlPreviewService {
 			return cached;
 		}
 
-		this.logger.info(meta.urlPreviewSummaryProxyUrl
+		this.logger.info(this.meta.urlPreviewSummaryProxyUrl
 			? `(Proxy) Getting preview of ${key} ...`
 			: `Getting preview of ${key} ...`);
 
 		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/bios.js b/packages/backend/src/server/web/bios.js
index 9ff5dca72ace8c8d9d1c4165722de5d321bb94fd..5dbb26f9e34dc9c21c981325f3a5eea72cc01db5 100644
--- a/packages/backend/src/server/web/bios.js
+++ b/packages/backend/src/server/web/bios.js
@@ -6,36 +6,6 @@
 'use strict';
 
 window.onload = async () => {
-	const account = JSON.parse(localStorage.getItem('account'));
-	const i = account.token;
-
-	const api = (endpoint, data = {}) => {
-		const promise = new Promise((resolve, reject) => {
-			// Append a credential
-			if (i) data.i = i;
-
-			// Send request
-			window.fetch(endpoint.indexOf('://') > -1 ? endpoint : `/api/${endpoint}`, {
-				method: 'POST',
-				body: JSON.stringify(data),
-				credentials: 'omit',
-				cache: 'no-cache'
-			}).then(async (res) => {
-				const body = res.status === 204 ? null : await res.json();
-
-				if (res.status === 200) {
-					resolve(body);
-				} else if (res.status === 204) {
-					resolve();
-				} else {
-					reject(body.error);
-				}
-			}).catch(reject);
-		});
-
-		return promise;
-	};
-
 	const content = document.getElementById('content');
 
 	document.getElementById('ls').addEventListener('click', () => {
diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js
new file mode 100644
index 0000000000000000000000000000000000000000..b07dce3ac42220553fa5d8f8c36b9434a35f2e2e
--- /dev/null
+++ b/packages/backend/src/server/web/boot.embed.js
@@ -0,0 +1,208 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+'use strict';
+
+// ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
+(async () => {
+	window.onerror = (e) => {
+		console.error(e);
+		renderError('SOMETHING_HAPPENED');
+	};
+	window.onunhandledrejection = (e) => {
+		console.error(e);
+		renderError('SOMETHING_HAPPENED_IN_PROMISE');
+	};
+
+	let forceError = localStorage.getItem('forceError');
+	if (forceError != null) {
+		renderError('FORCED_ERROR', 'This error is forced by having forceError in local storage.');
+		return;
+	}
+
+	// パラメータに応じてsplashのスタイルを変更
+	const params = new URLSearchParams(location.search);
+	if (params.has('rounded') && params.get('rounded') === 'false') {
+		document.documentElement.classList.add('norounded');
+	}
+	if (params.has('border') && params.get('border') === 'false') {
+		document.documentElement.classList.add('noborder');
+	}
+
+	// Force update when locales change
+	const langsVersion = LANGS_VERSION;
+	const localeVersion = localStorage.getItem('localeVersion');
+	if (localeVersion !== langsVersion) {
+		console.info(`Updating locales from version ${localeVersion ?? 'N/A'} to ${langsVersion}`);
+		localStorage.removeItem('localeVersion');
+		localStorage.removeItem('locale');
+	}
+
+	//#region Detect language & fetch translations
+	if (!localStorage.getItem('locale')) {
+		const supportedLangs = LANGS;
+		let lang = localStorage.getItem('lang');
+		if (lang == null || !supportedLangs.includes(lang)) {
+			if (supportedLangs.includes(navigator.language)) {
+				lang = navigator.language;
+			} else {
+				lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
+
+				// Fallback
+				if (lang == null) lang = 'en-US';
+			}
+		}
+
+		// for https://github.com/misskey-dev/misskey/issues/10202
+		if (lang == null || lang.toString == null || lang.toString() === 'null') {
+			console.error('invalid lang value detected!!!', typeof lang, lang);
+			lang = 'en-US';
+		}
+
+		const localRes = await window.fetch(`/assets/locales/${lang}.${langsVersion}.json`);
+		if (localRes.status === 200) {
+			localStorage.setItem('lang', lang);
+			localStorage.setItem('locale', await localRes.text());
+			localStorage.setItem('localeVersion', langsVersion);
+		} else {
+			renderError('LOCALE_FETCH');
+			return;
+		}
+	}
+	//#endregion
+
+	//#region Script
+	async function importAppScript() {
+		await import(`/embed_vite/${CLIENT_ENTRY}`)
+			.catch(async e => {
+				console.error(e);
+				renderError('APP_IMPORT');
+			});
+	}
+
+	// タイミングによっては、この時点でDOMの構築が済んでいる場合とそうでない場合とがある
+	if (document.readyState !== 'loading') {
+		importAppScript();
+	} else {
+		window.addEventListener('DOMContentLoaded', () => {
+			importAppScript();
+		});
+	}
+	//#endregion
+
+	async function addStyle(styleText) {
+		let css = document.createElement('style');
+		css.appendChild(document.createTextNode(styleText));
+		document.head.appendChild(css);
+	}
+
+	async function renderError(code) {
+		// Cannot set property 'innerHTML' of null を回避
+		if (document.readyState === 'loading') {
+			await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
+		}
+		document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg>
+		<div class="message">読み込みに失敗しました</div>
+		<div class="submessage">Failed to initialize Sharkey</div>
+		<div class="submessage">Error Code: ${code}</div>
+		<button onclick="location.reload(!0)">
+			<div>リロード</div>
+			<div><small>Reload</small></div>
+		</button>`;
+		addStyle(`
+		#sharkey_app,
+		#splash {
+			display: none !important;
+		}
+
+		html,
+		body {
+			margin: 0;
+		}
+
+		body {
+			position: relative;
+			color: #dee7e4;
+			font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+			line-height: 1.35;
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			justify-content: center;
+			min-height: 100vh;
+			margin: 0;
+			padding: 24px;
+			box-sizing: border-box;
+			overflow: hidden;
+
+			border-radius: var(--radius, 12px);
+			border: 1px solid rgba(231, 255, 251, 0.14);
+		}
+
+		body::before {
+			content: '';
+			position: fixed;
+			top: 0;
+			left: 0;
+			width: 100%;
+			height: 100%;
+			background: #192320;
+			border-radius: var(--radius, 12px);
+			z-index: -1;
+		}
+
+		html.embed.norounded body,
+		html.embed.norounded body::before {
+			border-radius: 0;
+		}
+
+		html.embed.noborder body {
+			border: none;
+		}
+
+		.icon {
+			max-width: 60px;
+			width: 100%;
+			height: auto;
+			margin-bottom: 20px;
+			color: #dec340;
+		}
+
+		.message {
+			text-align: center;
+			font-size: 20px;
+			font-weight: 700;
+			margin-bottom: 20px;
+		}
+
+		.submessage {
+			text-align: center;
+			font-size: 90%;
+			margin-bottom: 7.5px;
+		}
+
+		.submessage:last-of-type {
+			margin-bottom: 20px;
+		}
+
+		button {
+			padding: 7px 14px;
+			min-width: 100px;
+			font-weight: 700;
+			font-family: Hiragino Maru Gothic Pro, BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
+			line-height: 1.35;
+			border-radius: 99rem;
+			background-color: #b4e900;
+			color: #192320;
+			border: none;
+			cursor: pointer;
+			-webkit-tap-highlight-color: transparent;
+		}
+
+		button:hover {
+			background-color: #c6ff03;
+		}`);
+	}
+})();
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 38e37ce093f46978a32ad656adf388bffdd06722..ad92480c1c2b943cc5e63ba2628dd2e291762748 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -3,17 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-/**
- * BOOT LOADER
- * サーバーからレスポンスされるHTMLに埋め込まれるスクリプトで、以下の役割を持ちます。
- * - 翻訳ファイルをフェッチする。
- * - バージョンに基づいて適切なメインスクリプトを読み込む。
- * - キャッシュされたコンパイル済みテーマを適用する。
- * - クライアントの設定値に基づいて対応するHTMLクラス等を設定する。
- * テーマをこの段階で設定するのは、メインスクリプトが読み込まれる間もテーマを適用したいためです。
- * 注: webpackは介さないため、このファイルではrequireやimportは使えません。
- */
-
 'use strict';
 
 // ブロックの中に入れないと、定義した変数がブラウザのグローバルスコープに登録されてしまい邪魔なので
@@ -33,8 +22,17 @@
 		return;
 	}
 
+	// Force update when locales change
+	const langsVersion = LANGS_VERSION;
+	const localeVersion = localStorage.getItem('localeVersion');
+	if (localeVersion !== langsVersion) {
+		console.info(`Updating locales from version ${localeVersion ?? 'N/A'} to ${langsVersion}`);
+		localStorage.removeItem('localeVersion');
+		localStorage.removeItem('locale');
+	}
+
 	//#region Detect language & fetch translations
-	if (!localStorage.hasOwnProperty('locale')) {
+	if (!localStorage.getItem('locale')) {
 		const supportedLangs = LANGS;
 		let lang = localStorage.getItem('lang');
 		if (lang == null || !supportedLangs.includes(lang)) {
@@ -48,37 +46,17 @@
 			}
 		}
 
-		const metaRes = await window.fetch('/api/meta', {
-			method: 'POST',
-			body: JSON.stringify({}),
-			credentials: 'omit',
-			cache: 'no-cache',
-			headers: {
-				'Content-Type': 'application/json',
-			},
-		});
-		if (metaRes.status !== 200) {
-			renderError('META_FETCH');
-			return;
-		}
-		const meta = await metaRes.json();
-		const v = meta.version;
-		if (v == null) {
-			renderError('META_FETCH_V');
-			return;
-		}
-
 		// for https://github.com/misskey-dev/misskey/issues/10202
 		if (lang == null || lang.toString == null || lang.toString() === 'null') {
 			console.error('invalid lang value detected!!!', typeof lang, lang);
 			lang = 'en-US';
 		}
 
-		const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
+		const localRes = await window.fetch(`/assets/locales/${lang}.${langsVersion}.json`);
 		if (localRes.status === 200) {
 			localStorage.setItem('lang', lang);
 			localStorage.setItem('locale', await localRes.text());
-			localStorage.setItem('localeVersion', v);
+			localStorage.setItem('localeVersion', langsVersion);
 		} else {
 			renderError('LOCALE_FETCH');
 			return;
@@ -110,7 +88,7 @@
 	const themeFontFaceName = 'sharkey-theme-font-face';
 	if (theme) {
 		let existingFontFace;
-		document.fonts.forEach((v,k,s)=>{if (v.family === themeFontFaceName) existingFontFace=v;});
+		document.fonts.forEach((v) => { if (v.family === themeFontFaceName) existingFontFace = v;});
 		if (existingFontFace) document.fonts.delete(existingFontFace);
 
 		const themeProps = JSON.parse(theme);
@@ -124,8 +102,8 @@
 			document.fonts.add(fontFace);
 			fontFace.load().catch(
 				(failure) => {
-					console.log(failure)
-				}
+					console.log(failure);
+				},
 			);
 		}
 		for (const [k, v] of Object.entries(themeProps)) {
@@ -192,7 +170,7 @@
 
 		if (!errorsElement) {
 			document.body.innerHTML = `
-			<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-alert-triangle" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+			<svg class="icon-warning" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
 				<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
 				<path d="M12 9v2m0 4v.01"></path>
 				<path d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75"></path>
@@ -202,10 +180,10 @@
 				<span class="button-label-big">Reload / リロード</span>
 			</button>
 			<p><b>The following actions may solve the problem. / 以下を行うと解決する可能性があります。</b></p>
-			<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
 			<p>Update your os and browser / ブラウザおよびOSを最新バージョンに更新する</p>
 			<p>Disable an adblocker / アドブロッカーを無効にする</p>
-	 		<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
+			<p>Clear the browser cache / ブラウザのキャッシュをクリアする</p>
+			<p>&#40;Tor Browser&#41; Set dom.webaudio.enabled to true / dom.webaudio.enabledをtrueに設定する</p>
 			<details style="color: #86b300;">
 				<summary>Other options / その他のオプション</summary>
 				<a href="/flush">
@@ -238,7 +216,7 @@
 		<summary>
 			<code>ERROR CODE: ${code}</code>
 		</summary>
-		<code>${JSON.stringify(details)}</code>`;
+		<code>${details.toString()} ${JSON.stringify(details)}</code>`;
 		errorsElement.appendChild(detailsElement);
 		addStyle(`
 		* {
@@ -346,6 +324,6 @@
 			#errorInfo {
 				width: 50%;
 			}
-		}`)
+		}`);
 	}
 })();
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index b60a6da49a222954b585f40cd23b9fcc77786b34..1cd9cadecf7fca0aafac11a44b2d7f5d34b42bc8 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -47,6 +47,7 @@ html {
 	transform: translateY(80px);
 	color: var(--accent);
 }
+
 #splashSpinner > .spinner {
 	position: absolute;
 	top: 0;
diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css
new file mode 100644
index 0000000000000000000000000000000000000000..a7b110d80a1098e0b2db61174bccd85fe4e1f6a6
--- /dev/null
+++ b/packages/backend/src/server/web/style.embed.css
@@ -0,0 +1,99 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+html {
+	background-color: var(--bg);
+	color: var(--fg);
+}
+
+html.embed {
+	box-sizing: border-box;
+	background-color: transparent;
+	color-scheme: light dark;
+	max-width: 500px;
+}
+
+#splash {
+	position: fixed;
+	z-index: 10000;
+	top: 0;
+	left: 0;
+	width: 100vw;
+	height: 100vh;
+	cursor: wait;
+	background-color: var(--bg);
+	opacity: 1;
+	transition: opacity 0.5s ease;
+}
+
+html.embed #splash {
+	box-sizing: border-box;
+	min-height: 300px;
+	border-radius: var(--radius, 12px);
+	border: 1px solid var(--divider, #e8e8e8);
+}
+
+html.embed.norounded #splash {
+	border-radius: 0;
+}
+
+html.embed.noborder #splash {
+	border: none;
+}
+
+#splashIcon {
+	position: absolute;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	margin: auto;
+	width: 64px;
+	height: 64px;
+	pointer-events: none;
+}
+
+#splashSpinner {
+	position: absolute;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	margin: auto;
+	display: inline-block;
+	width: 28px;
+	height: 28px;
+	transform: translateY(70px);
+	color: var(--accent);
+}
+
+#splashSpinner > .spinner {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 28px;
+	height: 28px;
+	fill-rule: evenodd;
+	clip-rule: evenodd;
+	stroke-linecap: round;
+	stroke-linejoin: round;
+	stroke-miterlimit: 1.5;
+}
+#splashSpinner > .spinner.bg {
+	opacity: 0.275;
+}
+#splashSpinner > .spinner.fg {
+	animation: splashSpinner 0.5s linear infinite;
+}
+
+@keyframes splashSpinner {
+	0% {
+		transform: rotate(0deg);
+	}
+	100% {
+		transform: rotate(360deg);
+	}
+}
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
new file mode 100644
index 0000000000000000000000000000000000000000..6ef9281b8fb6c37f37ede3e9ee9ca0a57ee6dde8
--- /dev/null
+++ b/packages/backend/src/server/web/views/base-embed.pug
@@ -0,0 +1,74 @@
+block vars
+
+block loadClientEntry
+	- const entry = config.frontendEmbedEntry;
+
+doctype html
+
+html(class='embed')
+
+	head
+		meta(charset='utf-8')
+		meta(name='application-name' content='Sharkey')
+		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 || 'Sharkey')
+		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')
+		link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
+		link(rel='stylesheet' href=`/assets/phosphor-icons/bold/style.css?version=${version}`)
+		link(rel='stylesheet' href=`/static-assets/fonts/sharkey-icons/style.css?version=${version}`)
+		link(rel='modulepreload' href=`/embed_vite/${entry.file}`)
+
+		if !config.frontendEmbedManifestExists
+				script(type="module" src="/embed_vite/@vite/client")
+
+		if Array.isArray(entry.css)
+			each href in entry.css
+				link(rel='stylesheet' href=`/embed_vite/${href}`)
+
+		title
+			block title
+				= title || 'Sharkey'
+
+		block meta
+			meta(name='robots' content='noindex')
+
+		style
+			include ../style.embed.css
+
+		script.
+			var VERSION = "#{version}";
+			var CLIENT_ENTRY = "#{entry.file}";
+
+		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
+
+	body
+		noscript: p
+			| JavaScriptを有効にしてください
+			br
+			| Please turn on your JavaScript
+		div#splash
+			img#splashIcon(src= icon || '/static-assets/splash.png')
+			div#splashSpinner
+				<svg class="spinner bg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
+					<g transform="matrix(1,0,0,1,12,12)">
+						<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
+					</g>
+				</svg>
+				<svg class="spinner fg" viewBox="0 0 152 152" xmlns="http://www.w3.org/2000/svg">
+					<g transform="matrix(1,0,0,1,12,12)">
+						<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:24px;"/>
+					</g>
+				</svg>
+		block content
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 36cec20c857c92be0399a1ac794ccfbf0c472a3f..84c48328020f1708490085431348b8af112ea95c 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -1,7 +1,7 @@
 block vars
 
 block loadClientEntry
-	- const clientEntry = config.clientEntry;
+	- const entry = config.frontendEntry;
 
 doctype html
 
@@ -40,16 +40,15 @@ html
 		link(rel='prefetch' href=serverErrorImageUrl)
 		link(rel='prefetch' href=infoImageUrl)
 		link(rel='prefetch' href=notFoundImageUrl)
-		//- https://github.com/misskey-dev/misskey/issues/9842
 		link(rel='stylesheet' href=`/assets/phosphor-icons/bold/style.css?version=${version}`)
 		link(rel='stylesheet' href=`/static-assets/fonts/sharkey-icons/style.css?version=${version}`)
-		link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
+		link(rel='modulepreload' href=`/vite/${entry.file}`)
 
-		if !config.clientManifestExists
+		if !config.frontendManifestExists
 				script(type="module" src="/vite/@vite/client")
 
-		if Array.isArray(clientEntry.css)
-			each href in clientEntry.css
+		if Array.isArray(entry.css)
+			each href in entry.css
 				link(rel='stylesheet' href=`/vite/${href}`)
 
 		title
@@ -75,7 +74,7 @@ html
 
 		script.
 			var VERSION = "#{version}";
-			var CLIENT_ENTRY = "#{clientEntry.file}";
+			var CLIENT_ENTRY = "#{entry.file}";
 
 		script(type='application/json' id='misskey_meta' data-generated-at=now)
 			!= metaJson
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index d83d4140960beaa11e79b608ae959c7d90eec404..2aa4f279ea9f5e87f930a073b8ba9526db03205c 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -16,6 +16,7 @@
  * followRequestAccepted - 自分の送ったフォローリクエストが承認された
  * roleAssigned - ロールが付与された
  * achievementEarned - 実績を獲得
+ * exportCompleted - エクスポートが完了
  * app - アプリ通知
  * test - テスト通知(サーバー側)
  */
@@ -33,6 +34,7 @@ export const notificationTypes = [
 	'followRequestAccepted',
 	'roleAssigned',
 	'achievementEarned',
+	'exportCompleted',
 	'app',
 	'test',
 ] as const;
@@ -52,10 +54,25 @@ 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',
 	'approve',
+	'decline',
 	'unsuspend',
 	'updateUserNote',
 	'addCustomEmoji',
@@ -77,8 +94,12 @@ export const moderationLogTypes = [
 	'deleteGlobalAnnouncement',
 	'deleteUserAnnouncement',
 	'resetPassword',
+	'setRemoteInstanceNSFW',
+	'unsetRemoteInstanceNSFW',
 	'suspendRemoteInstance',
 	'unsuspendRemoteInstance',
+	'rejectRemoteInstanceReports',
+	'acceptRemoteInstanceReports',
 	'updateRemoteInstanceNote',
 	'markSensitiveDriveFile',
 	'unmarkSensitiveDriveFile',
@@ -119,6 +140,11 @@ export type ModerationLogPayloads = {
 		userUsername: string;
 		userHost: string | null;
 	};
+	decline: {
+		userId: string;
+		userUsername: string;
+		userHost: string | null;
+	};
 	unsuspend: {
 		userId: string;
 		userUsername: string;
@@ -227,6 +253,14 @@ export type ModerationLogPayloads = {
 		userUsername: string;
 		userHost: string | null;
 	};
+	setRemoteInstanceNSFW: {
+		id: string;
+		host: string;
+	};
+	unsetRemoteInstanceNSFW: {
+		id: string;
+		host: string;
+	};
 	suspendRemoteInstance: {
 		id: string;
 		host: string;
@@ -235,6 +269,14 @@ export type ModerationLogPayloads = {
 		id: string;
 		host: string;
 	};
+	rejectRemoteInstanceReports: {
+		id: string;
+		host: string;
+	};
+	acceptRemoteInstanceReports: {
+		id: string;
+		host: string;
+	};
 	updateRemoteInstanceNote: {
 		id: string;
 		host: string;
diff --git a/packages/backend/test-server/entry.ts b/packages/backend/test-server/entry.ts
index 866a7e1f5bc5da05e5172ef021fae5daa501788c..04bf62d2096d25a40bc240618b01469516d3cb08 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 06548fa7da039738829e9f0e28b7454b0460c209..48da6ba27f2703aab3b21db53cc542362649dbcf 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -123,12 +123,14 @@ describe('2要素認証', () => {
 		password: string,
 		'g-recaptcha-response'?: string | null,
 		'hcaptcha-response'?: string | null,
+		'frc-captcha-solution'?: string | null,
 	} => {
 		return {
 			username,
 			password,
 			'g-recaptcha-response': null,
 			'hcaptcha-response': null,
+			'frc-captcha-solution': null,
 		};
 	};
 
diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts
index 6ac14cd8dcdefe503f4d49eb75b977cd37fff2a8..881e88d40f63767f9cf18b79ccf912a3a611c6de 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/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts
index 7efd688ec279186c5c276ca96a2dd16b01bc7b88..c8f5debbc9b83837713ffcab58e59ea3f9632723 100644
--- a/packages/backend/test/e2e/fetch-resource.ts
+++ b/packages/backend/test/e2e/fetch-resource.ts
@@ -156,7 +156,7 @@ describe('Webリソース', () => {
 
 		describe(' has entry such ', () => {
 			beforeEach(() => {
-				post(alice, { text: "**a**" })
+				post(alice, { text: "**a**" });
 			});
 
 			test('MFMを含まない。', async () => {
@@ -169,7 +169,7 @@ describe('Webリソース', () => {
 					throw new Error("MFM shouldn't be included");
 				}
 			});
-		})
+		});
 	});
 
 	describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 5937eb9b4924a7961d0b545dc466a15c5079fc38..7d91b604268e3ae248e6eb98f3a10cecc5c81c15 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -9,10 +9,12 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import { MiNote } from '@/models/Note.js';
-import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { api, castAsError, initTestDb, post, role, signup, uploadFile, uploadUrl } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
+// Important: this must match the value of maxNoteLength in .config/ci.yml!
+const MAX_NOTE_TEXT_LENGTH = 3000;
+
 describe('Note', () => {
 	let Notes: Repository<MiNote>;
 
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index eb56f7bebf35db3ee7270d064c0e2b464a2dc2d4..7d2e14f85d6f3b1f2cbcd1535524fb436607c91f 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('ユーザー', () => {
 	// エンティティとしてのユーザーを主眼においたテストを記述する
@@ -108,6 +108,7 @@ describe('ユーザー', () => {
 			isRenoteMuted: user.isRenoteMuted ?? false,
 			notify: user.notify ?? 'none',
 			withReplies: user.withReplies ?? false,
+			followedMessage: user.isFollowing ? (user.followedMessage ?? null) : undefined,
 		});
 	};
 
@@ -117,6 +118,7 @@ describe('ユーザー', () => {
 			...userDetailedNotMe(user),
 			avatarId: user.avatarId,
 			bannerId: user.bannerId,
+			followedMessage: user.followedMessage,
 			isModerator: user.isModerator,
 			isAdmin: user.isAdmin,
 			injectFeaturedNote: user.injectFeaturedNote,
@@ -139,6 +141,7 @@ describe('ユーザー', () => {
 			hasUnreadNotification: user.hasUnreadNotification,
 			unreadNotificationsCount: user.unreadNotificationsCount,
 			hasPendingReceivedFollowRequest: user.hasPendingReceivedFollowRequest,
+			hasPendingSentFollowRequest: user.hasPendingSentFollowRequest,
 			unreadAnnouncements: user.unreadAnnouncements,
 			mutedWords: user.mutedWords,
 			hardMutedWords: user.hardMutedWords,
@@ -356,6 +359,7 @@ describe('ユーザー', () => {
 		// 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);
@@ -378,6 +382,7 @@ describe('ユーザー', () => {
 		assert.strictEqual(response.hasUnreadNotification, false);
 		assert.strictEqual(response.unreadNotificationsCount, 0);
 		assert.strictEqual(response.hasPendingReceivedFollowRequest, false);
+		assert.strictEqual(response.hasPendingSentFollowRequest, false);
 		assert.deepStrictEqual(response.unreadAnnouncements, []);
 		assert.deepStrictEqual(response.mutedWords, []);
 		assert.deepStrictEqual(response.mutedInstances, []);
@@ -419,6 +424,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' }) },
diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts
index 861bc6db669b73edaf196464671e2fd69443fe21..7c6dd6a55f2055be30dcf05c9337a26f53b814be 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 3c7e796700da32b0b6846941005f60a7d7ebdbe7..c8f3db8aac392a75bced6b5809afe8d5bb260c90 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/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts
index cf54ebd909e74d260776608ac5a31b785a1838af..89583e73045167d1572d559a0e0eed90577810d9 100644
--- a/packages/backend/test/unit/FileInfoService.ts
+++ b/packages/backend/test/unit/FileInfoService.ts
@@ -35,7 +35,7 @@ describe('FileInfoService', () => {
 		delete fi.porn;
 
 		return fi;
-	}
+	};
 
 	beforeAll(async () => {
 		app = await Test.createTestingModule({
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index b6cbe4c520d1ff20924509943349442ddb4b97b7..ef80d25f812549e4cbc2155d620572348c2a10c7 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -13,6 +13,7 @@ import * as lolex from '@sinonjs/fake-timers';
 import { GlobalModule } from '@/GlobalModule.js';
 import { RoleService } from '@/core/RoleService.js';
 import {
+	MiMeta,
 	MiRole,
 	MiRoleAssignment,
 	MiUser,
@@ -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);
diff --git a/packages/backend/test/unit/SigninWithPasskeyApiService.ts b/packages/backend/test/unit/SigninWithPasskeyApiService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4d73ba5af1c1833efb38599b72b45bb1e0f29e93
--- /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 790cd1490eb5c050b5b74c23e7fdacc18e3d6b85..5401dd74d80b10648e9719287458eea23052a046 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 0000000000000000000000000000000000000000..0e88835a0243f93144fa31e1688e5f33eb6d7800
--- /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 0000000000000000000000000000000000000000..5e63b86f8fb3815e55b6e13973df2afe66aab819
--- /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 889a6ac53f6b4effac727931ca66aa10e6a79ea0..53ced3dab360e153efff6098b9d90dd771afec53 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();
@@ -311,7 +314,7 @@ describe('ActivityPub', () => {
 			// actor2Note is from a different server and needs to be fetched again
 			assert.deepStrictEqual(
 				resolver.remoteGetTrials(),
-				[actor1.id, `${actor1.id}/outbox`, actor1.featured, actor2Note.id, actor2.id, `${actor2.id}/outbox` ],
+				[actor1.id, `${actor1.id}/outbox`, actor1.featured, actor2Note.id, actor2.id, `${actor2.id}/outbox`],
 			);
 
 			const note = await noteService.fetchNote(actor2Note.id);
@@ -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',
@@ -437,7 +440,7 @@ describe('ActivityPub', () => {
 		});
 	});
 
-	describe('JSON-LD', () =>{
+	describe('JSON-LD', () => {
 		test('Compaction', async () => {
 			const jsonLd = jsonLdService.use();
 
diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts
index ee16d421c4c9caebf82b1105c880e0254b5f7eaf..e4f42809f8ec486827740ac9f69ca9dc64cb7194 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/misc/from-tuple.ts b/packages/backend/test/unit/misc/from-tuple.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b523cb5782979a03e09d80f7d4205896617470ab
--- /dev/null
+++ b/packages/backend/test/unit/misc/from-tuple.ts
@@ -0,0 +1,13 @@
+import { fromTuple } from '@/misc/from-tuple.js';
+
+describe(fromTuple, () => {
+	it('should return value when value is not an array', () => {
+		const value = fromTuple('abc');
+		expect(value).toBe('abc');
+	});
+
+	it('should return first element when value is an array', () => {
+		const value = fromTuple(['abc']);
+		expect(value).toBe('abc');
+	});
+});
diff --git a/packages/backend/test/unit/misc/is-renote.ts b/packages/backend/test/unit/misc/is-renote.ts
index 080271e404164b1a74b047ee8a573b6e9aa13b04..1baa995f592b747999624ef091908cf489eddd4d 100644
--- a/packages/backend/test/unit/misc/is-renote.ts
+++ b/packages/backend/test/unit/misc/is-renote.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { isQuote, isRenote } from '@/misc/is-renote.js';
+import { isPureRenote, isQuote, isRenote } from '@/misc/is-renote.js';
 import { MiNote } from '@/models/Note.js';
 
 const base: MiNote = {
@@ -86,4 +86,24 @@ describe('misc:is-renote', () => {
 		expect(isRenote(note)).toBe(true);
 		expect(isQuote(note as any)).toBe(true);
 	});
+
+	describe('isPureRenote', () => {
+		it('should return true when note is pure renote', () => {
+			const note = new MiNote({ renoteId: 'abc123', fileIds: [] });
+			const result = isPureRenote(note);
+			expect(result).toBeTruthy();
+		});
+
+		it('should return false when note is quote', () => {
+			const note = new MiNote({ renoteId: 'abc123', text: 'text', fileIds: [] });
+			const result = isPureRenote(note);
+			expect(result).toBeFalsy();
+		});
+
+		it('should return false when note is not renote', () => {
+			const note = new MiNote({ renoteId: null, fileIds: [] });
+			const result = isPureRenote(note);
+			expect(result).toBeFalsy();
+		});
+	});
 });
diff --git a/packages/backend/test/unit/misc/is-system-account.ts b/packages/backend/test/unit/misc/is-system-account.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0ab1526309d3b474518a8cf821aaf71da0bc6e6
--- /dev/null
+++ b/packages/backend/test/unit/misc/is-system-account.ts
@@ -0,0 +1,47 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { isSystemAccount } from '@/misc/is-system-account.js';
+
+describe(isSystemAccount, () => {
+	it('should return true for instance.actor', () => {
+		expect(isSystemAccount({ username: 'instance.actor', host: null })).toBeTruthy();
+	});
+
+	it('should return true for relay.actor', () => {
+		expect(isSystemAccount({ username: 'relay.actor', host: null })).toBeTruthy();
+	});
+
+	it('should return true for any username with a dot', () => {
+		expect(isSystemAccount({ username: 'some.user', host: null })).toBeTruthy();
+		expect(isSystemAccount({ username: 'some.', host: null })).toBeTruthy();
+		expect(isSystemAccount({ username: '.user', host: null })).toBeTruthy();
+		expect(isSystemAccount({ username: '.', host: null })).toBeTruthy();
+	});
+
+	it('should return true for usernames with multiple dots', () => {
+		expect(isSystemAccount({ username: 'some.user.account', host: null })).toBeTruthy();
+		expect(isSystemAccount({ username: '..', host: null })).toBeTruthy();
+	});
+
+	it('should return false for usernames without a dot', () => {
+		expect(isSystemAccount({ username: 'instance_actor', host: null })).toBeFalsy();
+		expect(isSystemAccount({ username: 'instanceactor', host: null })).toBeFalsy();
+		expect(isSystemAccount({ username: 'relay_actor', host: null })).toBeFalsy();
+		expect(isSystemAccount({ username: 'relayactor', host: null })).toBeFalsy();
+		expect(isSystemAccount({ username: '', host: null })).toBeFalsy();
+	});
+
+	it('should return false for users from another instance', () => {
+		expect(isSystemAccount({ username: 'instance.actor', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: 'relay.actor', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: 'some.user', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: 'some.', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: '.user', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: '.', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: 'some.user.account', host: 'example.com' })).toBeFalsy();
+		expect(isSystemAccount({ username: '..', host: 'example.com' })).toBeFalsy();
+	});
+});
diff --git a/packages/backend/test/unit/models/LatestNote.ts b/packages/backend/test/unit/models/LatestNote.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8785cfc8c5dd2170d09080ef7de504ee59cd9e3c
--- /dev/null
+++ b/packages/backend/test/unit/models/LatestNote.ts
@@ -0,0 +1,149 @@
+import { SkLatestNote } from '@/models/LatestNote.js';
+import { MiNote } from '@/models/Note.js';
+
+describe(SkLatestNote, () => {
+	describe('keyFor', () => {
+		it('should include userId', () => {
+			const note = new MiNote({ userId: 'abc123', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.userId).toBe(note.userId);
+		});
+
+		it('should include isPublic when is public', () => {
+			const note = new MiNote({ visibility: 'public', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isPublic).toBeTruthy();
+		});
+
+		it('should include isPublic when is home-only', () => {
+			const note = new MiNote({ visibility: 'home', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isPublic).toBeFalsy();
+		});
+
+		it('should include isPublic when is followers-only', () => {
+			const note = new MiNote({ visibility: 'followers', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isPublic).toBeFalsy();
+		});
+
+		it('should include isPublic when is specified', () => {
+			const note = new MiNote({ visibility: 'specified', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isPublic).toBeFalsy();
+		});
+
+		it('should include isReply when is reply', () => {
+			const note = new MiNote({ replyId: 'abc123', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isReply).toBeTruthy();
+		});
+
+		it('should include isReply when is not reply', () => {
+			const note = new MiNote({ replyId: null, fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isReply).toBeFalsy();
+		});
+
+		it('should include isQuote when is quote', () => {
+			const note = new MiNote({ renoteId: 'abc123', text: 'text', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isQuote).toBeTruthy();
+		});
+
+		it('should include isQuote when is reblog', () => {
+			const note = new MiNote({ renoteId: 'abc123', fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isQuote).toBeFalsy();
+		});
+
+		it('should include isQuote when is neither quote nor reblog', () => {
+			const note = new MiNote({ renoteId: null, fileIds: [] });
+			const key = SkLatestNote.keyFor(note);
+			expect(key.isQuote).toBeFalsy();
+		});
+	});
+
+	describe('areEquivalent', () => {
+		it('should return true when keys match', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeTruthy();
+		});
+
+		it('should return true when keys match with different reply IDs', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: '3', renoteId: null, fileIds: [] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'public', replyId: '4', renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeTruthy();
+		});
+
+		it('should return true when keys match with different renote IDs', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: null, renoteId: '3', fileIds: ['1'] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'public', replyId: null, renoteId: '4', fileIds: ['1'] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeTruthy();
+		});
+
+		it('should return true when keys match with different file counts', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: ['1'] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: ['1', '2'] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeTruthy();
+		});
+
+		it('should return true when keys match with different private visibilities', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'home', replyId: null, renoteId: null, fileIds: [] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'followers', replyId: null, renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeTruthy();
+		});
+
+		it('should return false when user ID differs', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+			const second = new MiNote({ id: '2', userId: 'def456', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeFalsy();
+		});
+
+		it('should return false when visibility differs', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'home', replyId: null, renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeFalsy();
+		});
+
+		it('should return false when reply differs', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: '1', renoteId: null, fileIds: [] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeFalsy();
+		});
+
+		it('should return false when quote differs', () => {
+			const first = new MiNote({ id: '1', userId: 'abc123', visibility: 'public', replyId: null, renoteId: '3', fileIds: ['1'] });
+			const second = new MiNote({ id: '2', userId: 'abc123', visibility: 'public', replyId: null, renoteId: null, fileIds: [] });
+
+			const result = SkLatestNote.areEquivalent(first, second);
+
+			expect(result).toBeFalsy();
+		});
+	});
+});
diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json
index 2b15a5cc7a3fc8cd7ccda890116010cec8ad6d7d..392da169adfae8d9791b4b8545fdc812f6f4ba28 100644
--- a/packages/backend/tsconfig.json
+++ b/packages/backend/tsconfig.json
@@ -46,6 +46,7 @@
 		"./src/**/*.ts"
 	],
 	"exclude": [
+		"node_modules",
 		"./src/**/*.test.ts"
 	]
 }
diff --git a/packages/frontend-embed/.gitignore b/packages/frontend-embed/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..1aa0ac14e8288adbba89868e9b9b1f231342679e
--- /dev/null
+++ b/packages/frontend-embed/.gitignore
@@ -0,0 +1 @@
+/storybook-static
diff --git a/packages/frontend-embed/@types/global.d.ts b/packages/frontend-embed/@types/global.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..15373cbd2dd35eeb7f41f066f1bd25e165f16e35
--- /dev/null
+++ b/packages/frontend-embed/@types/global.d.ts
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+type FIXME = any;
+
+declare const _LANGS_: string[][];
+declare const _LANGS_VERSION_: string;
+declare const _VERSION_: string;
+declare const _ENV_: string;
+declare const _DEV_: boolean;
+declare const _PERF_PREFIX_: string;
+declare const _DATA_TRANSFER_DRIVE_FILE_: string;
+declare const _DATA_TRANSFER_DRIVE_FOLDER_: string;
+declare const _DATA_TRANSFER_DECK_COLUMN_: string;
+
+// for dev-mode
+declare const _LANGS_FULL_: string[][];
+
+// TagCanvas
+interface Window {
+	TagCanvas: any;
+}
diff --git a/packages/frontend-embed/@types/theme.d.ts b/packages/frontend-embed/@types/theme.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6ac1037493e672d5f38e593fcfb5e51b0cd6ef69
--- /dev/null
+++ b/packages/frontend-embed/@types/theme.d.ts
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+declare module '@@/themes/*.json5' {
+	import { Theme } from '@/theme.js';
+
+	const theme: Theme;
+
+	export default theme;
+}
diff --git a/packages/frontend-embed/assets/dummy.png b/packages/frontend-embed/assets/dummy.png
new file mode 100644
index 0000000000000000000000000000000000000000..39332b0c1beeda1edb90d78d25c16e7372aff030
Binary files /dev/null and b/packages/frontend-embed/assets/dummy.png differ
diff --git a/packages/frontend-embed/eslint.config.js b/packages/frontend-embed/eslint.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..e686da9cc3d2aa5d34469885decec1f0614dbd5a
--- /dev/null
+++ b/packages/frontend-embed/eslint.config.js
@@ -0,0 +1,109 @@
+import globals from 'globals';
+import tsParser from '@typescript-eslint/parser';
+import parser from 'vue-eslint-parser';
+import pluginVue from 'eslint-plugin-vue';
+import pluginMisskey from '@misskey-dev/eslint-plugin';
+import sharedConfig from '../shared/eslint.config.js';
+import localeRule from '../../eslint/locale.js';
+import { build as buildLocales } from '../../locales/index.js';
+
+export default [
+	...sharedConfig,
+	{
+		files: ['{src,test,js,@types}/**/*.vue'],
+		...pluginMisskey.configs.typescript,
+	},
+	...pluginVue.configs['flat/recommended'],
+	{
+		files: ['{src,test,js,@types}/**/*.{ts,vue}'],
+		plugins: { sharkey: { rules: { locale: localeRule } } },
+		languageOptions: {
+			globals: {
+				...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])),
+				...globals.browser,
+
+				// Node.js
+				module: false,
+				require: false,
+				__dirname: false,
+
+				// Misskey
+				_DEV_: false,
+				_LANGS_: false,
+				_VERSION_: false,
+				_ENV_: false,
+				_PERF_PREFIX_: false,
+				_DATA_TRANSFER_DRIVE_FILE_: false,
+				_DATA_TRANSFER_DRIVE_FOLDER_: false,
+				_DATA_TRANSFER_DECK_COLUMN_: false,
+			},
+			parser,
+			parserOptions: {
+				extraFileExtensions: ['.vue'],
+				parser: tsParser,
+				project: ['./tsconfig.json'],
+				sourceType: 'module',
+				tsconfigRootDir: import.meta.dirname,
+			},
+		},
+		rules: {
+			'sharkey/locale': ['error', buildLocales()['en-US']],
+
+			'@typescript-eslint/no-empty-interface': ['error', {
+				allowSingleExtends: true,
+			}],
+			// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
+			// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
+			'id-denylist': ['error', 'window', 'e'],
+			'no-shadow': ['warn'],
+			'vue/attributes-order': ['error', {
+				alphabetical: false,
+			}],
+			'vue/no-use-v-if-with-v-for': ['error', {
+				allowUsingIterationVar: false,
+			}],
+			'vue/no-ref-as-operand': 'error',
+			'vue/no-multi-spaces': ['error', {
+				ignoreProperties: false,
+			}],
+			'vue/no-v-html': 'warn',
+			'vue/order-in-components': 'error',
+			'vue/html-indent': ['warn', 'tab', {
+				attribute: 1,
+				baseIndent: 0,
+				closeBracket: 0,
+				alignAttributesVertically: true,
+				ignores: [],
+			}],
+			'vue/html-closing-bracket-spacing': ['warn', {
+				startTag: 'never',
+				endTag: 'never',
+				selfClosingTag: 'never',
+			}],
+			'vue/multi-word-component-names': 'warn',
+			'vue/require-v-for-key': 'warn',
+			'vue/no-unused-components': 'warn',
+			'vue/no-unused-vars': 'warn',
+			'vue/no-dupe-keys': 'warn',
+			'vue/valid-v-for': 'warn',
+			'vue/return-in-computed-property': 'warn',
+			'vue/no-setup-props-reactivity-loss': 'warn',
+			'vue/max-attributes-per-line': 'off',
+			'vue/html-self-closing': 'off',
+			'vue/singleline-html-element-content-newline': 'off',
+			'vue/v-on-event-hyphenation': ['error', 'never', {
+				autofix: true,
+			}],
+			'vue/attribute-hyphenation': ['error', 'never'],
+		},
+	},
+	{
+		ignores: [
+			"**/lib/",
+			"**/temp/",
+			"**/built/",
+			"**/coverage/",
+			"**/node_modules/",
+		]
+	},
+];
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..021a63068a12c40739196f70dbe7b4c1d81046f1
--- /dev/null
+++ b/packages/frontend-embed/package.json
@@ -0,0 +1,72 @@
+{
+	"name": "frontend-embed",
+	"private": true,
+	"type": "module",
+	"scripts": {
+		"watch": "vite",
+		"dev": "vite --config vite.config.local-dev.ts --debug hmr",
+		"build": "vite build",
+		"typecheck": "vue-tsc --noEmit",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
+		"lint": "pnpm typecheck && pnpm eslint"
+	},
+	"dependencies": {
+		"@discordapp/twemoji": "15.1.0",
+		"@phosphor-icons/web": "^2.0.3",
+		"@rollup/plugin-json": "6.1.0",
+		"@rollup/plugin-replace": "5.0.7",
+		"@rollup/pluginutils": "5.1.2",
+		"@transfem-org/sfm-js": "0.24.5",
+		"@twemoji/parser": "15.1.1",
+		"@vitejs/plugin-vue": "5.1.4",
+		"@vue/compiler-sfc": "3.5.10",
+		"astring": "1.9.0",
+		"buraha": "0.0.1",
+		"estree-walker": "3.0.3",
+		"misskey-js": "workspace:*",
+		"frontend-shared": "workspace:*",
+		"punycode": "2.3.1",
+		"rollup": "4.22.5",
+		"sass": "1.79.3",
+		"shiki": "1.12.0",
+		"tinycolor2": "1.6.0",
+		"tsc-alias": "1.8.10",
+		"tsconfig-paths": "4.2.0",
+		"typescript": "5.6.2",
+		"uuid": "10.0.0",
+		"json5": "2.2.3",
+		"vite": "5.4.8",
+		"vue": "3.5.10"
+	},
+	"devDependencies": {
+		"@misskey-dev/summaly": "5.1.0",
+		"@testing-library/vue": "8.1.0",
+		"@types/estree": "1.0.6",
+		"@types/micromatch": "4.0.9",
+		"@types/node": "20.14.12",
+		"@types/punycode": "2.1.4",
+		"@types/tinycolor2": "1.4.6",
+		"@types/uuid": "10.0.0",
+		"@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.5.10",
+		"acorn": "8.12.1",
+		"cross-env": "7.0.3",
+		"eslint-plugin-import": "2.30.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.8",
+		"msw": "2.3.4",
+		"nodemon": "3.1.7",
+		"prettier": "3.3.3",
+		"start-server-and-test": "2.0.8",
+		"vite-plugin-turbosnap": "1.0.3",
+		"vue-component-type-helpers": "2.1.6",
+		"vue-eslint-parser": "9.4.3",
+		"vue-tsc": "2.1.6"
+	}
+}
diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7a16efe4ab7eabfe7195abded5a0641844b48b50
--- /dev/null
+++ b/packages/frontend-embed/src/boot.ts
@@ -0,0 +1,139 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// https://vitejs.dev/config/build-options.html#build-modulepreload
+import 'vite/modulepreload-polyfill';
+
+import '@/style.scss';
+import { createApp, defineAsyncComponent } from 'vue';
+import defaultLightTheme from '@@/themes/l-light.json5';
+import defaultDarkTheme from '@@/themes/d-dark.json5';
+import { MediaProxy } from '@@/js/media-proxy.js';
+import { applyTheme, assertIsTheme } from '@/theme.js';
+import { fetchCustomEmojis } from '@/custom-emojis.js';
+import { DI } from '@/di.js';
+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 type { Theme } from '@/theme.js';
+
+console.log('Sharkey 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 {
+		const parsed = JSON.parse(theme);
+		if (assertIsTheme(parsed)) {
+			return parsed;
+		} else {
+			return null;
+		}
+	} catch (err) {
+		return null;
+	}
+}
+
+const lightTheme = parseThemeOrNull(serverMetadata.defaultLightTheme) ?? defaultLightTheme;
+const darkTheme = parseThemeOrNull(serverMetadata.defaultDarkTheme) ?? defaultDarkTheme;
+
+if (embedParams.colorMode === 'dark') {
+	applyTheme(darkTheme);
+} else if (embedParams.colorMode === 'light') {
+	applyTheme(lightTheme);
+} else {
+	if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+		applyTheme(darkTheme);
+	} else {
+		applyTheme(lightTheme);
+	}
+	window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => {
+		if (mql.matches) {
+			applyTheme(darkTheme);
+		} else {
+			applyTheme(lightTheme);
+		}
+	});
+}
+//#endregion
+
+// サイズの制限
+document.documentElement.style.maxWidth = '500px';
+
+// iframeIdの設定
+function setIframeIdHandler(event: MessageEvent) {
+	if (event.data?.type === 'misskey:embedParent:registerIframeId' && event.data.payload?.iframeId != null) {
+		setIframeId(event.data.payload.iframeId);
+		window.removeEventListener('message', setIframeIdHandler);
+	}
+}
+
+window.addEventListener('message', setIframeIdHandler);
+
+try {
+	await fetchCustomEmojis();
+} catch (err) { /* empty */ }
+
+const app = createApp(
+	defineAsyncComponent(() => import('@/ui.vue')),
+);
+
+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
+// なぜか2回実行されることがあるため、mountするdivを1つに制限する
+const rootEl = ((): HTMLElement => {
+	const MISSKEY_MOUNT_DIV_ID = 'sharkey_app';
+
+	const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
+
+	if (currentRoot) {
+		console.warn('multiple import detected');
+		return currentRoot;
+	}
+
+	const root = document.createElement('div');
+	root.id = MISSKEY_MOUNT_DIV_ID;
+	document.body.appendChild(root);
+	return root;
+})();
+
+postMessageToParentWindow('misskey:embed:ready');
+
+app.mount(rootEl);
+
+// boot.jsのやつを解除
+window.onerror = null;
+window.onunhandledrejection = null;
+
+removeSplash();
+
+function removeSplash() {
+	const splash = document.getElementById('splash');
+	if (splash) {
+		splash.style.opacity = '0';
+		splash.style.pointerEvents = 'none';
+
+		// transitionendイベントが発火しない場合があるため
+		window.setTimeout(() => {
+			splash.remove();
+		}, 1000);
+	}
+}
diff --git a/packages/frontend-embed/src/components/EmA.vue b/packages/frontend-embed/src/components/EmA.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1c236b9a3592e32e189366dec590aa72ab9ecb4f
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmA.vue
@@ -0,0 +1,21 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<a :href="to" target="_blank" rel="noopener">
+	<slot></slot>
+</a>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+
+const props = withDefaults(defineProps<{
+	to: string;
+	activeClass?: null | string;
+}>(), {
+	activeClass: null,
+});
+</script>
diff --git a/packages/frontend-embed/src/components/EmAcct.vue b/packages/frontend-embed/src/components/EmAcct.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6856b8272e86dd6693e0b528b5452395767f022c
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmAcct.vue
@@ -0,0 +1,24 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<span>
+	<span>@{{ user.username }}</span>
+	<span v-if="user.host || detail" style="opacity: 0.5;">@{{ user.host || host }}</span>
+</span>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+import { toUnicode } from 'punycode/';
+import { host as hostRaw } from '@@/js/config.js';
+
+defineProps<{
+	user: Misskey.entities.UserLite;
+	detail?: boolean;
+}>();
+
+const host = toUnicode(hostRaw);
+</script>
diff --git a/packages/frontend-embed/src/components/EmAvatar.vue b/packages/frontend-embed/src/components/EmAvatar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..58c35c8ef0cd96b18fe3ac53aef8defecd46dad3
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmAvatar.vue
@@ -0,0 +1,250 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<component :is="link ? EmA : 'span'" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat }]">
+	<EmImgWithBlurhash :class="$style.inner" :src="url" :hash="user.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
+	<div v-if="user.isCat" :class="[$style.ears]">
+		<div :class="$style.earLeft">
+			<div v-if="false" :class="$style.layer">
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+			</div>
+		</div>
+		<div :class="$style.earRight">
+			<div v-if="false" :class="$style.layer">
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+				<div :class="$style.plot" :style="{ backgroundImage: `url(${JSON.stringify(url)})` }"/>
+			</div>
+		</div>
+	</div>
+	<img
+		v-for="decoration in user.avatarDecorations"
+		:class="[$style.decoration]"
+		:src="getDecorationUrl(decoration)"
+		:style="{
+			rotate: getDecorationAngle(decoration),
+			scale: getDecorationScale(decoration),
+			translate: getDecorationOffset(decoration),
+		}"
+		alt=""
+	>
+</component>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as Misskey from 'misskey-js';
+import EmImgWithBlurhash from './EmImgWithBlurhash.vue';
+import EmA from './EmA.vue';
+import { userPage } from '@/utils.js';
+
+const props = withDefaults(defineProps<{
+	user: Misskey.entities.User;
+	link?: boolean;
+	preview?: boolean;
+	indicator?: boolean;
+}>(), {
+	link: false,
+	preview: false,
+	indicator: false,
+});
+
+const emit = defineEmits<{
+	(ev: 'click', v: MouseEvent): void;
+}>();
+
+const bound = computed(() => props.link
+	? { to: userPage(props.user) }
+	: {});
+
+const url = computed(() => {
+	if (props.user.avatarUrl == null) return null;
+	return props.user.avatarUrl;
+});
+
+function getDecorationUrl(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+	return decoration.url;
+}
+
+function getDecorationAngle(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+	const angle = decoration.angle ?? 0;
+	return angle === 0 ? undefined : `${angle * 360}deg`;
+}
+
+function getDecorationScale(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+	const scaleX = decoration.flipH ? -1 : 1;
+	return scaleX === 1 ? undefined : `${scaleX} 1`;
+}
+
+function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+	const offsetX = decoration.offsetX ?? 0;
+	const offsetY = decoration.offsetY ?? 0;
+	return offsetX === 0 && offsetY === 0 ? undefined : `${offsetX * 100}% ${offsetY * 100}%`;
+}
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	display: inline-block;
+	vertical-align: bottom;
+	flex-shrink: 0;
+	border-radius: 100%;
+	line-height: 16px;
+}
+
+.inner {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	top: 0;
+	border-radius: 100%;
+	z-index: 1;
+	overflow: clip;
+	object-fit: cover;
+	width: 100%;
+	height: 100%;
+}
+
+.indicator {
+	position: absolute;
+	z-index: 2;
+	bottom: 0;
+	left: 0;
+	width: 20%;
+	height: 20%;
+}
+
+.cat {
+	> .ears {
+		contain: strict;
+		position: absolute;
+		top: -50%;
+		left: -50%;
+		width: 100%;
+		height: 100%;
+		padding: 50%;
+		pointer-events: none;
+
+		> .earLeft,
+		> .earRight {
+			contain: strict;
+			display: inline-block;
+			height: 50%;
+			width: 50%;
+			background: currentColor;
+
+			&::after {
+				contain: strict;
+				content: '';
+				display: block;
+				width: 60%;
+				height: 60%;
+				margin: 20%;
+				background: #df548f;
+			}
+
+			> .layer {
+				contain: strict;
+				position: absolute;
+				top: 0;
+				width: 280%;
+				height: 280%;
+
+				> .plot {
+					contain: strict;
+					position: absolute;
+					width: 100%;
+					height: 100%;
+					clip-path: path('M0 0H1V1H0z');
+					transform: scale(32767);
+					transform-origin: 0 0;
+					opacity: 0.5;
+
+					&:first-child {
+						opacity: 1;
+					}
+
+					&:last-child {
+						opacity: calc(1 / 3);
+					}
+				}
+			}
+		}
+
+		> .earLeft {
+			transform: rotate(37.5deg) skew(30deg);
+
+			&, &::after {
+				border-radius: 25% 75% 75%;
+			}
+
+			> .layer {
+				left: 0;
+				transform:
+					skew(-30deg)
+					rotate(-37.5deg)
+					translate(-2.82842712475%, /* -2 * sqrt(2) */
+										-38.5857864376%); /* 40 - 2 * sqrt(2) */
+
+				> .plot {
+					background-position: 20% 10%; /* ~= 37.5deg */
+
+					&:first-child {
+						background-position-x: 21%;
+					}
+
+					&:last-child {
+						background-position-y: 11%;
+					}
+				}
+			}
+		}
+
+		> .earRight {
+			transform: rotate(-37.5deg) skew(-30deg);
+
+			&, &::after {
+				border-radius: 75% 25% 75% 75%;
+			}
+
+			> .layer {
+				right: 0;
+				transform:
+					skew(30deg)
+					rotate(37.5deg)
+					translate(2.82842712475%, /* 2 * sqrt(2) */
+										-38.5857864376%); /* 40 - 2 * sqrt(2) */
+
+				> .plot {
+					position: absolute;
+					background-position: 80% 10%; /* ~= 37.5deg */
+
+					&:first-child {
+						background-position-x: 79%;
+					}
+
+					&:last-child {
+						background-position-y: 11%;
+					}
+				}
+			}
+		}
+	}
+}
+
+.decoration {
+	position: absolute;
+	z-index: 1;
+	top: -50%;
+	left: -50%;
+	width: 200%;
+	pointer-events: none;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmCustomEmoji.vue b/packages/frontend-embed/src/components/EmCustomEmoji.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e4149cf363f33082203633eafb9d143b40603980
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmCustomEmoji.vue
@@ -0,0 +1,101 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<img
+	v-if="errored && fallbackToImage"
+	:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
+	src="/client-assets/dummy.png"
+	:title="alt"
+/>
+<span v-else-if="errored">:{{ customEmojiName }}:</span>
+<img
+	v-else
+	:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
+	:src="url"
+	:alt="alt"
+	:title="alt"
+	decoding="async"
+	@error="errored = true"
+	@load="errored = false"
+/>
+</template>
+
+<script lang="ts" setup>
+import { computed, inject, ref } from 'vue';
+import { customEmojisMap } from '@/custom-emojis.js';
+
+import { DI } from '@/di.js';
+
+const mediaProxy = inject(DI.mediaProxy)!;
+
+const props = defineProps<{
+	name: string;
+	normal?: boolean;
+	noStyle?: boolean;
+	host?: string | null;
+	url?: string;
+	useOriginalSize?: boolean;
+	menu?: boolean;
+	menuReaction?: boolean;
+	fallbackToImage?: boolean;
+}>();
+
+const customEmojiName = computed(() => (props.name[0] === ':' ? props.name.substring(1, props.name.length - 1) : props.name).replace('@.', ''));
+const isLocal = computed(() => !props.host && (customEmojiName.value.endsWith('@.') || !customEmojiName.value.includes('@')));
+
+const rawUrl = computed(() => {
+	if (props.url) {
+		return props.url;
+	}
+	if (isLocal.value) {
+		return customEmojisMap.get(customEmojiName.value)?.url ?? null;
+	}
+	return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`;
+});
+
+const url = computed(() => {
+	if (rawUrl.value == null) return undefined;
+
+	const proxied =
+		(rawUrl.value.startsWith('/emoji/') || (props.useOriginalSize && isLocal.value))
+			? rawUrl.value
+			: mediaProxy.getProxiedImageUrl(
+				rawUrl.value,
+				props.useOriginalSize ? undefined : 'emoji',
+				false,
+				true,
+			);
+	return proxied;
+});
+
+const alt = computed(() => `:${customEmojiName.value}:`);
+const errored = ref(url.value == null);
+</script>
+
+<style lang="scss" module>
+.root {
+	height: 2em;
+	vertical-align: middle;
+	transition: transform 0.2s ease;
+
+	&:hover {
+		transform: scale(1.2);
+	}
+}
+
+.normal {
+	height: 1.25em;
+	vertical-align: -0.25em;
+
+	&:hover {
+		transform: none;
+	}
+}
+
+.noStyle {
+	height: auto !important;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmEmoji.vue b/packages/frontend-embed/src/components/EmEmoji.vue
new file mode 100644
index 0000000000000000000000000000000000000000..224979707b971c0a5e84c87a7619944946f9c358
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmEmoji.vue
@@ -0,0 +1,26 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<img :class="$style.root" :src="url" :alt="props.emoji" decoding="async"/>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import { char2twemojiFilePath } from '@@/js/emoji-base.js';
+
+const props = defineProps<{
+	emoji: string;
+}>();
+
+const url = computed(() => char2twemojiFilePath(props.emoji));
+</script>
+
+<style lang="scss" module>
+.root {
+	height: 1.25em;
+	vertical-align: -0.25em;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmError.vue b/packages/frontend-embed/src/components/EmError.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d376b29a7f85cc3926cc02db18ee603cde187181
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmError.vue
@@ -0,0 +1,43 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+	<p :class="$style.text"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</p>
+	<button class="_buttonGray _buttonRounded" :class="$style.button" @click="() => emit('retry')">{{ i18n.ts.retry }}</button>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { i18n } from '@/i18n.js';
+
+const emit = defineEmits<{
+	(ev: 'retry'): void;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	padding: 32px;
+	text-align: center;
+  align-items: center;
+}
+
+.text {
+	margin: 0 0 8px 0;
+}
+
+.button {
+	margin: 0 auto;
+}
+
+.img {
+	vertical-align: bottom;
+  width: 128px;
+	height: 128px;
+	margin-bottom: 16px;
+	border-radius: 16px;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmImgWithBlurhash.vue b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bf976c71ae21260e38643760212ea5e1390d34a2
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmImgWithBlurhash.vue
@@ -0,0 +1,240 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
+	<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined" tabindex="-1"/>
+	<img v-show="!hide" key="img" ref="img" :height="imgHeight ?? undefined" :width="imgWidth ?? undefined" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async" tabindex="-1"/>
+</div>
+</template>
+
+<script lang="ts">
+import DrawBlurhash from '@/workers/draw-blurhash?worker';
+import TestWebGL2 from '@/workers/test-webgl2?worker';
+import { WorkerMultiDispatch } from '@@/js/worker-multi-dispatch.js';
+import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
+
+const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
+	// テスト環境で Web Worker インスタンスは作成できない
+	if (import.meta.env.MODE === 'test') {
+		const canvas = document.createElement('canvas');
+		canvas.width = 64;
+		canvas.height = 64;
+		resolve(canvas);
+		return;
+	}
+	const testWorker = new TestWebGL2();
+	testWorker.addEventListener('message', event => {
+		if (event.data.result) {
+			const workers = new WorkerMultiDispatch(
+				() => new DrawBlurhash(),
+				Math.min(navigator.hardwareConcurrency - 1, 4),
+			);
+			resolve(workers);
+			if (_DEV_) console.log('WebGL2 in worker is supported!');
+		} else {
+			const canvas = document.createElement('canvas');
+			canvas.width = 64;
+			canvas.height = 64;
+			resolve(canvas);
+			if (_DEV_) console.log('WebGL2 in worker is not supported...');
+		}
+		testWorker.terminate();
+	});
+});
+</script>
+
+<script lang="ts" setup>
+import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
+import { v4 as uuid } from 'uuid';
+import { render } from 'buraha';
+
+const props = withDefaults(defineProps<{
+	src?: string | null;
+	hash?: string | null;
+	alt?: string | null;
+	title?: string | null;
+	height?: number;
+	width?: number;
+	cover?: boolean;
+	forceBlurhash?: boolean;
+	onlyAvgColor?: boolean; // 軽量化のためにBlurhashを使わずに平均色だけを描画
+}>(), {
+	src: null,
+	alt: '',
+	title: null,
+	height: 64,
+	width: 64,
+	cover: true,
+	forceBlurhash: false,
+	onlyAvgColor: false,
+});
+
+const viewId = uuid();
+const canvas = shallowRef<HTMLCanvasElement>();
+const root = shallowRef<HTMLDivElement>();
+const img = shallowRef<HTMLImageElement>();
+const loaded = ref(false);
+const canvasWidth = ref(64);
+const canvasHeight = ref(64);
+const imgWidth = ref(props.width);
+const imgHeight = ref(props.height);
+const bitmapTmp = ref<CanvasImageSource | undefined>();
+const hide = computed(() => !loaded.value || props.forceBlurhash);
+
+function waitForDecode() {
+	if (props.src != null && props.src !== '') {
+		nextTick()
+			.then(() => img.value?.decode())
+			.then(() => {
+				loaded.value = true;
+			}, error => {
+				console.log('Error occurred during decoding image', img.value, error);
+			});
+	} else {
+		loaded.value = false;
+	}
+}
+
+watch([() => props.width, () => props.height, root], () => {
+	const ratio = props.width / props.height;
+	if (ratio > 1) {
+		canvasWidth.value = Math.round(64 * ratio);
+		canvasHeight.value = 64;
+	} else {
+		canvasWidth.value = 64;
+		canvasHeight.value = Math.round(64 / ratio);
+	}
+
+	const clientWidth = root.value?.clientWidth ?? 300;
+	imgWidth.value = clientWidth;
+	imgHeight.value = Math.round(clientWidth / ratio);
+}, {
+	immediate: true,
+});
+
+function drawImage(bitmap: CanvasImageSource) {
+	// canvasがない(mountedされていない)場合はTmpに保存しておく
+	if (!canvas.value) {
+		bitmapTmp.value = bitmap;
+		return;
+	}
+
+	// canvasがあれば描画する
+	bitmapTmp.value = undefined;
+	const ctx = canvas.value.getContext('2d');
+	if (!ctx) return;
+	ctx.drawImage(bitmap, 0, 0, canvasWidth.value, canvasHeight.value);
+}
+
+function drawAvg() {
+	if (!canvas.value) return;
+
+	const color = (props.hash != null && extractAvgColorFromBlurhash(props.hash)) || '#888';
+
+	const ctx = canvas.value.getContext('2d');
+	if (!ctx) return;
+
+	// avgColorでお茶をにごす
+	ctx.beginPath();
+	ctx.fillStyle = color;
+	ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value);
+}
+
+async function draw() {
+	if (import.meta.env.MODE === 'test' && props.hash == null) return;
+
+	drawAvg();
+
+	if (props.hash == null) return;
+
+	if (props.onlyAvgColor) return;
+
+	const work = await canvasPromise;
+	if (work instanceof WorkerMultiDispatch) {
+		work.postMessage(
+			{
+				id: viewId,
+				hash: props.hash,
+			},
+			undefined,
+		);
+	} else {
+		try {
+			render(props.hash, work);
+			drawImage(work);
+		} catch (error) {
+			console.error('Error occurred during drawing blurhash', error);
+		}
+	}
+}
+
+function workerOnMessage(event: MessageEvent) {
+	if (event.data.id !== viewId) return;
+	drawImage(event.data.bitmap as ImageBitmap);
+}
+
+canvasPromise.then(work => {
+	if (work instanceof WorkerMultiDispatch) {
+		work.addListener(workerOnMessage);
+	}
+
+	draw();
+});
+
+watch(() => props.src, () => {
+	waitForDecode();
+});
+
+watch(() => props.hash, () => {
+	draw();
+});
+
+onMounted(() => {
+	// drawImageがmountedより先に呼ばれている場合はここで描画する
+	if (bitmapTmp.value) {
+		drawImage(bitmapTmp.value);
+	}
+	waitForDecode();
+});
+
+onUnmounted(() => {
+	canvasPromise.then(work => {
+		if (work instanceof WorkerMultiDispatch) {
+			work.removeListener(workerOnMessage);
+		}
+	});
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	width: 100%;
+	height: 100%;
+
+	&.cover {
+		> .canvas,
+		> .img {
+			object-fit: cover;
+		}
+	}
+}
+
+.canvas,
+.img {
+	display: block;
+	width: 100%;
+	height: 100%;
+}
+
+.canvas {
+	object-fit: contain;
+}
+
+.img {
+	object-fit: contain;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmInstanceTicker.vue b/packages/frontend-embed/src/components/EmInstanceTicker.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4a116e317acab3a1785f81a44496bc99dddd1172
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmInstanceTicker.vue
@@ -0,0 +1,87 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root" :style="bg">
+	<img v-if="faviconUrl" :class="$style.icon" :src="faviconUrl"/>
+	<div :class="$style.name">{{ instance.name }}</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, inject } from 'vue';
+
+import { DI } from '@/di.js';
+
+const serverMetadata = inject(DI.serverMetadata)!;
+const mediaProxy = inject(DI.mediaProxy)!;
+
+const props = defineProps<{
+	instance?: {
+		faviconUrl?: string | null
+		name?: string | null
+		themeColor?: string | null
+	}
+}>();
+
+// 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,
+};
+
+const faviconUrl = computed(() => props.instance ? mediaProxy.getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : mediaProxy.getProxiedImageUrlNullable(serverMetadata.iconUrl, 'preview') ?? '/favicon.ico');
+
+const themeColor = props.instance?.themeColor ?? serverMetadata.themeColor ?? '#777777';
+
+const bg = {
+	background: `linear-gradient(90deg, ${themeColor}, ${themeColor}00)`,
+};
+</script>
+
+<style lang="scss" module>
+$height: 2ex;
+
+.root {
+	display: flex;
+	align-items: center;
+	height: $height;
+	border-radius: 4px 0 0 4px;
+	overflow: clip;
+	color: #fff;
+	text-shadow: /* .866 ≈ sin(60deg) */
+		1px 0 1px #000,
+		.866px .5px 1px #000,
+		.5px .866px 1px #000,
+		0 1px 1px #000,
+		-.5px .866px 1px #000,
+		-.866px .5px 1px #000,
+		-1px 0 1px #000,
+		-.866px -.5px 1px #000,
+		-.5px -.866px 1px #000,
+		0 -1px 1px #000,
+		.5px -.866px 1px #000,
+		.866px -.5px 1px #000;
+	mask-image: linear-gradient(90deg,
+		rgb(0,0,0),
+		rgb(0,0,0) calc(100% - 16px),
+		rgba(0,0,0,0) 100%
+	);
+}
+
+.icon {
+	height: $height;
+	flex-shrink: 0;
+}
+
+.name {
+	margin-left: 4px;
+	line-height: 1;
+	font-size: 0.9em;
+	font-weight: bold;
+	white-space: nowrap;
+	overflow: visible;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmLink.vue b/packages/frontend-embed/src/components/EmLink.vue
new file mode 100644
index 0000000000000000000000000000000000000000..aec9b330720aac1a837eb8d4fe5627e65f8a9efa
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmLink.vue
@@ -0,0 +1,40 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<component
+	:is="self ? EmA : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
+	:title="url"
+>
+	<slot></slot>
+	<i v-if="target === '_blank'" class="ti ti-external-link" :class="$style.icon"></i>
+</component>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import EmA from './EmA.vue';
+import { url as local } from '@@/js/config.js';
+
+const props = withDefaults(defineProps<{
+	url: string;
+	rel?: null | string;
+}>(), {
+});
+
+const self = props.url.startsWith(local);
+const attr = self ? 'to' : 'href';
+const target = self ? null : '_blank';
+
+const el = ref<HTMLElement | { $el: HTMLElement }>();
+
+</script>
+
+<style lang="scss" module>
+.icon {
+	padding-left: 2px;
+	font-size: .9em;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmLoading.vue b/packages/frontend-embed/src/components/EmLoading.vue
new file mode 100644
index 0000000000000000000000000000000000000000..49d8ace37bee32d60c09783c03bfd738c12bbc03
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmLoading.vue
@@ -0,0 +1,112 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, { [$style.inline]: inline, [$style.colored]: colored, [$style.mini]: mini, [$style.em]: em }]">
+	<div :class="$style.container">
+		<svg :class="[$style.spinner, $style.bg]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
+			<g transform="matrix(1.125,0,0,1.125,12,12)">
+				<circle cx="64" cy="64" r="64" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
+			</g>
+		</svg>
+		<svg :class="[$style.spinner, $style.fg, { [$style.static]: static }]" viewBox="0 0 168 168" xmlns="http://www.w3.org/2000/svg">
+			<g transform="matrix(1.125,0,0,1.125,12,12)">
+				<path d="M128,64C128,28.654 99.346,0 64,0C99.346,0 128,28.654 128,64Z" style="fill:none;stroke:currentColor;stroke-width:21.33px;"/>
+			</g>
+		</svg>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+
+const props = withDefaults(defineProps<{
+	static?: boolean;
+	inline?: boolean;
+	colored?: boolean;
+	mini?: boolean;
+	em?: boolean;
+}>(), {
+	static: false,
+	inline: false,
+	colored: true,
+	mini: false,
+	em: false,
+});
+</script>
+
+<style lang="scss" module>
+@keyframes spinner {
+	0% {
+		transform: rotate(0deg);
+	}
+	100% {
+		transform: rotate(360deg);
+	}
+}
+
+.root {
+	padding: 32px;
+	text-align: center;
+	cursor: wait;
+
+	--size: 38px;
+
+	&.colored {
+		color: var(--accent);
+	}
+
+	&.inline {
+		display: inline;
+		padding: 0;
+		--size: 32px;
+	}
+
+	&.mini {
+		padding: 16px;
+		--size: 32px;
+	}
+
+	&.em {
+		display: inline-block;
+		vertical-align: middle;
+		padding: 0;
+		--size: 1em;
+	}
+}
+
+.container {
+	position: relative;
+	width: var(--size);
+	height: var(--size);
+	margin: 0 auto;
+}
+
+.spinner {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: var(--size);
+	height: var(--size);
+	fill-rule: evenodd;
+	clip-rule: evenodd;
+	stroke-linecap: round;
+	stroke-linejoin: round;
+	stroke-miterlimit: 1.5;
+}
+
+.bg {
+	opacity: 0.275;
+}
+
+.fg {
+	animation: spinner 0.5s linear infinite;
+
+	&.static {
+		animation-play-state: paused;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmMediaBanner.vue b/packages/frontend-embed/src/components/EmMediaBanner.vue
new file mode 100644
index 0000000000000000000000000000000000000000..435da238a4e689ae7ebf51fceee3869013472957
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmMediaBanner.vue
@@ -0,0 +1,55 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<a :href="href" target="_blank" :class="$style.root">
+	<div :class="$style.label">
+		<template v-if="media.type.startsWith('audio')"><i class="ti ti-music"></i> {{ i18n.ts.audio }}</template>
+		<template v-else><i class="ti ti-file"></i> {{ i18n.ts.file }}</template>
+	</div>
+	<div :class="$style.go">
+		<i class="ti ti-chevron-right"></i>
+	</div>
+</a>
+</template>
+
+<script setup lang="ts">
+import * as Misskey from 'misskey-js';
+import { i18n } from '@/i18n.js';
+
+defineProps<{
+	media: Misskey.entities.DriveFile;
+	href: string;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	box-sizing: border-box;
+	display: flex;
+	align-items: center;
+	width: 100%;
+	padding: var(--margin);
+	margin-top: 4px;
+	border: 1px solid var(--inputBorder);
+	border-radius: var(--radius);
+	background-color: var(--panel);
+	transition: background-color .1s, border-color .1s;
+
+	&:hover {
+		text-decoration: none;
+		border-color: var(--inputBorderHover);
+		background-color: var(--buttonHoverBg);
+	}
+}
+
+.label {
+	font-size: .9em;
+}
+
+.go {
+	margin-left: auto;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue
new file mode 100644
index 0000000000000000000000000000000000000000..076386e87675af4a4cf6364aa99ff5eb693098f5
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmMediaImage.vue
@@ -0,0 +1,162 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[hide ? $style.hidden : $style.visible]" @click="onclick">
+	<a
+		:title="image.comment || image.name"
+		:class="$style.imageContainer"
+		:href="href ?? image.url"
+		target="_blank"
+		rel="noopener"
+	>
+		<ImgWithBlurhash
+			:hash="image.blurhash"
+			:src="hide ? null : url"
+			:forceBlurhash="hide"
+			:cover="hide || cover"
+			:alt="image.comment || image.name"
+			:title="image.comment || image.name"
+			:width="image.properties.width"
+			:height="image.properties.height"
+			:style="hide ? 'filter: brightness(0.7);' : null"
+		/>
+	</a>
+	<template v-if="hide">
+		<div :class="$style.hiddenText">
+			<div :class="$style.hiddenTextWrapper">
+				<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</b>
+				<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ i18n.ts.image }}</b>
+				<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
+			</div>
+		</div>
+	</template>
+	<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>
+	<i v-if="!hide" class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
+import * as Misskey from 'misskey-js';
+import ImgWithBlurhash from '@/components/EmImgWithBlurhash.vue';
+import { i18n } from '@/i18n.js';
+
+const props = withDefaults(defineProps<{
+	image: Misskey.entities.DriveFile;
+	href?: string;
+	raw?: boolean;
+	cover?: boolean;
+}>(), {
+	cover: false,
+});
+
+const hide = ref(props.image.isSensitive);
+
+const url = computed(() => (props.raw)
+	? props.image.url
+	: props.image.thumbnailUrl,
+);
+
+async function onclick(ev: MouseEvent) {
+	if (hide.value) {
+		ev.stopPropagation();
+		hide.value = false;
+	}
+}
+</script>
+
+<style lang="scss" module>
+.hidden {
+	position: relative;
+}
+
+.hiddenText {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 1;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	cursor: pointer;
+}
+
+.hide {
+	display: block;
+	position: absolute;
+	border-radius: 6px;
+	background-color: var(--fg);
+	color: var(--accentLighten);
+	font-size: 12px;
+	opacity: .5;
+	padding: 5px 8px;
+	text-align: center;
+	cursor: pointer;
+	top: 12px;
+	right: 12px;
+}
+
+.hiddenTextWrapper {
+	display: table-cell;
+	text-align: center;
+	font-size: 0.8em;
+	color: #fff;
+}
+
+.visible {
+	position: relative;
+	//box-shadow: 0 0 0 1px var(--divider) inset;
+	background: var(--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(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%);
+}
+
+html[data-color-scheme=light] .visible {
+	--c: rgb(0 0 0 / 2%);
+	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%);
+}
+
+.imageContainer {
+	display: block;
+	overflow: hidden;
+	width: 100%;
+	height: 100%;
+	background-position: center;
+	background-size: contain;
+	background-repeat: no-repeat;
+}
+
+.indicators {
+	display: inline-flex;
+	position: absolute;
+	top: 10px;
+	left: 10px;
+	pointer-events: none;
+	opacity: .5;
+	gap: 6px;
+}
+
+.indicator {
+	/* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */
+	background-color: black;
+	border-radius: 6px;
+	color: var(--accentLighten);
+	display: inline-block;
+	font-weight: bold;
+	font-size: 0.8em;
+	padding: 2px 5px;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmMediaList.vue b/packages/frontend-embed/src/components/EmMediaList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0b2d835abe9ea2885ef623e2bb0f895ee32d183e
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmMediaList.vue
@@ -0,0 +1,146 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+	<div v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :class="$style.banner">
+		<XBanner :media="media" :href="originalEntityUrl"/>
+	</div>
+	<div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
+		<div
+			:class="[
+				$style.medias,
+				count === 1 ? [$style.n1] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany,
+			]"
+		>
+			<div v-for="media in mediaList.filter(media => previewable(media))" :class="$style.media">
+				<XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.mediaInner" :video="media" :href="originalEntityUrl"/>
+				<XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.mediaInner" class="image" :image="media" :raw="raw" :href="originalEntityUrl"/>
+			</div>
+		</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as Misskey from 'misskey-js';
+import XBanner from './EmMediaBanner.vue';
+import XImage from './EmMediaImage.vue';
+import XVideo from './EmMediaVideo.vue';
+import { FILE_TYPE_BROWSERSAFE } from '@@/js/const.js';
+
+const props = defineProps<{
+	mediaList: Misskey.entities.DriveFile[];
+	raw?: boolean;
+
+	/** 埋め込みページ用 親要素の正規URL */
+	originalEntityUrl: string;
+}>();
+
+const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
+
+const previewable = (file: Misskey.entities.DriveFile): boolean => {
+	if (file.type === 'image/svg+xml') return true; // svgのwebpublic/thumbnailはpngなのでtrue
+	// FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切
+	return (file.type.startsWith('video') || file.type.startsWith('image')) && FILE_TYPE_BROWSERSAFE.includes(file.type);
+};
+</script>
+
+<style lang="scss" module>
+.container {
+	position: relative;
+	width: 100%;
+	margin-top: 4px;
+}
+
+.medias {
+	display: grid;
+	grid-gap: 8px;
+
+	height: 100%;
+	width: 100%;
+
+	&.n1 {
+		grid-template-rows: 1fr;
+
+		// default but fallback (expand)
+		min-height: 64px;
+		max-height: clamp(
+			64px,
+			50cqh,
+			min(360px, 50vh)
+		);
+
+		&.n116_9 {
+			min-height: initial;
+			max-height: initial;
+			aspect-ratio: 16 / 9; // fallback
+		}
+
+		&.n11_1{
+			min-height: initial;
+			max-height: initial;
+			aspect-ratio: 1 / 1; // fallback
+		}
+
+		&.n12_3 {
+			min-height: initial;
+			max-height: initial;
+			aspect-ratio: 2 / 3; // fallback
+		}
+	}
+
+	&.n2 {
+		aspect-ratio: 16/9;
+		grid-template-columns: 1fr 1fr;
+		grid-template-rows: 1fr;
+	}
+
+	&.n3 {
+		aspect-ratio: 16/9;
+		grid-template-columns: 1fr 0.5fr;
+		grid-template-rows: 1fr 1fr;
+
+		> .media:nth-child(1) {
+			grid-row: 1 / 3;
+		}
+
+		> .media:nth-child(3) {
+			grid-column: 2 / 3;
+			grid-row: 2 / 3;
+		}
+	}
+
+	&.n4 {
+		aspect-ratio: 16/9;
+		grid-template-columns: 1fr 1fr;
+		grid-template-rows: 1fr 1fr;
+	}
+
+	&.nMany {
+		grid-template-columns: 1fr 1fr;
+
+		> .media {
+			aspect-ratio: 16/9;
+		}
+	}
+}
+
+.media {
+	overflow: hidden; // clipにするとバグる
+	border-radius: 8px;
+	position: relative;
+
+	>.mediaInner {
+		width: 100%;
+		height: 100%;
+	}
+}
+
+.banner {
+	position: relative;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmMediaVideo.vue b/packages/frontend-embed/src/components/EmMediaVideo.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ce751f9acdf83fd49c5d61a4ffcd97ff46acba25
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmMediaVideo.vue
@@ -0,0 +1,64 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<a :href="href" target="_blank" :class="$style.root">
+	<img v-if="!video.isSensitive && video.thumbnailUrl" :class="$style.thumbnail" :src="video.thumbnailUrl">
+	<div :class="$style.videoOverlayPlayButton"><i class="ti ti-player-play-filled"></i></div>
+</a>
+</template>
+
+<script setup lang="ts">
+import * as Misskey from 'misskey-js';
+
+defineProps<{
+	video: Misskey.entities.DriveFile;
+	href: string;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	box-sizing: border-box;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	width: 100%;
+	height: auto;
+	aspect-ratio: 16 / 9;
+	padding: var(--margin);
+	border: 1px solid var(--divider);
+	border-radius: var(--radius);
+	background-color: #000;
+
+	&:hover {
+		text-decoration: none;
+	}
+}
+
+.thumbnail {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	object-fit: cover;
+}
+
+.videoOverlayPlayButton {
+	background: var(--accent);
+	color: #fff;
+	padding: 1rem;
+	border-radius: 99rem;
+
+	font-size: 1rem;
+	line-height: 1rem;
+
+	&:focus-visible {
+		outline: none;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a631783507a9440753a83477f47662995239bb1a
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmMention.vue
@@ -0,0 +1,46 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkA :class="[$style.root]" :to="url" :style="{ background: bgCss }">
+	<span>
+		<span>@{{ username }}</span>
+		<span v-if="(host != localHost)" :class="$style.host">@{{ toUnicode(host) }}</span>
+	</span>
+</MkA>
+</template>
+
+<script lang="ts" setup>
+import { toUnicode } from 'punycode';
+import { } from 'vue';
+import tinycolor from 'tinycolor2';
+import { host as localHost } from '@@/js/config.js';
+
+const props = defineProps<{
+	username: string;
+	host: string;
+}>();
+
+const canonical = props.host === localHost ? `@${props.username}` : `@${props.username}@${toUnicode(props.host)}`;
+
+const url = `/${canonical}`;
+
+const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--mention'));
+bg.setAlpha(0.1);
+const bgCss = bg.toRgbString();
+</script>
+
+<style lang="scss" module>
+.root {
+	display: inline-block;
+	padding: 4px 8px 4px 4px;
+	border-radius: 999px;
+	color: var(--mention);
+}
+
+.host {
+	opacity: 0.5;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts
new file mode 100644
index 0000000000000000000000000000000000000000..40a09df939fc6c19b8b62cf5433ef01669c2e42b
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmMfm.ts
@@ -0,0 +1,492 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { VNode, h, SetupContext, provide } from 'vue';
+import * as mfm from '@transfem-org/sfm-js';
+import * as Misskey from 'misskey-js';
+import EmUrl from '@/components/EmUrl.vue';
+import EmTime from '@/components/EmTime.vue';
+import EmLink from '@/components/EmLink.vue';
+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;
+	const num = parseFloat(str);
+	if (isNaN(num)) return null;
+	return num;
+}
+
+const QUOTE_STYLE = `
+display: block;
+margin: 8px;
+padding: 6px 0 6px 12px;
+color: var(--fg);
+border-left: solid 3px var(--fg);
+opacity: 0.7;
+`.split('\n').join(' ');
+
+type MfmProps = {
+	text: string;
+	plain?: boolean;
+	nowrap?: boolean;
+	author?: Misskey.entities.UserLite;
+	isNote?: boolean;
+	emojiUrls?: Record<string, string>;
+	rootScale?: number;
+	nyaize?: boolean | 'respect';
+	parsedNodes?: mfm.MfmNode[] | null;
+	enableEmojiMenu?: boolean;
+	enableEmojiMenuReaction?: boolean;
+	linkNavigationBehavior?: string;
+};
+
+type MfmEvents = {
+	clickEv(id: string): void;
+};
+
+// 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 === 'respect' && props.author?.isCat && props.author?.speakAsCat;
+
+	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+	if (props.text == null || props.text === '') return;
+
+	const rootAst = props.parsedNodes ?? (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
+
+	const validTime = (t: string | boolean | null | undefined) => {
+		if (t == null) return null;
+		if (typeof t === 'boolean') return null;
+		return t.match(/^\-?[0-9.]+s$/) ? t : null;
+	};
+
+	const validColor = (c: unknown): string | null => {
+		if (typeof c !== 'string') return null;
+		return c.match(/^[0-9a-f]{3,6}$/i) ? c : null;
+	};
+
+	const useAnim = true;
+
+	const isBlock = props.isBlock ?? false;
+
+	/**
+	 * Gen Vue Elements from MFM AST
+	 * @param ast MFM AST
+	 * @param scale How times large the text is
+	 * @param disableNyaize Whether nyaize is disabled or not
+	 */
+	const genEl = (ast: mfm.MfmNode[], scale: number, disableNyaize = false) => ast.map((token): VNode | string | (VNode | string)[] => {
+		switch (token.type) {
+			case 'text': {
+				let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
+				if (!disableNyaize && shouldNyaize) {
+					text = Misskey.nyaize(text);
+				}
+
+				if (!props.plain) {
+					const res: (VNode | string)[] = [];
+					for (const t of text.split('\n')) {
+						res.push(h('br'));
+						res.push(t);
+					}
+					res.shift();
+					return res;
+				} else {
+					return [text.replace(/\n/g, ' ')];
+				}
+			}
+
+			case 'bold': {
+				return [h('b', genEl(token.children, scale))];
+			}
+
+			case 'strike': {
+				return [h('del', genEl(token.children, scale))];
+			}
+
+			case 'italic': {
+				return h('i', {
+					style: 'font-style: oblique;',
+				}, genEl(token.children, scale));
+			}
+
+			case 'fn': {
+				// TODO: CSSを文字列で組み立てていくと token.props.args.~~~ 経由でCSSインジェクションできるのでよしなにやる
+				let style: string | undefined;
+				switch (token.props.name) {
+					case 'tada': {
+						const speed = validTime(token.props.args.speed) ?? '1s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = 'font-size: 150%;' + (useAnim ? `animation: global-tada ${speed} linear infinite both; animation-delay: ${delay};` : '');
+						break;
+					}
+					case 'jelly': {
+						const speed = validTime(token.props.args.speed) ?? '1s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both; animation-delay: ${delay};` : '');
+						break;
+					}
+					case 'twitch': {
+						const speed = validTime(token.props.args.speed) ?? '0.5s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = useAnim ? `animation: mfm-twitch ${speed} ease infinite; animation-delay: ${delay};` : '';
+						break;
+					}
+					case 'shake': {
+						const speed = validTime(token.props.args.speed) ?? '0.5s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = useAnim ? `animation: mfm-shake ${speed} ease infinite; animation-delay: ${delay};` : '';
+						break;
+					}
+					case 'spin': {
+						const direction =
+							token.props.args.left ? 'reverse' :
+							token.props.args.alternate ? 'alternate' :
+							'normal';
+						const anime =
+							token.props.args.x ? 'mfm-spinX' :
+							token.props.args.y ? 'mfm-spinY' :
+							'mfm-spin';
+						const speed = validTime(token.props.args.speed) ?? '1.5s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction}; animation-delay: ${delay};` : '';
+						break;
+					}
+					case 'jump': {
+						const speed = validTime(token.props.args.speed) ?? '0.75s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = useAnim ? `animation: mfm-jump ${speed} linear infinite; animation-delay: ${delay};` : '';
+						break;
+					}
+					case 'bounce': {
+						const speed = validTime(token.props.args.speed) ?? '0.75s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom; animation-delay: ${delay};` : '';
+						break;
+					}
+					case 'flip': {
+						const transform =
+							(token.props.args.h && token.props.args.v) ? 'scale(-1, -1)' :
+							token.props.args.v ? 'scaleY(-1)' :
+							'scaleX(-1)';
+						style = `transform: ${transform};`;
+						break;
+					}
+					case 'x2': {
+						return h('span', {
+							class: 'mfm-x2',
+						}, genEl(token.children, scale * 2));
+					}
+					case 'x3': {
+						return h('span', {
+							class: 'mfm-x3',
+						}, genEl(token.children, scale * 3));
+					}
+					case 'x4': {
+						return h('span', {
+							class: 'mfm-x4',
+						}, genEl(token.children, scale * 4));
+					}
+					case 'font': {
+						const family =
+							token.props.args.serif ? 'serif' :
+							token.props.args.monospace ? 'monospace' :
+							token.props.args.cursive ? 'cursive' :
+							token.props.args.fantasy ? 'fantasy' :
+							token.props.args.emoji ? 'emoji' :
+							token.props.args.math ? 'math' :
+							null;
+						if (family) style = `font-family: ${family};`;
+						break;
+					}
+					case 'blur': {
+						return h('span', {
+							class: '_mfm_blur_',
+						}, genEl(token.children, scale));
+					}
+					case 'rainbow': {
+						if (!useAnim) {
+							return h('span', {
+								class: '_mfm_rainbow_fallback_',
+							}, genEl(token.children, scale));
+						}
+						const speed = validTime(token.props.args.speed) ?? '1s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						style = `animation: mfm-rainbow ${speed} linear infinite; animation-delay: ${delay};`;
+						break;
+					}
+					case 'sparkle': {
+						return genEl(token.children, scale);
+					}
+					case 'fade': {
+						const direction = token.props.args.out
+							? 'alternate-reverse'
+							: 'alternate';
+						const speed = validTime(token.props.args.speed) ?? '1.5s';
+						const delay = validTime(token.props.args.delay) ?? '0s';
+						const loop = safeParseFloat(token.props.args.loop) ?? 'infinite';
+						style = `animation: mfm-fade ${speed} ${delay} linear ${loop}; animation-direction: ${direction};`;
+						break;
+					}
+					case 'rotate': {
+						const degrees = safeParseFloat(token.props.args.deg) ?? 90;
+						style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
+						break;
+					}
+					case 'followmouse': {
+						return genEl(token.children, scale);
+					}
+					case 'position': {
+						const x = safeParseFloat(token.props.args.x) ?? 0;
+						const y = safeParseFloat(token.props.args.y) ?? 0;
+						style = `transform: translateX(${x}em) translateY(${y}em);`;
+						break;
+					}
+					case 'crop': {
+						const top = Number.parseFloat(
+							(token.props.args.top ?? '0').toString(),
+						);
+						const right = Number.parseFloat(
+							(token.props.args.right ?? '0').toString(),
+						);
+						const bottom = Number.parseFloat(
+							(token.props.args.bottom ?? '0').toString(),
+						);
+						const left = Number.parseFloat(
+							(token.props.args.left ?? '0').toString(),
+						);
+						style = `clip-path: inset(${top}% ${right}% ${bottom}% ${left}%);`;
+						break;
+					}
+					case 'scale': {
+						const x = Math.min(safeParseFloat(token.props.args.x) ?? 1, 5);
+						const y = Math.min(safeParseFloat(token.props.args.y) ?? 1, 5);
+						style = `transform: scale(${x}, ${y});`;
+						scale = scale * Math.max(x, y);
+						break;
+					}
+					case 'fg': {
+						let color = validColor(token.props.args.color);
+						color = color ?? 'f00';
+						style = `color: #${color}; overflow-wrap: anywhere;`;
+						break;
+					}
+					case 'bg': {
+						let color = validColor(token.props.args.color);
+						color = color ?? 'f00';
+						style = `background-color: #${color}; overflow-wrap: anywhere;`;
+						break;
+					}
+					case 'border': {
+						let color = validColor(token.props.args.color);
+						color = color ? `#${color}` : 'var(--accent)';
+						let b_style = token.props.args.style;
+						if (
+							typeof b_style !== 'string' ||
+							!['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset']
+								.includes(b_style)
+						) b_style = 'solid';
+						const width = safeParseFloat(token.props.args.width) ?? 1;
+						const radius = safeParseFloat(token.props.args.radius) ?? 0;
+						style = `border: ${width}px ${b_style} ${color}; border-radius: ${radius}px;${token.props.args.noclip ? '' : ' overflow: clip;'}`;
+						break;
+					}
+					case 'ruby': {
+						if (token.children.length === 1) {
+							const child = token.children[0];
+							let text = child.type === 'text' ? child.props.text : '';
+							if (!disableNyaize && shouldNyaize) {
+								text = Misskey.nyaize(text);
+							}
+							return h('ruby', {}, [text.split(' ')[0], h('rt', text.split(' ')[1])]);
+						} else {
+							const rt = token.children.at(-1)!;
+							let text = rt.type === 'text' ? rt.props.text : '';
+							if (!disableNyaize && shouldNyaize) {
+								text = Misskey.nyaize(text);
+							}
+							return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]);
+						}
+					}
+					case 'unixtime': {
+						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;',
+						}, [
+							h('i', {
+								class: 'ti ti-clock',
+								style: 'margin-right: 0.25em;',
+							}),
+							h(EmTime, {
+								key: Math.random(),
+								time: unixtime * 1000,
+								mode: 'detail',
+							}),
+						]);
+					}
+					case 'clickable': {
+						return h('span', { onClick(ev: MouseEvent): void {
+							ev.stopPropagation();
+							ev.preventDefault();
+							const clickEv = typeof token.props.args.ev === 'string' ? token.props.args.ev : '';
+							emit('clickEv', clickEv);
+						} }, genEl(token.children, scale));
+					}
+				}
+				if (style === undefined) {
+					return h('span', {}, ['$[', token.props.name, ' ', ...genEl(token.children, scale), ']']);
+				} else {
+					return h('span', {
+						style: 'display: inline-block; ' + style,
+					}, genEl(token.children, scale));
+				}
+			}
+
+			case 'small': {
+				return [h('small', {
+					style: 'opacity: 0.7;',
+				}, genEl(token.children, scale))];
+			}
+
+			case 'center': {
+				return [h('div', {
+					style: 'text-align:center;',
+				}, h('bdi', genEl(token.children, scale)))];
+			}
+
+			case 'url': {
+				return [h('bdi', h(EmUrl, {
+					key: Math.random(),
+					url: token.props.url,
+					rel: 'nofollow noopener',
+				}))];
+			}
+
+			case 'link': {
+				return [h('bdi', h(EmLink, {
+					key: Math.random(),
+					url: token.props.url,
+					rel: 'nofollow noopener',
+				}, genEl(token.children, scale, true)))];
+			}
+
+			case 'mention': {
+				return [h('bdi', h(EmMention, {
+					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,
+				}))];
+			}
+
+			case 'hashtag': {
+				return [h('bdi', h(EmA, {
+					key: Math.random(),
+					to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
+					style: 'color:var(--hashtag);',
+				}, `#${token.props.hashtag}`))];
+			}
+
+			case 'blockCode': {
+				return [h('bdi', { class: 'block' }, h('code', {
+					key: Math.random(),
+					lang: token.props.lang ?? undefined,
+				}, token.props.code))];
+			}
+
+			case 'inlineCode': {
+				return [h('bdi', h('code', {
+					key: Math.random(),
+				}, token.props.code))];
+			}
+
+			case 'quote': {
+				if (!props.nowrap) {
+					return [h('bdi', { class: 'block' }, h('div', {
+						style: QUOTE_STYLE,
+					}, h('bdi', genEl(token.children, scale, true))))];
+				} else {
+					return [h('span', {
+						style: QUOTE_STYLE,
+					}, h('bdi', genEl(token.children, scale, true)))];
+				}
+			}
+
+			case 'emojiCode': {
+				if (props.author?.host == null) {
+					return [h(EmCustomEmoji, {
+						key: Math.random(),
+						name: token.props.name,
+						normal: props.plain,
+						host: null,
+						useOriginalSize: scale >= 2.5,
+						menu: props.enableEmojiMenu,
+						menuReaction: props.enableEmojiMenuReaction,
+						fallbackToImage: false,
+					})];
+				} else {
+					// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+					if (props.emojiUrls && (props.emojiUrls[token.props.name] == null)) {
+						return [h('span', `:${token.props.name}:`)];
+					} else {
+						return [h(EmCustomEmoji, {
+							key: Math.random(),
+							name: token.props.name,
+							url: props.emojiUrls && props.emojiUrls[token.props.name],
+							normal: props.plain,
+							host: props.author.host,
+							useOriginalSize: scale >= 2.5,
+						})];
+					}
+				}
+			}
+
+			case 'unicodeEmoji': {
+				return [h(EmEmoji, {
+					key: Math.random(),
+					emoji: token.props.emoji,
+					menu: props.enableEmojiMenu,
+					menuReaction: props.enableEmojiMenuReaction,
+				})];
+			}
+
+			case 'mathInline': {
+				return [h('bdi', h('code', token.props.formula))];
+			}
+
+			case 'mathBlock': {
+				return [h('bdi', h('code', token.props.formula))];
+			}
+
+			case 'search': {
+				return [h('bdi', h('div', {
+					key: Math.random(),
+				}, token.props.query))];
+			}
+
+			case 'plain': {
+				return [h('bdi', h('span', genEl(token.children, scale, true)))];
+			}
+
+			default: {
+				// eslint-disable-next-line @typescript-eslint/no-explicit-any
+				console.error('unrecognized ast type:', (token as any).type);
+
+				return [];
+			}
+		}
+	}).flat(Infinity) as (VNode | string)[];
+
+	return h('bdi', { ...( isBlock ? { class: 'block' } : {}) }, h('span', {
+		// https://codeday.me/jp/qa/20190424/690106.html
+		style: props.nowrap ? 'white-space: pre; word-wrap: normal; overflow: hidden; text-overflow: ellipsis;' : 'white-space: pre-wrap;',
+	}, genEl(rootAst, props.rootScale ?? 1)));
+}
diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4677284747b18e9c80c37220828c105fe940f759
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmNote.vue
@@ -0,0 +1,612 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+	v-show="!isDeleted"
+	ref="rootEl"
+	:class="[$style.root]"
+	:tabindex="isDeleted ? '-1' : '0'"
+>
+	<EmNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
+	<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
+	<!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
+	<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
+	<div v-if="isRenote" :class="$style.renote">
+		<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
+		<EmAvatar :class="$style.renoteAvatar" :user="note.user" link/>
+		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
+		<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
+			<template #user>
+				<EmA :class="$style.renoteUserName" :to="userPage(note.user)">
+					<EmUserName :user="note.user"/>
+				</EmA>
+			</template>
+		</I18n>
+		<div :class="$style.renoteInfo">
+			<button ref="renoteTime" :class="$style.renoteTime" class="_button">
+				<i class="ti ti-dots" :class="$style.renoteMenu"></i>
+				<EmTime :time="note.createdAt"/>
+			</button>
+			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
+				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
+				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
+				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+			</span>
+			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
+			<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
+		</div>
+	</div>
+	<article :class="$style.article">
+		<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
+		<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'" :isBlock="true"/>
+					<button style="display: block; width: 100%; margin: 4px 0;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
+				</p>
+				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
+					<div :class="$style.text">
+						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
+						<EmA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA>
+						<EmMfm
+							v-if="appearNote.text"
+							:parsedNodes="parsed"
+							:text="appearNote.text"
+							:author="appearNote.user"
+							:nyaize="'respect'"
+							:emojiUrls="appearNote.emojis"
+							:enableEmojiMenu="!true"
+							:enableEmojiMenuReaction="true"
+							:isBlock="true"
+						/>
+					</div>
+					<div v-if="appearNote.files && appearNote.files.length > 0">
+						<EmMediaList :mediaList="appearNote.files" :originalEntityUrl="`${url}/notes/${appearNote.id}`"/>
+					</div>
+					<EmPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :readOnly="true" :class="$style.poll"/>
+					<div v-if="appearNote.renote" :class="$style.quote"><EmNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
+					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
+						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
+					</button>
+					<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
+						<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
+					</button>
+				</div>
+				<EmA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</EmA>
+			</div>
+			<EmReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" :note="appearNote" :maxNumber="16">
+				<template #more>
+					<EmA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</EmA>
+				</template>
+			</EmReactionsViewer>
+			<footer :class="$style.footer">
+				<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button">
+					<i class="ti ti-arrow-back-up"></i>
+				</a>
+				<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button">
+					<i class="ti ti-repeat"></i>
+				</a>
+				<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button">
+					<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+					<i v-else class="ti ti-plus"></i>
+				</a>
+				<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.footerButton, $style.footerButtonLink]" class="_button">
+					<i class="ti ti-dots"></i>
+				</a>
+			</footer>
+		</div>
+	</article>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, inject, ref, shallowRef } from 'vue';
+import * as mfm from '@transfem-org/sfm-js';
+import * as Misskey from 'misskey-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';
+import EmMfm from '@/components/EmMfm.js';
+import EmA from '@/components/EmA.vue';
+import EmAvatar from '@/components/EmAvatar.vue';
+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;
+}
+
+const props = withDefaults(defineProps<{
+	note: Misskey.entities.Note;
+	pinned?: boolean;
+}>(), {
+});
+
+const emit = defineEmits<{
+	(ev: 'reaction', emoji: string): void;
+	(ev: 'removeReaction', emoji: string): void;
+}>();
+
+const inChannel = inject('inChannel', null);
+
+const note = ref((props.note));
+
+const isRenote = Misskey.note.isPureRenote(note.value);
+
+const rootEl = shallowRef<HTMLElement>();
+const renoteTime = shallowRef<HTMLElement>();
+const appearNote = computed(() => getAppearNote(note.value));
+const showContent = ref(false);
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const isLong = shouldCollapsed(appearNote.value, []);
+const collapsed = ref(appearNote.value.cw == null && isLong);
+const isDeleted = ref(false);
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	transition: box-shadow 0.1s ease;
+	font-size: 1.05em;
+	overflow: clip;
+	contain: content;
+
+	// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
+	// 下の方までスクロールすると上のノートの高さがここで決め打ちされたものに変化し、表示しているノートの位置が変わってしまう
+	// ノートがマウントされたときに自身の高さを取得し contain-intrinsic-size を設定しなおせばほぼ解決できそうだが、
+	// 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる
+	// 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?)
+	//content-visibility: auto;
+  //contain-intrinsic-size: 0 128px;
+
+	&:focus-visible {
+		outline: none;
+
+		&::after {
+			content: "";
+			pointer-events: none;
+			display: block;
+			position: absolute;
+			z-index: 10;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			margin: auto;
+			width: calc(100% - 8px);
+			height: calc(100% - 8px);
+			border: dashed 2px var(--focus);
+			border-radius: var(--radius);
+			box-sizing: border-box;
+		}
+	}
+
+	.footer {
+		position: relative;
+		z-index: 1;
+	}
+
+	&:hover > .article > .main > .footer > .footerButton {
+		opacity: 1;
+	}
+
+	&.showActionsOnlyHover {
+		.footer {
+			visibility: hidden;
+			position: absolute;
+			top: 12px;
+			right: 12px;
+			padding: 0 4px;
+			margin-bottom: 0 !important;
+			background: var(--popup);
+			border-radius: 8px;
+			box-shadow: 0px 4px 32px var(--shadow);
+		}
+
+		.footerButton {
+			font-size: 90%;
+
+			&:not(:last-child) {
+				margin-right: 0;
+			}
+		}
+	}
+
+	&.showActionsOnlyHover:hover {
+		.footer {
+			visibility: visible;
+		}
+	}
+}
+
+.tip {
+	display: flex;
+	align-items: center;
+	padding: 16px 32px 8px 32px;
+	line-height: 24px;
+	font-size: 90%;
+	white-space: pre;
+	color: #d28a3f;
+}
+
+.tip + .article {
+	padding-top: 8px;
+}
+
+.replyTo {
+	opacity: 0.7;
+	padding-bottom: 0;
+}
+
+.renote {
+	position: relative;
+	display: flex;
+	align-items: center;
+	padding: 16px 32px 8px 32px;
+	line-height: 28px;
+	white-space: pre;
+	color: var(--renote);
+
+	& + .article {
+		padding-top: 8px;
+	}
+
+	> .colorBar {
+		height: calc(100% - 6px);
+	}
+}
+
+.renoteAvatar {
+	flex-shrink: 0;
+	display: inline-block;
+	width: 28px;
+	height: 28px;
+	margin: 0 8px 0 0;
+}
+
+.renoteText {
+	overflow: hidden;
+	flex-shrink: 1;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+.renoteUserName {
+	font-weight: bold;
+}
+
+.renoteInfo {
+	margin-left: auto;
+	font-size: 0.9em;
+}
+
+.renoteTime {
+	flex-shrink: 0;
+	color: inherit;
+}
+
+.renoteMenu {
+	margin-right: 4px;
+}
+
+.collapsedRenoteTarget {
+	display: flex;
+	align-items: center;
+	line-height: 28px;
+	white-space: pre;
+	padding: 0 32px 18px;
+}
+
+.collapsedRenoteTargetAvatar {
+	flex-shrink: 0;
+	display: inline-block;
+	width: 28px;
+	height: 28px;
+	margin: 0 8px 0 0;
+}
+
+.collapsedRenoteTargetText {
+	overflow: hidden;
+	flex-shrink: 1;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	font-size: 90%;
+	opacity: 0.7;
+	cursor: pointer;
+
+	&:hover {
+		text-decoration: underline;
+	}
+}
+
+.article {
+	position: relative;
+	display: flex;
+	padding: 28px 32px;
+}
+
+.colorBar {
+	position: absolute;
+	top: 8px;
+	left: 8px;
+	width: 5px;
+	height: calc(100% - 16px);
+	border-radius: 999px;
+	pointer-events: none;
+}
+
+.avatar {
+	flex-shrink: 0;
+	display: block !important;
+	margin: 0 14px 0 0;
+	width: 58px;
+	height: 58px;
+	position: sticky !important;
+	top: calc(22px + var(--stickyTop, 0px));
+	left: 0;
+}
+
+.main {
+	flex: 1;
+	min-width: 0;
+}
+
+.cw {
+	cursor: default;
+	display: block;
+	margin: 0;
+	padding: 0;
+	overflow-wrap: break-word;
+}
+
+.showLess {
+	width: 100%;
+	margin-top: 14px;
+	position: sticky;
+	bottom: calc(var(--stickyBottom, 0px) + 14px);
+}
+
+.showLessLabel {
+	display: inline-block;
+	background: var(--popup);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
+
+.contentCollapsed {
+	position: relative;
+	max-height: 9em;
+	overflow: clip;
+}
+
+.collapsed {
+	display: block;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	z-index: 2;
+	width: 100%;
+	height: 64px;
+	background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+
+	&:hover > .collapsedLabel {
+		background: var(--panelHighlight);
+	}
+}
+
+.collapsedLabel {
+	display: inline-block;
+	background: var(--panel);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
+
+.text {
+	overflow-wrap: break-word;
+}
+
+.replyIcon {
+	color: var(--accent);
+	margin-right: 0.5em;
+}
+
+.translation {
+	border: solid 0.5px var(--divider);
+	border-radius: var(--radius);
+	padding: 12px;
+	margin-top: 8px;
+}
+
+.urlPreview {
+	margin-top: 8px;
+}
+
+.poll {
+	font-size: 80%;
+}
+
+.quote {
+	padding: 8px 0;
+}
+
+.quoteNote {
+	padding: 16px;
+	border: dashed 1px var(--renote);
+	border-radius: 8px;
+	overflow: clip;
+}
+
+.channel {
+	opacity: 0.7;
+	font-size: 80%;
+}
+
+.footer {
+	margin-bottom: -14px;
+}
+
+.footerButton {
+	margin: 0;
+	padding: 8px;
+	opacity: 0.7;
+
+	&:not(:last-child) {
+		margin-right: 28px;
+	}
+
+	&:hover {
+		color: var(--fgHighlighted);
+	}
+}
+
+.footerButtonLink:hover,
+.footerButtonLink:focus,
+.footerButtonLink:active {
+	text-decoration: none;
+}
+
+.footerButtonCount {
+	display: inline;
+	margin: 0 0 0 8px;
+	opacity: 0.7;
+}
+
+@container (max-width: 580px) {
+	.root {
+		font-size: 0.95em;
+	}
+
+	.renote {
+		padding: 12px 26px 0 26px;
+	}
+
+	.article {
+		padding: 24px 26px;
+	}
+
+	.avatar {
+		width: 50px;
+		height: 50px;
+	}
+}
+
+@container (max-width: 500px) {
+	.root {
+		font-size: 0.9em;
+	}
+
+	.renote {
+		padding: 10px 22px 0 22px;
+	}
+
+	.article {
+		padding: 20px 22px;
+	}
+
+	.footer {
+		margin-bottom: -8px;
+	}
+}
+
+@container (max-width: 480px) {
+	.renote {
+		padding: 8px 16px 0 16px;
+	}
+
+	.tip {
+		padding: 8px 16px 0 16px;
+	}
+
+	.collapsedRenoteTarget {
+		padding: 0 16px 9px;
+		margin-top: 4px;
+	}
+
+	.article {
+		padding: 14px 16px;
+	}
+}
+
+@container (max-width: 450px) {
+	.avatar {
+		margin: 0 10px 0 0;
+		width: 46px;
+		height: 46px;
+		top: calc(14px + var(--stickyTop, 0px));
+	}
+}
+
+@container (max-width: 400px) {
+	.root:not(.showActionsOnlyHover) {
+		.footerButton {
+			&:not(:last-child) {
+				margin-right: 18px;
+			}
+		}
+	}
+}
+
+@container (max-width: 350px) {
+	.root:not(.showActionsOnlyHover) {
+		.footerButton {
+			&:not(:last-child) {
+				margin-right: 12px;
+			}
+		}
+	}
+
+	.colorBar {
+		top: 6px;
+		left: 6px;
+		width: 4px;
+		height: calc(100% - 12px);
+	}
+}
+
+@container (max-width: 300px) {
+	.avatar {
+		width: 44px;
+		height: 44px;
+	}
+
+	.root:not(.showActionsOnlyHover) {
+		.footerButton {
+			&:not(:last-child) {
+				margin-right: 8px;
+			}
+		}
+	}
+}
+
+@container (max-width: 250px) {
+	.quoteNote {
+		padding: 12px;
+	}
+}
+
+.reactionOmitted {
+	display: inline-block;
+	margin-left: 8px;
+	opacity: .8;
+	font-size: 95%;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0068e05bc4c3284fa3511ccd037c1f9a6013fc27
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue
@@ -0,0 +1,491 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+	v-show="!isDeleted"
+	ref="rootEl"
+	:class="$style.root"
+>
+	<EmNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
+	<div v-if="isRenote" :class="$style.renote">
+		<EmAvatar :class="$style.renoteAvatar" :user="note.user" link/>
+		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
+		<span :class="$style.renoteText">
+			<I18n :src="i18n.ts.renotedBy" tag="span">
+				<template #user>
+					<EmA :class="$style.renoteName" :to="userPage(note.user)">
+						<EmUserName :user="note.user"/>
+					</EmA>
+				</template>
+			</I18n>
+		</span>
+		<div :class="$style.renoteInfo">
+			<div class="$style.renoteTime">
+				<EmTime :time="note.createdAt"/>
+			</div>
+			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
+				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
+				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
+				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+			</span>
+			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
+		</div>
+	</div>
+	<article :class="$style.note">
+		<header :class="$style.noteHeader">
+			<EmAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link/>
+			<div :class="$style.noteHeaderBody">
+				<div :class="$style.noteHeaderBodyUpper">
+					<div style="min-width: 0;">
+						<div class="_nowrap">
+							<EmA :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
+								<EmUserName :nowrap="true" :user="appearNote.user"/>
+							</EmA>
+							<span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span>
+						</div>
+						<div :class="$style.noteHeaderUsername"><EmAcct :user="appearNote.user"/></div>
+					</div>
+					<div :class="$style.noteHeaderInfo">
+						<a :href="url" :class="$style.noteHeaderInstanceIconLink" target="_blank" rel="noopener noreferrer">
+							<img :src="serverMetadata.iconUrl || '/favicon.ico'" alt="" :class="$style.noteHeaderInstanceIcon"/>
+						</a>
+					</div>
+				</div>
+				<EmInstanceTicker v-if="appearNote.user.instance != null" :instance="appearNote.user.instance"/>
+			</div>
+		</header>
+		<div :class="[$style.noteContent, { [$style.contentCollapsed]: collapsed }]">
+			<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'" :isBlock="true"/>
+				<button style="display: block; width: 100%; margin: 4px 0;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
+			</p>
+			<div v-show="appearNote.cw == null || showContent">
+				<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
+				<EmA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA>
+				<EmMfm
+					v-if="appearNote.text"
+					:parsedNodes="parsed"
+					:text="appearNote.text"
+					:author="appearNote.user"
+					:nyaize="'respect'"
+					:emojiUrls="appearNote.emojis"
+					:isBlock="true"
+				/>
+				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
+				<div v-if="appearNote.files && appearNote.files.length > 0">
+					<EmMediaList :mediaList="appearNote.files" :originalEntityUrl="`${url}/notes/${appearNote.id}`"/>
+				</div>
+				<EmPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :readOnly="true" :class="$style.poll"/>
+				<div v-if="appearNote.renote" :class="$style.quote"><EmNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
+				<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
+					<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
+				</button>
+				<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
+					<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
+				</button>
+			</div>
+			<EmA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</EmA>
+		</div>
+		<footer>
+			<div :class="$style.noteFooterInfo">
+				<span v-if="appearNote.visibility !== 'public'" style="display: inline-block; margin-right: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]">
+					<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
+					<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i>
+					<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+				</span>
+				<span v-if="appearNote.localOnly" style="display: inline-block; margin-right: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
+				<EmA :to="notePage(appearNote)">
+					<EmTime :time="appearNote.createdAt" mode="detail" colored/>
+				</EmA>
+			</div>
+			<EmReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly'" ref="reactionsViewer" :maxNumber="16" :note="appearNote">
+				<template #more>
+					<EmA :to="`/notes/${appearNote.id}`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</EmA>
+				</template>
+			</EmReactionsViewer>
+			<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button">
+				<i class="ti ti-arrow-back-up"></i>
+			</a>
+			<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button">
+				<i class="ti ti-repeat"></i>
+				<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ (appearNote.renoteCount) }}</p>
+			</a>
+			<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button">
+				<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+				<i v-else class="ti ti-plus"></i>
+				<p v-if="(appearNote.reactionAcceptance === 'likeOnly') && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ (appearNote.reactionCount) }}</p>
+			</a>
+			<a :href="`/notes/${appearNote.id}`" target="_blank" rel="noopener" :class="[$style.noteFooterButton, $style.footerButtonLink]" class="_button">
+				<i class="ti ti-dots"></i>
+			</a>
+		</footer>
+	</article>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, inject, ref } from 'vue';
+import * as mfm from '@transfem-org/sfm-js';
+import * as Misskey from 'misskey-js';
+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';
+import EmAvatar from '@/components/EmAvatar.vue';
+import EmTime from '@/components/EmTime.vue';
+import EmUserName from '@/components/EmUserName.vue';
+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 { url } from '@@/js/config.js';
+import EmMfm from '@/components/EmMfm.js';
+
+const props = defineProps<{
+	note: Misskey.entities.Note;
+}>();
+
+const serverMetadata = inject(DI.serverMetadata)!;
+
+const inChannel = inject('inChannel', null);
+
+const note = ref(props.note);
+
+const isRenote = (
+	note.value.renote != null &&
+	note.value.reply == null &&
+	note.value.text == null &&
+	note.value.cw == null &&
+	note.value.fileIds && note.value.fileIds.length === 0 &&
+	note.value.poll == null
+);
+
+const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
+const showContent = ref(false);
+const isDeleted = ref(false);
+const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
+const isLong = shouldCollapsed(appearNote.value, []);
+const collapsed = ref(appearNote.value.cw == null && isLong);
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	transition: box-shadow 0.1s ease;
+	overflow: clip;
+	contain: content;
+}
+
+.replyTo {
+	opacity: 0.7;
+	padding-bottom: 0;
+}
+
+.renote {
+	display: flex;
+	align-items: center;
+	padding: 16px 32px 8px 32px;
+	line-height: 28px;
+	white-space: pre;
+	color: var(--renote);
+}
+
+.renoteAvatar {
+	flex-shrink: 0;
+	display: inline-block;
+	width: 28px;
+	height: 28px;
+	margin: 0 8px 0 0;
+	border-radius: 6px;
+}
+
+.renoteText {
+	overflow: hidden;
+	flex-shrink: 1;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+
+.renoteName {
+	font-weight: bold;
+}
+
+.renoteInfo {
+	margin-left: auto;
+	font-size: 0.9em;
+}
+
+.renoteTime {
+	flex-shrink: 0;
+	color: inherit;
+}
+
+.renote + .note {
+	padding-top: 8px;
+}
+
+.note {
+	padding: 24px 32px 16px;
+	font-size: 1.2em;
+
+	&:hover > .main > .footer > .button {
+		opacity: 1;
+	}
+}
+
+.noteHeader {
+	display: flex;
+	position: relative;
+	margin-bottom: 16px;
+	align-items: center;
+}
+
+.noteHeaderAvatar {
+	display: block;
+	flex-shrink: 0;
+	width: 50px;
+	height: 50px;
+}
+
+.noteHeaderBody {
+	flex: 1;
+	display: flex;
+	min-width: 0;
+	flex-direction: column;
+	justify-content: center;
+	padding-left: 16px;
+	font-size: 0.95em;
+}
+
+.noteHeaderBodyUpper {
+	display: flex;
+	min-width: 0;
+}
+
+.noteHeaderName {
+	font-weight: bold;
+	line-height: 1.3;
+}
+
+.isBot {
+	display: inline-block;
+	margin: 0 0.5em;
+	padding: 4px 6px;
+	font-size: 80%;
+	line-height: 1;
+	border: solid 0.5px var(--divider);
+	border-radius: 4px;
+}
+
+.noteHeaderInfo {
+	margin-left: auto;
+	display: flex;
+	gap: 0.5em;
+	align-items: center;
+}
+
+.noteHeaderInstanceIconLink {
+	display: inline-block;
+	margin-left: 4px;
+}
+
+.noteHeaderInstanceIcon {
+	width: 32px;
+	height: 32px;
+	border-radius: 4px;
+}
+
+.noteHeaderUsername {
+	margin-bottom: 2px;
+	line-height: 1.3;
+	word-wrap: anywhere;
+}
+
+.noteContent {
+	container-type: inline-size;
+	overflow-wrap: break-word;
+}
+
+.cw {
+	cursor: default;
+	display: block;
+	margin: 0;
+	padding: 0;
+	overflow-wrap: break-word;
+}
+
+.noteReplyTarget {
+	color: var(--accent);
+	margin-right: 0.5em;
+}
+
+.rn {
+	margin-left: 4px;
+	font-style: oblique;
+	color: var(--renote);
+}
+
+.reactionOmitted {
+	display: inline-block;
+	margin-left: 8px;
+	opacity: .8;
+	font-size: 95%;
+}
+
+.poll {
+	font-size: 80%;
+}
+
+.quote {
+	padding: 8px 0;
+}
+
+.quoteNote {
+	padding: 16px;
+	border: dashed 1px var(--renote);
+	border-radius: 8px;
+	overflow: clip;
+}
+
+.channel {
+	opacity: 0.7;
+	font-size: 80%;
+}
+
+.showLess {
+	width: 100%;
+	margin-top: 14px;
+	position: sticky;
+	bottom: calc(var(--stickyBottom, 0px) + 14px);
+}
+
+.showLessLabel {
+	display: inline-block;
+	background: var(--popup);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
+
+.contentCollapsed {
+	position: relative;
+	max-height: 9em;
+	overflow: clip;
+}
+
+.collapsed {
+	display: block;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	z-index: 2;
+	width: 100%;
+	height: 64px;
+	background: linear-gradient(0deg, var(--panel), var(--X15));
+
+	&:hover > .collapsedLabel {
+		background: var(--panelHighlight);
+	}
+}
+
+.collapsedLabel {
+	display: inline-block;
+	background: var(--panel);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
+
+.noteFooterInfo {
+	margin: 16px 0;
+	opacity: 0.7;
+	font-size: 0.9em;
+}
+
+.noteFooterButton {
+	margin: 0;
+	padding: 8px;
+	opacity: 0.7;
+
+	&:not(:last-child) {
+		margin-right: 28px;
+	}
+
+	&:hover {
+		color: var(--fgHighlighted);
+	}
+}
+
+.footerButtonLink:hover,
+.footerButtonLink:focus,
+.footerButtonLink:active {
+	text-decoration: none;
+}
+
+.noteFooterButtonCount {
+	display: inline;
+	margin: 0 0 0 8px;
+	opacity: 0.7;
+
+	&.reacted {
+		color: var(--accent);
+	}
+}
+
+@container (max-width: 500px) {
+	.root {
+		font-size: 0.9em;
+	}
+}
+
+@container (max-width: 450px) {
+	.renote {
+		padding: 8px 16px 0 16px;
+	}
+
+	.note {
+		padding: 16px;
+	}
+
+	.noteHeaderAvatar {
+		width: 50px;
+		height: 50px;
+	}
+}
+
+@container (max-width: 350px) {
+	.noteFooterButton {
+		&:not(:last-child) {
+			margin-right: 18px;
+		}
+	}
+}
+
+@container (max-width: 300px) {
+	.root {
+		font-size: 0.825em;
+	}
+
+	.noteHeaderAvatar {
+		width: 50px;
+		height: 50px;
+	}
+
+	.noteFooterButton {
+		&:not(:last-child) {
+			margin-right: 12px;
+		}
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmNoteHeader.vue b/packages/frontend-embed/src/components/EmNoteHeader.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7d0b9bacadc65d3414ed420f80ca80698136e30a
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmNoteHeader.vue
@@ -0,0 +1,104 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<header :class="$style.root">
+	<EmA :class="$style.name" :to="userPage(note.user)">
+		<EmUserName :user="note.user"/>
+	</EmA>
+	<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" :class="$style.badgeRole" :src="role.iconUrl!"/>
+	</div>
+	<div :class="$style.info">
+		<EmA :to="notePage(note)">
+			<EmTime :time="note.createdAt" colored/>
+		</EmA>
+		<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;">
+			<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
+			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
+			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+		</span>
+		<span v-if="note.localOnly" style="margin-left: 0.5em;"><i class="ti ti-rocket-off"></i></span>
+		<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
+	</div>
+</header>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import * as Misskey from 'misskey-js';
+import { notePage } from '@/utils.js';
+import { userPage } from '@/utils.js';
+import EmA from '@/components/EmA.vue';
+import EmUserName from '@/components/EmUserName.vue';
+import EmAcct from '@/components/EmAcct.vue';
+import EmTime from '@/components/EmTime.vue';
+
+defineProps<{
+	note: Misskey.entities.Note;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	display: flex;
+	align-items: baseline;
+	white-space: nowrap;
+}
+
+.name {
+	flex-shrink: 1;
+	display: block;
+	margin: 0 .5em 0 0;
+	padding: 0;
+	overflow: hidden;
+	font-size: 1em;
+	font-weight: bold;
+	text-decoration: none;
+	text-overflow: ellipsis;
+
+	&:hover {
+		text-decoration: underline;
+	}
+}
+
+.isBot {
+	flex-shrink: 0;
+	align-self: center;
+	margin: 0 .5em 0 0;
+	padding: 1px 6px;
+	font-size: 80%;
+	border: solid 0.5px var(--divider);
+	border-radius: 3px;
+}
+
+.username {
+	flex-shrink: 9999999;
+	margin: 0 .5em 0 0;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.info {
+	flex-shrink: 0;
+	margin-left: auto;
+	font-size: 0.9em;
+}
+
+.badgeRoles {
+	margin: 0 .5em 0 0;
+}
+
+.badgeRole {
+	height: 1.3em;
+	vertical-align: -20%;
+
+	& + .badgeRole {
+		margin-left: 0.2em;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d4afc0b4d7f83cf110fcca30e9f112737415483d
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmNoteSimple.vue
@@ -0,0 +1,106 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+	<EmAvatar :class="$style.avatar" :user="note.user" link preview/>
+	<div :class="$style.main">
+		<EmNoteHeader :class="$style.header" :note="note" :mini="true"/>
+		<div>
+			<p v-if="note.cw != null" :class="$style.cw">
+				<EmMfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis" :isBlock="true"/>
+				<button style="display: block; width: 100%;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
+			</p>
+			<div v-show="note.cw == null || showContent">
+				<EmSubNoteContent :class="$style.text" :note="note"/>
+			</div>
+		</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { i18n } from '@/i18n.js';
+import EmAvatar from '@/components/EmAvatar.vue';
+import EmNoteHeader from '@/components/EmNoteHeader.vue';
+import EmSubNoteContent from '@/components/EmSubNoteContent.vue';
+import EmMfm from '@/components/EmMfm.js';
+
+const props = defineProps<{
+	note: Misskey.entities.Note;
+}>();
+
+const showContent = ref(false);
+</script>
+
+<style lang="scss" module>
+.root {
+	display: flex;
+	margin: 0;
+	padding: 0;
+	font-size: 0.95em;
+}
+
+.avatar {
+	flex-shrink: 0;
+	display: block;
+	margin: 0 10px 0 0;
+	width: 34px;
+	height: 34px;
+	border-radius: 8px;
+	position: sticky !important;
+	top: calc(16px + var(--stickyTop, 0px));
+	left: 0;
+}
+
+.main {
+	flex: 1;
+	min-width: 0;
+}
+
+.header {
+	margin-bottom: 2px;
+}
+
+.cw {
+	cursor: default;
+	display: block;
+	margin: 0;
+	padding: 0;
+	overflow-wrap: break-word;
+}
+
+.text {
+	cursor: default;
+	margin: 0;
+	padding: 0;
+}
+
+@container (min-width: 250px) {
+	.avatar {
+		margin: 0 10px 0 0;
+		width: 40px;
+		height: 40px;
+	}
+}
+
+@container (min-width: 350px) {
+	.avatar {
+		margin: 0 10px 0 0;
+		width: 44px;
+		height: 44px;
+	}
+}
+
+@container (min-width: 500px) {
+	.avatar {
+		margin: 0 12px 0 0;
+		width: 48px;
+		height: 48px;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue
new file mode 100644
index 0000000000000000000000000000000000000000..9fbd1eae35f29f6781b1e3f07e2a02a115fe2b7a
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmNoteSub.vue
@@ -0,0 +1,151 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, { [$style.children]: depth > 1 }]">
+	<div :class="$style.main">
+		<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
+		<EmAvatar :class="$style.avatar" :user="note.user" link preview/>
+		<div :class="$style.body">
+			<EmNoteHeader :class="$style.header" :note="note" :mini="true"/>
+			<div>
+				<p v-if="note.cw != null" :class="$style.cw">
+					<EmMfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :nyaize="'respect'" :isBlock="true"/>
+					<button style="display: block; width: 100%;" class="_buttonGray _buttonRounded" @click="showContent = !showContent">{{ showContent ? i18n.ts._cw.hide : i18n.ts._cw.show }}</button>
+				</p>
+				<div v-show="note.cw == null || showContent">
+					<EmSubNoteContent :class="$style.text" :note="note"/>
+				</div>
+			</div>
+		</div>
+	</div>
+	<template v-if="depth < 5">
+		<EmNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="$style.reply" :detail="true" :depth="depth + 1"/>
+	</template>
+	<div v-else :class="$style.more">
+		<EmA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ti ti-chevron-double-right"></i></EmA>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import EmA from '@/components/EmA.vue';
+import EmAvatar from '@/components/EmAvatar.vue';
+import EmNoteHeader from '@/components/EmNoteHeader.vue';
+import EmSubNoteContent from '@/components/EmSubNoteContent.vue';
+import { notePage } from '@/utils.js';
+import { misskeyApi } from '@/misskey-api.js';
+import { i18n } from '@/i18n.js';
+import EmMfm from '@/components/EmMfm.js';
+
+const props = withDefaults(defineProps<{
+	note: Misskey.entities.Note;
+	detail?: boolean;
+
+	// how many notes are in between this one and the note being viewed in detail
+	depth?: number;
+}>(), {
+	depth: 1,
+});
+
+const showContent = ref(false);
+const replies = ref<Misskey.entities.Note[]>([]);
+
+if (props.detail) {
+	misskeyApi('notes/children', {
+		noteId: props.note.id,
+		limit: 5,
+	}).then(res => {
+		replies.value = res;
+	});
+}
+</script>
+
+<style lang="scss" module>
+.root {
+	padding: 16px 32px;
+	font-size: 0.9em;
+	position: relative;
+
+	&.children {
+		padding: 10px 0 0 16px;
+		font-size: 1em;
+	}
+}
+
+.main {
+	display: flex;
+}
+
+.colorBar {
+	position: absolute;
+	top: 8px;
+	left: 8px;
+	width: 5px;
+	height: calc(100% - 8px);
+	border-radius: 999px;
+	pointer-events: none;
+}
+
+.avatar {
+	flex-shrink: 0;
+	display: block;
+	margin: 0 8px 0 0;
+	width: 38px;
+	height: 38px;
+	border-radius: 8px;
+}
+
+.body {
+	flex: 1;
+	min-width: 0;
+}
+
+.header {
+	margin-bottom: 2px;
+}
+
+.cw {
+	cursor: default;
+	display: block;
+	margin: 0;
+	padding: 0;
+	overflow-wrap: break-word;
+}
+
+.text {
+	margin: 0;
+	padding: 0;
+}
+
+.reply, .more {
+	border-left: solid 0.5px var(--divider);
+	margin-top: 10px;
+}
+
+.more {
+	padding: 10px 0 0 16px;
+}
+
+@container (max-width: 450px) {
+	.root {
+		padding: 14px 16px;
+
+		&.children {
+			padding: 10px 0 0 8px;
+		}
+	}
+}
+
+.muted {
+	text-align: center;
+	padding: 8px !important;
+	border: 1px solid var(--divider);
+	margin: 8px 8px 0 8px;
+	border-radius: 8px;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue
new file mode 100644
index 0000000000000000000000000000000000000000..3418d97f77e486d61a623de4fc7618478657125b
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmNotes.vue
@@ -0,0 +1,52 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<EmPagination ref="pagingComponent" :pagination="pagination" :disableAutoLoad="disableAutoLoad">
+	<template #empty>
+		<div class="_fullinfo">
+			<div>{{ i18n.ts.noNotes }}</div>
+		</div>
+	</template>
+
+	<template #default="{ items: notes }">
+		<div :class="[$style.root]">
+			<EmNote v-for="note in notes" :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
+		</div>
+	</template>
+</EmPagination>
+</template>
+
+<script lang="ts" setup>
+import { useTemplateRef } from 'vue';
+import EmNote from '@/components/EmNote.vue';
+import EmPagination, { Paging } from '@/components/EmPagination.vue';
+import { i18n } from '@/i18n.js';
+
+withDefaults(defineProps<{
+	pagination: Paging;
+	noGap?: boolean;
+	disableAutoLoad?: boolean;
+	ad?: boolean;
+}>(), {
+	ad: true,
+});
+
+const pagingComponent = useTemplateRef('pagingComponent');
+
+defineExpose({
+	pagingComponent,
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	background: var(--panel);
+}
+
+.note {
+	border-bottom: 0.5px solid var(--divider);
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmPagination.vue b/packages/frontend-embed/src/components/EmPagination.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5d5317a91281ac62afbf56f119ca86b5d27cb6bd
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmPagination.vue
@@ -0,0 +1,504 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<EmLoading v-if="fetching"/>
+
+<EmError v-else-if="error" @retry="init()"/>
+
+<div v-else-if="empty" key="_empty_" class="empty">
+	<slot name="empty">
+		<div class="_fullinfo">
+			<div>{{ i18n.ts.nothing }}</div>
+		</div>
+	</slot>
+</div>
+
+<div v-else ref="rootEl">
+	<div v-show="pagination.reversed && more" key="_more_" class="_margin">
+		<button v-if="!moreFetching" class="_buttonPrimary" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMoreAhead">
+			{{ i18n.ts.loadMore }}
+		</button>
+		<EmLoading v-else class="loading"/>
+	</div>
+	<slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot>
+	<div v-show="!pagination.reversed && more" key="_more_" class="_margin">
+		<button v-if="!moreFetching" class="_buttonRounded _buttonPrimary" :class="$style.more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" @click="fetchMore">
+			{{ i18n.ts.loadMore }}
+		</button>
+		<EmLoading v-else class="loading"/>
+	</div>
+</div>
+</template>
+
+<script lang="ts">
+import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
+import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
+import { misskeyApi } from '@/misskey-api.js';
+import { i18n } from '@/i18n.js';
+
+const SECOND_FETCH_LIMIT = 30;
+const TOLERANCE = 16;
+const APPEAR_MINIMUM_INTERVAL = 600;
+
+export type Paging<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints> = {
+	endpoint: E;
+	limit: number;
+	params?: Misskey.Endpoints[E]['req'] | ComputedRef<Misskey.Endpoints[E]['req']>;
+
+	/**
+	 * 検索APIのような、ページング不可なエンドポイントを利用する場合
+	 * (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
+	 */
+	noPaging?: boolean;
+
+	/**
+	 * items 配列の中身を逆順にする(新しい方が最後)
+	 */
+	reversed?: boolean;
+
+	offsetMode?: boolean;
+
+	pageEl?: HTMLElement;
+};
+
+type MisskeyEntity = {
+	id: string;
+	createdAt: string;
+	_shouldInsertAd_?: boolean;
+	[x: string]: any;
+};
+
+type MisskeyEntityMap = Map<string, MisskeyEntity>;
+
+function arrayToEntries(entities: MisskeyEntity[]): [string, MisskeyEntity][] {
+	return entities.map(en => [en.id, en]);
+}
+
+function concatMapWithArray(map: MisskeyEntityMap, entities: MisskeyEntity[]): MisskeyEntityMap {
+	return new Map([...map, ...arrayToEntries(entities)]);
+}
+
+</script>
+<script lang="ts" setup>
+import EmError from '@/components/EmError.vue';
+import EmLoading from '@/components/EmLoading.vue';
+
+const props = withDefaults(defineProps<{
+	pagination: Paging;
+	disableAutoLoad?: boolean;
+	displayLimit?: number;
+}>(), {
+	displayLimit: 20,
+});
+
+const emit = defineEmits<{
+	(ev: 'queue', count: number): void;
+	(ev: 'status', error: boolean): void;
+}>();
+
+const rootEl = shallowRef<HTMLElement>();
+
+// 遡り中かどうか
+const backed = ref(false);
+
+const scrollRemove = ref<(() => void) | null>(null);
+
+/**
+ * 表示するアイテムのソース
+ * 最新が0番目
+ */
+const items = ref<MisskeyEntityMap>(new Map());
+
+/**
+ * タブが非アクティブなどの場合に更新を貯めておく
+ * 最新が0番目
+ */
+const queue = ref<MisskeyEntityMap>(new Map());
+
+const offset = ref(0);
+
+/**
+ * 初期化中かどうか(trueならEmLoadingで全て隠す)
+ */
+const fetching = ref(true);
+
+const moreFetching = ref(false);
+const more = ref(false);
+const preventAppearFetchMore = ref(false);
+const preventAppearFetchMoreTimer = ref<number | null>(null);
+const isBackTop = ref(false);
+const empty = computed(() => items.value.size === 0);
+const error = ref(false);
+
+const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
+const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body);
+
+const visibility = useDocumentVisibility();
+
+let isPausingUpdate = false;
+let timerForSetPause: number | null = null;
+const BACKGROUND_PAUSE_WAIT_SEC = 10;
+
+// 先頭が表示されているかどうかを検出
+// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
+const scrollObserver = ref<IntersectionObserver>();
+
+watch([() => props.pagination.reversed, scrollableElement], () => {
+	if (scrollObserver.value) scrollObserver.value.disconnect();
+
+	scrollObserver.value = new IntersectionObserver(entries => {
+		backed.value = entries[0].isIntersecting;
+	}, {
+		root: scrollableElement.value,
+		rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
+		threshold: 0.01,
+	});
+}, { immediate: true });
+
+watch(rootEl, () => {
+	scrollObserver.value?.disconnect();
+	nextTick(() => {
+		if (rootEl.value) scrollObserver.value?.observe(rootEl.value);
+	});
+});
+
+watch([backed, contentEl], () => {
+	if (!backed.value) {
+		if (!contentEl.value) return;
+
+		scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE);
+	} else {
+		if (scrollRemove.value) scrollRemove.value();
+		scrollRemove.value = null;
+	}
+});
+
+// パラメータに何らかの変更があった際、再読込したい(チャンネル等のIDが変わったなど)
+watch(() => [props.pagination.endpoint, props.pagination.params], init, { deep: true });
+
+watch(queue, (a, b) => {
+	if (a.size === 0 && b.size === 0) return;
+	emit('queue', queue.value.size);
+}, { deep: true });
+
+watch(error, (n, o) => {
+	if (n === o) return;
+	emit('status', n);
+});
+
+async function init(): Promise<void> {
+	items.value = new Map();
+	queue.value = new Map();
+	fetching.value = true;
+	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
+	await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, {
+		...params,
+		limit: props.pagination.limit ?? 10,
+		allowPartial: true,
+	}).then(res => {
+		for (let i = 0; i < res.length; i++) {
+			const item = res[i];
+			if (i === 3) item._shouldInsertAd_ = true;
+		}
+
+		if (res.length === 0 || props.pagination.noPaging) {
+			concatItems(res);
+			more.value = false;
+		} else {
+			if (props.pagination.reversed) moreFetching.value = true;
+			concatItems(res);
+			more.value = true;
+		}
+
+		offset.value = res.length;
+		error.value = false;
+		fetching.value = false;
+	}, err => {
+		error.value = true;
+		fetching.value = false;
+	});
+}
+
+const reload = (): Promise<void> => {
+	return init();
+};
+
+const fetchMore = async (): Promise<void> => {
+	if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return;
+	moreFetching.value = true;
+	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
+	await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, {
+		...params,
+		limit: SECOND_FETCH_LIMIT,
+		...(props.pagination.offsetMode ? {
+			offset: offset.value,
+		} : {
+			untilId: Array.from(items.value.keys()).at(-1),
+		}),
+	}).then(res => {
+		for (let i = 0; i < res.length; i++) {
+			const item = res[i];
+			if (i === 10) item._shouldInsertAd_ = true;
+		}
+
+		const reverseConcat = _res => {
+			const oldHeight = scrollableElement.value ? scrollableElement.value.scrollHeight : getBodyScrollHeight();
+			const oldScroll = scrollableElement.value ? scrollableElement.value.scrollTop : window.scrollY;
+
+			items.value = concatMapWithArray(items.value, _res);
+
+			return nextTick(() => {
+				if (scrollableElement.value) {
+					scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
+				} else {
+					window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
+				}
+
+				return nextTick();
+			});
+		};
+
+		if (res.length === 0) {
+			if (props.pagination.reversed) {
+				reverseConcat(res).then(() => {
+					more.value = false;
+					moreFetching.value = false;
+				});
+			} else {
+				items.value = concatMapWithArray(items.value, res);
+				more.value = false;
+				moreFetching.value = false;
+			}
+		} else {
+			if (props.pagination.reversed) {
+				reverseConcat(res).then(() => {
+					more.value = true;
+					moreFetching.value = false;
+				});
+			} else {
+				items.value = concatMapWithArray(items.value, res);
+				more.value = true;
+				moreFetching.value = false;
+			}
+		}
+		offset.value += res.length;
+	}, err => {
+		moreFetching.value = false;
+	});
+};
+
+const fetchMoreAhead = async (): Promise<void> => {
+	if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return;
+	moreFetching.value = true;
+	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
+	await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, {
+		...params,
+		limit: SECOND_FETCH_LIMIT,
+		...(props.pagination.offsetMode ? {
+			offset: offset.value,
+		} : {
+			sinceId: Array.from(items.value.keys()).at(-1),
+		}),
+	}).then(res => {
+		if (res.length === 0) {
+			items.value = concatMapWithArray(items.value, res);
+			more.value = false;
+		} else {
+			items.value = concatMapWithArray(items.value, res);
+			more.value = true;
+		}
+		offset.value += res.length;
+		moreFetching.value = false;
+	}, err => {
+		moreFetching.value = false;
+	});
+};
+
+/**
+ * Appear(IntersectionObserver)によってfetchMoreが呼ばれる場合、
+ * APPEAR_MINIMUM_INTERVALミリ秒以内に2回fetchMoreが呼ばれるのを防ぐ
+ */
+const fetchMoreApperTimeoutFn = (): void => {
+	preventAppearFetchMore.value = false;
+	preventAppearFetchMoreTimer.value = null;
+};
+const fetchMoreAppearTimeout = (): void => {
+	preventAppearFetchMore.value = true;
+	preventAppearFetchMoreTimer.value = window.setTimeout(fetchMoreApperTimeoutFn, APPEAR_MINIMUM_INTERVAL);
+};
+
+const appearFetchMore = async (): Promise<void> => {
+	if (preventAppearFetchMore.value) return;
+	await fetchMore();
+	fetchMoreAppearTimeout();
+};
+
+const appearFetchMoreAhead = async (): Promise<void> => {
+	if (preventAppearFetchMore.value) return;
+	await fetchMoreAhead();
+	fetchMoreAppearTimeout();
+};
+
+const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE);
+
+watch(visibility, () => {
+	if (visibility.value === 'hidden') {
+		timerForSetPause = window.setTimeout(() => {
+			isPausingUpdate = true;
+			timerForSetPause = null;
+		},
+		BACKGROUND_PAUSE_WAIT_SEC * 1000);
+	} else { // 'visible'
+		if (timerForSetPause) {
+			clearTimeout(timerForSetPause);
+			timerForSetPause = null;
+		} else {
+			isPausingUpdate = false;
+			if (isTop()) {
+				executeQueue();
+			}
+		}
+	}
+});
+
+/**
+ * 最新のものとして1つだけアイテムを追加する
+ * ストリーミングから降ってきたアイテムはこれで追加する
+ * @param item アイテム
+ */
+const prepend = (item: MisskeyEntity): void => {
+	if (items.value.size === 0) {
+		items.value.set(item.id, item);
+		fetching.value = false;
+		return;
+	}
+
+	if (isTop() && !isPausingUpdate) unshiftItems([item]);
+	else prependQueue(item);
+};
+
+/**
+ * 新着アイテムをitemsの先頭に追加し、displayLimitを適用する
+ * @param newItems 新しいアイテムの配列
+ */
+function unshiftItems(newItems: MisskeyEntity[]) {
+	const length = newItems.length + items.value.size;
+	items.value = new Map([...arrayToEntries(newItems), ...items.value].slice(0, props.displayLimit));
+
+	if (length >= props.displayLimit) more.value = true;
+}
+
+/**
+ * 古いアイテムをitemsの末尾に追加し、displayLimitを適用する
+ * @param oldItems 古いアイテムの配列
+ */
+function concatItems(oldItems: MisskeyEntity[]) {
+	const length = oldItems.length + items.value.size;
+	items.value = new Map([...items.value, ...arrayToEntries(oldItems)].slice(0, props.displayLimit));
+
+	if (length >= props.displayLimit) more.value = true;
+}
+
+function executeQueue() {
+	unshiftItems(Array.from(queue.value.values()));
+	queue.value = new Map();
+}
+
+function prependQueue(newItem: MisskeyEntity) {
+	queue.value = new Map([[newItem.id, newItem], ...queue.value].slice(0, props.displayLimit) as [string, MisskeyEntity][]);
+}
+
+/*
+ * アイテムを末尾に追加する(使うの?)
+ */
+const appendItem = (item: MisskeyEntity): void => {
+	items.value.set(item.id, item);
+};
+
+const removeItem = (id: string) => {
+	items.value.delete(id);
+	queue.value.delete(id);
+};
+
+const updateItem = (id: MisskeyEntity['id'], replacer: (old: MisskeyEntity) => MisskeyEntity): void => {
+	const item = items.value.get(id);
+	if (item) items.value.set(id, replacer(item));
+
+	const queueItem = queue.value.get(id);
+	if (queueItem) queue.value.set(id, replacer(queueItem));
+};
+
+onActivated(() => {
+	isBackTop.value = false;
+});
+
+onDeactivated(() => {
+	isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl.value ? rootEl.value.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
+});
+
+function toBottom() {
+	scrollToBottom(contentEl.value!);
+}
+
+onBeforeMount(() => {
+	init().then(() => {
+		if (props.pagination.reversed) {
+			nextTick(() => {
+				setTimeout(toBottom, 800);
+
+				// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
+				// more = trueを遅らせる
+				setTimeout(() => {
+					moreFetching.value = false;
+				}, 2000);
+			});
+		}
+	});
+});
+
+onBeforeUnmount(() => {
+	if (timerForSetPause) {
+		clearTimeout(timerForSetPause);
+		timerForSetPause = null;
+	}
+	if (preventAppearFetchMoreTimer.value) {
+		clearTimeout(preventAppearFetchMoreTimer.value);
+		preventAppearFetchMoreTimer.value = null;
+	}
+	scrollObserver.value?.disconnect();
+});
+
+defineExpose({
+	items,
+	queue,
+	backed: backed.value,
+	more,
+	reload,
+	prepend,
+	append: appendItem,
+	removeItem,
+	updateItem,
+});
+</script>
+
+<style lang="scss" module>
+.transition_fade_enterActive,
+.transition_fade_leaveActive {
+	transition: opacity 0.125s ease;
+}
+.transition_fade_enterFrom,
+.transition_fade_leaveTo {
+	opacity: 0;
+}
+
+.more {
+	display: block;
+	margin-left: auto;
+	margin-right: auto;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmPoll.vue b/packages/frontend-embed/src/components/EmPoll.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a2b1203449403116027b4cd0b17725392269d68f
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmPoll.vue
@@ -0,0 +1,82 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+	<ul :class="$style.choices">
+		<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>
+				<EmMfm :text="choice.text" :plain="true"/>
+				<span style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span>
+			</span>
+		</li>
+	</ul>
+	<p :class="$style.info">
+		<span>{{ i18n.tsx._poll.totalVotes({ n: total }) }}</span>
+	</p>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import * as Misskey from 'misskey-js';
+import { i18n } from '@/i18n.js';
+import EmMfm from '@/components/EmMfm.js';
+
+function sum(xs: number[]): number {
+	return xs.reduce((a, b) => a + b, 0);
+}
+
+const props = defineProps<{
+	noteId: string;
+	poll: NonNullable<Misskey.entities.Note['poll']>;
+}>();
+
+const total = computed(() => sum(props.poll.choices.map(x => x.votes)));
+</script>
+
+<style lang="scss" module>
+.choices {
+	display: block;
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
+
+.choice {
+	display: block;
+	position: relative;
+	margin: 4px 0;
+	padding: 4px;
+	//border: solid 0.5px var(--divider);
+	background: var(--accentedBg);
+	border-radius: 4px;
+	overflow: clip;
+}
+
+.bg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	height: 100%;
+	background: var(--accent);
+	background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
+	transition: width 1s ease;
+}
+
+.fg {
+	position: relative;
+	display: inline-block;
+	padding: 3px 5px;
+	background: var(--panel);
+	border-radius: 3px;
+}
+
+.info {
+	color: var(--fg);
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmReactionIcon.vue b/packages/frontend-embed/src/components/EmReactionIcon.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5c38ecb0ed3b4a192afb15da3e1ebe8a72c3a40a
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmReactionIcon.vue
@@ -0,0 +1,23 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<EmCustomEmoji v-if="reaction[0] === ':'" ref="elRef" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl" :fallbackToImage="true"/>
+<EmEmoji v-else ref="elRef" :emoji="reaction" :normal="true" :noStyle="noStyle"/>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import EmCustomEmoji from './EmCustomEmoji.vue';
+import EmEmoji from './EmEmoji.vue';
+
+const props = defineProps<{
+	reaction: string;
+	noStyle?: boolean;
+	emojiUrl?: string;
+	withTooltip?: boolean;
+}>();
+
+</script>
diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2e43eb8d170be6df285048ec15483e2b3c0d3b4a
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue
@@ -0,0 +1,99 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<button
+	class="_button"
+	:class="[$style.root, { [$style.reacted]: note.myReaction == reaction }]"
+>
+	<EmReactionIcon :class="$style.limitWidth" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
+	<span :class="$style.count">{{ count }}</span>
+</button>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import * as Misskey from 'misskey-js';
+import EmReactionIcon from '@/components/EmReactionIcon.vue';
+
+const props = defineProps<{
+	reaction: string;
+	count: number;
+	isInitial: boolean;
+	note: Misskey.entities.Note;
+}>();
+</script>
+
+<style lang="scss" module>
+.root {
+	display: inline-flex;
+	height: 42px;
+	margin: 2px;
+	padding: 0 6px;
+	font-size: 1.5em;
+	border-radius: 6px;
+	align-items: center;
+	justify-content: center;
+
+	&.canToggle {
+		background: var(--buttonBg);
+
+		&:hover {
+			background: rgba(0, 0, 0, 0.1);
+		}
+	}
+
+	&:not(.canToggle) {
+		cursor: default;
+	}
+
+	&.small {
+		height: 32px;
+		font-size: 1em;
+		border-radius: 4px;
+
+		> .count {
+			font-size: 0.9em;
+			line-height: 32px;
+		}
+	}
+
+	&.large {
+		height: 52px;
+		font-size: 2em;
+		border-radius: 8px;
+
+		> .count {
+			font-size: 0.6em;
+			line-height: 52px;
+		}
+	}
+
+	&.reacted, &.reacted:hover {
+		background: var(--accentedBg);
+		color: var(--accent);
+		box-shadow: 0 0 0 1px var(--accent) inset;
+
+		> .count {
+			color: var(--accent);
+		}
+
+		> .icon {
+			filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
+		}
+	}
+}
+
+.limitWidth {
+	max-width: 70px;
+	object-fit: contain;
+}
+
+.count {
+	font-size: 0.7em;
+	line-height: 42px;
+	margin: 0 0 0 4px;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.vue b/packages/frontend-embed/src/components/EmReactionsViewer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..014dd1c935f8bc594aca398806dd751b2949204b
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmReactionsViewer.vue
@@ -0,0 +1,104 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+	<XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note" @reactionToggled="onMockToggleReaction"/>
+	<slot v-if="hasMoreReactions" name="more"></slot>
+</div>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+import { inject, watch, ref } from 'vue';
+import XReaction from '@/components/EmReactionsViewer.reaction.vue';
+
+const props = withDefaults(defineProps<{
+	note: Misskey.entities.Note;
+	maxNumber?: number;
+}>(), {
+	maxNumber: Infinity,
+});
+
+const mock = inject<boolean>('mock', false);
+
+const emit = defineEmits<{
+	(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
+}>();
+
+const initialReactions = new Set(Object.keys(props.note.reactions));
+
+const reactions = ref<[string, number][]>([]);
+const hasMoreReactions = ref(false);
+
+if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) {
+	reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction];
+}
+
+function onMockToggleReaction(emoji: string, count: number) {
+	if (!mock) return;
+
+	const i = reactions.value.findIndex((item) => item[0] === emoji);
+	if (i < 0) return;
+
+	emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
+}
+
+watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {
+	let newReactions: [string, number][] = [];
+	hasMoreReactions.value = Object.keys(newSource).length > maxNumber;
+
+	for (let i = 0; i < reactions.value.length; i++) {
+		const reaction = reactions.value[i][0];
+		if (reaction in newSource && newSource[reaction] !== 0) {
+			reactions.value[i][1] = newSource[reaction];
+			newReactions.push(reactions.value[i]);
+		}
+	}
+
+	const newReactionsNames = newReactions.map(([x]) => x);
+	newReactions = [
+		...newReactions,
+		...Object.entries(newSource)
+			.sort(([, a], [, b]) => b - a)
+			.filter(([y], i) => i < maxNumber && !newReactionsNames.includes(y)),
+	];
+
+	newReactions = newReactions.slice(0, props.maxNumber);
+
+	if (props.note.myReaction && !newReactions.map(([x]) => x).includes(props.note.myReaction)) {
+		newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]);
+	}
+
+	reactions.value = newReactions;
+}, { immediate: true, deep: true });
+</script>
+
+<style lang="scss" module>
+.transition_x_move,
+.transition_x_enterActive,
+.transition_x_leaveActive {
+	transition: opacity 0.2s cubic-bezier(0,.5,.5,1), transform 0.2s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_x_enterFrom,
+.transition_x_leaveTo {
+	opacity: 0;
+	transform: scale(0.7);
+}
+.transition_x_leaveActive {
+	position: absolute;
+}
+
+.root {
+	display: flex;
+	flex-wrap: wrap;
+	align-items: center;
+	margin: 4px -2px 0 -2px;
+
+	&:empty {
+		display: none;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f433573df5074913d8c0dc4335119516fbfe685f
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue
@@ -0,0 +1,114 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="[$style.root, { [$style.collapsed]: collapsed }]">
+	<div>
+		<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
+		<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span>
+		<EmA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></EmA>
+		<EmMfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis" :isBlock="true"/>
+		<EmA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</EmA>
+	</div>
+	<details v-if="note.files && note.files.length > 0">
+		<summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary>
+		<EmMediaList :mediaList="note.files" :originalEntityUrl="`${url}/notes/${note.id}`"/>
+	</details>
+	<details v-if="note.poll">
+		<summary>{{ i18n.ts.poll }}</summary>
+		<EmPoll :noteId="note.id" :poll="note.poll"/>
+	</details>
+	<button v-if="isLong && collapsed" :class="$style.fade" class="_button" @click="collapsed = false">
+		<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
+	</button>
+	<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
+		<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
+	</button>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import EmMediaList from '@/components/EmMediaList.vue';
+import EmPoll from '@/components/EmPoll.vue';
+import { i18n } from '@/i18n.js';
+import { url } from '@@/js/config.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
+import EmA from '@/components/EmA.vue';
+import EmMfm from '@/components/EmMfm.js';
+
+const props = defineProps<{
+	note: Misskey.entities.Note;
+}>();
+
+const isLong = shouldCollapsed(props.note, []);
+
+const collapsed = ref(isLong);
+</script>
+
+<style lang="scss" module>
+.root {
+	overflow-wrap: break-word;
+
+	&.collapsed {
+		position: relative;
+		max-height: 9em;
+		overflow: clip;
+
+		> .fade {
+			display: block;
+			position: absolute;
+			bottom: 0;
+			left: 0;
+			width: 100%;
+			height: 64px;
+			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
+
+			> .fadeLabel {
+				display: inline-block;
+				background: var(--panel);
+				padding: 6px 10px;
+				font-size: 0.8em;
+				border-radius: 999px;
+				box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+			}
+
+			&:hover {
+				> .fadeLabel {
+					background: var(--panelHighlight);
+				}
+			}
+		}
+	}
+}
+
+.reply {
+	margin-right: 6px;
+	color: var(--accent);
+}
+
+.rp {
+	margin-left: 4px;
+	font-style: oblique;
+	color: var(--renote);
+}
+
+.showLess {
+	width: 100%;
+	margin-top: 14px;
+	position: sticky;
+	bottom: calc(var(--stickyBottom, 0px) + 14px);
+}
+
+.showLessLabel {
+	display: inline-block;
+	background: var(--popup);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmTime.vue b/packages/frontend-embed/src/components/EmTime.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c3986f7d7031956f541f43cf156ecfefefda912e
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmTime.vue
@@ -0,0 +1,107 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<time :title="absolute" :class="{ [$style.old1]: colored && (ago > 60 * 60 * 24 * 90), [$style.old2]: colored && (ago > 60 * 60 * 24 * 180) }">
+	<template v-if="invalid">{{ i18n.ts._ago.invalid }}</template>
+	<template v-else-if="mode === 'relative'">{{ relative }}</template>
+	<template v-else-if="mode === 'absolute'">{{ absolute }}</template>
+	<template v-else-if="mode === 'detail'">{{ absolute }} ({{ relative }})</template>
+</time>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, ref, computed } from 'vue';
+import { i18n } from '@/i18n.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
+
+const props = withDefaults(defineProps<{
+	time: Date | string | number | null;
+	origin?: Date | null;
+	mode?: 'relative' | 'absolute' | 'detail';
+	colored?: boolean;
+}>(), {
+	origin: null,
+	mode: 'relative',
+});
+
+function getDateSafe(n: Date | string | number) {
+	try {
+		if (n instanceof Date) {
+			return n;
+		}
+		return new Date(n);
+	} catch (err) {
+		return {
+			getTime: () => NaN,
+		};
+	}
+}
+
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const _time = props.time == null ? NaN : getDateSafe(props.time).getTime();
+const invalid = Number.isNaN(_time);
+const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
+
+// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const now = ref(props.origin?.getTime() ?? Date.now());
+const ago = computed(() => (now.value - _time) / 1000/*ms*/);
+
+const relative = computed<string>(() => {
+	if (props.mode === 'absolute') return ''; // absoluteではrelativeを使わないので計算しない
+	if (invalid) return i18n.ts._ago.invalid;
+
+	return (
+		ago.value >= 31536000 ? i18n.tsx._ago.yearsAgo({ n: Math.round(ago.value / 31536000).toString() }) :
+		ago.value >= 2592000 ? i18n.tsx._ago.monthsAgo({ n: Math.round(ago.value / 2592000).toString() }) :
+		ago.value >= 604800 ? i18n.tsx._ago.weeksAgo({ n: Math.round(ago.value / 604800).toString() }) :
+		ago.value >= 86400 ? i18n.tsx._ago.daysAgo({ n: Math.round(ago.value / 86400).toString() }) :
+		ago.value >= 3600 ? i18n.tsx._ago.hoursAgo({ n: Math.round(ago.value / 3600).toString() }) :
+		ago.value >= 60 ? i18n.tsx._ago.minutesAgo({ n: (~~(ago.value / 60)).toString() }) :
+		ago.value >= 10 ? i18n.tsx._ago.secondsAgo({ n: (~~(ago.value % 60)).toString() }) :
+		ago.value >= -3 ? i18n.ts._ago.justNow :
+		ago.value < -31536000 ? i18n.tsx._timeIn.years({ n: Math.round(-ago.value / 31536000).toString() }) :
+		ago.value < -2592000 ? i18n.tsx._timeIn.months({ n: Math.round(-ago.value / 2592000).toString() }) :
+		ago.value < -604800 ? i18n.tsx._timeIn.weeks({ n: Math.round(-ago.value / 604800).toString() }) :
+		ago.value < -86400 ? i18n.tsx._timeIn.days({ n: Math.round(-ago.value / 86400).toString() }) :
+		ago.value < -3600 ? i18n.tsx._timeIn.hours({ n: Math.round(-ago.value / 3600).toString() }) :
+		ago.value < -60 ? i18n.tsx._timeIn.minutes({ n: (~~(-ago.value / 60)).toString() }) :
+		i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() })
+	);
+});
+
+let tickId: number;
+let currentInterval: number;
+
+function tick() {
+	now.value = Date.now();
+	const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
+
+	if (currentInterval !== nextInterval) {
+		if (tickId) window.clearInterval(tickId);
+		currentInterval = nextInterval;
+		tickId = window.setInterval(tick, nextInterval);
+	}
+}
+
+if (!invalid && props.origin === null && (props.mode === 'relative' || props.mode === 'detail')) {
+	onMounted(() => {
+		tick();
+	});
+	onUnmounted(() => {
+		if (tickId) window.clearInterval(tickId);
+	});
+}
+</script>
+
+<style lang="scss" module>
+.old1 {
+	color: var(--warn);
+}
+
+.old1.old2 {
+	color: var(--error);
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmTimelineContainer.vue b/packages/frontend-embed/src/components/EmTimelineContainer.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6c30b1102d9ed2decdb7bbd84390be9b8fe263f7
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmTimelineContainer.vue
@@ -0,0 +1,39 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.timelineRoot">
+	<div v-if="showHeader" :class="$style.header"><slot name="header"></slot></div>
+	<div :class="$style.body"><slot name="body"></slot></div>
+</div>
+</template>
+
+<script setup lang="ts">
+withDefaults(defineProps<{
+	showHeader?: boolean;
+}>(), {
+	showHeader: true,
+});
+</script>
+
+<style module lang="scss">
+.timelineRoot {
+	background-color: var(--panel);
+	height: 100%;
+	max-height: var(--embedMaxHeight, none);
+	display: flex;
+	flex-direction: column;
+}
+
+.header {
+	flex-shrink: 0;
+	border-bottom: 1px solid var(--divider);
+}
+
+.body {
+	flex-grow: 1;
+	overflow-y: auto;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmUrl.vue b/packages/frontend-embed/src/components/EmUrl.vue
new file mode 100644
index 0000000000000000000000000000000000000000..94424cab28913417610e5012283909ea3c220266
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmUrl.vue
@@ -0,0 +1,96 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<component
+	:is="self ? EmA : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target"
+	@contextmenu.stop="() => {}"
+>
+	<template v-if="!self">
+		<span :class="$style.schema">{{ schema }}//</span>
+		<span :class="$style.hostname">{{ hostname }}</span>
+		<span v-if="port != ''">:{{ port }}</span>
+	</template>
+	<template v-if="pathname === '/' && self">
+		<span :class="$style.self">{{ hostname }}</span>
+	</template>
+	<span v-if="pathname != ''" :class="$style.pathname">{{ self ? pathname.substring(1) : pathname }}</span>
+	<span :class="$style.query">{{ query }}</span>
+	<span :class="$style.hash">{{ hash }}</span>
+	<i v-if="target === '_blank'" :class="$style.icon" class="ti ti-external-link"></i>
+</component>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { toUnicode as decodePunycode } from 'punycode/';
+import EmA from './EmA.vue';
+import { url as local } from '@@/js/config.js';
+
+function safeURIDecode(str: string): string {
+	try {
+		return decodeURIComponent(str);
+	} catch {
+		return str;
+	}
+}
+
+const props = withDefaults(defineProps<{
+	url: string;
+	rel?: string;
+	showUrlPreview?: boolean;
+}>(), {
+	showUrlPreview: true,
+});
+
+const self = props.url.startsWith(local);
+const url = new URL(props.url);
+if (!['http:', 'https:'].includes(url.protocol)) throw new Error('invalid url');
+const el = ref();
+
+const schema = url.protocol;
+const hostname = decodePunycode(url.hostname);
+const port = url.port;
+const pathname = safeURIDecode(url.pathname);
+const query = safeURIDecode(url.search);
+const hash = safeURIDecode(url.hash);
+const attr = self ? 'to' : 'href';
+const target = self ? null : '_blank';
+</script>
+
+<style lang="scss" module>
+.root {
+	word-break: break-all;
+}
+
+.icon {
+	padding-left: 2px;
+	font-size: .9em;
+}
+
+.self {
+	font-weight: bold;
+}
+
+.schema {
+	opacity: 0.5;
+}
+
+.hostname {
+	font-weight: bold;
+}
+
+.pathname {
+	opacity: 0.8;
+}
+
+.query {
+	opacity: 0.5;
+}
+
+.hash {
+	font-style: italic;
+}
+</style>
diff --git a/packages/frontend-embed/src/components/EmUserName.vue b/packages/frontend-embed/src/components/EmUserName.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c0c7c443caebb7045ad59c792aacb31dce2a17ca
--- /dev/null
+++ b/packages/frontend-embed/src/components/EmUserName.vue
@@ -0,0 +1,21 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<EmMfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import * as Misskey from 'misskey-js';
+import EmMfm from './EmMfm.js';
+
+const props = withDefaults(defineProps<{
+	user: Misskey.entities.User;
+	nowrap?: boolean;
+}>(), {
+	nowrap: true,
+});
+</script>
diff --git a/packages/frontend-embed/src/components/I18n.vue b/packages/frontend-embed/src/components/I18n.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b621110ec9ede8f68dc5df979627d27036176dd2
--- /dev/null
+++ b/packages/frontend-embed/src/components/I18n.vue
@@ -0,0 +1,51 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<render/>
+</template>
+
+<script setup lang="ts" generic="T extends string | ParameterizedString">
+import { computed, h } from 'vue';
+import type { ParameterizedString } from '../../../../locales/index.js';
+
+const props = withDefaults(defineProps<{
+	src: T;
+	tag?: string;
+	// eslint-disable-next-line vue/require-default-prop
+	textTag?: string;
+}>(), {
+	tag: 'span',
+});
+
+const slots = defineSlots<T extends ParameterizedString<infer R> ? { [K in R]: () => unknown } : NonNullable<unknown>>();
+
+const parsed = computed(() => {
+	let str = props.src as string;
+	const value: (string | { arg: string; })[] = [];
+	for (;;) {
+		const nextBracketOpen = str.indexOf('{');
+		const nextBracketClose = str.indexOf('}');
+
+		if (nextBracketOpen === -1) {
+			value.push(str);
+			break;
+		} else {
+			if (nextBracketOpen > 0) value.push(str.substring(0, nextBracketOpen));
+			value.push({
+				arg: str.substring(nextBracketOpen + 1, nextBracketClose),
+			});
+		}
+
+		str = str.substring(nextBracketClose + 1);
+	}
+
+	return value;
+});
+
+const render = () => {
+	return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
+};
+</script>
diff --git a/packages/frontend-embed/src/custom-emojis.ts b/packages/frontend-embed/src/custom-emojis.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5b40885c1d441064ea410859ce5af8b526a3f33
--- /dev/null
+++ b/packages/frontend-embed/src/custom-emojis.ts
@@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { shallowRef, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import { misskeyApi, misskeyApiGet } from '@/misskey-api.js';
+
+function get(key: string) {
+	const value = localStorage.getItem(key);
+	if (value === null) return null;
+	return JSON.parse(value);
+}
+
+function set(key: string, value: any) {
+	localStorage.setItem(key, JSON.stringify(value));
+}
+
+const storageCache = await get('emojis');
+export const customEmojis = shallowRef<Misskey.entities.EmojiSimple[]>(Array.isArray(storageCache) ? storageCache : []);
+
+export const customEmojisMap = new Map<string, Misskey.entities.EmojiSimple>();
+watch(customEmojis, emojis => {
+	customEmojisMap.clear();
+	for (const emoji of emojis) {
+		customEmojisMap.set(emoji.name, emoji);
+	}
+}, { immediate: true });
+
+export async function fetchCustomEmojis(force = false) {
+	const now = Date.now();
+
+	let res;
+	if (force) {
+		res = await misskeyApi('emojis', {});
+	} else {
+		const lastFetchedAt = await get('lastEmojisFetchedAt');
+		if (lastFetchedAt && (now - lastFetchedAt) < 1000 * 60 * 60) return;
+		res = await misskeyApiGet('emojis', {});
+	}
+
+	customEmojis.value = res.emojis;
+	set('emojis', res.emojis);
+	set('lastEmojisFetchedAt', now);
+}
+
+let cachedTags;
+export function getCustomEmojiTags() {
+	if (cachedTags) return cachedTags;
+
+	const tags = new Set();
+	for (const emoji of customEmojis.value) {
+		for (const tag of emoji.aliases) {
+			tags.add(tag);
+		}
+	}
+	const res = Array.from(tags);
+	cachedTags = res;
+	return res;
+}
diff --git a/packages/frontend-embed/src/di.ts b/packages/frontend-embed/src/di.ts
new file mode 100644
index 0000000000000000000000000000000000000000..22f62766309a93ec7394fd3f177ad2bf6657c634
--- /dev/null
+++ b/packages/frontend-embed/src/di.ts
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+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/i18n.ts b/packages/frontend-embed/src/i18n.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6ad503b0898a53004660609444b91ddaba9ea160
--- /dev/null
+++ b/packages/frontend-embed/src/i18n.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { markRaw } from 'vue';
+import { I18n } from '@@/js/i18n.js';
+import type { Locale } from '../../../locales/index.js';
+import { locale } from '@@/js/config.js';
+
+export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
+
+export function updateI18n(newLocale: Locale) {
+	i18n.locale = newLocale;
+}
diff --git a/packages/frontend-embed/src/index.html b/packages/frontend-embed/src/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..55be2f4ec14c49a0ba10081d2b14c82adb825564
--- /dev/null
+++ b/packages/frontend-embed/src/index.html
@@ -0,0 +1,36 @@
+<!--
+  SPDX-FileCopyrightText: syuilo and misskey-project
+  SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<!--
+  開発モードのviteはこのファイルを起点にサーバーを起動します。
+  このファイルに書かれた [t]js のリンクと (s)cssのリンクと、その依存関係にあるファイルはビルドされます
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+	<meta charset="UTF-8" />
+	<title>[DEV] Loading...</title>
+	<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
+	<meta
+		http-equiv="Content-Security-Policy"
+		content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
+			worker-src 'self';
+			script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh https://cdn.jsdelivr.net;
+			style-src 'self' 'unsafe-inline';
+			img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
+			media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
+			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com;
+			frame-src *;"
+	/>
+	<meta property="og:site_name" content="[DEV BUILD] Sharkey" />
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+
+<body>
+<div id="sharkey_app"></div>
+<script type="module" src="./boot.ts"></script>
+</body>
+</html>
diff --git a/packages/frontend-embed/src/misskey-api.ts b/packages/frontend-embed/src/misskey-api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d3c679359741f7ba0fabc40dce62aa62db0faaf
--- /dev/null
+++ b/packages/frontend-embed/src/misskey-api.ts
@@ -0,0 +1,99 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import { ref } from 'vue';
+import { apiUrl } from '@@/js/config.js';
+
+export const pendingApiRequestsCount = ref(0);
+
+// Implements Misskey.api.ApiClient.request
+export function misskeyApi<
+	ResT = void,
+	E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
+	P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
+	_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+>(
+	endpoint: E,
+	data: P = {} as any,
+	signal?: AbortSignal,
+): Promise<_ResT> {
+	if (endpoint.includes('://')) throw new Error('invalid endpoint');
+	pendingApiRequestsCount.value++;
+
+	const onFinally = () => {
+		pendingApiRequestsCount.value--;
+	};
+
+	const promise = new Promise<_ResT>((resolve, reject) => {
+		// Send request
+		window.fetch(`${apiUrl}/${endpoint}`, {
+			method: 'POST',
+			body: JSON.stringify(data),
+			credentials: 'omit',
+			cache: 'no-cache',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+			signal,
+		}).then(async (res) => {
+			const body = res.status === 204 ? null : await res.json();
+
+			if (res.status === 200) {
+				resolve(body);
+			} else if (res.status === 204) {
+				resolve(undefined as _ResT); // void -> undefined
+			} else {
+				reject(body.error);
+			}
+		}).catch(reject);
+	});
+
+	promise.then(onFinally, onFinally);
+
+	return promise;
+}
+
+// Implements Misskey.api.ApiClient.request
+export function misskeyApiGet<
+	ResT = void,
+	E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
+	P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
+	_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+>(
+	endpoint: E,
+	data: P = {} as any,
+): Promise<_ResT> {
+	pendingApiRequestsCount.value++;
+
+	const onFinally = () => {
+		pendingApiRequestsCount.value--;
+	};
+
+	const query = new URLSearchParams(data as any);
+
+	const promise = new Promise<_ResT>((resolve, reject) => {
+		// Send request
+		window.fetch(`${apiUrl}/${endpoint}?${query}`, {
+			method: 'GET',
+			credentials: 'omit',
+			cache: 'default',
+		}).then(async (res) => {
+			const body = res.status === 204 ? null : await res.json();
+
+			if (res.status === 200) {
+				resolve(body);
+			} else if (res.status === 204) {
+				resolve(undefined as _ResT); // void -> undefined
+			} else {
+				reject(body.error);
+			}
+		}).catch(reject);
+	});
+
+	promise.then(onFinally, onFinally);
+
+	return promise;
+}
diff --git a/packages/frontend-embed/src/pages/clip.vue b/packages/frontend-embed/src/pages/clip.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2528dc4b807f9f2f4d4c1c67638d0697cb6c8a5b
--- /dev/null
+++ b/packages/frontend-embed/src/pages/clip.vue
@@ -0,0 +1,143 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+	<EmTimelineContainer v-if="clip" :showHeader="embedParams.header">
+		<template #header>
+			<div :class="$style.clipHeader">
+				<div :class="$style.headerClipIconRoot">
+					<i class="ti ti-paperclip"></i>
+				</div>
+				<div :class="$style.headerTitle" @click="top">
+					<div class="_nowrap"><a :href="`/clips/${clip.id}`" target="_blank" rel="noopener">{{ clip.name }}</a></div>
+					<div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div>
+				</div>
+				<a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer">
+					<img
+						:class="$style.instanceIcon"
+						:src="serverMetadata.iconUrl || '/favicon.ico'"
+					/>
+				</a>
+			</div>
+		</template>
+		<template #body>
+			<EmNotes
+				ref="notesEl"
+				:pagination="pagination"
+				:disableAutoLoad="!embedParams.autoload"
+				:noGap="true"
+				:ad="false"
+			/>
+		</template>
+	</EmTimelineContainer>
+	<XNotFound v-else/>
+</div>
+</template>
+
+<script setup lang="ts">
+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 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 { assertServerContext } from '@/server-context.js';
+import { DI } from '@/di.js';
+
+const props = defineProps<{
+	clipId: string;
+}>();
+
+const embedParams = inject(DI.embedParams, defaultEmbedParams);
+
+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 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>
+.clipHeader {
+	padding: 8px 16px;
+	display: flex;
+	min-width: 0;
+	align-items: center;
+	gap: var(--margin);
+	overflow: hidden;
+
+	.headerClipIconRoot {
+		flex-shrink: 0;
+		width: 32px;
+		height: 32px;
+		line-height: 32px;
+		font-size: 14px;
+		text-align: center;
+		background-color: var(--accentedBg);
+		color: var(--accent);
+		border-radius: 50%;
+	}
+
+	.headerTitle {
+		flex-grow: 1;
+		font-weight: 700;
+		line-height: 1.1;
+		min-width: 0;
+
+		.sub {
+			font-size: 0.8em;
+			font-weight: 400;
+			opacity: 0.7;
+		}
+	}
+
+	.instanceIconLink {
+		flex-shrink: 0;
+		display: block;
+		margin-left: auto;
+		height: 24px;
+	}
+
+	.instanceIcon {
+		height: 24px;
+		border-radius: 3px;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/pages/not-found.vue b/packages/frontend-embed/src/pages/not-found.vue
new file mode 100644
index 0000000000000000000000000000000000000000..bbb03b4e642d25bf820adc01aa7d43c66ba46bc3
--- /dev/null
+++ b/packages/frontend-embed/src/pages/not-found.vue
@@ -0,0 +1,24 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+	<div class="_fullinfo">
+		<img :src="notFoundImageUrl" class="_ghost"/>
+		<div>{{ i18n.ts.notFoundDescription }}</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { inject, computed } from 'vue';
+import { DEFAULT_NOT_FOUND_IMAGE_URL } from '@@/js/const.js';
+import { DI } from '@/di.js';
+import { i18n } from '@/i18n.js';
+
+const serverMetadata = inject(DI.serverMetadata)!;
+
+const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
+</script>
diff --git a/packages/frontend-embed/src/pages/note.vue b/packages/frontend-embed/src/pages/note.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6f6c8c0f639b41f9a436c6b962239bb5e33c63bf
--- /dev/null
+++ b/packages/frontend-embed/src/pages/note.vue
@@ -0,0 +1,52 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.noteEmbedRoot">
+	<EmNoteDetailed v-if="note && !prohibited" :note="note"/>
+	<XNotFound v-else/>
+</div>
+</template>
+
+<script setup lang="ts">
+import { inject, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import EmNoteDetailed from '@/components/EmNoteDetailed.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 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);
+}
+</style>
diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b481b3ebe5527c6c3b45eba2c5d52a3974d89746
--- /dev/null
+++ b/packages/frontend-embed/src/pages/tag.vue
@@ -0,0 +1,126 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+	<EmTimelineContainer v-if="tag" :showHeader="embedParams.header">
+		<template #header>
+			<div :class="$style.clipHeader">
+				<div :class="$style.headerClipIconRoot">
+					<i class="ti ti-hash"></i>
+				</div>
+				<div :class="$style.headerTitle" @click="top">
+					<div class="_nowrap"><a :href="`/tags/${tag}`" target="_blank" rel="noopener">#{{ tag }}</a></div>
+					<div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div>
+				</div>
+				<a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer">
+					<img
+						:class="$style.instanceIcon"
+						:src="serverMetadata.iconUrl || '/favicon.ico'"
+					/>
+				</a>
+			</div>
+		</template>
+		<template #body>
+			<EmNotes
+				ref="notesEl"
+				:pagination="pagination"
+				:disableAutoLoad="!embedParams.autoload"
+				:noGap="true"
+				:ad="false"
+			/>
+		</template>
+	</EmTimelineContainer>
+	<XNotFound v-else/>
+</div>
+</template>
+
+<script setup lang="ts">
+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 { url, instanceName } from '@@/js/config.js';
+import { isLink } from '@@/js/is-link.js';
+import { DI } from '@/di.js';
+import { defaultEmbedParams } from '@@/js/embed-page.js';
+
+const props = defineProps<{
+	tag: string;
+}>();
+
+const serverMetadata = inject(DI.serverMetadata)!;
+
+const embedParams = inject(DI.embedParams, defaultEmbedParams);
+
+const pagination = computed(() => ({
+	endpoint: 'notes/search-by-tag',
+	params: {
+		tag: props.tag,
+	},
+} as Paging));
+
+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>
+.clipHeader {
+	padding: 8px 16px;
+	display: flex;
+	min-width: 0;
+	align-items: center;
+	gap: var(--margin);
+	overflow: hidden;
+
+	.headerClipIconRoot {
+		flex-shrink: 0;
+		width: 32px;
+		height: 32px;
+		line-height: 32px;
+		font-size: 14px;
+		text-align: center;
+		background-color: var(--accentedBg);
+		color: var(--accent);
+		border-radius: 50%;
+	}
+
+	.headerTitle {
+		flex-grow: 1;
+		font-weight: 700;
+		line-height: 1.1;
+		min-width: 0;
+
+		.sub {
+			font-size: 0.8em;
+			font-weight: 400;
+			opacity: 0.7;
+		}
+	}
+
+	.instanceIconLink {
+		flex-shrink: 0;
+		display: block;
+		margin-left: auto;
+		height: 24px;
+	}
+
+	.instanceIcon {
+		height: 24px;
+		border-radius: 3px;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue
new file mode 100644
index 0000000000000000000000000000000000000000..85e6f52d5079384c202b2b925490b0af9671f76a
--- /dev/null
+++ b/packages/frontend-embed/src/pages/user-timeline.vue
@@ -0,0 +1,158 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div>
+	<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" @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">
+								<EmUserName :user="user"/>
+							</a>
+							<span v-else>{{ i18n.ts.user }}</span>
+						</template>
+					</I18n>
+					<div :class="$style.sub">{{ i18n.tsx.fromX({ x: instanceName }) }}</div>
+				</div>
+				<a :href="url" :class="$style.instanceIconLink" target="_blank" rel="noopener noreferrer">
+					<img
+						:class="$style.instanceIcon"
+						:src="serverMetadata.iconUrl || '/favicon.ico'"
+					/>
+				</a>
+			</div>
+		</template>
+		<template #body>
+			<EmNotes
+				ref="notesEl"
+				:pagination="pagination"
+				:disableAutoLoad="!embedParams.autoload"
+				:noGap="true"
+				:ad="false"
+			/>
+		</template>
+	</EmTimelineContainer>
+	<XNotFound v-else/>
+</div>
+</template>
+
+<script setup lang="ts">
+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 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 { assertServerContext } from '@/server-context.js';
+import { DI } from '@/di.js';
+
+const props = defineProps<{
+	userId: string;
+}>();
+
+const embedParams = inject(DI.embedParams, defaultEmbedParams);
+
+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 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>
+.userHeader {
+	padding: 8px 16px;
+	display: flex;
+	min-width: 0;
+	align-items: center;
+	gap: var(--margin);
+	overflow: hidden;
+
+	.avatarLink {
+		display: block;
+	}
+
+	.avatar {
+		display: inline-block;
+		width: 32px;
+		height: 32px;
+	}
+
+	.headerTitle {
+		flex-grow: 1;
+		font-weight: 700;
+		line-height: 1.1;
+		min-width: 0;
+
+		.sub {
+			font-size: 0.8em;
+			font-weight: 400;
+			opacity: 0.7;
+		}
+	}
+
+	.instanceIconLink {
+		flex-shrink: 0;
+		display: block;
+		margin-left: auto;
+		height: 24px;
+	}
+
+	.instanceIcon {
+		height: 24px;
+		border-radius: 3px;
+	}
+}
+</style>
diff --git a/packages/frontend-embed/src/post-message.ts b/packages/frontend-embed/src/post-message.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fd8eb8a5d2a094b3c76187db48299b44e56aa41f
--- /dev/null
+++ b/packages/frontend-embed/src/post-message.ts
@@ -0,0 +1,49 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const postMessageEventTypes = [
+	'misskey:embed:ready',
+	'misskey:embed:changeHeight',
+] as const;
+
+export type PostMessageEventType = typeof postMessageEventTypes[number];
+
+export interface PostMessageEventPayload extends Record<PostMessageEventType, any> {
+	'misskey:embed:ready': undefined;
+	'misskey:embed:changeHeight': {
+		height: number;
+	};
+}
+
+export type MiPostMessageEvent<T extends PostMessageEventType = PostMessageEventType> = {
+	type: T;
+	iframeId?: string;
+	payload?: PostMessageEventPayload[T];
+}
+
+let defaultIframeId: string | null = null;
+
+export function setIframeId(id: string): void {
+	if (defaultIframeId != null) return;
+
+	if (_DEV_) console.log('setIframeId', id);
+	defaultIframeId = id;
+}
+
+/**
+ * 親フレームにイベントを送信
+ */
+export function postMessageToParentWindow<T extends PostMessageEventType = PostMessageEventType>(type: T, payload?: PostMessageEventPayload[T], iframeId: string | null = null): void {
+	let _iframeId = iframeId;
+	if (_iframeId == null) {
+		_iframeId = defaultIframeId;
+	}
+	if (_DEV_) console.log('postMessageToParentWindow', type, _iframeId, payload);
+	window.parent.postMessage({
+		type,
+		iframeId: _iframeId,
+		payload,
+	}, '*');
+}
diff --git a/packages/frontend-embed/src/server-context.ts b/packages/frontend-embed/src/server-context.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a84a1a726a03c92cba081891d20e4f4a6879c1fd
--- /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/server-metadata.ts b/packages/frontend-embed/src/server-metadata.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6c94aacd4840ef0fd732f72d757ce4af986e2705
--- /dev/null
+++ b/packages/frontend-embed/src/server-metadata.ts
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import { misskeyApi } from '@/misskey-api.js';
+
+const providedMetaEl = document.getElementById('misskey_meta');
+
+const _serverMetadata: Misskey.entities.MetaDetailed | null = (providedMetaEl && providedMetaEl.textContent) ? JSON.parse(providedMetaEl.textContent) : null;
+
+// NOTE: devモードのときしか _serverMetadata が null になることは無い
+export const serverMetadata: Misskey.entities.MetaDetailed = _serverMetadata ?? await misskeyApi('meta', {
+	detail: true,
+});
diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss
new file mode 100644
index 0000000000000000000000000000000000000000..979e959f52c60d9a57274281e1602315f6b95222
--- /dev/null
+++ b/packages/frontend-embed/src/style.scss
@@ -0,0 +1,477 @@
+@charset "utf-8";
+
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ *
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+:root {
+	--radius: 12px;
+	--marginFull: 14px;
+	--marginHalf: 10px;
+
+	--margin: var(--marginFull);
+}
+
+html {
+	background-color: transparent;
+	color-scheme: light dark;
+	color: var(--fg);
+	accent-color: var(--accent);
+	overflow: clip;
+	overflow-wrap: break-word;
+	font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif;
+	font-size: 14px;
+	line-height: 1.35;
+	text-size-adjust: 100%;
+	tab-size: 2;
+	-webkit-text-size-adjust: 100%;
+
+	&, * {
+		scrollbar-color: var(--scrollbarHandle) transparent;
+		scrollbar-width: thin;
+
+		&::-webkit-scrollbar {
+			width: 6px;
+			height: 6px;
+		}
+
+		&::-webkit-scrollbar-track {
+			background: inherit;
+		}
+
+		&::-webkit-scrollbar-thumb {
+			background: var(--scrollbarHandle);
+
+			&:hover {
+				background: var(--scrollbarHandleHover);
+			}
+
+			&:active {
+				background: var(--accent);
+			}
+		}
+	}
+}
+
+html, body {
+	height: 100%;
+	touch-action: manipulation;
+	margin: 0;
+	padding: 0;
+	scroll-behavior: smooth;
+}
+
+#sharkey_app {
+	height: 100%;
+}
+
+a {
+	text-decoration: none;
+	cursor: pointer;
+	color: inherit;
+	tap-highlight-color: transparent;
+	-webkit-tap-highlight-color: transparent;
+	-webkit-touch-callout: none;
+
+	&:focus-visible {
+		outline-offset: 2px;
+	}
+
+	&:hover {
+		text-decoration: underline;
+	}
+
+	&[target="_blank"] {
+		-webkit-touch-callout: default;
+	}
+}
+
+rt {
+	white-space: initial;
+}
+
+:focus-visible {
+	outline: var(--focus) solid 2px;
+	outline-offset: -2px;
+
+	&:hover {
+		text-decoration: none;
+	}
+}
+
+.ti {
+	width: 1.28em;
+	vertical-align: -12%;
+	line-height: 1em;
+
+	&::before {
+		font-size: 128%;
+	}
+}
+
+.ti-fw {
+	display: inline-block;
+	text-align: center;
+}
+
+._nowrap {
+	white-space: pre !important;
+	word-wrap: normal !important; // https://codeday.me/jp/qa/20190424/690106.html
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+._button {
+	user-select: none;
+	-webkit-user-select: none;
+	-webkit-touch-callout: none;
+	appearance: none;
+	display: inline-block;
+	padding: 0;
+	margin: 0; // for Safari
+	background: none;
+	border: none;
+	cursor: pointer;
+	color: inherit;
+	touch-action: manipulation;
+	tap-highlight-color: transparent;
+	-webkit-tap-highlight-color: transparent;
+	font-size: 1em;
+	font-family: inherit;
+	line-height: inherit;
+	max-width: 100%;
+
+	&:disabled {
+		opacity: 0.5;
+		cursor: default;
+	}
+}
+
+._buttonGray {
+	@extend ._button;
+	background: var(--buttonBg);
+
+	&:not(:disabled):hover {
+		background: var(--buttonHoverBg);
+	}
+}
+
+._buttonPrimary {
+	@extend ._button;
+	color: var(--fgOnAccent);
+	background: var(--accent);
+
+	&:not(:disabled):hover {
+		background: hsl(from var(--accent) h s calc(l + 5));
+	}
+
+	&:not(:disabled):active {
+		background: hsl(from var(--accent) h s calc(l - 5));
+	}
+}
+
+._buttonGradate {
+	@extend ._buttonPrimary;
+	color: var(--fgOnAccent);
+	background: linear-gradient(90deg, var(--buttonGradateA), var(--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)));
+	}
+
+	&: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)));
+	}
+}
+
+._buttonRounded {
+	font-size: 0.95em;
+	padding: 0.5em 1em;
+	min-width: 100px;
+	border-radius: 99rem;
+
+	&._buttonPrimary,
+	&._buttonGradate {
+		font-weight: 700;
+	}
+}
+
+._help {
+	color: var(--accent);
+	cursor: help;
+}
+
+._textButton {
+	@extend ._button;
+	color: var(--accent);
+
+	&:focus-visible {
+		outline-offset: 2px;
+	}
+
+	&:not(:disabled):hover {
+		text-decoration: underline;
+	}
+}
+
+._panel {
+	background: var(--panel);
+	border-radius: var(--radius);
+	overflow: clip;
+}
+
+._margin {
+	margin: var(--margin) 0;
+}
+
+._gaps_m {
+	display: flex;
+	flex-direction: column;
+	gap: 1.5em;
+}
+
+._gaps_s {
+	display: flex;
+	flex-direction: column;
+	gap: 0.75em;
+}
+
+._gaps {
+	display: flex;
+	flex-direction: column;
+	gap: var(--margin);
+}
+
+._buttons {
+	display: flex;
+	gap: 8px;
+	flex-wrap: wrap;
+}
+
+._buttonsCenter {
+	@extend ._buttons;
+
+	justify-content: center;
+}
+
+._borderButton {
+	@extend ._button;
+	display: block;
+	width: 100%;
+	padding: 10px;
+	box-sizing: border-box;
+	text-align: center;
+	border: solid 0.5px var(--divider);
+	border-radius: var(--radius);
+
+	&:active {
+		border-color: var(--accent);
+	}
+}
+
+._popup {
+	background: var(--popup);
+	border-radius: var(--radius);
+	contain: content;
+}
+
+._acrylic {
+	background: var(--acrylicPanel);
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+}
+
+._fullinfo {
+	padding: 64px 32px;
+	text-align: center;
+
+	> img {
+		vertical-align: bottom;
+		height: 128px;
+		margin-bottom: 16px;
+		border-radius: 16px;
+	}
+}
+
+._link {
+	color: var(--link);
+}
+
+._caption {
+	font-size: 0.8em;
+	opacity: 0.7;
+}
+
+._monospace {
+	font-family: Fira code, Fira Mono, Consolas, Menlo, Courier, monospace !important;
+}
+
+// MFM -----------------------------
+
+bdi.block { display: block }
+
+._mfm_blur_ {
+	filter: blur(6px);
+	transition: filter 0.3s;
+
+	&:hover {
+		filter: blur(0px);
+	}
+}
+
+.mfm-x2 {
+	--mfm-zoom-size: 200%;
+}
+
+.mfm-x3 {
+	--mfm-zoom-size: 400%;
+}
+
+.mfm-x4 {
+	--mfm-zoom-size: 600%;
+}
+
+.mfm-x2, .mfm-x3, .mfm-x4 {
+	font-size: var(--mfm-zoom-size);
+
+	.mfm-x2, .mfm-x3, .mfm-x4 {
+		/* only half effective */
+		font-size: calc(var(--mfm-zoom-size) / 2 + 50%);
+
+		.mfm-x2, .mfm-x3, .mfm-x4 {
+			/* disabled */
+			font-size: 100%;
+		}
+	}
+}
+
+._mfm_rainbow_fallback_ {
+	background-image: linear-gradient(to right, rgb(255, 0, 0) 0%, rgb(255, 165, 0) 17%, rgb(255, 255, 0) 33%, rgb(0, 255, 0) 50%, rgb(0, 255, 255) 67%, rgb(0, 0, 255) 83%, rgb(255, 0, 255) 100%);
+	-webkit-background-clip: text;
+	background-clip: text;
+	color: transparent;
+}
+
+@keyframes mfm-spin {
+	0% { transform: rotate(0deg); }
+	100% { transform: rotate(360deg); }
+}
+
+@keyframes mfm-spinX {
+	0% { transform: perspective(128px) rotateX(0deg); }
+	100% { transform: perspective(128px) rotateX(360deg); }
+}
+
+@keyframes mfm-spinY {
+	0% { transform: perspective(128px) rotateY(0deg); }
+	100% { transform: perspective(128px) rotateY(360deg); }
+}
+
+@keyframes mfm-jump {
+	0% { transform: translateY(0); }
+	25% { transform: translateY(-16px); }
+	50% { transform: translateY(0); }
+	75% { transform: translateY(-8px); }
+	100% { transform: translateY(0); }
+}
+
+@keyframes mfm-bounce {
+	0% { transform: translateY(0) scale(1, 1); }
+	25% { transform: translateY(-16px) scale(1, 1); }
+	50% { transform: translateY(0) scale(1, 1); }
+	75% { transform: translateY(0) scale(1.5, 0.75); }
+	100% { transform: translateY(0) scale(1, 1); }
+}
+
+// const val = () => `translate(${Math.floor(Math.random() * 20) - 10}px, ${Math.floor(Math.random() * 20) - 10}px)`;
+// let css = '';
+// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
+@keyframes mfm-twitch {
+	0% { transform: translate(7px, -2px) }
+	5% { transform: translate(-3px, 1px) }
+	10% { transform: translate(-7px, -1px) }
+	15% { transform: translate(0px, -1px) }
+	20% { transform: translate(-8px, 6px) }
+	25% { transform: translate(-4px, -3px) }
+	30% { transform: translate(-4px, -6px) }
+	35% { transform: translate(-8px, -8px) }
+	40% { transform: translate(4px, 6px) }
+	45% { transform: translate(-3px, 1px) }
+	50% { transform: translate(2px, -10px) }
+	55% { transform: translate(-7px, 0px) }
+	60% { transform: translate(-2px, 4px) }
+	65% { transform: translate(3px, -8px) }
+	70% { transform: translate(6px, 7px) }
+	75% { transform: translate(-7px, -2px) }
+	80% { transform: translate(-7px, -8px) }
+	85% { transform: translate(9px, 3px) }
+	90% { transform: translate(-3px, -2px) }
+	95% { transform: translate(-10px, 2px) }
+	100% { transform: translate(-2px, -6px) }
+}
+
+// const val = () => `translate(${Math.floor(Math.random() * 6) - 3}px, ${Math.floor(Math.random() * 6) - 3}px) rotate(${Math.floor(Math.random() * 24) - 12}deg)`;
+// let css = '';
+// for (let i = 0; i <= 100; i += 5) { css += `${i}% { transform: ${val()} }\n`; }
+@keyframes mfm-shake {
+	0% { transform: translate(-3px, -1px) rotate(-8deg) }
+	5% { transform: translate(0px, -1px) rotate(-10deg) }
+	10% { transform: translate(1px, -3px) rotate(0deg) }
+	15% { transform: translate(1px, 1px) rotate(11deg) }
+	20% { transform: translate(-2px, 1px) rotate(1deg) }
+	25% { transform: translate(-1px, -2px) rotate(-2deg) }
+	30% { transform: translate(-1px, 2px) rotate(-3deg) }
+	35% { transform: translate(2px, 1px) rotate(6deg) }
+	40% { transform: translate(-2px, -3px) rotate(-9deg) }
+	45% { transform: translate(0px, -1px) rotate(-12deg) }
+	50% { transform: translate(1px, 2px) rotate(10deg) }
+	55% { transform: translate(0px, -3px) rotate(8deg) }
+	60% { transform: translate(1px, -1px) rotate(8deg) }
+	65% { transform: translate(0px, -1px) rotate(-7deg) }
+	70% { transform: translate(-1px, -3px) rotate(6deg) }
+	75% { transform: translate(0px, -2px) rotate(4deg) }
+	80% { transform: translate(-2px, -1px) rotate(3deg) }
+	85% { transform: translate(1px, -3px) rotate(-10deg) }
+	90% { transform: translate(1px, 0px) rotate(3deg) }
+	95% { transform: translate(-2px, 0px) rotate(-3deg) }
+	100% { transform: translate(2px, 1px) rotate(2deg) }
+}
+
+@keyframes mfm-rubberBand {
+	from { transform: scale3d(1, 1, 1); }
+	30% { transform: scale3d(1.25, 0.75, 1); }
+	40% { transform: scale3d(0.75, 1.25, 1); }
+	50% { transform: scale3d(1.15, 0.85, 1); }
+	65% { transform: scale3d(0.95, 1.05, 1); }
+	75% { transform: scale3d(1.05, 0.95, 1); }
+	to { transform: scale3d(1, 1, 1); }
+}
+
+@keyframes mfm-rainbow {
+	0% { filter: hue-rotate(0deg) contrast(150%) saturate(150%); }
+	100% { filter: hue-rotate(360deg) contrast(150%) saturate(150%); }
+}
+
+@keyframes mfm-fade {
+	0% {
+	  opacity: 0;
+	}
+	100% {
+	  opacity: 1;
+	}
+}
+
+@media (prefers-reduced-motion) {
+	@keyframes mfm-spin { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-spinX { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-spinY { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-jump { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-bounce { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-twitch { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-shake { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-rubberBand { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-rainbow { 0% { transform: none; filter: none; opacity: 1 } }
+	@keyframes mfm-fade	{ 0% { transform: none; filter: none; opacity: 1 } }
+}
diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts
new file mode 100644
index 0000000000000000000000000000000000000000..23e70cd0d375c5246141399c8f800592e9b9cd8e
--- /dev/null
+++ b/packages/frontend-embed/src/theme.ts
@@ -0,0 +1,108 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import tinycolor from 'tinycolor2';
+import lightTheme from '@@/themes/_light.json5';
+import darkTheme from '@@/themes/_dark.json5';
+import type { BundledTheme } from 'shiki/themes';
+
+export type Theme = {
+	id: string;
+	name: string;
+	author: string;
+	desc?: string;
+	base?: 'dark' | 'light';
+	props: Record<string, string>;
+	codeHighlighter?: {
+		base: BundledTheme;
+		overrides?: Record<string, any>;
+	} | {
+		base: '_none_';
+		overrides: Record<string, any>;
+	};
+};
+
+let timeout: number | null = null;
+
+export function assertIsTheme(theme: Record<string, unknown>): theme is Theme {
+	return typeof theme === 'object' && theme !== null && 'id' in theme && 'name' in theme && 'author' in theme && 'props' in theme;
+}
+
+export function applyTheme(theme: Theme, persist = true) {
+	if (timeout) window.clearTimeout(timeout);
+
+	document.documentElement.classList.add('_themeChanging_');
+
+	timeout = window.setTimeout(() => {
+		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));
+
+	if (_theme.base) {
+		const base = [lightTheme, darkTheme].find(x => x.id === _theme.base);
+		if (base) _theme.props = Object.assign({}, base.props, _theme.props);
+	}
+
+	const props = compile(_theme);
+
+	for (const tag of document.head.children) {
+		if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
+			tag.setAttribute('content', props['htmlThemeColor']);
+			break;
+		}
+	}
+
+	for (const [k, v] of Object.entries(props)) {
+		document.documentElement.style.setProperty(`--${k}`, v.toString());
+	}
+
+	// iframeを正常に透過させるために、cssのcolor-schemeは `light dark;` 固定にしてある。style.scss参照
+}
+
+function compile(theme: Theme): Record<string, string> {
+	function getColor(val: string): tinycolor.Instance {
+		if (val[0] === '@') { // ref (prop)
+			return getColor(theme.props[val.substring(1)]);
+		} else if (val[0] === '$') { // ref (const)
+			return getColor(theme.props[val]);
+		} else if (val[0] === ':') { // func
+			const parts = val.split('<');
+			const func = parts.shift().substring(1);
+			const arg = parseFloat(parts.shift());
+			const color = getColor(parts.join('<'));
+
+			switch (func) {
+				case 'darken': return color.darken(arg);
+				case 'lighten': return color.lighten(arg);
+				case 'alpha': return color.setAlpha(arg);
+				case 'hue': return color.spin(arg);
+				case 'saturate': return color.saturate(arg);
+			}
+		}
+
+		// other case
+		return tinycolor(val);
+	}
+
+	const props = {};
+
+	for (const [k, v] of Object.entries(theme.props)) {
+		if (k.startsWith('$')) continue; // ignore const
+
+		props[k] = v.startsWith('"') ? v.replace(/^"\s*/, '') : genValue(getColor(v));
+	}
+
+	return props;
+}
+
+function genValue(c: tinycolor.Instance): string {
+	return c.toRgbString();
+}
diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8da5f46a96e80069e3777b568e448ade3984cf2a
--- /dev/null
+++ b/packages/frontend-embed/src/ui.vue
@@ -0,0 +1,110 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+	ref="rootEl"
+	:class="[
+		$style.rootForEmbedPage,
+		{
+			[$style.rounded]: embedRounded,
+			[$style.noBorder]: embedNoBorder,
+		}
+	]"
+	:style="maxHeight > 0 ? { maxHeight: `${maxHeight}px`, '--embedMaxHeight': `${maxHeight}px` } : {}"
+>
+	<div
+		:class="$style.routerViewContainer"
+	>
+		<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>
+
+<script lang="ts" setup>
+import { ref, shallowRef, onMounted, onUnmounted, inject } from 'vue';
+import { postMessageToParentWindow } from '@/post-message.js';
+import { DI } from '@/di.js';
+import { defaultEmbedParams } from '@@/js/embed-page.js';
+import EmNotePage from '@/pages/note.vue';
+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 = safeURIDecode(location.pathname.split('/')[3]);
+if (_DEV_) console.log(page, contentId);
+
+const embedParams = inject(DI.embedParams, defaultEmbedParams);
+
+//#region Embed Style
+const embedRounded = ref(embedParams.rounded);
+const embedNoBorder = ref(!embedParams.border);
+const maxHeight = ref(embedParams.maxHeight ?? 0);
+//#endregion
+
+//#region Embed Resizer
+const rootEl = shallowRef<HTMLElement | null>(null);
+
+let previousHeight = 0;
+const resizeObserver = new ResizeObserver(async () => {
+	const height = rootEl.value!.scrollHeight + (embedNoBorder.value ? 0 : 2); // border 上下1px
+	if (Math.abs(previousHeight - height) < 1) return; // 1px未満の変化は無視
+	postMessageToParentWindow('misskey:embed:changeHeight', {
+		height: (maxHeight.value > 0 && height > maxHeight.value) ? maxHeight.value : height,
+	});
+	previousHeight = height;
+});
+onMounted(() => {
+	resizeObserver.observe(rootEl.value!);
+});
+onUnmounted(() => {
+	resizeObserver.disconnect();
+});
+//#endregion
+</script>
+
+<style lang="scss" module>
+.rootForEmbedPage {
+	box-sizing: border-box;
+	border: 1px solid var(--divider);
+	background-color: var(--bg);
+	overflow: hidden;
+	position: relative;
+	height: auto;
+
+	&.rounded {
+		border-radius: var(--radius);
+	}
+
+	&.noBorder {
+		border: none;
+	}
+}
+
+.routerViewContainer {
+	container-type: inline-size;
+	max-height: var(--embedMaxHeight, none);
+}
+</style>
diff --git a/packages/frontend-embed/src/utils.ts b/packages/frontend-embed/src/utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..939648aa383d0fd89662b5c94324745474350aae
--- /dev/null
+++ b/packages/frontend-embed/src/utils.ts
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import { url } from '@@/js/config.js';
+
+export const acct = (user: Misskey.Acct) => {
+	return Misskey.acct.toString(user);
+};
+
+export const userName = (user: Misskey.entities.User) => {
+	return user.name || user.username;
+};
+
+export const userPage = (user: Misskey.Acct, path?: string, absolute = false) => {
+	return `${absolute ? url : ''}/@${acct(user)}${(path ? `/${path}` : '')}`;
+};
+
+export const notePage = (note: Misskey.entities.Note) => {
+	return `/notes/${note.id}`;
+};
diff --git a/packages/frontend-embed/src/workers/draw-blurhash.ts b/packages/frontend-embed/src/workers/draw-blurhash.ts
new file mode 100644
index 0000000000000000000000000000000000000000..22de6cd3a8c5aaa227949ce755437ee70441cd6e
--- /dev/null
+++ b/packages/frontend-embed/src/workers/draw-blurhash.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { render } from 'buraha';
+
+const canvas = new OffscreenCanvas(64, 64);
+
+onmessage = (event) => {
+	// console.log(event.data);
+	if (!('id' in event.data && typeof event.data.id === 'string')) {
+		return;
+	}
+	if (!('hash' in event.data && typeof event.data.hash === 'string')) {
+		return;
+	}
+
+	render(event.data.hash, canvas);
+	const bitmap = canvas.transferToImageBitmap();
+	postMessage({ id: event.data.id, bitmap });
+};
diff --git a/packages/frontend-embed/src/workers/test-webgl2.ts b/packages/frontend-embed/src/workers/test-webgl2.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b203ebe666b8ab1718738c6cb5fb11b2e3d5985a
--- /dev/null
+++ b/packages/frontend-embed/src/workers/test-webgl2.ts
@@ -0,0 +1,14 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+const canvas = globalThis.OffscreenCanvas && new OffscreenCanvas(1, 1);
+// 環境によってはOffscreenCanvasが存在しないため
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+const gl = canvas?.getContext('webgl2');
+if (gl) {
+	postMessage({ result: true });
+} else {
+	postMessage({ result: false });
+}
diff --git a/packages/frontend-embed/src/workers/tsconfig.json b/packages/frontend-embed/src/workers/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..8ee893046590f2c9aba8e4cbb8a51a722bdfa6b6
--- /dev/null
+++ b/packages/frontend-embed/src/workers/tsconfig.json
@@ -0,0 +1,5 @@
+{
+	"compilerOptions": {
+		"lib": ["esnext", "webworker"],
+	}
+}
diff --git a/packages/frontend-embed/tsconfig.json b/packages/frontend-embed/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..1af34f378c7505005d4549bb19e01f73b403b6a2
--- /dev/null
+++ b/packages/frontend-embed/tsconfig.json
@@ -0,0 +1,56 @@
+{
+	"compilerOptions": {
+		"allowJs": true,
+		"noEmitOnError": false,
+		"noImplicitAny": false,
+		"noImplicitReturns": true,
+		"noUnusedParameters": false,
+		"noUnusedLocals": false,
+		"noFallthroughCasesInSwitch": true,
+		"declaration": false,
+		"sourceMap": false,
+		"target": "ES2022",
+		"module": "nodenext",
+		"moduleResolution": "nodenext",
+		"removeComments": false,
+		"noLib": false,
+		"strict": true,
+		"strictNullChecks": true,
+		"experimentalDecorators": true,
+		"resolveJsonModule": true,
+		"allowSyntheticDefaultImports": true,
+		"isolatedModules": true,
+		"useDefineForClassFields": true,
+		"skipLibCheck": true,
+		"baseUrl": ".",
+		"paths": {
+			"@/*": ["./src/*"],
+			"@@/*": ["../frontend-shared/*"]
+		},
+		"typeRoots": [
+			"./@types",
+			"./node_modules/@types",
+			"./node_modules/@vue-macros",
+			"./node_modules"
+		],
+		"types": [
+			"vite/client"
+		],
+		"lib": [
+			"esnext",
+			"dom",
+			"dom.iterable"
+		],
+		"jsx": "preserve"
+	},
+	"compileOnSave": false,
+	"include": [
+		"./src/**/*.ts",
+		"./src/**/*.vue",
+		"./@types/**/*.ts"
+	],
+	"exclude": [
+		"node_modules",
+		".storybook/**/*"
+	]
+}
diff --git a/packages/frontend-embed/vite.config.local-dev.ts b/packages/frontend-embed/vite.config.local-dev.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bf2f478887d6e9a8501aaed6e0d0fbad0808c227
--- /dev/null
+++ b/packages/frontend-embed/vite.config.local-dev.ts
@@ -0,0 +1,96 @@
+import dns from 'dns';
+import { readFile } from 'node:fs/promises';
+import type { IncomingMessage } from 'node:http';
+import { defineConfig } from 'vite';
+import type { UserConfig } from 'vite';
+import * as yaml from 'js-yaml';
+import locales from '../../locales/index.js';
+import { getConfig } from './vite.config.js';
+
+dns.setDefaultResultOrder('ipv4first');
+
+const defaultConfig = getConfig();
+
+const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8'));
+
+const httpUrl = `http://localhost:${port}/`;
+const websocketUrl = `ws://localhost:${port}/`;
+
+// activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す
+function varyHandler(req: IncomingMessage) {
+	if (req.headers.accept?.includes('application/activity+json')) {
+		return null;
+	}
+	return '/index.html';
+}
+
+const devConfig: UserConfig = {
+	// 基本の設定は vite.config.js から引き継ぐ
+	...defaultConfig,
+	root: 'src',
+	publicDir: '../assets',
+	base: '/embed',
+	server: {
+		host: 'localhost',
+		port: 5174,
+		proxy: {
+			'/api': {
+				changeOrigin: true,
+				target: httpUrl,
+			},
+			'/assets': httpUrl,
+			'/static-assets': httpUrl,
+			'/client-assets': httpUrl,
+			'/files': httpUrl,
+			'/twemoji': httpUrl,
+			'/fluent-emoji': httpUrl,
+			'/sw.js': httpUrl,
+			'/streaming': {
+				target: websocketUrl,
+				ws: true,
+			},
+			'/favicon.ico': httpUrl,
+			'/robots.txt': httpUrl,
+			'/embed.js': httpUrl,
+			'/identicon': {
+				target: httpUrl,
+				rewrite(path) {
+					return path.replace('@localhost:5173', '');
+				},
+			},
+			'/url': httpUrl,
+			'/proxy': httpUrl,
+			'/_info_card_': httpUrl,
+			'/bios': httpUrl,
+			'/cli': httpUrl,
+			'/inbox': httpUrl,
+			'/emoji/': httpUrl,
+			'/notes': {
+				target: httpUrl,
+				bypass: varyHandler,
+			},
+			'/users': {
+				target: httpUrl,
+				bypass: varyHandler,
+			},
+			'/.well-known': {
+				target: httpUrl,
+			},
+		},
+	},
+	build: {
+		...defaultConfig.build,
+		rollupOptions: {
+			...defaultConfig.build?.rollupOptions,
+			input: 'index.html',
+		},
+	},
+
+	define: {
+		...defaultConfig.define,
+		_LANGS_FULL_: JSON.stringify(Object.entries(locales)),
+	},
+};
+
+export default defineConfig(({ command, mode }) => devConfig);
+
diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b95533c2cdfead7f005948497c485734b2d70216
--- /dev/null
+++ b/packages/frontend-embed/vite.config.ts
@@ -0,0 +1,156 @@
+import path from 'path';
+import pluginVue from '@vitejs/plugin-vue';
+import { type UserConfig, defineConfig } from 'vite';
+import { localesVersion } from '../../locales/version.js';
+import locales from '../../locales/index.js';
+import meta from '../../package.json';
+import packageInfo from './package.json' with { type: 'json' };
+import pluginJson5 from './vite.json5.js';
+import { pluginReplaceIcons } from '../frontend/vite.replaceIcons.js';
+
+const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
+
+/**
+ * Misskeyのフロントエンドにバンドルせず、CDNなどから別途読み込むリソースを記述する。
+ * CDNを使わずにバンドルしたい場合、以下の配列から該当要素を削除orコメントアウトすればOK
+ */
+const externalPackages = [
+	// shiki(コードブロックのシンタックスハイライトで使用中)はテーマ・言語の定義の容量が大きいため、それらはCDNから読み込む
+	{
+		name: 'shiki',
+		match: /^shiki\/(?<subPkg>(langs|themes))$/,
+		path(id: string, pattern: RegExp): string {
+			const match = pattern.exec(id)?.groups;
+			return match
+				? `https://esm.sh/shiki@${packageInfo.dependencies.shiki}/${match['subPkg']}`
+				: id;
+		},
+	},
+];
+
+const hash = (str: string, seed = 0): number => {
+	let h1 = 0xdeadbeef ^ seed,
+		h2 = 0x41c6ce57 ^ seed;
+	for (let i = 0, ch; i < str.length; i++) {
+		ch = str.charCodeAt(i);
+		h1 = Math.imul(h1 ^ ch, 2654435761);
+		h2 = Math.imul(h2 ^ ch, 1597334677);
+	}
+
+	h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
+	h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
+
+	return 4294967296 * (2097151 & h2) + (h1 >>> 0);
+};
+
+const BASE62_DIGITS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+function toBase62(n: number): string {
+	if (n === 0) {
+		return '0';
+	}
+	let result = '';
+	while (n > 0) {
+		result = BASE62_DIGITS[n % BASE62_DIGITS.length] + result;
+		n = Math.floor(n / BASE62_DIGITS.length);
+	}
+
+	return result;
+}
+
+export function getConfig(): UserConfig {
+	return {
+		base: '/embed_vite/',
+
+		server: {
+			port: 5174,
+		},
+
+		plugins: [
+			pluginVue(),
+			pluginJson5(),
+			...pluginReplaceIcons(),
+		],
+
+		resolve: {
+			extensions,
+			alias: {
+				'@/': __dirname + '/src/',
+				'@@/': __dirname + '/../frontend-shared/',
+				'/client-assets/': __dirname + '/assets/',
+				'/static-assets/': __dirname + '/../backend/assets/'
+			},
+		},
+
+		css: {
+			modules: {
+				generateScopedName(name, filename, _css): string {
+					const id = (path.relative(__dirname, filename.split('?')[0]) + '-' + name).replace(/[\\\/\.\?&=]/g, '-').replace(/(src-|vue-)/g, '');
+					const shortId = id.replace(/^(components(-global)?|widgets|ui(-_common_)?)-/, '');
+					return shortId + '-' + toBase62(hash(id)).substring(0, 4);
+				},
+			},
+		},
+
+		define: {
+			_VERSION_: JSON.stringify(meta.version),
+			_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
+			_LANGS_VERSION_: JSON.stringify(localesVersion),
+			_ENV_: JSON.stringify(process.env.NODE_ENV),
+			_DEV_: process.env.NODE_ENV !== 'production',
+			_PERF_PREFIX_: JSON.stringify('Misskey:'),
+			__VUE_OPTIONS_API__: false,
+			__VUE_PROD_DEVTOOLS__: false,
+		},
+
+		build: {
+			target: [
+				'chrome116',
+				'firefox116',
+				'safari16',
+			],
+			manifest: 'manifest.json',
+			rollupOptions: {
+				input: {
+					app: './src/boot.ts',
+				},
+				external: externalPackages.map(p => p.match),
+				output: {
+					manualChunks: {
+						vue: ['vue'],
+					},
+					chunkFileNames: process.env.NODE_ENV === 'production' ? '[hash:8].js' : '[name]-[hash:8].js',
+					assetFileNames: process.env.NODE_ENV === 'production' ? '[hash:8][extname]' : '[name]-[hash:8][extname]',
+					paths(id) {
+						for (const p of externalPackages) {
+							if (p.match.test(id)) {
+								return p.path(id, p.match);
+							}
+						}
+
+						return id;
+					},
+				},
+			},
+			cssCodeSplit: true,
+			outDir: __dirname + '/../../built/_frontend_embed_vite_',
+			assetsDir: '.',
+			emptyOutDir: false,
+			sourcemap: process.env.NODE_ENV === 'development',
+			reportCompressedSize: false,
+
+			// https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies
+			commonjsOptions: {
+				include: [/misskey-js/, /node_modules/],
+			},
+		},
+
+		worker: {
+			format: 'es',
+		},
+	};
+}
+
+const config = defineConfig(({ command, mode }) => getConfig());
+
+export default config;
diff --git a/packages/frontend-embed/vite.json5.ts b/packages/frontend-embed/vite.json5.ts
new file mode 100644
index 0000000000000000000000000000000000000000..87b67c2142414a94763808ebd83e06cdab3d588c
--- /dev/null
+++ b/packages/frontend-embed/vite.json5.ts
@@ -0,0 +1,48 @@
+// Original: https://github.com/rollup/plugins/tree/8835dd2aed92f408d7dc72d7cc25a9728e16face/packages/json
+
+import JSON5 from 'json5';
+import { Plugin } from 'rollup';
+import { createFilter, dataToEsm } from '@rollup/pluginutils';
+import { RollupJsonOptions } from '@rollup/plugin-json';
+
+// json5 extends SyntaxError with additional fields (without subclassing)
+// https://github.com/json5/json5/blob/de344f0619bda1465a6e25c76f1c0c3dda8108d9/lib/parse.js#L1111-L1112
+interface Json5SyntaxError extends SyntaxError {
+	lineNumber: number;
+	columnNumber: number;
+}
+
+export default function json5(options: RollupJsonOptions = {}): Plugin {
+	const filter = createFilter(options.include, options.exclude);
+	const indent = 'indent' in options ? options.indent : '\t';
+
+	return {
+		name: 'json5',
+
+		// eslint-disable-next-line no-shadow
+		transform(json, id) {
+			if (id.slice(-6) !== '.json5' || !filter(id)) return null;
+
+			try {
+				const parsed = JSON5.parse(json);
+				return {
+					code: dataToEsm(parsed, {
+						preferConst: options.preferConst,
+						compact: options.compact,
+						namedExports: options.namedExports,
+						indent,
+					}),
+					map: { mappings: '' },
+				};
+			} catch (err) {
+				if (!(err instanceof SyntaxError)) {
+					throw err;
+				}
+				const message = 'Could not parse JSON5 file';
+				const { lineNumber, columnNumber } = err as Json5SyntaxError;
+				this.warn({ message, id, loc: { line: lineNumber, column: columnNumber } });
+				return null;
+			}
+		},
+	};
+}
diff --git a/packages/frontend-embed/vue-shims.d.ts b/packages/frontend-embed/vue-shims.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eba994772dd4680f58296de2c82d714a8f044ba3
--- /dev/null
+++ b/packages/frontend-embed/vue-shims.d.ts
@@ -0,0 +1,6 @@
+/* eslint-disable */
+declare module "*.vue" {
+	import { defineComponent } from "vue";
+	const component: ReturnType<typeof defineComponent>;
+	export default component;
+}
diff --git a/packages/frontend-shared/.gitignore b/packages/frontend-shared/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5f6be09d7c01e1fa28715890bf8a17338cc73bc7
--- /dev/null
+++ b/packages/frontend-shared/.gitignore
@@ -0,0 +1,2 @@
+/storybook-static
+js-built
diff --git a/packages/frontend-shared/@types/global.d.ts b/packages/frontend-shared/@types/global.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..308515564b26b97049450671d2c2fa46dd8315f9
--- /dev/null
+++ b/packages/frontend-shared/@types/global.d.ts
@@ -0,0 +1,26 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type FIXME = any;
+
+declare const _LANGS_: string[][];
+declare const _LANGS_VERSION_: string;
+declare const _VERSION_: string;
+declare const _ENV_: string;
+declare const _DEV_: boolean;
+declare const _PERF_PREFIX_: string;
+declare const _DATA_TRANSFER_DRIVE_FILE_: string;
+declare const _DATA_TRANSFER_DRIVE_FOLDER_: string;
+declare const _DATA_TRANSFER_DECK_COLUMN_: string;
+
+// for dev-mode
+declare const _LANGS_FULL_: string[][];
+
+// TagCanvas
+interface Window {
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
+	TagCanvas: any;
+}
diff --git a/packages/frontend-shared/build.js b/packages/frontend-shared/build.js
new file mode 100644
index 0000000000000000000000000000000000000000..17b6da8d30a2ab847aef0181ffcf82db83057f27
--- /dev/null
+++ b/packages/frontend-shared/build.js
@@ -0,0 +1,106 @@
+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('./js/**/**.{ts,tsx}');
+
+/** @type {import('esbuild').BuildOptions} */
+const options = {
+	entryPoints,
+	minify: process.env.NODE_ENV === 'production',
+	outdir: './js-built',
+	target: 'es2022',
+	platform: 'browser',
+	format: 'esm',
+	sourcemap: 'linked',
+};
+
+// js-built配下をすべて削除する
+fs.rmSync('./js-built', { recursive: true, force: true });
+
+if (process.argv.map(arg => arg.toLowerCase()).includes('--watch')) {
+	await watchSrc();
+} else {
+	await buildSrc();
+}
+
+async function buildSrc() {
+	console.log(`[${_package.name}] start building...`);
+
+	await build(options)
+		.then(() => {
+			console.log(`[${_package.name}] build succeeded.`);
+		})
+		.catch((err) => {
+			process.stderr.write(err.stderr);
+			process.exit(1);
+		});
+
+	if (process.env.NODE_ENV === 'production') {
+		console.log(`[${_package.name}] skip building d.ts because NODE_ENV is production.`);
+	} else {
+		await buildDts();
+	}
+
+	fs.copyFileSync('./js/emojilist.json', './js-built/emojilist.json');
+
+	console.log(`[${_package.name}] finish building.`);
+}
+
+function buildDts() {
+	return execa(
+		'tsc',
+		[
+			'--project', 'tsconfig.json',
+			'--outDir', 'js-built',
+			'--declaration', 'true',
+			'--emitDeclarationOnly', 'true',
+		],
+		{
+			stdout: process.stdout,
+			stderr: process.stderr,
+		},
+	);
+}
+
+async function watchSrc() {
+	const plugins = [{
+		name: 'gen-dts',
+		setup(build) {
+			build.onStart(() => {
+				console.log(`[${_package.name}] detect changed...`);
+			});
+			build.onEnd(async result => {
+				if (result.errors.length > 0) {
+					console.error(`[${_package.name}] watch build failed:`, result);
+					return;
+				}
+				await buildDts();
+			});
+		},
+	}];
+
+	console.log(`[${_package.name}] start watching...`);
+
+	const context = await esbuild.context({ ...options, plugins });
+	await context.watch();
+
+	await new Promise((resolve, reject) => {
+		process.on('SIGHUP', resolve);
+		process.on('SIGINT', resolve);
+		process.on('SIGTERM', resolve);
+		process.on('uncaughtException', reject);
+		process.on('exit', resolve);
+	}).finally(async () => {
+		await context.dispose();
+		console.log(`[${_package.name}] finish watching.`);
+	});
+}
diff --git a/packages/frontend-shared/eslint.config.js b/packages/frontend-shared/eslint.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..689f7870c0e4e785c582d0f7cef8fdff104d121e
--- /dev/null
+++ b/packages/frontend-shared/eslint.config.js
@@ -0,0 +1,109 @@
+import globals from 'globals';
+import tsParser from '@typescript-eslint/parser';
+import parser from 'vue-eslint-parser';
+import pluginVue from 'eslint-plugin-vue';
+import pluginMisskey from '@misskey-dev/eslint-plugin';
+import sharedConfig from '../shared/eslint.config.js';
+
+// eslint-disable-next-line import/no-default-export
+export default [
+	...sharedConfig,
+	{
+		files: ['**/*.vue'],
+		...pluginMisskey.configs.typescript,
+	},
+	...pluginVue.configs['flat/recommended'],
+	{
+		files: [
+			'@types/**/*.ts',
+			'js/**/*.ts',
+			'**/*.vue',
+		],
+		languageOptions: {
+			globals: {
+				...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])),
+				...globals.browser,
+
+				// Node.js
+				module: false,
+				require: false,
+				__dirname: false,
+
+				// Misskey
+				_DEV_: false,
+				_LANGS_: false,
+				_VERSION_: false,
+				_ENV_: false,
+				_PERF_PREFIX_: false,
+				_DATA_TRANSFER_DRIVE_FILE_: false,
+				_DATA_TRANSFER_DRIVE_FOLDER_: false,
+				_DATA_TRANSFER_DECK_COLUMN_: false,
+			},
+			parser,
+			parserOptions: {
+				extraFileExtensions: ['.vue'],
+				parser: tsParser,
+				project: ['./tsconfig.json'],
+				sourceType: 'module',
+				tsconfigRootDir: import.meta.dirname,
+			},
+		},
+		rules: {
+			'@typescript-eslint/no-empty-interface': ['error', {
+				allowSingleExtends: true,
+			}],
+			// window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
+			// e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
+			'id-denylist': ['error', 'window', 'e'],
+			'no-shadow': ['warn'],
+			'vue/attributes-order': ['error', {
+				alphabetical: false,
+			}],
+			'vue/no-use-v-if-with-v-for': ['error', {
+				allowUsingIterationVar: false,
+			}],
+			'vue/no-ref-as-operand': 'error',
+			'vue/no-multi-spaces': ['error', {
+				ignoreProperties: false,
+			}],
+			'vue/no-v-html': 'warn',
+			'vue/order-in-components': 'error',
+			'vue/html-indent': ['warn', 'tab', {
+				attribute: 1,
+				baseIndent: 0,
+				closeBracket: 0,
+				alignAttributesVertically: true,
+				ignores: [],
+			}],
+			'vue/html-closing-bracket-spacing': ['warn', {
+				startTag: 'never',
+				endTag: 'never',
+				selfClosingTag: 'never',
+			}],
+			'vue/multi-word-component-names': 'warn',
+			'vue/require-v-for-key': 'warn',
+			'vue/no-unused-components': 'warn',
+			'vue/no-unused-vars': 'warn',
+			'vue/no-dupe-keys': 'warn',
+			'vue/valid-v-for': 'warn',
+			'vue/return-in-computed-property': 'warn',
+			'vue/no-setup-props-reactivity-loss': 'warn',
+			'vue/max-attributes-per-line': 'off',
+			'vue/html-self-closing': 'off',
+			'vue/singleline-html-element-content-newline': 'off',
+			'vue/v-on-event-hyphenation': ['error', 'never', {
+				autofix: true,
+			}],
+			'vue/attribute-hyphenation': ['error', 'never'],
+		},
+	},
+	{
+		ignores: [
+			"**/lib/",
+			"**/temp/",
+			"**/built/",
+			"**/coverage/",
+			"**/node_modules/",
+		]
+	},
+];
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend-shared/js/collapsed.ts
similarity index 86%
rename from packages/frontend/src/scripts/collapsed.ts
rename to packages/frontend-shared/js/collapsed.ts
index 4ec88a3c657329bc4533be4b7db2e060a38bdb18..af1f88cb73b1566e376a24e6ac854f3e348c22de 100644
--- a/packages/frontend/src/scripts/collapsed.ts
+++ b/packages/frontend-shared/js/collapsed.ts
@@ -7,7 +7,7 @@ import * as Misskey from 'misskey-js';
 
 export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): boolean {
 	const collapsed = note.cw == null && (
-		note.text != null && (
+		(note.text != null && (
 			(note.text.includes('$[x2')) ||
 			(note.text.includes('$[x3')) ||
 			(note.text.includes('$[x4')) ||
@@ -15,7 +15,7 @@ export function shouldCollapsed(note: Misskey.entities.Note, urls: string[]): bo
 			(note.text.split('\n').length > 9) ||
 			(note.text.length > 500) ||
 			(urls.length >= 4)
-		) || note.files.length >= 5
+		)) || (note.files != null && note.files.length >= 5)
 	);
 
 	return collapsed;
diff --git a/packages/frontend-shared/js/config.ts b/packages/frontend-shared/js/config.ts
new file mode 100644
index 0000000000000000000000000000000000000000..9a23e0e7f3fa7ccdf3e97077c4ba37ee2edcbb34
--- /dev/null
+++ b/packages/frontend-shared/js/config.ts
@@ -0,0 +1,29 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { Locale } from '../../../locales/index.js';
+
+// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
+const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
+
+export const host = address.host;
+export const hostname = address.hostname;
+export const url = address.origin;
+export const apiUrl = location.origin + '/api';
+export const wsOrigin = location.origin;
+export const lang = localStorage.getItem('lang') ?? 'en-US';
+export const langs = _LANGS_;
+export const langsVersion = _LANGS_VERSION_;
+const preParseLocale = localStorage.getItem('locale');
+export let locale: Locale = preParseLocale ? JSON.parse(preParseLocale) : null;
+export const version = _VERSION_;
+export const instanceName = (siteName === 'Sharkey' || siteName == null) ? host : siteName;
+export const ui = localStorage.getItem('ui');
+export const debug = localStorage.getItem('debug') === 'true';
+
+export function updateLocale(newLocale: Locale): void {
+	locale = newLocale;
+}
diff --git a/packages/frontend/src/const.ts b/packages/frontend-shared/js/const.ts
similarity index 90%
rename from packages/frontend/src/const.ts
rename to packages/frontend-shared/js/const.ts
index c94c0d440882f10581d3c0365fab3b7ed9596474..8236cc9a4db5e9465bfd2fe1fcbd6f6c88ae212e 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend-shared/js/const.ts
@@ -118,8 +118,10 @@ export const notificationTypes = [
 	'followRequestAccepted',
 	'roleAssigned',
 	'achievementEarned',
+	'exportCompleted',
+	'test',
 	'app',
-	'edited'
+	'edited',
 ] as const;
 export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
 
@@ -152,6 +154,11 @@ export const ROLE_POLICIES = [
 	'userEachUserListsLimit',
 	'rateLimitFactor',
 	'avatarDecorationLimit',
+	'canImportAntennas',
+	'canImportBlocking',
+	'canImportFollowing',
+	'canImportMuting',
+	'canImportUserLists',
 ] as const;
 
 // なんか動かない
@@ -160,9 +167,9 @@ export const ROLE_POLICIES = [
 export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
 export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM';
 
-export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://launcher.moe/error.png';
-export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://launcher.moe/missingpage.webp';
-export const DEFAULT_INFO_IMAGE_URL = 'https://launcher.moe/nothinghere.png';
+export const DEFAULT_SERVER_ERROR_IMAGE_URL = '/client-assets/status/error.png';
+export const DEFAULT_NOT_FOUND_IMAGE_URL = '/client-assets/status/missingpage.webp';
+export const DEFAULT_INFO_IMAGE_URL = '/client-assets/status/nothinghere.png';
 
 export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime', 'crop', 'fade', 'followmouse'];
 export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
diff --git a/packages/frontend-shared/js/embed-page.ts b/packages/frontend-shared/js/embed-page.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d5555a98c3b0fae39ba44479cdcd5fa999d87e00
--- /dev/null
+++ b/packages/frontend-shared/js/embed-page.ts
@@ -0,0 +1,97 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+//#region Embed関連の定義
+
+/** 埋め込みの対象となるエンティティ(/embed/xxx の xxx の部分と対応させる) */
+const embeddableEntities = [
+	'notes',
+	'user-timeline',
+	'clips',
+	'tags',
+] as const;
+
+/** 埋め込みの対象となるエンティティ */
+export type EmbeddableEntity = typeof embeddableEntities[number];
+
+/** 内部でスクロールがあるページ */
+export const embedRouteWithScrollbar: EmbeddableEntity[] = [
+	'clips',
+	'tags',
+	'user-timeline',
+];
+
+/** 埋め込みコードのパラメータ */
+export type EmbedParams = {
+	maxHeight?: number;
+	colorMode?: 'light' | 'dark';
+	rounded?: boolean;
+	border?: boolean;
+	autoload?: boolean;
+	header?: boolean;
+};
+
+/** 正規化されたパラメータ */
+export type ParsedEmbedParams = Required<Omit<EmbedParams, 'maxHeight' | 'colorMode'>> & Pick<EmbedParams, 'maxHeight' | 'colorMode'>;
+
+/** パラメータのデフォルトの値 */
+export const defaultEmbedParams = {
+	maxHeight: undefined,
+	colorMode: undefined,
+	rounded: true,
+	border: true,
+	autoload: false,
+	header: true,
+} as const satisfies EmbedParams;
+
+//#endregion
+
+/**
+ * パラメータを正規化する(埋め込みページ初期化用)
+ * @param searchParams URLSearchParamsもしくはクエリ文字列
+ * @returns 正規化されたパラメータ
+ */
+export function parseEmbedParams(searchParams: URLSearchParams | string): ParsedEmbedParams {
+	let _searchParams: URLSearchParams;
+	if (typeof searchParams === 'string') {
+		_searchParams = new URLSearchParams(searchParams);
+	} else if (searchParams instanceof URLSearchParams) {
+		_searchParams = searchParams;
+	} else {
+		throw new Error('searchParams must be URLSearchParams or string');
+	}
+
+	function convertBoolean(value: string | null): boolean | undefined {
+		if (value === 'true') {
+			return true;
+		} else if (value === 'false') {
+			return false;
+		}
+		return undefined;
+	}
+
+	function convertNumber(value: string | null): number | undefined {
+		if (value != null && !isNaN(Number(value))) {
+			return Number(value);
+		}
+		return undefined;
+	}
+
+	function convertColorMode(value: string | null): 'light' | 'dark' | undefined {
+		if (value != null && ['light', 'dark'].includes(value)) {
+			return value as 'light' | 'dark';
+		}
+		return undefined;
+	}
+
+	return {
+		maxHeight: convertNumber(_searchParams.get('maxHeight')) ?? defaultEmbedParams.maxHeight,
+		colorMode: convertColorMode(_searchParams.get('colorMode')) ?? defaultEmbedParams.colorMode,
+		rounded: convertBoolean(_searchParams.get('rounded')) ?? defaultEmbedParams.rounded,
+		border: convertBoolean(_searchParams.get('border')) ?? defaultEmbedParams.border,
+		autoload: convertBoolean(_searchParams.get('autoload')) ?? defaultEmbedParams.autoload,
+		header: convertBoolean(_searchParams.get('header')) ?? defaultEmbedParams.header,
+	};
+}
diff --git a/packages/frontend/src/scripts/emoji-base.ts b/packages/frontend-shared/js/emoji-base.ts
similarity index 91%
rename from packages/frontend/src/scripts/emoji-base.ts
rename to packages/frontend-shared/js/emoji-base.ts
index 16a5a6aa5b3fa6739f1f4c4ff55fea3853495c71..858cd801de7f7bd964e567464570c8656631848d 100644
--- a/packages/frontend/src/scripts/emoji-base.ts
+++ b/packages/frontend-shared/js/emoji-base.ts
@@ -20,8 +20,8 @@ export function char2fluentEmojiFilePath(char: string): string {
 	// Fluent Emojiは国旗非対応 https://github.com/microsoft/fluentui-emoji/issues/25
 	if (codes[0]?.startsWith('1f1')) return char2twemojiFilePath(char);
 	if (!codes.includes('200d')) codes = codes.filter(x => x !== 'fe0f');
-	codes = codes.filter(x => x && x.length);
-	const fileName = codes.map(x => x!.padStart(4, '0')).join('-');
+	codes = codes.filter(x => x != null && x.length > 0);
+	const fileName = (codes as string[]).map(x => x.padStart(4, '0')).join('-');
 	return `${fluentEmojiPngBase}/${fileName}.png`;
 }
 
diff --git a/packages/frontend/src/emojilist.json b/packages/frontend-shared/js/emojilist.json
similarity index 100%
rename from packages/frontend/src/emojilist.json
rename to packages/frontend-shared/js/emojilist.json
diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend-shared/js/emojilist.ts
similarity index 96%
rename from packages/frontend/src/scripts/emojilist.ts
rename to packages/frontend-shared/js/emojilist.ts
index 6565feba97d2d1e1b3831cd5fabc882b9d49d149..bde30a864fd0259531399c4e0f8ca7ae6a4acfd0 100644
--- a/packages/frontend/src/scripts/emojilist.ts
+++ b/packages/frontend-shared/js/emojilist.ts
@@ -12,12 +12,12 @@ export type UnicodeEmojiDef = {
 }
 
 // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb
-import _emojilist from '../emojilist.json';
+import _emojilist from './emojilist.json';
 
 export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({
 	name: x[1] as string,
 	char: x[0] as string,
-	category: unicodeEmojiCategories[x[2]],
+	category: unicodeEmojiCategories[x[2] as number],
 }));
 
 const unicodeEmojisMap = new Map<string, UnicodeEmojiDef>(
diff --git a/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts b/packages/frontend-shared/js/extract-avg-color-from-blurhash.ts
similarity index 100%
rename from packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts
rename to packages/frontend-shared/js/extract-avg-color-from-blurhash.ts
diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend-shared/js/i18n.ts
similarity index 78%
rename from packages/frontend/src/scripts/i18n.ts
rename to packages/frontend-shared/js/i18n.ts
index c2f44a33cc4713c2d33cad610b7eca85cd9df57c..18232691fa7e6069df874782e93e8070a795cc97 100644
--- a/packages/frontend/src/scripts/i18n.ts
+++ b/packages/frontend-shared/js/i18n.ts
@@ -2,7 +2,10 @@
  * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import type { ILocale, ParameterizedString } from '../../../../locales/index.js';
+import type { ILocale, ParameterizedString } from '../../../locales/index.js';
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+type TODO = any;
 
 type FlattenKeys<T extends ILocale, TPrediction> = keyof {
 	[K in keyof T as T[K] extends ILocale
@@ -32,15 +35,18 @@ type Tsx<T extends ILocale> = {
 
 export class I18n<T extends ILocale> {
 	private tsxCache?: Tsx<T>;
+	private devMode: boolean;
+
+	constructor(public locale: T, devMode = false) {
+		this.devMode = devMode;
 
-	constructor(public locale: T) {
 		//#region BIND
 		this.t = this.t.bind(this);
 		//#endregion
 	}
 
 	public get ts(): T {
-		if (_DEV_) {
+		if (this.devMode) {
 			class Handler<TTarget extends ILocale> implements ProxyHandler<TTarget> {
 				get(target: TTarget, p: string | symbol): unknown {
 					const value = target[p as keyof TTarget];
@@ -72,7 +78,7 @@ export class I18n<T extends ILocale> {
 	}
 
 	public get tsx(): Tsx<T> {
-		if (_DEV_) {
+		if (this.devMode) {
 			if (this.tsxCache) {
 				return this.tsxCache;
 			}
@@ -113,7 +119,7 @@ export class I18n<T extends ILocale> {
 							return () => value;
 						}
 
-						return (arg) => {
+						return (arg: TODO) => {
 							let str = quasis[0];
 
 							for (let i = 0; i < expressions.length; i++) {
@@ -137,7 +143,6 @@ export class I18n<T extends ILocale> {
 			return this.tsxCache = new Proxy(this.locale, new Handler()) as unknown as Tsx<T>;
 		}
 
-		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 		if (this.tsxCache) {
 			return this.tsxCache;
 		}
@@ -153,7 +158,7 @@ export class I18n<T extends ILocale> {
 				const value = target[k as keyof typeof target];
 
 				if (typeof value === 'object') {
-					result[k] = build(value as ILocale);
+					(result as TODO)[k] = build(value as ILocale);
 				} else if (typeof value === 'string') {
 					const quasis: string[] = [];
 					const expressions: string[] = [];
@@ -180,7 +185,7 @@ export class I18n<T extends ILocale> {
 						continue;
 					}
 
-					result[k] = (arg) => {
+					(result as TODO)[k] = (arg: TODO) => {
 						let str = quasis[0];
 
 						for (let i = 0; i < expressions.length; i++) {
@@ -209,9 +214,9 @@ export class I18n<T extends ILocale> {
 		let str: string | ParameterizedString | ILocale = this.locale;
 
 		for (const k of key.split('.')) {
-			str = str[k];
+			str = (str as TODO)[k];
 
-			if (_DEV_) {
+			if (this.devMode) {
 				if (typeof str === 'undefined') {
 					console.error(`Unexpected locale key: ${key}`);
 					return key;
@@ -220,7 +225,7 @@ export class I18n<T extends ILocale> {
 		}
 
 		if (args) {
-			if (_DEV_) {
+			if (this.devMode) {
 				const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter));
 
 				if (missing.length) {
@@ -231,7 +236,7 @@ export class I18n<T extends ILocale> {
 			for (const [k, v] of Object.entries(args)) {
 				const search = `{${k}}`;
 
-				if (_DEV_) {
+				if (this.devMode) {
 					if (!(str as string).includes(search)) {
 						console.error(`Unexpected locale parameter: ${k} at ${key}`);
 					}
@@ -244,51 +249,3 @@ export class I18n<T extends ILocale> {
 		return str;
 	}
 }
-
-if (import.meta.vitest) {
-	const { describe, expect, it } = import.meta.vitest;
-
-	describe('i18n', () => {
-		it('t', () => {
-			const i18n = new I18n({
-				foo: 'foo',
-				bar: {
-					baz: 'baz',
-					qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
-					quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
-				},
-			});
-
-			expect(i18n.t('foo')).toBe('foo');
-			expect(i18n.t('bar.baz')).toBe('baz');
-			expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
-			expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
-		});
-		it('ts', () => {
-			const i18n = new I18n({
-				foo: 'foo',
-				bar: {
-					baz: 'baz',
-					qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
-					quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
-				},
-			});
-
-			expect(i18n.ts.foo).toBe('foo');
-			expect(i18n.ts.bar.baz).toBe('baz');
-		});
-		it('tsx', () => {
-			const i18n = new I18n({
-				foo: 'foo',
-				bar: {
-					baz: 'baz',
-					qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
-					quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
-				},
-			});
-
-			expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
-			expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
-		});
-	});
-}
diff --git a/packages/frontend-shared/js/intl-const.ts b/packages/frontend-shared/js/intl-const.ts
new file mode 100644
index 0000000000000000000000000000000000000000..33b65b6e9b782bca8d0eca7840d237ed74cf77e9
--- /dev/null
+++ b/packages/frontend-shared/js/intl-const.ts
@@ -0,0 +1,51 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { lang } from '@@/js/config.js';
+
+// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
+
+let _dateTimeFormat: Intl.DateTimeFormat;
+try {
+	_dateTimeFormat = new Intl.DateTimeFormat(versatileLang, {
+		year: 'numeric',
+		month: 'numeric',
+		day: 'numeric',
+		hour: 'numeric',
+		minute: 'numeric',
+		second: 'numeric',
+	});
+} catch (err) {
+	console.warn(err);
+	if (_DEV_) console.log('[Intl] Fallback to en-US');
+
+	// Fallback to en-US
+	_dateTimeFormat = new Intl.DateTimeFormat('en-US', {
+		year: 'numeric',
+		month: 'numeric',
+		day: 'numeric',
+		hour: 'numeric',
+		minute: 'numeric',
+		second: 'numeric',
+	});
+}
+export const dateTimeFormat = _dateTimeFormat;
+
+export const timeZone = dateTimeFormat.resolvedOptions().timeZone;
+
+export const hemisphere = /^(australia|pacific|antarctica|indian)\//i.test(timeZone) ? 'S' : 'N';
+
+let _numberFormat: Intl.NumberFormat;
+try {
+	_numberFormat = new Intl.NumberFormat(versatileLang);
+} catch (err) {
+	console.warn(err);
+	if (_DEV_) console.log('[Intl] Fallback to en-US');
+
+	// Fallback to en-US
+	_numberFormat = new Intl.NumberFormat('en-US');
+}
+export const numberFormat = _numberFormat;
diff --git a/packages/frontend-shared/js/is-link.ts b/packages/frontend-shared/js/is-link.ts
new file mode 100644
index 0000000000000000000000000000000000000000..946f86400e16e75b4f4e73b460b98006363be0ec
--- /dev/null
+++ b/packages/frontend-shared/js/is-link.ts
@@ -0,0 +1,12 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function isLink(el: HTMLElement) {
+	if (el.tagName === 'A') return true;
+	if (el.parentElement) {
+		return isLink(el.parentElement);
+	}
+	return false;
+}
diff --git a/packages/frontend-shared/js/media-proxy.ts b/packages/frontend-shared/js/media-proxy.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2837870c9a639d7cda6934c1ea805f87e6837bda
--- /dev/null
+++ b/packages/frontend-shared/js/media-proxy.ts
@@ -0,0 +1,63 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as Misskey from 'misskey-js';
+import { query } from './url.js';
+
+export class MediaProxy {
+	private serverMetadata: Misskey.entities.MetaDetailed;
+	private url: string;
+
+	constructor(serverMetadata: Misskey.entities.MetaDetailed, url: string) {
+		this.serverMetadata = serverMetadata;
+		this.url = url;
+	}
+
+	public getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin = false, noFallback = false): string {
+		const localProxy = `${this.url}/proxy`;
+		let _imageUrl = imageUrl;
+
+		if (imageUrl.startsWith(this.serverMetadata.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
+			// もう既にproxyっぽそうだったらurlを取り出す
+			_imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl;
+		}
+
+		return `${mustOrigin ? localProxy : this.serverMetadata.mediaProxy}/${
+			type === 'preview' ? 'preview.webp'
+			: 'image.webp'
+		}?${query({
+			url: _imageUrl,
+			...(!noFallback ? { 'fallback': '1' } : {}),
+			...(type ? { [type]: '1' } : {}),
+			...(mustOrigin ? { origin: '1' } : {}),
+		})}`;
+	}
+
+	public getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
+		if (imageUrl == null) return null;
+		return this.getProxiedImageUrl(imageUrl, type);
+	}
+
+	public getStaticImageUrl(baseUrl: string): string {
+		const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, this.url);
+
+		if (u.href.startsWith(`${this.url}/emoji/`)) {
+			// もう既にemojiっぽそうだったらsearchParams付けるだけ
+			u.searchParams.set('static', '1');
+			return u.href;
+		}
+
+		if (u.href.startsWith(this.serverMetadata.mediaProxy + '/')) {
+			// もう既にproxyっぽそうだったらsearchParams付けるだけ
+			u.searchParams.set('static', '1');
+			return u.href;
+		}
+
+		return `${this.serverMetadata.mediaProxy}/static.webp?${query({
+			url: u.href,
+			static: '1',
+		})}`;
+	}
+}
diff --git a/packages/frontend/src/scripts/scroll.ts b/packages/frontend-shared/js/scroll.ts
similarity index 82%
rename from packages/frontend/src/scripts/scroll.ts
rename to packages/frontend-shared/js/scroll.ts
index f0274034b5b3d027824c9041236386d240b577f3..4f2e9105c3404720caed3e6bc31d2efc482ccc53 100644
--- a/packages/frontend/src/scripts/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;
 
-	const onScroll = ev => {
+	// 以下のケースにおいて、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();
 		}
 	};
@@ -69,7 +77,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
 	}
 
 	const containerOrWindow = container ?? window;
-	const onScroll = ev => {
+	const onScroll = () => {
 		if (!document.body.contains(el)) return;
 		if (isBottomVisible(el, 1, container)) {
 			cb();
@@ -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/src/scripts/url.ts b/packages/frontend-shared/js/url.ts
similarity index 70%
rename from packages/frontend/src/scripts/url.ts
rename to packages/frontend-shared/js/url.ts
index 5a8265af9e1050d0c7e6ac260828d26f92b2f499..eb830b1eeaec7ac22ab64d01b2fb8ba5058ae246 100644
--- a/packages/frontend/src/scripts/url.ts
+++ b/packages/frontend-shared/js/url.ts
@@ -8,18 +8,18 @@
  * 2. プロパティがundefinedの時はクエリを付けない
  * (new URLSearchParams(obj)ではそこまで丁寧なことをしてくれない)
  */
-export function query(obj: Record<string, any>): string {
+export function query(obj: Record<string, string | number | boolean>): string {
 	const params = Object.entries(obj)
-		.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
-		.reduce((a, [k, v]) => (a[k] = v, a), {} as Record<string, any>);
+		.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) // eslint-disable-line @typescript-eslint/no-unnecessary-condition
+		.reduce<Record<string, string | number | boolean>>((a, [k, v]) => (a[k] = v, a), {});
 
 	return Object.entries(params)
 		.map((p) => `${p[0]}=${encodeURIComponent(p[1])}`)
 		.join('&');
 }
 
-export function appendQuery(url: string, query: string): string {
-	return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
+export function appendQuery(url: string, queryString: string): string {
+	return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${queryString}`;
 }
 
 export function extractDomain(url: string) {
diff --git a/packages/frontend/src/scripts/use-document-visibility.ts b/packages/frontend-shared/js/use-document-visibility.ts
similarity index 85%
rename from packages/frontend/src/scripts/use-document-visibility.ts
rename to packages/frontend-shared/js/use-document-visibility.ts
index a8f4d5e03ae2158d609ca78dcc1a8f85f6a4064b..b1197e68dae2bcdedeefb09de5cf7b3202d289b3 100644
--- a/packages/frontend/src/scripts/use-document-visibility.ts
+++ b/packages/frontend-shared/js/use-document-visibility.ts
@@ -3,7 +3,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { onMounted, onUnmounted, ref, Ref } from 'vue';
+import { onMounted, onUnmounted, ref } from 'vue';
+import type { Ref } from 'vue';
 
 export function useDocumentVisibility(): Ref<DocumentVisibilityState> {
 	const visibility = ref(document.visibilityState);
diff --git a/packages/frontend/src/scripts/use-interval.ts b/packages/frontend-shared/js/use-interval.ts
similarity index 100%
rename from packages/frontend/src/scripts/use-interval.ts
rename to packages/frontend-shared/js/use-interval.ts
diff --git a/packages/frontend/src/scripts/worker-multi-dispatch.ts b/packages/frontend-shared/js/worker-multi-dispatch.ts
similarity index 84%
rename from packages/frontend/src/scripts/worker-multi-dispatch.ts
rename to packages/frontend-shared/js/worker-multi-dispatch.ts
index 6b3fcd938334d03595e57508d2dfe6aeada60085..5d393ed1edef9138bff1a57d753fad2a7b0044d5 100644
--- a/packages/frontend/src/scripts/worker-multi-dispatch.ts
+++ b/packages/frontend-shared/js/worker-multi-dispatch.ts
@@ -3,16 +3,18 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-function defaultUseWorkerNumber(prev: number, totalWorkers: number) {
+function defaultUseWorkerNumber(prev: number) {
 	return prev + 1;
 }
 
-export class WorkerMultiDispatch<POST = any, RETURN = any> {
+type WorkerNumberGetter = (prev: number, totalWorkers: number) => number;
+
+export class WorkerMultiDispatch<POST = unknown, RETURN = unknown> {
 	private symbol = Symbol('WorkerMultiDispatch');
 	private workers: Worker[] = [];
 	private terminated = false;
 	private prevWorkerNumber = 0;
-	private getUseWorkerNumber = defaultUseWorkerNumber;
+	private getUseWorkerNumber: WorkerNumberGetter;
 	private finalizationRegistry: FinalizationRegistry<symbol>;
 
 	constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) {
@@ -29,7 +31,7 @@ export class WorkerMultiDispatch<POST = any, RETURN = any> {
 		if (_DEV_) console.log('WorkerMultiDispatch: Created', this);
 	}
 
-	public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = this.getUseWorkerNumber) {
+	public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: WorkerNumberGetter = this.getUseWorkerNumber) {
 		let workerNumber = useWorkerNumber(this.prevWorkerNumber, this.workers.length);
 		workerNumber = Math.abs(Math.round(workerNumber)) % this.workers.length;
 		if (_DEV_) console.log('WorkerMultiDispatch: Posting message to worker', workerNumber, useWorkerNumber);
@@ -46,12 +48,14 @@ export class WorkerMultiDispatch<POST = any, RETURN = any> {
 		return workerNumber;
 	}
 
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
 		this.workers.forEach(worker => {
 			worker.addEventListener('message', callback, options);
 		});
 	}
 
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
 		this.workers.forEach(worker => {
 			worker.removeEventListener('message', callback, options);
diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..82fa62cb322b2ae83ba300bdc113b73b686b8940
--- /dev/null
+++ b/packages/frontend-shared/package.json
@@ -0,0 +1,39 @@
+{
+	"name": "frontend-shared",
+	"type": "module",
+	"main": "./js-built/index.js",
+	"types": "./js-built/index.d.ts",
+	"exports": {
+		".": {
+			"import": "./js-built/index.js",
+			"types": "./js-built/index.d.ts"
+		},
+		"./*": {
+			"import": "./js-built/*",
+			"types": "./js-built/*"
+		}
+	},
+	"scripts": {
+		"build": "node ./build.js",
+		"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
+		"typecheck": "tsc --noEmit",
+		"lint": "pnpm typecheck && pnpm eslint"
+	},
+	"devDependencies": {
+		"@types/node": "20.14.12",
+		"@typescript-eslint/eslint-plugin": "7.17.0",
+		"@typescript-eslint/parser": "7.17.0",
+		"esbuild": "0.23.0",
+		"eslint-plugin-vue": "9.27.0",
+		"typescript": "5.5.4",
+		"vue-eslint-parser": "9.4.3"
+	},
+	"files": [
+		"js-built"
+	],
+	"dependencies": {
+		"misskey-js": "workspace:*",
+		"vue": "3.4.37"
+	}
+}
diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5
similarity index 89%
rename from packages/frontend/src/themes/_dark.json5
rename to packages/frontend-shared/themes/_dark.json5
index 7b70aa1e0941cb7e5184e7b8f7a6c142c55f14ea..e4649311c375ec58369fe9f5200fe5e7ba9bfc12 100644
--- a/packages/frontend/src/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',
@@ -54,11 +55,13 @@
 		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',
@@ -78,22 +81,14 @@
 		codeBoolean: '#c59eff',
 		deckBg: '#000',
 		htmlThemeColor: '@bg',
-		X2: ':darken<2<@panel',
 		X3: 'rgba(255, 255, 255, 0.05)',
 		X4: 'rgba(255, 255, 255, 0.1)',
 		X5: 'rgba(255, 255, 255, 0.05)',
 		X6: 'rgba(255, 255, 255, 0.15)',
 		X7: 'rgba(255, 255, 255, 0.05)',
-		X8: ':lighten<5<@accent',
-		X9: ':darken<5<@accent',
-		X10: ':alpha<0.4<@accent',
 		X11: 'rgba(0, 0, 0, 0.3)',
 		X12: 'rgba(255, 255, 255, 0.1)',
 		X13: 'rgba(255, 255, 255, 0.15)',
-		X14: ':alpha<0.5<@navBg',
-		X15: ':alpha<0<@panel',
-		X16: ':alpha<0.7<@panel',
-		X17: ':alpha<0.8<@bg',
 	},
 
 	codeHighlighter: {
diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5
similarity index 89%
rename from packages/frontend/src/themes/_light.json5
rename to packages/frontend-shared/themes/_light.json5
index d797aec734954d47773f8f782ca1b9169064e15b..b6218a5f1d6ee942a1eb4c1ae2868d47a75708dc 100644
--- a/packages/frontend/src/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',
@@ -54,11 +55,13 @@
 		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',
@@ -78,22 +81,14 @@
 		codeBoolean: '#62b70c',
 		deckBg: ':darken<3<@bg',
 		htmlThemeColor: '@bg',
-		X2: ':darken<2<@panel',
 		X3: 'rgba(0, 0, 0, 0.05)',
 		X4: 'rgba(0, 0, 0, 0.1)',
 		X5: 'rgba(0, 0, 0, 0.05)',
 		X6: 'rgba(0, 0, 0, 0.25)',
 		X7: 'rgba(0, 0, 0, 0.05)',
-		X8: ':lighten<5<@accent',
-		X9: ':darken<5<@accent',
-		X10: ':alpha<0.4<@accent',
 		X11: 'rgba(0, 0, 0, 0.1)',
 		X12: 'rgba(0, 0, 0, 0.1)',
 		X13: 'rgba(0, 0, 0, 0.15)',
-		X14: ':alpha<0.5<@navBg',
-		X15: ':alpha<0<@panel',
-		X16: ':alpha<0.7<@panel',
-		X17: ':alpha<0.8<@bg',
 	},
 
 	codeHighlighter: {
diff --git a/packages/frontend/src/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5
similarity index 87%
rename from packages/frontend/src/themes/d-astro.json5
rename to packages/frontend-shared/themes/d-astro.json5
index fee25cc4a4205d5b2c40b827650b8c1cadbaa720..a674a5c5c9ce73a8b7097e2426b315af5bd2ba8a 100644
--- a/packages/frontend/src/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',
@@ -42,7 +41,6 @@
 		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',
@@ -57,20 +55,13 @@
 		wallpaperOverlay: 'rgba(0, 0, 0, 0.5)',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
-		X2: ':darken<2<@panel',
 		X3: 'rgba(255, 255, 255, 0.05)',
 		X4: 'rgba(255, 255, 255, 0.1)',
 		X5: 'rgba(255, 255, 255, 0.05)',
 		X6: 'rgba(255, 255, 255, 0.15)',
 		X7: 'rgba(255, 255, 255, 0.05)',
-		X8: ':lighten<5<@accent',
-		X9: ':darken<5<@accent',
-		X10: ':alpha<0.4<@accent',
 		X11: 'rgba(0, 0, 0, 0.3)',
 		X12: 'rgba(255, 255, 255, 0.1)',
 		X13: 'rgba(255, 255, 255, 0.15)',
-		X14: ':alpha<0.5<@navBg',
-		X15: ':alpha<0<@panel',
-		X16: ':alpha<0.7<@panel',
 	},
 }
diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend-shared/themes/d-botanical.json5
similarity index 100%
rename from packages/frontend/src/themes/d-botanical.json5
rename to packages/frontend-shared/themes/d-botanical.json5
diff --git a/packages/frontend/src/themes/d-cherry.json5 b/packages/frontend-shared/themes/d-cherry.json5
similarity index 100%
rename from packages/frontend/src/themes/d-cherry.json5
rename to packages/frontend-shared/themes/d-cherry.json5
diff --git a/packages/frontend/src/themes/d-dark.json5 b/packages/frontend-shared/themes/d-dark.json5
similarity index 100%
rename from packages/frontend/src/themes/d-dark.json5
rename to packages/frontend-shared/themes/d-dark.json5
diff --git a/packages/frontend/src/themes/d-future.json5 b/packages/frontend-shared/themes/d-future.json5
similarity index 100%
rename from packages/frontend/src/themes/d-future.json5
rename to packages/frontend-shared/themes/d-future.json5
diff --git a/packages/frontend/src/themes/d-green-lime.json5 b/packages/frontend-shared/themes/d-green-lime.json5
similarity index 100%
rename from packages/frontend/src/themes/d-green-lime.json5
rename to packages/frontend-shared/themes/d-green-lime.json5
diff --git a/packages/frontend/src/themes/d-green-orange.json5 b/packages/frontend-shared/themes/d-green-orange.json5
similarity index 100%
rename from packages/frontend/src/themes/d-green-orange.json5
rename to packages/frontend-shared/themes/d-green-orange.json5
diff --git a/packages/frontend/src/themes/d-ice.json5 b/packages/frontend-shared/themes/d-ice.json5
similarity index 100%
rename from packages/frontend/src/themes/d-ice.json5
rename to packages/frontend-shared/themes/d-ice.json5
diff --git a/packages/frontend/src/themes/d-persimmon.json5 b/packages/frontend-shared/themes/d-persimmon.json5
similarity index 100%
rename from packages/frontend/src/themes/d-persimmon.json5
rename to packages/frontend-shared/themes/d-persimmon.json5
diff --git a/packages/frontend/src/themes/d-u0.json5 b/packages/frontend-shared/themes/d-u0.json5
similarity index 93%
rename from packages/frontend/src/themes/d-u0.json5
rename to packages/frontend-shared/themes/d-u0.json5
index 3bd0b9483c3df1aee7ed7f1004539acde9062e65..32ac9ec5cfff2c920ef0a14edb62a60436d125e9 100644
--- a/packages/frontend/src/themes/d-u0.json5
+++ b/packages/frontend-shared/themes/d-u0.json5
@@ -3,14 +3,11 @@
 	base: 'dark',
 	name: 'Mi U0 Dark',
 	props: {
-		X2: ':darken<2<@panel',
 		X3: 'rgba(255, 255, 255, 0.05)',
 		X4: 'rgba(255, 255, 255, 0.1)',
 		X5: 'rgba(255, 255, 255, 0.05)',
 		X6: 'rgba(255, 255, 255, 0.15)',
 		X7: 'rgba(255, 255, 255, 0.05)',
-		X8: ':lighten<5<@accent',
-		X9: ':darken<5<@accent',
 		bg: '#172426',
 		fg: '#dadada',
 		X10: ':alpha<0.4<@accent',
@@ -41,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',
@@ -64,7 +60,6 @@
 		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',
diff --git a/packages/frontend/src/themes/l-apricot.json5 b/packages/frontend-shared/themes/l-apricot.json5
similarity index 100%
rename from packages/frontend/src/themes/l-apricot.json5
rename to packages/frontend-shared/themes/l-apricot.json5
diff --git a/packages/frontend/src/themes/l-botanical.json5 b/packages/frontend-shared/themes/l-botanical.json5
similarity index 100%
rename from packages/frontend/src/themes/l-botanical.json5
rename to packages/frontend-shared/themes/l-botanical.json5
diff --git a/packages/frontend/src/themes/l-cherry.json5 b/packages/frontend-shared/themes/l-cherry.json5
similarity index 100%
rename from packages/frontend/src/themes/l-cherry.json5
rename to packages/frontend-shared/themes/l-cherry.json5
diff --git a/packages/frontend/src/themes/l-coffee.json5 b/packages/frontend-shared/themes/l-coffee.json5
similarity index 100%
rename from packages/frontend/src/themes/l-coffee.json5
rename to packages/frontend-shared/themes/l-coffee.json5
diff --git a/packages/frontend/src/themes/l-light.json5 b/packages/frontend-shared/themes/l-light.json5
similarity index 100%
rename from packages/frontend/src/themes/l-light.json5
rename to packages/frontend-shared/themes/l-light.json5
diff --git a/packages/frontend/src/themes/l-rainy.json5 b/packages/frontend-shared/themes/l-rainy.json5
similarity index 100%
rename from packages/frontend/src/themes/l-rainy.json5
rename to packages/frontend-shared/themes/l-rainy.json5
diff --git a/packages/frontend/src/themes/l-sushi.json5 b/packages/frontend-shared/themes/l-sushi.json5
similarity index 100%
rename from packages/frontend/src/themes/l-sushi.json5
rename to packages/frontend-shared/themes/l-sushi.json5
diff --git a/packages/frontend/src/themes/l-u0.json5 b/packages/frontend-shared/themes/l-u0.json5
similarity index 96%
rename from packages/frontend/src/themes/l-u0.json5
rename to packages/frontend-shared/themes/l-u0.json5
index dbc777d49368107c638f60e7368e1c3b00563c8c..0b952b003ae52f0e8c1ea9af06f4fadaf93cf034 100644
--- a/packages/frontend/src/themes/l-u0.json5
+++ b/packages/frontend-shared/themes/l-u0.json5
@@ -3,14 +3,11 @@
 	base: 'light',
 	name: 'Mi U0 Light',
 	props: {
-		X2: ':darken<2<@panel',
 		X3: 'rgba(255, 255, 255, 0.05)',
 		X4: 'rgba(255, 255, 255, 0.1)',
 		X5: 'rgba(255, 255, 255, 0.05)',
 		X6: 'rgba(255, 255, 255, 0.15)',
 		X7: 'rgba(255, 255, 255, 0.05)',
-		X8: ':lighten<5<@accent',
-		X9: ':darken<5<@accent',
 		bg: '#e7e7eb',
 		fg: '#5f5f5f',
 		X10: ':alpha<0.4<@accent',
diff --git a/packages/frontend/src/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5
similarity index 86%
rename from packages/frontend/src/themes/l-vivid.json5
rename to packages/frontend-shared/themes/l-vivid.json5
index 3368855b5e96896449a95c6227cc3e87862551d4..f1c63dde6eaf618408068f10d87618b5bfbac029 100644
--- a/packages/frontend/src/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',
@@ -45,7 +44,6 @@
 		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',
@@ -60,21 +58,13 @@
 		fgTransparentWeak: ':alpha<0.75<@fg',
 		panelHeaderDivider: '@divider',
 		scrollbarHandleHover: 'rgba(0, 0, 0, 0.4)',
-		X2: ':darken<2<@panel',
 		X3: 'rgba(0, 0, 0, 0.05)',
 		X4: 'rgba(0, 0, 0, 0.1)',
 		X5: 'rgba(0, 0, 0, 0.05)',
 		X6: 'rgba(0, 0, 0, 0.25)',
 		X7: 'rgba(0, 0, 0, 0.05)',
-		X8: ':lighten<5<@accent',
-		X9: ':darken<5<@accent',
-		X10: ':alpha<0.4<@accent',
 		X11: 'rgba(0, 0, 0, 0.1)',
 		X12: 'rgba(0, 0, 0, 0.1)',
 		X13: 'rgba(0, 0, 0, 0.15)',
-		X14: ':alpha<0.5<@navBg',
-		X15: ':alpha<0<@panel',
-		X16: ':alpha<0.7<@panel',
-		X17: ':alpha<0.8<@bg',
 	},
 }
diff --git a/packages/frontend/src/themes/rosepine-dawn.json5 b/packages/frontend-shared/themes/rosepine-dawn.json5
similarity index 100%
rename from packages/frontend/src/themes/rosepine-dawn.json5
rename to packages/frontend-shared/themes/rosepine-dawn.json5
diff --git a/packages/frontend/src/themes/rosepine.json5 b/packages/frontend-shared/themes/rosepine.json5
similarity index 100%
rename from packages/frontend/src/themes/rosepine.json5
rename to packages/frontend-shared/themes/rosepine.json5
diff --git a/packages/frontend-shared/tsconfig.json b/packages/frontend-shared/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..48228d4e4833a5cf0700e949506a1904cc853722
--- /dev/null
+++ b/packages/frontend-shared/tsconfig.json
@@ -0,0 +1,42 @@
+{
+	"$schema": "https://json.schemastore.org/tsconfig",
+	"compilerOptions": {
+		"target": "ES2022",
+		"module": "nodenext",
+		"moduleResolution": "nodenext",
+		"declaration": true,
+		"declarationMap": true,
+		"sourceMap": false,
+		"outDir": "./js-built/",
+		"removeComments": true,
+		"resolveJsonModule": true,
+		"strict": true,
+		"strictFunctionTypes": true,
+		"strictNullChecks": true,
+		"experimentalDecorators": true,
+		"noImplicitReturns": true,
+		"esModuleInterop": true,
+		"skipLibCheck": true,
+		"baseUrl": ".",
+		"paths": {
+			"@/*": ["./*"],
+			"@@/*": ["./*"]
+		},
+		"typeRoots": [
+			"./@types",
+			"./node_modules/@types"
+		],
+		"lib": [
+			"esnext",
+			"dom"
+		]
+	},
+	"include": [
+		"@types/**/*.ts",
+		"js/**/*"
+	],
+	"exclude": [
+		"node_modules",
+		"test/**/*"
+	]
+}
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index 490a441b70553924ede8753a8f3e7873de5c1ff7..42d1a10f0ad093c106a699d4348e87d5ebff7c7c 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -405,8 +405,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/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts
index e174c72b4856ebf030fc76e4daee4b1e57b27237..1b6a605a6e2765ff1870c4fb15ac222e7ca56d54 100644
--- a/packages/frontend/.storybook/preload-theme.ts
+++ b/packages/frontend/.storybook/preload-theme.ts
@@ -32,7 +32,7 @@ const keys = [
 	'rosepine-dawn',
 ]
 
-await Promise.all(keys.map((key) => readFile(new URL(`../src/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => {
+await Promise.all(keys.map((key) => readFile(new URL(`../../frontend-shared/themes/${key}.json5`, import.meta.url), 'utf8'))).then((sources) => {
 	writeFile(
 		new URL('./themes.ts', import.meta.url),
 		`export default ${JSON.stringify(
diff --git a/packages/frontend/@types/global.d.ts b/packages/frontend/@types/global.d.ts
index 1025d1bedbb8a6f69fef263191c43bbd677548be..15373cbd2dd35eeb7f41f066f1bd25e165f16e35 100644
--- a/packages/frontend/@types/global.d.ts
+++ b/packages/frontend/@types/global.d.ts
@@ -6,6 +6,7 @@
 type FIXME = any;
 
 declare const _LANGS_: string[][];
+declare const _LANGS_VERSION_: string;
 declare const _VERSION_: string;
 declare const _ENV_: string;
 declare const _DEV_: boolean;
diff --git a/packages/frontend/@types/theme.d.ts b/packages/frontend/@types/theme.d.ts
index 0a7281898d9896a1fc82f38c2d61bcb23102c0f2..70afc356c19d9d281a640f686f1974dc1b541805 100644
--- a/packages/frontend/@types/theme.d.ts
+++ b/packages/frontend/@types/theme.d.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-declare module '@/themes/*.json5' {
+declare module '@@/themes/*.json5' {
 	import { Theme } from '@/scripts/theme.js';
 
 	const theme: Theme;
diff --git a/packages/frontend/assets/sharkey.svg b/packages/frontend/assets/sharkey.svg
index 82f8c8cfa82e27105e89ffbe9b5c32a93a6edd5f..ae49863e5b3946517316ac2650eb71b83a30aad9 100644
Binary files a/packages/frontend/assets/sharkey.svg and b/packages/frontend/assets/sharkey.svg differ
diff --git a/packages/frontend/assets/status/error.png b/packages/frontend/assets/status/error.png
new file mode 100644
index 0000000000000000000000000000000000000000..9f21236c39379d34cad8cb6a195ab793fcd9f65f
Binary files /dev/null and b/packages/frontend/assets/status/error.png differ
diff --git a/packages/frontend/assets/status/missingpage.webp b/packages/frontend/assets/status/missingpage.webp
new file mode 100644
index 0000000000000000000000000000000000000000..3ac83b31101ee6d7bf21a456a28e80d809f5e419
Binary files /dev/null and b/packages/frontend/assets/status/missingpage.webp differ
diff --git a/packages/frontend/assets/status/nothinghere.png b/packages/frontend/assets/status/nothinghere.png
new file mode 100644
index 0000000000000000000000000000000000000000..5ebe210acdaa5008678a64f3595af606755f5e57
Binary files /dev/null and b/packages/frontend/assets/status/nothinghere.png differ
diff --git a/packages/frontend/eslint.config.js b/packages/frontend/eslint.config.js
index dd8f03dac5102ecb648e0beb6516a40c5421a621..565b3d6ae7b69d00c969586d359457d875cb7b19 100644
--- a/packages/frontend/eslint.config.js
+++ b/packages/frontend/eslint.config.js
@@ -4,16 +4,19 @@ import parser from 'vue-eslint-parser';
 import pluginVue from 'eslint-plugin-vue';
 import pluginMisskey from '@misskey-dev/eslint-plugin';
 import sharedConfig from '../shared/eslint.config.js';
+import localeRule from '../../eslint/locale.js';
+import { build as buildLocales } from '../../locales/index.js';
 
 export default [
 	...sharedConfig,
 	{
-		files: ['src/**/*.vue'],
+		files: ['{src,test,js,@types}/**/*.vue'],
 		...pluginMisskey.configs.typescript,
 	},
 	...pluginVue.configs['flat/recommended'],
 	{
-		files: ['src/**/*.{ts,vue}'],
+		files: ['{src,test,js,@types}/**/*.{ts,vue}'],
+		plugins: { sharkey: { rules: { locale: localeRule } } },
 		languageOptions: {
 			globals: {
 				...Object.fromEntries(Object.entries(globals.node).map(([key]) => [key, 'off'])),
@@ -44,6 +47,8 @@ export default [
 			},
 		},
 		rules: {
+			'sharkey/locale': ['error', buildLocales()['en-US']],
+
 			'@typescript-eslint/no-empty-interface': ['error', {
 				allowSingleExtends: true,
 			}],
@@ -92,4 +97,15 @@ export default [
 			'vue/attribute-hyphenation': ['error', 'never'],
 		},
 	},
+	{
+		ignores: [
+			"**/lib/",
+			"**/temp/",
+			"**/built/",
+			"**/coverage/",
+			"**/node_modules/",
+			"**/libopenmpt/",
+			"**/storybook-static/"
+		]
+	},
 ];
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 5da671b9b61986d6b0177085f5dcd1121543edf1..1b4b5d5bd1c2c66df41f037aa4018ace7544d6fa 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -13,130 +13,130 @@
 		"test": "vitest --run --globals",
 		"test-and-coverage": "vitest --run --coverage --globals",
 		"typecheck": "vue-tsc --noEmit",
-		"eslint": "eslint --quiet \"src/**/*.{ts,vue}\" --cache",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
 		"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",
 		"@phosphor-icons/web": "^2.0.3",
 		"@rollup/plugin-json": "6.1.0",
 		"@rollup/plugin-replace": "5.0.7",
-		"@rollup/pluginutils": "5.1.0",
+		"@rollup/pluginutils": "5.1.2",
 		"@transfem-org/sfm-js": "0.24.5",
 		"@syuilo/aiscript": "0.19.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.10",
 		"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.10.4",
 		"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",
 		"idb-keyval": "6.2.1",
 		"insert-text-at-cursor": "0.3.0",
 		"is-file-animated": "1.0.2",
 		"json5": "2.2.3",
-		"katex": "0.16.9",
+		"katex": "0.16.10",
 		"matter-js": "0.19.0",
 		"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",
+		"rollup": "4.22.5",
 		"sanitize-html": "2.13.0",
-		"sass": "1.77.8",
+		"sass": "1.79.3",
 		"shiki": "1.12.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.10",
 		"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.3",
+		"@storybook/addon-essentials": "8.3.3",
+		"@storybook/addon-interactions": "8.3.3",
+		"@storybook/addon-links": "8.3.3",
+		"@storybook/addon-mdx-gfm": "8.3.3",
+		"@storybook/addon-storysource": "8.3.3",
+		"@storybook/blocks": "8.3.3",
+		"@storybook/components": "8.3.3",
+		"@storybook/core-events": "8.3.3",
+		"@storybook/manager-api": "8.3.3",
+		"@storybook/preview-api": "8.3.3",
+		"@storybook/react": "8.3.3",
+		"@storybook/react-vite": "8.3.3",
+		"@storybook/test": "8.3.3",
+		"@storybook/theming": "8.3.3",
+		"@storybook/types": "8.3.3",
+		"@storybook/vue3": "8.3.3",
+		"@storybook/vue3-vite": "8.3.3",
 		"@testing-library/vue": "8.1.0",
-		"@types/escape-regexp": "0.0.3",
-		"@types/estree": "1.0.5",
+		"@types/estree": "1.0.6",
+		"@types/katex": "^0.16.7",
 		"@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.10",
 		"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.30.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.3",
 		"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/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts
index 09495dece4c45eddc926b6da3856a03766183324..1601f247d759084bc061ac0b6b8f6c906e04bf13 100644
--- a/packages/frontend/src/_dev_boot_.ts
+++ b/packages/frontend/src/_dev_boot_.ts
@@ -3,11 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-// devモードで起動される際(index.htmlを使うとき)はrouterが暴発してしまってうまく読み込めない。
-// よって、devモードとして起動されるときはビルド時に組み込む形としておく。
-// (pnpm start時はpugファイルの中で静的リソースとして読み込むようになっており、この問題は起こっていない)
-import '@phosphor-icons/web/bold';
-
 await main();
 
 import('@/_boot_.js');
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 4fdd51c33bbd35eaaea7299baf0a455014ebc40a..e3416f2c296f2042f9fea67d429cc79a409fbfe6 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -8,9 +8,9 @@ import * as Misskey from 'misskey-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 type { MenuItem, MenuButton } from '@/types/menu.js';
 import { del, get, set } from '@/scripts/idb-proxy.js';
-import { apiUrl } from '@/config.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';
@@ -289,14 +289,26 @@ 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: [{
@@ -307,7 +319,7 @@ export async function openAccountMenu(opts: {
 				action: () => { createAccount(); },
 			}],
 		}, {
-			type: 'link' as const,
+			type: 'link',
 			icon: 'ti ti-users',
 			text: i18n.ts.manageAccounts,
 			to: '/settings/accounts',
@@ -316,14 +328,18 @@ export async function openAccountMenu(opts: {
 			icon: 'ph-power ph-bold ph-lg',
 			text: i18n.ts.logout,
 			action: () => { signout(); },
-		}]], 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',
+	});
 }
 
 if (_DEV_) {
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 94040c64132f69d676b7413198a35375f9a89ebf..af8bbf57d28761e08bc7336b70190ecd24451977 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -5,10 +5,10 @@
 
 import { computed, watch, version as vueVersion, App } from 'vue';
 import { compareVersions } from 'compare-versions';
+import { version, lang, langsVersion, 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 '@/config.js';
 import { applyTheme } from '@/scripts/theme.js';
 import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js';
 import { updateI18n } from '@/i18n.js';
@@ -22,7 +22,8 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js';
 import { deckStore } from '@/ui/deck/deck-store.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { fetchCustomEmojis } from '@/custom-emojis.js';
-import { setupRouter } from '@/router/definition.js';
+import { setupRouter } from '@/router/main.js';
+import { createMainRouter } from '@/router/definition.js';
 
 export async function common(createVue: () => App<Element>) {
 	console.info(`Sharkey v${version}`);
@@ -80,14 +81,15 @@ export async function common(createVue: () => App<Element>) {
 
 	//#region Detect language & fetch translations
 	const localeVersion = miLocalStorage.getItem('localeVersion');
-	const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null);
+	const localeOutdated = (localeVersion == null || localeVersion !== langsVersion || locale == null);
 	if (localeOutdated) {
-		const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
+		console.info(`Updating locales from version ${localeVersion ?? 'N/A'} to ${langsVersion}`);
+		const res = await window.fetch(`/assets/locales/${lang}.${langsVersion}.json`);
 		if (res.status === 200) {
 			const newLocale = await res.text();
 			const parsedNewLocale = JSON.parse(newLocale);
 			miLocalStorage.setItem('locale', newLocale);
-			miLocalStorage.setItem('localeVersion', version);
+			miLocalStorage.setItem('localeVersion', langsVersion);
 			updateLocale(parsedNewLocale);
 			updateI18n(parsedNewLocale);
 		}
@@ -145,10 +147,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'));
@@ -243,7 +244,7 @@ export async function common(createVue: () => App<Element>) {
 
 	const app = createVue();
 
-	setupRouter(app);
+	setupRouter(app, createMainRouter);
 
 	if (_DEV_) {
 		app.config.performance = true;
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index c10930a03820bc7a20e52a1e28bbdfefc224ae10..395d7d9ad1cbf2262c57d948c3202eb03b5f8636 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -6,7 +6,7 @@
 import { createApp, defineAsyncComponent, markRaw } from 'vue';
 import { common } from './common.js';
 import type * as Misskey from 'misskey-js';
-import { ui } from '@/config.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';
@@ -23,6 +23,7 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
 import { mainRouter } from '@/router/main.js';
 import { setFavIconDot } from '@/scripts/favicon-dot.js';
 import { type Keymap, makeHotkey } from '@/scripts/hotkey.js';
+import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
 
 export async function mainBoot() {
 	const { isClientUpdated } = await common(() => createApp(
@@ -61,6 +62,18 @@ export async function mainBoot() {
 		}
 	});
 
+	stream.on('emojiAdded', emojiData => {
+		addCustomEmoji(emojiData.emoji);
+	});
+
+	stream.on('emojiUpdated', emojiData => {
+		updateCustomEmojis(emojiData.emojis);
+	});
+
+	stream.on('emojiDeleted', emojiData => {
+		removeCustomEmojis(emojiData.emojis);
+	});
+
 	for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) {
 		import('@/plugin.js').then(async ({ install }) => {
 			// Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740
@@ -216,19 +229,25 @@ export async function mainBoot() {
 			claimAchievement('collectAchievements30');
 		}
 
-		window.setInterval(() => {
-			if (Math.floor(Math.random() * 20000) === 0) {
-				claimAchievement('justPlainLucky');
-			}
-		}, 1000 * 10);
+		if (!claimedAchievements.includes('justPlainLucky')) {
+			window.setInterval(() => {
+				if (Math.floor(Math.random() * 20000) === 0) {
+					claimAchievement('justPlainLucky');
+				}
+			}, 1000 * 10);
+		}
 
-		window.setTimeout(() => {
-			claimAchievement('client30min');
-		}, 1000 * 60 * 30);
+		if (!claimedAchievements.includes('client30min')) {
+			window.setTimeout(() => {
+				claimAchievement('client30min');
+			}, 1000 * 60 * 30);
+		}
 
-		window.setTimeout(() => {
-			claimAchievement('client60min');
-		}, 1000 * 60 * 60);
+		if (!claimedAchievements.includes('client60min')) {
+			window.setTimeout(() => {
+				claimAchievement('client60min');
+			}, 1000 * 60 * 60);
+		}
 
 		// 邪魔
 		//const lastUsed = miLocalStorage.getItem('lastUsed');
diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index 7c8c7dbd304b877c7fd0c822dbcf00e934baac24..d13eedaade4380ea7ad90b51a6de216b73c505a4 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -4,40 +4,49 @@ 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 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>
+			<div class="keyvalCtn">
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.registeredDate }}</template>
+					<template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template>
+				</MkKeyValue>
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.reporter }}</template>
+					<template #value><MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></template>
+				</MkKeyValue>
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.createdAt }}</template>
+					<template #value><MkTime :time="report.createdAt" mode="absolute"/> (<MkTime :time="report.createdAt" mode="relative"/>)</template>
+				</MkKeyValue>
 			</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" :isBlock="true" :linkNavigationBehavior="'window'"/>
-		</div>
-		<hr/>
-		<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></div>
-		<div v-if="report.assignee">
-			{{ i18n.ts.moderator }}:
-			<MkAcct :user="report.assignee"/>
+			<hr>
 		</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 class="detail">
+			<div>
+				<Mfm :text="report.comment" :isBlock="true" :linkNavigationBehavior="'window'"/>
+			</div>
+			<hr/>
+			<div v-if="report.assignee" class="assignee">
+				{{ i18n.ts.moderator }}:
+				<MkA :to="`/admin/user/${report.assignee.id}`" class="_link" :behavior="'window'">@{{ report.assignee.username }}</MkA>
+			</div>
+			<div class="action">
+				<MkSwitch v-model="forward" c: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>
-</div>
 </template>
 
 <script lang="ts" setup>
@@ -72,13 +81,13 @@ function resolve() {
 <style lang="scss" scoped>
 .bcekxzvu {
 	display: flex;
+	flex-direction: column;
+	transition: .1s;
 
 	> .target {
-		width: 35%;
 		box-sizing: border-box;
 		text-align: left;
-		padding: 24px;
-		border-right: solid 1px var(--divider);
+		padding: 24px 24px 0px 24px;
 
 		> .info {
 			display: flex;
@@ -100,16 +109,36 @@ function resolve() {
 				padding: 0 8px;
 				flex: 1;
 
+				white-space: pre;
+				overflow: hidden;
+
 				> .name {
 					font-weight: bold;
 				}
 			}
 		}
+
+		> .keyvalCtn {
+			display: inline-flex;
+			gap: 15px;
+			margin-top: 15px;
+		}
 	}
 
 	> .detail {
-		flex: 1;
-		padding: 24px;
+		display: flex;
+		flex-direction: column;
+		padding: 0px 24px 24px 24px;
+
+		.assignee {
+			margin-bottom: 15px;
+		}
+
+		.action {
+			display: flex;
+			flex-direction: column;
+			gap: 15px;
+		}
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index 6c0774b63426239e3c75edec379226a2fbc32651..796524fce949cff704971fd24066fe120df05ae2 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -16,7 +16,7 @@ import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkMention from './MkMention.vue';
 import { i18n } from '@/i18n.js';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const user = ref<Misskey.entities.UserLite>();
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 7e150f7dd59de038f9224632b1082b19b2ec1d39..e2af4f034e439d83502a81213053f9fb29a596ca 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(--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) {
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index b04a1c92e7cf55a455ebd1207dfcc043e4603090..de5207f3504e5575099e94fa272d770dec84ab71 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -46,17 +46,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
 import sanitizeHtml from 'sanitize-html';
+import { emojilist, getEmojiName } from '@@/js/emojilist.js';
 import contains from '@/scripts/contains.js';
-import { char2twemojiFilePath, char2fluentEmojiFilePath, char2tossfaceFilePath } from '@/scripts/emoji-base.js';
+import { char2twemojiFilePath, char2fluentEmojiFilePath, char2tossfaceFilePath } from '@@/js/emoji-base.js';
 import { acct } from '@/filters/user.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
-import { emojilist, getEmojiName } from '@/scripts/emojilist.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { customEmojis } from '@/custom-emojis.js';
-import { MFM_TAGS, MFM_PARAMS } from '@/const.js';
+import { MFM_TAGS, MFM_PARAMS } from '@@/js/const.js';
 import { searchEmoji, EmojiDef } from '@/scripts/search-emoji.js';
 
 const lib = emojilist.filter(x => x.category !== 'flags');
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index f968fc586109aaf2228a52e22a2e459b6231299a..e30f74460db3fc978aa05eac651d580ec9bbdd35 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -171,11 +171,11 @@ function onMousedown(evt: MouseEvent): void {
 		background: var(--accent);
 
 		&:not(:disabled):hover {
-			background: var(--X8);
+			background: hsl(from var(--accent) h s calc(l + 5));
 		}
 
 		&:not(:disabled):active {
-			background: var(--X8);
+			background: hsl(from var(--accent) h s calc(l + 5));
 		}
 	}
 
@@ -220,15 +220,16 @@ function onMousedown(evt: MouseEvent): void {
 		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
 
 		&:not(:disabled):hover {
-			background: linear-gradient(90deg, var(--X8), var(--X8));
+			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		}
 
 		&:not(:disabled):active {
-			background: linear-gradient(90deg, var(--X8), var(--X8));
+			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--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 c5b6e0caede7f712c6408f54c35e9357377524b7..ab00ea9930ac4a737e98fccc1dbe9833695624ac 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -27,9 +27,12 @@ export type Captcha = {
 	execute(id: string): void;
 	reset(id?: string): void;
 	getResponse(id: string): string;
+	WidgetInstance(container: string | Node, options: {
+		readonly [_ in 'sitekey' | 'doneCallback' | 'errorCallback' | 'puzzleEndpoint']?: unknown;
+	}): void;
 };
 
-export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
+export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha' | 'fc';
 
 type CaptchaContainer = {
 	readonly [_ in CaptchaProvider]?: Captcha;
@@ -60,6 +63,7 @@ const variable = computed(() => {
 		case 'recaptcha': return 'grecaptcha';
 		case 'turnstile': return 'turnstile';
 		case 'mcaptcha': return 'mcaptcha';
+		case 'fc': return 'friendlyChallenge';
 	}
 });
 
@@ -70,6 +74,7 @@ const src = computed(() => {
 		case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off';
 		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 'fc': return 'https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.18/widget.min.js';
 		case 'mcaptcha': return null;
 	}
 });
@@ -110,6 +115,14 @@ async function requestRender() {
 				key: props.sitekey,
 			},
 		});
+	} else if (variable.value === 'friendlyChallenge' && captchaEl.value instanceof Element) {
+		new captcha.value.WidgetInstance(captchaEl.value, {
+			sitekey: props.sitekey,
+			doneCallback: callback,
+			errorCallback: callback,
+		});
+		// The following line is needed so that the design gets applied without it the captcha will look broken
+		captchaEl.value.className = 'frc-captcha';
 	} else {
 		window.setTimeout(requestRender, 1);
 	}
diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue
index a50416befce239dfc9a413aa84b55420e0d855e4..2f7ec34d44ebbc3f827e854241b70c58c4e37d2a 100644
--- a/packages/frontend/src/components/MkChannelPreview.vue
+++ b/packages/frontend/src/components/MkChannelPreview.vue
@@ -117,7 +117,7 @@ const bannerStyle = computed(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), var(--X15));
+			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 		}
 
 		> .name {
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index 4b2456224991c22f3ce57c912c56c9e65570f82b..57d325b11ad38e16f7213c45f5a81264f4f74083 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -13,29 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </div>
 </template>
 
-<script lang="ts" setup>
-/* eslint-disable id-denylist --
-  Chart.js has a `data` attribute in most chart definitions, which triggers the
-  id-denylist violation when setting it. This is causing about 60+ lint issues.
-  As this is part of Chart.js's API it makes sense to disable the check here.
-*/
-import { onMounted, ref, shallowRef, watch } from 'vue';
-import { Chart } from 'chart.js';
-import * as Misskey from 'misskey-js';
-import { misskeyApiGet } from '@/scripts/misskey-api.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { chartVLine } from '@/scripts/chart-vline.js';
-import { alpha } from '@/scripts/color.js';
-import date from '@/filters/date.js';
-import bytes from '@/filters/bytes.js';
-import { initChart } from '@/scripts/init-chart.js';
-import { chartLegend } from '@/scripts/chart-legend.js';
-import MkChartLegend from '@/components/MkChartLegend.vue';
-
-initChart();
-
-type ChartSrc =
+<script lang="ts">
+export type ChartSrc =
 	| 'federation'
 	| 'ap-request'
 	| 'users'
@@ -62,7 +41,30 @@ type ChartSrc =
 	| 'per-user-pv'
 	| 'per-user-following'
 	| 'per-user-followers'
-	| 'per-user-drive'
+	| 'per-user-drive';
+</script>
+
+<script lang="ts" setup>
+/* eslint-disable id-denylist --
+  Chart.js has a `data` attribute in most chart definitions, which triggers the
+  id-denylist violation when setting it. This is causing about 60+ lint issues.
+  As this is part of Chart.js's API it makes sense to disable the check here.
+*/
+import { onMounted, ref, shallowRef, watch } from 'vue';
+import { Chart } from 'chart.js';
+import * as Misskey from 'misskey-js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
+import { defaultStore } from '@/store.js';
+import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
+import { chartVLine } from '@/scripts/chart-vline.js';
+import { alpha } from '@/scripts/color.js';
+import date from '@/filters/date.js';
+import bytes from '@/filters/bytes.js';
+import { initChart } from '@/scripts/init-chart.js';
+import { chartLegend } from '@/scripts/chart-legend.js';
+import MkChartLegend from '@/components/MkChartLegend.vue';
+
+initChart();
 
 const props = withDefaults(defineProps<{
 	src: ChartSrc;
diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index 00506fb73513d6e3dba787706f6d409e94da9ad4..9a0a9fba0515297755b5d60f4687a825f9c11290 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, onMounted, onUnmounted, ref } from 'vue';
 import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
 import * as os from '@/os.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import * as game from '@/scripts/clicker-game.js';
 import number from '@/filters/number.js';
 import { claimAchievement } from '@/scripts/achievements.js';
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index 2475e3dc89c75a72c9b69a3af10a10b0fc490068..05cde89dd973a7cd689181ebd35d140707cbca26 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div :class="$style.codeBlockRoot">
-	<button :class="$style.codeBlockCopyButton" class="_button" @click="copy">
+	<button v-if="copyButton" :class="$style.codeBlockCopyButton" class="_button" @click="copy">
 		<i class="ti ti-copy"></i>
 	</button>
 	<Suspense>
@@ -32,12 +32,17 @@ import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
 	code: string;
+	forceShow?: boolean;
+	copyButton?: boolean;
 	lang?: string;
-}>();
+}>(), {
+	copyButton: true,
+	forceShow: false,
+});
 
-const show = ref(!defaultStore.state.dataSaver.code);
+const show = ref(props.forceShow === true ? true : !defaultStore.state.dataSaver.code);
 
 const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
 
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index d1fe5dbdcf267e9ee8f83432f3e873aa37669a9c..5f71e289b80bc5a246c5a0c4ae2e8a355faf16a8 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -216,7 +216,7 @@ onUnmounted(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			background: linear-gradient(0deg, var(--panel), var(--X15));
+			background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 8ea8fa6cf3a3a97af6be81c68104bdf4d200aafe..f51fefa0c0776b1fdd5a2d7349babbbee68e01a4 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 54f6f39c9dc50679b16cd9f1a6273483db656bd7..2e1e92cbdfee4b3c2f04e0abb620d97dea1f883b 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -39,7 +39,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
 import * as os from '@/os.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
 
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index 9976cd00c9f498f2062b72e2bf0768c1521c33bb..98bf5191f724ba749c5c95983177b895d54f1c50 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -43,9 +43,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 +62,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 +85,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']}`,
 						}),
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 825c1d0513662e7bf972014752d6ecbca3fd26d8..7dc381b662a246ba583cffa110c1c2b69ef1ccc6 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
 		</div>
 		<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
-		<div v-if="text" :class="$style.text"><Mfm :text="text" :isBlock="true" /></div>
+		<div v-if="text" :class="$style.text"><Mfm :text="text" :isBlock="true" :plain="plain" /></div>
 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
 			<template #caption>
@@ -105,6 +105,7 @@ const props = withDefaults(defineProps<{
 	cancelableByBgClick?: boolean;
 	okText?: string;
 	cancelText?: string;
+	plain?: boolean;
 }>(), {
 	type: 'info',
 	showOkButton: true,
diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue
index 930f0f54cc29f94dc84ba06b3262b944e2e43327..1dfdebf0d4f3bc77972f882c705c6a3d99d1c19a 100644
--- a/packages/frontend/src/components/MkDonation.vue
+++ b/packages/frontend/src/components/MkDonation.vue
@@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import MkButton from '@/components/MkButton.vue';
 import MkLink from '@/components/MkLink.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { miLocalStorage } from '@/local-storage.js';
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index 3990d0b861691fe5f7b01e47d27095e05523777c..44788a6ffb9ab7bf4a12f37f6dab1a9f3dcb2968 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -42,7 +42,7 @@ 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';
+import type { MenuItem } from '@/types/menu.js';
 
 const props = withDefaults(defineProps<{
 	folder: Misskey.entities.DriveFolder;
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index 2d1c7c95f0d16cce887b1ed17daef997ba0dfced..a471457b449ad902a012f27543724403f1be7eec 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -634,7 +634,9 @@ function fetchMoreFiles() {
 }
 
 function getMenu() {
-	const menu: MenuItem[] = [{
+	const menu: MenuItem[] = [];
+
+	menu.push({
 		type: 'switch',
 		text: i18n.ts.keepOriginalUploading,
 		ref: keepOriginal,
@@ -652,19 +654,25 @@ 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, {
+	});
+
+	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;
 }
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue
index 9a6d272113c258d060e46e6e045e41ca11677999..543cf24022c5edfb5cc30f5cd08b6eb5acfc24b4 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(() => {
@@ -67,6 +74,18 @@ const isThumbnailAvailable = computed(() => {
 	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(--warn);
+}
+
 .iconSub {
 	position: absolute;
 	width: 30%;
diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c060c3a659735e23f0008308d91e3ce1e8d0b440
--- /dev/null
+++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
@@ -0,0 +1,414 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkModalWindow
+	ref="dialogEl"
+	:width="1000"
+	:height="600"
+	:scroll="false"
+	:withOkButton="false"
+	@close="cancel()"
+	@closed="$emit('closed')"
+>
+	<template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template>
+
+	<div :class="$style.embedCodeGenRoot">
+		<Transition
+			mode="out-in"
+			:enterActiveClass="$style.transition_x_enterActive"
+			:leaveActiveClass="$style.transition_x_leaveActive"
+			:enterFromClass="$style.transition_x_enterFrom"
+			:leaveToClass="$style.transition_x_leaveTo"
+		>
+			<div v-if="phase === 'input'" key="input" :class="$style.embedCodeGenInputRoot">
+				<div
+					:class="$style.embedCodeGenPreviewRoot"
+				>
+					<MkLoading v-if="iframeLoading" :class="$style.embedCodeGenPreviewSpinner"/>
+					<div :class="$style.embedCodeGenPreviewWrapper">
+						<div class="_acrylic" :class="$style.embedCodeGenPreviewTitle">{{ i18n.ts.preview }}</div>
+						<div ref="resizerRootEl" :class="$style.embedCodeGenPreviewResizerRoot" inert>
+							<div
+								:class="$style.embedCodeGenPreviewResizer"
+								:style="{ transform: iframeStyle }"
+							>
+								<iframe
+									ref="iframeEl"
+									:src="embedPreviewUrl"
+									:class="$style.embedCodeGenPreviewIframe"
+									:style="{ height: `${iframeHeight}px` }"
+									@load="iframeOnLoad"
+								></iframe>
+							</div>
+						</div>
+					</div>
+				</div>
+				<div :class="$style.embedCodeGenSettings" class="_gaps">
+					<MkInput v-if="isEmbedWithScrollbar" v-model="maxHeight" type="number" :min="0">
+						<template #label>{{ i18n.ts._embedCodeGen.maxHeight }}</template>
+						<template #suffix>px</template>
+						<template #caption>{{ i18n.ts._embedCodeGen.maxHeightDescription }}</template>
+					</MkInput>
+					<MkSelect v-model="colorMode">
+						<template #label>{{ i18n.ts.theme }}</template>
+						<option value="auto">{{ i18n.ts.syncDeviceDarkMode }}</option>
+						<option value="light">{{ i18n.ts.light }}</option>
+						<option value="dark">{{ i18n.ts.dark }}</option>
+					</MkSelect>
+					<MkSwitch v-if="isEmbedWithScrollbar" v-model="header">{{ i18n.ts._embedCodeGen.header }}</MkSwitch>
+					<MkSwitch v-model="rounded">{{ i18n.ts._embedCodeGen.rounded }}</MkSwitch>
+					<MkSwitch v-model="border">{{ i18n.ts._embedCodeGen.border }}</MkSwitch>
+					<MkInfo v-if="isEmbedWithScrollbar && (!maxHeight || maxHeight <= 0)" warn>{{ i18n.ts._embedCodeGen.maxHeightWarn }}</MkInfo>
+					<MkInfo v-if="typeof maxHeight === 'number' && (maxHeight <= 0 || maxHeight > 700)">{{ i18n.ts._embedCodeGen.previewIsNotActual }}</MkInfo>
+					<div class="_buttons">
+						<MkButton :disabled="iframeLoading" @click="applyToPreview">{{ i18n.ts._embedCodeGen.applyToPreview }}</MkButton>
+						<MkButton :disabled="iframeLoading" primary @click="generate">{{ i18n.ts._embedCodeGen.generateCode }} <i class="ti ti-arrow-right"></i></MkButton>
+					</div>
+				</div>
+			</div>
+			<div v-else-if="phase === 'result'" key="result" :class="$style.embedCodeGenResultRoot">
+				<div :class="$style.embedCodeGenResultWrapper" class="_gaps">
+					<div class="_gaps_s">
+						<div :class="$style.embedCodeGenResultHeadingIcon"><i class="ti ti-check"></i></div>
+						<div :class="$style.embedCodeGenResultHeading">{{ i18n.ts._embedCodeGen.codeGenerated }}</div>
+						<div :class="$style.embedCodeGenResultDescription">{{ i18n.ts._embedCodeGen.codeGeneratedDescription }}</div>
+					</div>
+					<div class="_gaps_s">
+						<MkCode :code="result" lang="html" :forceShow="true" :copyButton="false" :class="$style.embedCodeGenResultCode"/>
+						<MkButton :class="$style.embedCodeGenResultButtons" rounded primary @click="doCopy"><i class="ti ti-copy"></i> {{ i18n.ts.copy }}</MkButton>
+					</div>
+					<MkButton :class="$style.embedCodeGenResultButtons" rounded transparent @click="close">{{ i18n.ts.close }}</MkButton>
+				</div>
+			</div>
+		</Transition>
+	</div>
+</MkModalWindow>
+</template>
+
+<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';
+
+import MkInput from '@/components/MkInput.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkButton from '@/components/MkButton.vue';
+
+import MkCode from '@/components/MkCode.vue';
+import MkInfo from '@/components/MkInfo.vue';
+
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { normalizeEmbedParams, getEmbedCode } from '@/scripts/get-embed-code.js';
+
+const emit = defineEmits<{
+	(ev: 'ok'): void;
+	(ev: 'cancel'): void;
+	(ev: 'closed'): void;
+}>();
+
+const props = defineProps<{
+	entity: EmbeddableEntity;
+	id: string;
+	params?: EmbedParams;
+}>();
+
+//#region Modalの制御
+const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+
+function cancel() {
+	emit('cancel');
+	dialogEl.value?.close();
+}
+
+function close() {
+	dialogEl.value?.close();
+}
+
+const phase = ref<'input' | 'result'>('input');
+//#endregion
+
+//#region 埋め込みURL生成・カスタマイズ
+
+// 本URL生成用params
+const paramsForUrl = computed<EmbedParams>(() => ({
+	header: header.value,
+	maxHeight: typeof maxHeight.value === 'number' ? Math.max(0, maxHeight.value) : undefined,
+	colorMode: colorMode.value === 'auto' ? undefined : colorMode.value,
+	rounded: rounded.value,
+	border: border.value,
+}));
+
+// プレビュー用params(手動で更新を掛けるのでref)
+const paramsForPreview = ref<EmbedParams>(props.params ?? {});
+
+const embedPreviewUrl = computed(() => {
+	const paramClass = new URLSearchParams(normalizeEmbedParams(paramsForPreview.value));
+	if (paramClass.has('maxHeight')) {
+		const maxHeight = parseInt(paramClass.get('maxHeight')!);
+		paramClass.set('maxHeight', maxHeight === 0 ? '500' : Math.min(maxHeight, 700).toString()); // プレビューであまりにも縮小されると見づらいため、700pxまでに制限
+	}
+	return `${url}/embed/${props.entity}/${props.id}${paramClass.toString() ? '?' + paramClass.toString() : ''}`;
+});
+
+const isEmbedWithScrollbar = computed(() => embedRouteWithScrollbar.includes(props.entity));
+const header = ref(props.params?.header ?? true);
+const maxHeight = ref(props.params?.maxHeight !== 0 ? props.params?.maxHeight ?? undefined : 500);
+
+const colorMode = ref<'light' | 'dark' | 'auto'>(props.params?.colorMode ?? 'auto');
+const rounded = ref(props.params?.rounded ?? true);
+const border = ref(props.params?.border ?? true);
+
+function applyToPreview() {
+	const currentPreviewUrl = embedPreviewUrl.value;
+
+	paramsForPreview.value = {
+		header: header.value,
+		maxHeight: typeof maxHeight.value === 'number' ? Math.max(0, maxHeight.value) : undefined,
+		colorMode: colorMode.value === 'auto' ? undefined : colorMode.value,
+		rounded: rounded.value,
+		border: border.value,
+	};
+
+	nextTick(() => {
+		if (currentPreviewUrl === embedPreviewUrl.value) {
+			// URLが変わらなくてもリロード
+			iframeEl.value?.contentWindow?.location.reload();
+		}
+	});
+}
+
+const result = ref('');
+
+function generate() {
+	result.value = getEmbedCode(`/embed/${props.entity}/${props.id}`, paramsForUrl.value);
+	phase.value = 'result';
+}
+
+function doCopy() {
+	copyToClipboard(result.value);
+	os.success();
+}
+//#endregion
+
+//#region プレビューのリサイズ
+const resizerRootEl = shallowRef<HTMLDivElement>();
+const iframeLoading = ref(true);
+const iframeEl = shallowRef<HTMLIFrameElement>();
+const iframeHeight = ref(0);
+const iframeScale = ref(1);
+const iframeStyle = computed(() => {
+	return `translate(-50%, -50%) scale(${iframeScale.value})`;
+});
+const resizeObserver = new ResizeObserver(() => {
+	calcScale();
+});
+
+function iframeOnLoad() {
+	iframeEl.value?.contentWindow?.addEventListener('beforeunload', () => {
+		iframeLoading.value = true;
+		nextTick(() => {
+			iframeHeight.value = 0;
+			iframeScale.value = 1;
+		});
+	});
+}
+
+function windowEventHandler(event: MessageEvent) {
+	if (event.source !== iframeEl.value?.contentWindow) {
+		return;
+	}
+	if (event.data.type === 'misskey:embed:ready') {
+		iframeEl.value!.contentWindow?.postMessage({
+			type: 'misskey:embedParent:registerIframeId',
+			payload: {
+				iframeId: 'embedCodeGen', // 同じタイミングで複数のembed iframeがある際の区別用なのでここではなんでもいい
+			},
+		});
+	}
+	if (event.data.type === 'misskey:embed:changeHeight') {
+		iframeHeight.value = event.data.payload.height;
+		nextTick(() => {
+			calcScale();
+			iframeLoading.value = false; // 初回の高さ変更まで待つ
+		});
+	}
+}
+
+function calcScale() {
+	if (!resizerRootEl.value) return;
+	const previewWidth = resizerRootEl.value.clientWidth - 40; // 左右の余白 20pxずつ
+	const previewHeight = resizerRootEl.value.clientHeight - 40; // 上下の余白 20pxずつ
+	const iframeWidth = 500;
+	const scale = Math.min(previewWidth / iframeWidth, previewHeight / iframeHeight.value, 1); // 拡大はしないので1を上限に
+	iframeScale.value = scale;
+}
+
+onMounted(() => {
+	window.addEventListener('message', windowEventHandler);
+	if (!resizerRootEl.value) return;
+	resizeObserver.observe(resizerRootEl.value);
+});
+
+function reset() {
+	window.removeEventListener('message', windowEventHandler);
+	resizeObserver.disconnect();
+
+	// プレビューのリセット
+	iframeHeight.value = 0;
+	iframeScale.value = 1;
+	iframeLoading.value = true;
+	result.value = '';
+	phase.value = 'input';
+}
+
+onDeactivated(() => {
+	reset();
+});
+
+onUnmounted(() => {
+	reset();
+});
+//#endregion
+</script>
+
+<style module>
+.transition_x_enterActive,
+.transition_x_leaveActive {
+	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
+}
+.transition_x_enterFrom {
+	opacity: 0;
+	transform: translateX(50px);
+}
+.transition_x_leaveTo {
+	opacity: 0;
+	transform: translateX(-50px);
+}
+
+.embedCodeGenRoot {
+	container-type: inline-size;
+	height: 100%;
+}
+
+.embedCodeGenInputRoot {
+	height: 100%;
+	display: grid;
+	grid-template-columns: 1fr 400px;
+}
+
+.embedCodeGenPreviewRoot {
+	position: relative;
+	background-color: var(--bg);
+	background-size: auto auto;
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--panel) 6px, var(--panel) 12px);
+	cursor: not-allowed;
+}
+
+.embedCodeGenPreviewWrapper {
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+	pointer-events: none;
+	user-select: none;
+	-webkit-user-drag: none;
+}
+
+.embedCodeGenPreviewTitle {
+	position: absolute;
+	z-index: 100;
+	top: 8px;
+	left: 8px;
+	padding: 6px 10px;
+	border-radius: 6px;
+	font-size: 85%;
+}
+
+.embedCodeGenPreviewSpinner {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+	pointer-events: none;
+	user-select: none;
+	-webkit-user-drag: none;
+}
+
+.embedCodeGenPreviewResizerRoot {
+	position: relative;
+	flex: 1 0;
+}
+
+.embedCodeGenPreviewResizer {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+}
+
+.embedCodeGenPreviewIframe {
+	display: block;
+	border: none;
+	width: 500px;
+	color-scheme: light dark;
+}
+
+.embedCodeGenSettings {
+	padding: 24px;
+	overflow-y: scroll;
+}
+
+.embedCodeGenResultRoot {
+	box-sizing: border-box;
+	padding: 24px;
+	height: 100%;
+	max-width: 700px;
+	margin: 0 auto;
+	display: flex;
+	align-items: center;
+}
+
+.embedCodeGenResultHeading {
+	text-align: center;
+	font-size: 1.2em;
+}
+
+.embedCodeGenResultHeadingIcon {
+	margin: 0 auto;
+	background-color: var(--accentedBg);
+	color: var(--accent);
+	text-align: center;
+	height: 64px;
+	width: 64px;
+	font-size: 24px;
+	line-height: 64px;
+	border-radius: 50%;
+}
+
+.embedCodeGenResultDescription {
+	text-align: center;
+	white-space: pre-wrap;
+}
+
+.embedCodeGenResultWrapper,
+.embedCodeGenResultCode {
+	width: 100%;
+}
+
+.embedCodeGenResultButtons {
+	margin: 0 auto;
+}
+
+@container (max-width: 800px) {
+	.embedCodeGenInputRoot {
+		grid-template-columns: 1fr;
+		grid-template-rows: 1fr 1fr;
+	}
+}
+</style>
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue
index 008613c27e74aa215afea0c84d880db8a2af42fa..151843b18ce660982ac911a393782c71d918e78a 100644
--- a/packages/frontend/src/components/MkEmojiPicker.section.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.section.vue
@@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref, computed, Ref } from 'vue';
-import { CustomEmojiFolderTree, getEmojiName } from '@/scripts/emojilist.js';
+import { CustomEmojiFolderTree, getEmojiName } from '@@/js/emojilist.js';
 import { i18n } from '@/i18n.js';
 import { customEmojis } from '@/custom-emojis.js';
 import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 297d5f899eba549fb11b74932e36a2e3d3755cb3..949ed4db91382640195d2818468c2c1ed22b9b9c 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -117,7 +117,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, shallowRef, computed, watch, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
-import XSection from '@/components/MkEmojiPicker.section.vue';
 import {
 	emojilist,
 	emojiCharByCategory,
@@ -126,7 +125,8 @@ import {
 	getEmojiName,
 	CustomEmojiFolderTree,
 	getUnicodeEmoji,
-} from '@/scripts/emojilist.js';
+} from '@@/js/emojilist.js';
+import XSection from '@/components/MkEmojiPicker.section.vue';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import * as os from '@/os.js';
 import { isTouchUsing } from '@/scripts/touch.js';
@@ -233,7 +233,7 @@ watch(q, () => {
 
 			// 名前にキーワードが含まれている
 			for (const emoji of emojis) {
-				if (keywords.every(keyword => emoji.name.includes(keyword))) {
+				if (keywords.every(keyword => emoji.name.toLowerCase().includes(keyword))) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -242,7 +242,7 @@ watch(q, () => {
 
 			// 名前またはエイリアスにキーワードが含まれている
 			for (const emoji of emojis) {
-				if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.aliases.some(alias => alias.includes(keyword)))) {
+				if (keywords.every(keyword => emoji.name.toLowerCase().includes(keyword) || emoji.aliases.some(alias => alias.toLowerCase().includes(keyword)))) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -254,7 +254,7 @@ watch(q, () => {
 			if (matches.size >= max) return matches;
 
 			for (const emoji of emojis) {
-				if (emoji.aliases.some(alias => alias === newQ)) {
+				if (emoji.aliases.some(alias => alias.toLowerCase() === newQ)) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -262,7 +262,7 @@ watch(q, () => {
 			if (matches.size >= max) return matches;
 
 			for (const emoji of emojis) {
-				if (emoji.name.startsWith(newQ)) {
+				if (emoji.name.toLowerCase().startsWith(newQ)) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -270,7 +270,7 @@ watch(q, () => {
 			if (matches.size >= max) return matches;
 
 			for (const emoji of emojis) {
-				if (emoji.aliases.some(alias => alias.startsWith(newQ))) {
+				if (emoji.aliases.some(alias => alias.toLowerCase().startsWith(newQ))) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -278,7 +278,7 @@ watch(q, () => {
 			if (matches.size >= max) return matches;
 
 			for (const emoji of emojis) {
-				if (emoji.name.includes(newQ)) {
+				if (emoji.name.toLowerCase().includes(newQ)) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -286,7 +286,7 @@ watch(q, () => {
 			if (matches.size >= max) return matches;
 
 			for (const emoji of emojis) {
-				if (emoji.aliases.some(alias => alias.includes(newQ))) {
+				if (emoji.aliases.some(alias => alias.toLowerCase().includes(newQ))) {
 					matches.add(emoji);
 					if (matches.size >= max) break;
 				}
@@ -611,6 +611,7 @@ defineExpose({
 						width: auto;
 						height: auto;
 						min-width: 0;
+						padding: 0;
 
 						&:disabled {
 							cursor: not-allowed;
@@ -717,7 +718,7 @@ defineExpose({
 
 				> .item {
 					position: relative;
-					padding: 0;
+					padding: 0 3px;
 					width: var(--eachSize);
 					height: var(--eachSize);
 					contain: strict;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 0ec0679393faded61d150a1a3a82bf359c202dcd..18e80d84458031eac725dcc0f18f92449b7c7711 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/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue
index bab844c27f83a40519eca363112cc67395f9b5d6..7af68a32ba681e2c0d439e698927a07b444196fb 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>
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 229cd59056778eb83d7d5d0623119d238a1b80f1..392963fdb94d9607caeb6f96da75ca4d2b998d03 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -41,6 +41,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<MkSpacer :marginMin="14" :marginMax="22">
 							<slot></slot>
 						</MkSpacer>
+						<div v-if="$slots.footer" :class="$style.footer">
+							<slot name="footer"></slot>
+						</div>
 					</div>
 				</KeepAlive>
 			</Transition>
@@ -136,7 +139,7 @@ onMounted(() => {
 	width: 100%;
 	box-sizing: border-box;
 	padding: 9px 12px 9px 12px;
-	background: var(--buttonBg);
+	background: var(--folderHeaderBg);
 	-webkit-backdrop-filter: var(--blur, blur(15px));
 	backdrop-filter: var(--blur, blur(15px));
 	border-radius: var(--radius-sm);
@@ -144,7 +147,7 @@ onMounted(() => {
 
 	&:hover {
 		text-decoration: none;
-		background: var(--buttonHoverBg);
+		background: var(--folderHeaderHoverBg);
 	}
 
 	&:focus-within {
@@ -153,7 +156,7 @@ onMounted(() => {
 
 	&.active {
 		color: var(--accent);
-		background: var(--buttonHoverBg);
+		background: var(--folderHeaderHoverBg);
 	}
 
 	&.opened {
@@ -224,4 +227,18 @@ onMounted(() => {
 		background: var(--bg);
 	}
 }
+
+.footer {
+	position: sticky !important;
+	z-index: 1;
+	bottom: var(--stickyBottom, 0px);
+	left: 0;
+	padding: 12px;
+	background: var(--acrylicBg);
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+	background-size: auto auto;
+	background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--panel) 5px, var(--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 3fdf673eb3667253d143656a8b4fa6f853d97054..52497a29948340f5fe1aa3c8c79975b0246756f7 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>
@@ -43,7 +43,7 @@ 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 '@/config.js';
+import { host } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1e88d59d8edeefbf5ae2581015d73dfb5fb17b81
--- /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(--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/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 8d301f16bd4e4e279d46af23b6cc802350f9d93b..c04d0864fb1e8a27877be449fefd3ddd7df3554c 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 import DrawBlurhash from '@/workers/draw-blurhash?worker';
 import TestWebGL2 from '@/workers/test-webgl2?worker';
-import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js';
-import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash.js';
+import { WorkerMultiDispatch } from '@@/js/worker-multi-dispatch.js';
+import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
 
 const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
 	// テスト環境で Web Worker インスタンスは作成できない
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 06389b40130b3f6baeb6dd1f631f95c4e00598b7..42e1146e270691319443533f2587c2932368f536 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
 import { debounce } from 'throttle-debounce';
 import MkButton from '@/components/MkButton.vue';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js';
 
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index 094d2f177f3ba1b9edd0d9558f3f9974a05f6ec7..46d42248d3b0c8c70325c958bd4c3159f721161a 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { instance as Instance } from '@/instance.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index de51a98789dcda264cb29d19407a9f0f84473093..4aee64f78e52cf4280bbeca873d73675c6c0c19f 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -11,8 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-else-if="isExpired" style="color: var(--error)">{{ i18n.ts.expired }}</span>
 		<span v-else style="color: var(--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/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index 07cf9e0c37e0caa82a38c533ac170ed48d2780f1..263cd95eb10580b89ef2f39486a8b7f985cd41c4 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
 	:behavior="props.navigationBehavior"
 	:title="url"
+	@click.prevent="self ? true : warningExternalWebsite(url)"
 	@click.stop
 >
 	<slot></slot>
@@ -17,11 +18,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import * as os from '@/os.js';
 import { isEnabledUrlPreview } from '@/instance.js';
 import { MkABehavior } from '@/components/global/MkA.vue';
+import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
 
 const props = withDefaults(defineProps<{
 	url: string;
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue
index 93affd930f106f740eca65bf9acd39f1dbfc4d6c..2d7cde1af283c42bfd3ba97cca5504a90ec2926b 100644
--- a/packages/frontend/src/components/MkMediaAudio.vue
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -175,9 +175,7 @@ async function show() {
 const menuShowing = ref(false);
 
 function showMenu(ev: MouseEvent) {
-	let menu: MenuItem[] = [];
-
-	menu = [
+	const menu: MenuItem[] = [
 		// TODO: 再生キューに追加
 		{
 			type: 'switch',
@@ -225,7 +223,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}`,
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index ed8d43273fbcb94545a97382c1fb7e66d1855dfe..77a86ff2fb5d887eeea63972ae0b85b7564f73e0 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -5,12 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div :class="$style.root">
-	<div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
+	<MkMediaAudio v-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
+	<div v-else-if="media.isSensitive && hide" :class="$style.sensitive" @click="show">
 		<span style="font-size: 1.6em;"><i class="ti ti-alert-triangle"></i></span>
 		<b>{{ i18n.ts.sensitive }}</b>
 		<span>{{ i18n.ts.clickToShow }}</span>
 	</div>
-	<MkMediaAudio v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
 	<a
 		v-else :class="$style.download"
 		:href="media.url"
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index cac419d42bd0298b3c058d0a3f7fea9febf07fb5..02c054956cf44098a771a7f6d9271be56fa82b42 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 ? {
@@ -54,6 +54,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';
@@ -75,7 +76,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
@@ -112,27 +112,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>
@@ -197,10 +209,19 @@ function showMenu(ev: MouseEvent) {
 	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%);
 	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(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%);
+}
+
+html[data-color-scheme=light] .visible {
+	--c: rgb(0 0 0 / 2%);
+	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%);
+}
+
 .menu {
 	display: block;
 	position: absolute;
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 4bc2b9fba7df8ed506dde94b321d78a8a08fa739..39fa6ff012275596b35ef669392365edae77d11e 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -39,7 +39,7 @@ import XImage from '@/components/MkMediaImage.vue';
 import XVideo from '@/components/MkMediaVideo.vue';
 import XModPlayer from '@/components/SkModPlayer.vue';
 import * as os from '@/os.js';
-import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES } from '@/const.js';
+import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES } from '@@/js/const.js';
 import { defaultStore } from '@/store.js';
 import { focusParent } from '@/scripts/focus.js';
 
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 1c3c9a312b0aa05f7fdfe11b1b849a4125f7f97a..0502bdd40112cb7f966217f758cd9d9694a6fe0d 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -195,9 +195,7 @@ async function show() {
 const menuShowing = ref(false);
 
 function showMenu(ev: MouseEvent) {
-	let menu: MenuItem[] = [];
-
-	menu = [
+	const menu: MenuItem[] = [
 		// TODO: 再生キューに追加
 		{
 			type: 'switch',
@@ -250,7 +248,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}`,
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index 80a1b684598c1bedc9b02f9ed3e4cef9e136f462..de2048b6f210c5396dc42b50e79ad3120b653ea6 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { toUnicode } from 'punycode';
 import { computed } from 'vue';
 import tinycolor from 'tinycolor2';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index 235790556c5bb667f8ab92b3f53c21b06603e24f..086573ba6dd61ad8f27d37666b1adf82439ac18f 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 0537f4f98873b8c1e66bf9ec97b7025836bf2f95..fe6df7090cbc399a930d26ccea542ac6ac3b3461 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)',
@@ -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,67 @@ 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 {
+			> .item {
+				padding: 6px 20px;
+				font-size: 1em;
+				line-height: 24px;
+			}
 		}
 	}
 
 	&.asDrawer {
-		padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
-		width: 100%;
-		border-radius: var(--radius-lg);
-		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: var(--radius);
+		> .menu {
+			padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0;
+			width: 100%;
+			border-radius: var(--radius-lg);
+			border-bottom-right-radius: 0;
+			border-bottom-left-radius: 0;
+
+			> .item {
+				font-size: 1em;
+				padding: 12px 24px;
+
+				&::before {
+					width: calc(100% - 24px);
+					border-radius: var(--radius);
+				}
+
+				> .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;
 	}
 }
 
diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue
index f2f2bf47a8b59a1bf0d26be87ea8f10c7dd34b70..1b6f6cef31e45a25b60a37d063eda62d04ca7a27 100644
--- a/packages/frontend/src/components/MkMiniChart.vue
+++ b/packages/frontend/src/components/MkMiniChart.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { watch, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import tinycolor from 'tinycolor2';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 
 const props = defineProps<{
 	src: number[];
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index f8032f9b431bb547ce7249fb2fff2b518700d5ef..c766a338239abdeb5fb38eb97ef6a7dcaca425b8 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 c3c78120362194363f7cee980503fda2c3017223..f26959888b428a57792f3f68d5283bffc46c971e 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -94,12 +94,12 @@ defineExpose({
 
 	--root-margin: 24px;
 
+	--headerHeight: 46px;
+	--headerHeightNarrow: 42px;
+
 	@media (max-width: 500px) {
 		--root-margin: 16px;
 	}
-
-	--headerHeight: 46px;
-	--headerHeightNarrow: 42px;
 }
 
 .header {
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index e2f0a4e492351ccd486424aaf16ad9f20ed26b1d..7ba4f0b9d9135cca7699bca449fbac2d995d0a91 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -62,7 +62,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div style="container-type: inline-size;">
 				<bdi>
 				<p v-if="appearNote.cw != null" :class="$style.cw">
-					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :isBlock="true" :author="appearNote.user" :nyaize="'respect'"/>
+					<Mfm
+						v-if="appearNote.cw != ''"
+						:text="appearNote.cw"
+						:author="appearNote.user"
+						:nyaize="'respect'"
+						:enableEmojiMenu="true"
+						:enableEmojiMenuReaction="true"
+						:isBlock="true"
+					/>
 					<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
 				</p>
 				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
@@ -94,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div v-if="appearNote.files && appearNote.files.length > 0">
 						<MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/>
 					</div>
-					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
+					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll" @click.stop/>
 					<div v-if="isEnabledUrlPreview">
 						<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
 					</div>
@@ -148,7 +156,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<i class="ph-heart ph-bold ph-lg"></i>
 				</button>
 				<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()" @click.stop>
-					<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
+					<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i>
 					<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
 					<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 					<i v-else class="ph-smiley ph-bold ph-lg"></i>
@@ -192,6 +200,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
+import { isLink } from '@@/js/is-link.js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
@@ -224,13 +233,13 @@ 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 type { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
-import { shouldCollapsed } from '@/scripts/collapsed.js';
 import { useRouter } from '@/router/supplier.js';
 import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
-import { host } from '@/config.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';
@@ -565,7 +574,8 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -589,7 +599,8 @@ function quote() {
 	} else {
 		os.post({
 			renote: appearNote.value,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -742,16 +753,6 @@ function onContextmenu(ev: MouseEvent): void {
 		return;
 	}
 
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
-		if (el.tagName === 'AUDIO') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
-
 	if (ev.target && isLink(ev.target as HTMLElement)) return;
 	if (window.getSelection()?.toString() !== '') return;
 
@@ -882,7 +883,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	// 今度はその処理自体がパフォーマンス低下の原因にならないか懸念される。また、被リアクションでも高さは変化するため、やはり多少のズレは生じる
 	// 一度レンダリングされた要素はブラウザがよしなにサイズを覚えておいてくれるような実装になるまで待った方が良さそう(なるのか?)
 	//content-visibility: auto;
-  //contain-intrinsic-size: 0 128px;
+	//contain-intrinsic-size: 0 128px;
 
 	&:focus-visible {
 		outline: none;
@@ -1126,7 +1127,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	z-index: 2;
 	width: 100%;
 	height: 64px;
-	//background: linear-gradient(0deg, var(--panel), var(--X15));
+	//background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 
 	&:hover > .collapsedLabel {
 		background: var(--panelHighlight);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 64559ef2655d8bc5e6d0ad19ba545a95fb6e9943..5acb18c871537032cd042fee3fdb47416663d4ce 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -69,7 +69,15 @@ 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" :isBlock="true" :author="appearNote.user" :nyaize="'respect'"/>
+				<Mfm
+					v-if="appearNote.cw != ''"
+					:text="appearNote.cw"
+					:author="appearNote.user"
+					:nyaize="'respect'"
+					:enableEmojiMenu="true"
+					:enableEmojiMenuReaction="true"
+					:isBlock="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">
@@ -100,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div v-if="appearNote.files && appearNote.files.length > 0">
 					<MkMediaList ref="galleryEl" :mediaList="appearNote.files"/>
 				</div>
-				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
+				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll"/>
 				<div v-if="isEnabledUrlPreview">
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
 				</div>
@@ -149,7 +157,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<i class="ph-heart ph-bold ph-lg"></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-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i>
 				<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
 				<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 				<i v-else class="ph-smiley ph-bold ph-lg"></i>
@@ -227,6 +235,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
+import { isLink } from '@@/js/is-link.js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@@ -250,9 +259,10 @@ 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 '@/config.js';
+import { host } from '@@/js/config.js';
 import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
+
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
 import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
@@ -550,7 +560,8 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -574,7 +585,8 @@ function quote() {
 	} else {
 		os.post({
 			renote: appearNote.value,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -701,14 +713,6 @@ function toggleReact() {
 }
 
 function onContextmenu(ev: MouseEvent): void {
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
-
 	if (ev.target && isLink(ev.target as HTMLElement)) return;
 	if (window.getSelection()?.toString() !== '') return;
 
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index fd5da0d68784509a5048559fc8869184eef8a499..cd6fdf576ce476510a13afe340d78358408d7dc6 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -5,14 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <header :class="$style.root">
-	<div v-if="mock" :class="$style.name">
-		<MkUserName :user="note.user"/>
-	</div>
-	<MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
-		<MkUserName :user="note.user"/>
-	</MkA>
-	<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
-	<div :class="$style.username"><MkAcct :user="note.user"/></div>
+	<component :is="defaultStore.state.enableCondensedLine ? 'MkCondensedLine' : 'div'" :minScale="0.5" style="min-width: 0;">
+		<div style="display: flex; white-space: nowrap; align-items: baseline;">
+			<div v-if="mock" :class="$style.name">
+				<MkUserName :user="note.user"/>
+			</div>
+			<MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
+				<MkUserName :user="note.user"/>
+			</MkA>
+			<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
+			<div :class="$style.username"><MkAcct :user="note.user"/></div>
+		</div>
+	</component>
 	<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!"/>
 	</div>
@@ -43,6 +47,7 @@ import { notePage } from '@/filters/note.js';
 import { userPage } from '@/filters/user.js';
 import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
 import { popupMenu } from '@/os.js';
+import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note;
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 9caed62ce2447a36d3004331c6ec42d7d466958a..45276839add436e63e9e5df0dc7d8fa3fd0be13b 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -339,7 +339,8 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
@@ -363,7 +364,8 @@ function quote() {
 	} else {
 		os.post({
 			renote: appearNote.value,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 99486761983206e493ec450ad0c3c3edccf452dd..7bec9bdc65022b133a0f4cfba0fb9dd87efb20dd 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -13,7 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<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=""/>
+		<MkAvatar v-else-if="notification.type === 'exportCompleted'" :class="$style.icon" :user="$i" link preview/>
+		<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 +26,7 @@ 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_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
 				[$style.t_pollEnded]: notification.type === 'edited',
 			}]"
@@ -38,6 +40,7 @@ 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>
 			<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>
@@ -49,7 +52,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>
@@ -60,6 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<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 === '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>
@@ -102,10 +106,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">
@@ -126,7 +140,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>
@@ -171,6 +185,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 = () => {
@@ -200,6 +228,14 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification)
 	overflow-wrap: break-word;
 	display: flex;
 	contain: content;
+
+	--eventFollow: #36aed2;
+	--eventRenote: #36d298;
+	--eventReply: #007aff;
+	--eventReactionHeart: var(--love);
+	--eventReaction: #e99a0b;
+	--eventAchievement: #cb9a11;
+	--eventOther: #88a6b7;
 }
 
 .head {
@@ -308,6 +344,12 @@ 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);
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index 71b38d99ed9c92897842402b5ca269ce79634b8c..47a9c79e4551c2f72a97f51ea58a7d5f1ffa82a6 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -35,7 +35,7 @@ import MkSwitch from './MkSwitch.vue';
 import MkInfo from './MkInfo.vue';
 import MkButton from './MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
-import { notificationTypes } from '@/const.js';
+import { notificationTypes } from '@@/js/const.js';
 import { i18n } from '@/i18n.js';
 
 type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 2dd6c21ef6d383256cbb3ff78e6142234d6b0106..a395734add40dd589f832c136dfebae8b13f45d3 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -30,7 +30,7 @@ import XNotification from '@/components/MkNotification.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
-import { notificationTypes } from '@/const.js';
+import { notificationTypes } from '@@/js/const.js';
 import { infoImageUrl } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index a0bc0c628e8753ba766506f0b692f45fc39ad38e..94cbaf5c917ad83378f701f466a15a9bacd068bb 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -62,7 +62,7 @@ onUnmounted(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			//background: linear-gradient(0deg, var(--panel), var(--X15));
+			//background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 8f6109ca04b782b4c5f9e508a1aee772a4bfbde0..f67a1e5b634566a6baff09be481420187bd599b1 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -34,13 +34,13 @@ 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 '@/config.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 '@/scripts/scroll.js';
+import { getScrollContainer } from '@@/js/scroll.js';
 import { useRouterFactory } from '@/router/supplier.js';
 import { mainRouter } from '@/router/main.js';
 import MkUserName from './global/MkUserName.vue';
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index 4f2a4d270311f28a45029ea16a900045c8c5aafc..f37cb10f6dfd3b133cb0054e991353f3941ee64d 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -45,10 +45,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch, type Ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
+import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
-import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
 import { defaultStore } from '@/store.js';
 import { MisskeyEntity } from '@/types/date-separated-list.js';
 import { i18n } from '@/i18n.js';
@@ -104,6 +104,7 @@ const props = withDefaults(defineProps<{
 const emit = defineEmits<{
 	(ev: 'queue', count: number): void;
 	(ev: 'status', error: boolean): void;
+	(ev: 'init'): void;
 }>();
 
 const rootEl = shallowRef<HTMLElement>();
@@ -125,8 +126,6 @@ const items = ref<MisskeyEntityMap>(new Map());
  */
 const queue = ref<MisskeyEntityMap>(new Map());
 
-const offset = ref(0);
-
 /**
  * 初期化中かどうか(trueならMkLoadingで全て隠す)
  */
@@ -179,7 +178,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;
@@ -229,9 +230,10 @@ async function init(): Promise<void> {
 			more.value = true;
 		}
 
-		offset.value = res.length;
 		error.value = false;
 		fetching.value = false;
+
+		emit('init');
 	}, err => {
 		error.value = true;
 		fetching.value = false;
@@ -251,7 +253,7 @@ const fetchMore = async (): Promise<void> => {
 		...params,
 		limit: SECOND_FETCH_LIMIT,
 		...(offsetMode ? {
-			offset: offset.value,
+			offset: items.value.size,
 		} : {
 			untilId: Array.from(items.value.keys()).at(-1),
 		}),
@@ -301,7 +303,6 @@ const fetchMore = async (): Promise<void> => {
 				moreFetching.value = false;
 			}
 		}
-		offset.value += res.length;
 	}, err => {
 		moreFetching.value = false;
 	});
@@ -316,7 +317,7 @@ const fetchMoreAhead = async (): Promise<void> => {
 		...params,
 		limit: SECOND_FETCH_LIMIT,
 		...(offsetMode ? {
-			offset: offset.value,
+			offset: items.value.size,
 		} : {
 			sinceId: Array.from(items.value.keys()).at(-1),
 		}),
@@ -328,7 +329,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 393ac4efbac0c10c323b252973dacbb47792c682..592a511fb012cbbe753fafd6ff40911afbd41306 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div :class="{ [$style.done]: closed || isVoted }">
 	<ul :class="$style.choices">
-		<li v-for="(choice, i) in poll.choices" :key="i" :class="$style.choice" @click="vote(i)">
+		<li v-for="(choice, i) in props.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>
@@ -24,6 +24,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-if="isVoted">{{ i18n.ts._poll.voted }}</span>
 		<span v-else-if="closed">{{ i18n.ts._poll.closed }}</span>
 		<span v-if="remaining > 0"> · {{ timer }}</span>
+		<span v-if="!closed && $i && !props.local"> · </span>
+		<a v-if="!closed && $i && !props.local" style="color: inherit;" @click="refreshVotes()">{{ i18n.ts.reload }}</a>
 	</p>
 </div>
 </template>
@@ -31,19 +33,21 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-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 '@/config.js';
-import { useInterval } from '@/scripts/use-interval.js';
-import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
+import { host } from '@@/js/config.js';
+import { useInterval } from '@@/js/use-interval.js';
+import { $i } from '@/account.js';
 
 const props = defineProps<{
 	noteId: string;
 	poll: NonNullable<Misskey.entities.Note['poll']>;
 	readOnly?: boolean;
+	local?: boolean;
 }>();
 
 const remaining = ref(-1);
@@ -85,9 +89,10 @@ if (props.poll.expiresAt) {
 }
 
 const vote = async (id) => {
+	if (props.readOnly || closed.value || isVoted.value) return;
+
 	pleaseLogin(undefined, pleaseLoginContext.value);
 
-	if (props.readOnly || closed.value || isVoted.value) return;
 	if (!props.poll.multiple) {
 		const { canceled } = await os.confirm({
 			type: 'question',
@@ -108,6 +113,17 @@ const vote = async (id) => {
 	});
 	if (!showResult.value) showResult.value = !props.poll.multiple;
 };
+
+const refreshVotes = async () => {
+	pleaseLogin(undefined, pleaseLoginContext.value);
+
+	if (props.readOnly || closed.value) return;
+	await misskeyApi('notes/polls/refresh', {
+		noteId: props.noteId,
+	// Sadly due to being in the same component and the poll being a prop we require to break Vue's recommendation of not mutating the prop to update it.
+	// eslint-disable-next-line vue/no-mutating-props
+	}).then((res: any) => res.poll ? props.poll.choices = res.poll.choices : null );
+};
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index ff29b6619376dcc3d82c5329d7bf4499d94b278a..d14873008b5b0b1def74bad72aabbf81b449b038 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -13,7 +13,7 @@ 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[];
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 2bc607fbb68213309e1833fc7e913345a6d77c1b..4a29b27ac4dfb383cccd4f20a58bb79dc467c209 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 		<div :class="$style.headerRight">
 			<template v-if="!(channel != null && fixed)">
-				<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" @click="setVisibility">
+				<button v-if="channel == null" ref="visibilityButton" v-click-anime v-tooltip="i18n.ts.visibility" :class="['_button', $style.headerRightItem, $style.visibility]" :disabled="editId != null" @click="setVisibility">
 					<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
 					<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
 					<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
 				</button>
 			</template>
-			<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified'" @click="toggleLocalOnly">
+			<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" class="_button" :class="[$style.headerRightItem, { [$style.danger]: localOnly }]" :disabled="channel != null || visibility === 'specified' || editId != null" @click="toggleLocalOnly">
 				<span v-if="!localOnly"><i class="ti ti-rocket"></i></span>
 				<span v-else><i class="ti ti-rocket-off"></i></span>
 			</button>
@@ -65,7 +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">
+	<div v-show="useCw" :class="$style.cwFrame">
+		<input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown">
+		<div v-if="maxCwLength - cwLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: cwLength > maxCwLength }]">{{ maxCwLength - cwLength }}</div>
+	</div>
 	<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 dir="auto" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/>
@@ -106,11 +109,11 @@ import * as mfm from '@transfem-org/sfm-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 '@/config.js';
 import { erase, unique } from '@/scripts/array.js';
 import { extractMentions } from '@/scripts/extract-mentions.js';
 import { formatTimeString } from '@/scripts/format-time-string.js';
@@ -247,13 +250,16 @@ const submitText = computed((): string => {
 });
 
 const textLength = computed((): number => {
-	return (text.value + imeText.value).trim().length;
+	return (text.value + imeText.value).length;
 });
 
 const maxTextLength = computed((): number => {
 	return instance ? instance.maxNoteTextLength : 1000;
 });
 
+const cwLength = computed(() => cw.value?.length ?? 0);
+const maxCwLength = computed(() => instance.maxCwLength);
+
 const canPost = computed((): boolean => {
 	return !props.mock && !posting.value && !posted.value &&
 		(
@@ -264,6 +270,7 @@ const canPost = computed((): boolean => {
 			quoteId.value != null
 		) &&
 		(textLength.value <= maxTextLength.value) &&
+		(cwLength.value <= maxCwLength.value) &&
 		(!poll.value || poll.value.choices.length >= 2);
 });
 
@@ -630,11 +637,22 @@ async function onPaste(ev: ClipboardEvent) {
 
 	if (paste.length > 1000) {
 		ev.preventDefault();
-		os.confirm({
-			type: 'info',
+		os.actions({
+			type: 'question',
 			text: i18n.ts.attachAsFileQuestion,
-		}).then(({ canceled }) => {
-			if (canceled) {
+			actions: [
+				{
+					value: 'yes',
+					text: i18n.ts.yes,
+					primary: true,
+				},
+				{
+					value: 'no',
+					text: i18n.ts.no,
+				},
+			],
+		}).then(({ result }) => {
+			if (result !== 'yes') {
 				insertTextAtCursor(textareaEl.value, paste);
 				return;
 			}
@@ -1172,13 +1190,13 @@ defineExpose({
 
 	&:not(:disabled):hover {
 		> .inner {
-			background: linear-gradient(90deg, var(--X8), var(--X8));
+			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		}
 	}
 
 	&:not(:disabled):active {
 		> .inner {
-			background: linear-gradient(90deg, var(--X8), var(--X8));
+			background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		}
 	}
 }
@@ -1245,6 +1263,7 @@ defineExpose({
 	min-height: 75px;
 	max-height: 150px;
 	overflow: auto;
+	background-size: auto auto;
 }
 
 .targetNote {
@@ -1304,10 +1323,13 @@ defineExpose({
 	}
 }
 
-.cw {
+.cwFrame {
 	z-index: 1;
 	padding-bottom: 8px;
 	border-bottom: solid 0.5px var(--divider);
+
+	width: 100%;
+	position: relative;
 }
 
 .hashtags {
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index a3fb7c691fcf5989ae1dc86951a70e0887d2d2dc..f90fcfef33bdbc0124ae97d7a04a5a998065017e 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -7,7 +7,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 <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)">
+			<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,6 +33,7 @@ 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));
 
@@ -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;
@@ -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,6 +206,10 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 	border-radius: var(--radius-xs);
 	overflow: hidden;
 	cursor: move;
+
+	&:focus-visible {
+		outline-offset: 4px;
+	}
 }
 
 .thumbnail {
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index 947c0ee4d065d003b60e1f9a988e2e70510297ee..811a6378f2a0c4fdc94429178ec1bafa90646851 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkModal ref="modal" :preferType="'dialog'" @click="modal?.close()" @closed="onModalClosed()" @esc="modal?.close()">
-	<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal?.close()" @esc="modal?.close()"/>
+	<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="onCancel" @esc="onCancel"/>
 </MkModal>
 </template>
 
@@ -37,7 +37,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'closed'): void;
+	(ev: 'closed', cancelled: boolean): void;
 }>();
 
 const modal = shallowRef<InstanceType<typeof MkModal>>();
@@ -47,10 +47,18 @@ function onPosted() {
 	modal.value?.close({
 		useSendAnimation: true,
 	});
+	emit('closed', false);
+}
+
+function onCancel() {
+	// for some reason onModalClosed does not get called properly when closing the model through other functions.
+	modal.value?.close();
+	// emit is required so that the dialog gets properly disposed otherwise it will float around as a "zombie"
+	emit('closed', true);
 }
 
 function onModalClosed() {
-	emit('closed');
+	emit('closed', true);
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkPreview.vue b/packages/frontend/src/components/MkPreview.vue
index 649dee2fdb7146574b20906c6002b485d0aad4b3..6efd99d14bf5ba8f78d064d2ee37ef5bdd50a009 100644
--- a/packages/frontend/src/components/MkPreview.vue
+++ b/packages/frontend/src/components/MkPreview.vue
@@ -42,7 +42,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkRadio from '@/components/MkRadio.vue';
 import * as os from '@/os.js';
-import * as config from '@/config.js';
+import * as config from '@@/js/config.js';
 import { $i } from '@/account.js';
 
 const text = ref('');
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index e0d0b561beb08f961fca6b1bb4a15928c6e66a4f..4fb4c6fe56a305cb1718bdaec4077cdaab9b9e8e 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import { i18n } from '@/i18n.js';
-import { getScrollContainer } from '@/scripts/scroll.js';
+import { getScrollContainer } from '@@/js/scroll.js';
 import { isHorizontalSwipeSwiping } from '@/scripts/touch.js';
 
 const SCROLL_STOP = 10;
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue
index 244fcdceaeb391604537ab1ef954266d79a00307..22c187c3578a401fb55c6b0eca89a2dbfe56a660 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);
@@ -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/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue
index 15409a216a300cb814b45f321ca7d4a7a4069201..77ca841ad0c03175386f832b5263d282bc54c978 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 60118fadd2a4f87c04f01afe2963d0cea949a664..6fdeb3a3abbe74f517bf3620f19f648ede1e409b 100644
--- a/packages/frontend/src/components/MkReactionsViewer.details.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.details.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.users">
 			<div v-for="u in users" :key="u.id" :class="$style.user">
 				<MkAvatar :class="$style.avatar" :user="u"/>
-				<MkUserName :user="u" :nowrap="true"/>
+				<MkUserName :user="u" :nowrap="true" :class="$style.username"/>
 			</div>
 			<div v-if="count > 10" :class="$style.more">+{{ count - 10 }}</div>
 		</div>
@@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { } from 'vue';
+import { getEmojiName } from '@@/js/emojilist.js';
 import MkTooltip from './MkTooltip.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
-import { getEmojiName } from '@/scripts/emojilist.js';
 
 defineProps<{
 	showing: boolean;
@@ -63,6 +63,7 @@ function getReactionName(reaction: string): string {
 .reactionIcon {
 	display: block;
 	width: 60px;
+	max-height: 60px;
 	font-size: 60px; // unicodeな絵文字についてはwidthが効かないため
 	object-fit: contain;
 	margin: 0 auto;
@@ -98,4 +99,11 @@ function getReactionName(reaction: string): string {
 .more {
 	padding-top: 4px;
 }
+
+.username {
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	min-width: 0;
+	overflow: hidden;
+}
 </style>
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 6506035f8f8f2dbdf3acd41901308840f4697a0c..957ee0e76b37c981a6845bdf9d48c87c8b139c03 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, inject, onMounted, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
+import { getUnicodeEmoji } from '@@/js/emojilist.js';
 import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
 import XDetails from '@/components/MkReactionsViewer.details.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
@@ -34,7 +35,6 @@ import { i18n } from '@/i18n.js';
 import * as sound from '@/scripts/sound.js';
 import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
 import { customEmojisMap } from '@/custom-emojis.js';
-import { getUnicodeEmoji } from '@/scripts/emojilist.js';
 
 const props = defineProps<{
 	reaction: string;
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 8254ac83cfe6a2be90d50e50dbd655da8764d8a9..150a5c6d542749dcdb578f9ee36229571dd3aa96 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -44,9 +44,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 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 '@/scripts/use-interval.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';
 
 const props = defineProps<{
 	modelValue: string | null;
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index 42fa2bf4a78dbe4239b231d0d5a59a37c9f8a105..82e0df8a016f57fa28918ccd3155fad5f1afb38f 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<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>
+			<MkInput 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>
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<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">
+				<MkButton v-if="!queryingKey" @click="query2FaKey">
 					{{ i18n.ts.retry }}
 				</MkButton>
 			</div>
@@ -45,10 +45,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<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>
@@ -57,6 +53,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton type="submit" :disabled="signing" large primary rounded style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 			</div>
 		</div>
+		<div v-if="!totpLogin && usePasswordLessLogin" :class="$style.orHr">
+			<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
+		</div>
+		<div v-if="!totpLogin && usePasswordLessLogin" class="twofa-group tap-group">
+			<MkButton v-if="!queryingKey" type="submit" :disabled="signing" style="margin: auto auto;" rounded large primary @click="onPasskeyLogin">
+				<i class="ti ti-device-usb" style="font-size: medium;"></i>
+				{{ signing ? i18n.ts.loggingIn : i18n.ts.signinWithPasskey }}
+			</MkButton>
+			<p v-if="queryingKey">{{ i18n.ts.useSecurityKey }}</p>
+		</div>
 	</div>
 </form>
 </template>
@@ -66,20 +72,24 @@ import { defineAsyncComponent, ref } from 'vue';
 import { toUnicode } from 'punycode/';
 import * as Misskey from 'misskey-js';
 import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
+import { SigninWithPasskeyResponse } from 'misskey-js/entities.js';
+import { query, extractDomain } from '@@/js/url.js';
+import { host as configHost } from '@@/js/config.js';
+import MkDivider from './MkDivider.vue';
 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 '@/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { query, extractDomain } from '@/scripts/url.js';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
+import { showSystemAccountDialog } from '@/scripts/show-system-account-dialog.js';
 
 const signing = ref(false);
 const user = ref<Misskey.entities.UserDetailed | null>(null);
+const usePasswordLessLogin = ref<Misskey.entities.UserDetailed['usePasswordLessLogin']>(true);
 const username = ref('');
 const password = ref('');
 const token = ref('');
@@ -88,6 +98,7 @@ const totpLogin = ref(false);
 const isBackupCode = ref(false);
 const queryingKey = ref(false);
 let credentialRequest: CredentialRequestOptions | null = null;
+const passkey_context = ref('');
 
 const emit = defineEmits<{
 	(ev: 'login', v: any): void;
@@ -106,12 +117,19 @@ const props = withDefaults(defineProps<{
 });
 
 function onUsernameChange(): void {
+	const usernameRequested = username.value;
 	misskeyApi('users/show', {
-		username: username.value,
+		username: usernameRequested,
 	}).then(userResponse => {
-		user.value = userResponse;
+		if (userResponse.username === username.value) {
+			user.value = userResponse;
+			usePasswordLessLogin.value = userResponse.usePasswordLessLogin;
+		}
 	}, () => {
-		user.value = null;
+		if (usernameRequested === username.value) {
+			user.value = null;
+			usePasswordLessLogin.value = true;
+		}
 	});
 }
 
@@ -121,7 +139,7 @@ function onLogin(res: any): Promise<void> | void {
 	}
 }
 
-async function queryKey(): Promise<void> {
+async function query2FaKey(): Promise<void> {
 	if (credentialRequest == null) return;
 	queryingKey.value = true;
 	await webAuthnRequest(credentialRequest)
@@ -150,6 +168,47 @@ async function queryKey(): Promise<void> {
 		});
 }
 
+function onPasskeyLogin(): void {
+	signing.value = true;
+	if (webAuthnSupported()) {
+		misskeyApi('signin-with-passkey', {})
+			.then((res: SigninWithPasskeyResponse) => {
+				totpLogin.value = false;
+				signing.value = false;
+				queryingKey.value = true;
+				passkey_context.value = res.context ?? '';
+				credentialRequest = parseRequestOptionsFromJSON({
+					publicKey: res.option,
+				});
+			})
+			.then(() => queryPasskey())
+			.catch(loginFailed);
+	}
+}
+
+async function queryPasskey(): Promise<void> {
+	if (credentialRequest == null) return;
+	queryingKey.value = true;
+	console.log('Waiting passkey auth...');
+	await webAuthnRequest(credentialRequest)
+		.catch((err) => {
+			console.warn('Passkey Auth fail!: ', err);
+			queryingKey.value = false;
+			return Promise.reject(null);
+		}).then(credential => {
+			credentialRequest = null;
+			queryingKey.value = false;
+			signing.value = true;
+			return misskeyApi('signin-with-passkey', {
+				credential: credential.toJSON(),
+				context: passkey_context.value,
+			});
+		}).then((res: SigninWithPasskeyResponse) => {
+			emit('login', res.signinResponse);
+			return onLogin(res.signinResponse);
+		});
+}
+
 function onSubmit(): void {
 	signing.value = true;
 	if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
@@ -164,7 +223,7 @@ function onSubmit(): void {
 					publicKey: res,
 				});
 			})
-				.then(() => queryKey())
+				.then(() => query2FaKey())
 				.catch(loginFailed);
 		} else {
 			totpLogin.value = true;
@@ -204,6 +263,10 @@ function loginFailed(err: any): void {
 			showSuspendedDialog();
 			break;
 		}
+		case 's8dhsj9s-a93j-493j-ja9k-kas9sj20aml2': {
+			showSystemAccountDialog();
+			break;
+		}
 		case '22d05606-fbcf-421a-a2db-b32610dcfd1b': {
 			os.alert({
 				type: 'error',
@@ -212,6 +275,30 @@ function loginFailed(err: any): void {
 			});
 			break;
 		}
+		case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.unknownWebAuthnKey,
+			});
+			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({
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index 524c62b4d3aaee8c469895dbd8d0e79c29716ea3..d48780e9de6def3bf9797395c802afb6a124e305 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkModalWindow
 	ref="dialog"
 	:width="400"
-	:height="430"
+	:height="450"
 	@close="onClose"
 	@closed="emit('closed')"
 >
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index e673b6d5308a52949f5c379bc73ddfc3d39c664a..4c55831a3a522dea706813055cd1ebeaafbaa019 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -70,6 +70,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<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.enableFC" ref="fc" v-model="fcResponse" :class="$style.captcha" provider="fc" :sitekey="instance.fcSiteKey"/>
 			<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"/>
@@ -88,7 +89,7 @@ import * as Misskey from 'misskey-js';
 import MkButton from './MkButton.vue';
 import MkInput from './MkInput.vue';
 import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
-import * as config from '@/config.js';
+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';
@@ -112,6 +113,7 @@ const host = toUnicode(config.host);
 const hcaptcha = ref<Captcha | undefined>();
 const recaptcha = ref<Captcha | undefined>();
 const turnstile = ref<Captcha | undefined>();
+const fc = ref<Captcha | undefined>();
 
 const username = ref<string>('');
 const password = ref<string>('');
@@ -128,6 +130,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 fcResponse = ref<string | null>(null);
 const usernameAbortController = ref<null | AbortController>(null);
 const emailAbortController = ref<null | AbortController>(null);
 
@@ -137,6 +140,7 @@ const shouldDisableSubmitting = computed((): boolean => {
 		instance.enableMcaptcha && !mCaptchaResponse.value ||
 		instance.enableRecaptcha && !reCaptchaResponse.value ||
 		instance.enableTurnstile && !turnstileResponse.value ||
+		instance.enableFC && !fcResponse.value ||
 		instance.emailRequiredForSignup && emailState.value !== 'ok' ||
 		usernameState.value !== 'ok' ||
 		passwordRetypeState.value !== 'match';
@@ -266,6 +270,7 @@ async function onSubmit(): Promise<void> {
 			'm-captcha-response': mCaptchaResponse.value,
 			'g-recaptcha-response': reCaptchaResponse.value,
 			'turnstile-response': turnstileResponse.value,
+			'frc-captcha-solution': fcResponse.value,
 		});
 		if (instance.emailRequiredForSignup) {
 			os.alert({
@@ -297,6 +302,7 @@ async function onSubmit(): Promise<void> {
 		hcaptcha.value?.reset?.();
 		recaptcha.value?.reset?.();
 		turnstile.value?.reset?.();
+		fc.value?.reset?.();
 
 		os.alert({
 			type: 'error',
diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
index cd884f0b19c22574c5ac8b2f98addfd4bed5bd13..7743a89242b7e180c799299c2317c867e4ad4104 100644
--- a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
+++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import MkButton from '@/components/MkButton.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import { miLocalStorage } from '@/local-storage.js';
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 8386a783fc8d4e05f1c265fc5da7033458cb7dc7..6bd00fcc2aa5879d3c565f4bf2508ac2f0f61990 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -46,7 +46,7 @@ import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
-import { shouldCollapsed } from '@/scripts/collapsed.js';
+import { shouldCollapsed } from '@@/js/collapsed.js';
 import { defaultStore } from '@/store.js';
 import { useRouter } from '@/router/supplier.js';
 import * as os from '@/os.js';
@@ -110,7 +110,7 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			//background: linear-gradient(0deg, var(--panel), var(--X15));
+			// background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 041ae881095e4e90b8d6938e04787cf956c15422..430e3c79588be24a76ed64b169180735646d81d8 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -100,14 +100,14 @@ defineProps<{
 
 	&.grid {
 		> .group {
+			margin-left: 0;
+			margin-right: 0;
+
 			& + .group {
 				padding-top: 0;
 				border-top: none;
 			}
 
-			margin-left: 0;
-			margin-right: 0;
-
 			> .title {
 				font-size: 1em;
 				opacity: 0.7;
diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts
index 69b8edd85aec7f2729b9e8a02f75d6839a0783c1..19e4eea733cd9a2a4b618e3e3d41e06b21c68659 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 f5c7a3160bb4d9df1b9dc7f74469d0aef48784d0..ec3b1c90caf98b3464fb10fe4213cd302e5db4fb 100644
--- a/packages/frontend/src/components/MkSystemWebhookEditor.vue
+++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue
@@ -35,16 +35,31 @@ 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>
+
+						<div v-show="mode === 'edit'" :class="$style.description">
+							{{ i18n.ts._webhookSettings.testRemarks }}
+						</div>
 					</div>
 				</MkFolder>
 
@@ -66,6 +81,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 {
@@ -180,6 +196,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) {
@@ -235,4 +266,29 @@ onMounted(async () => {
 	-webkit-backdrop-filter: var(--blur, blur(15px));
 	backdrop-filter: var(--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(--fgTransparentWeak);
+}
 </style>
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index 9adc8d466c86f10befa98ce7310df9abee13450b..1f5a2b938167feef5ed643bc82f85bf3bebbf2aa 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -158,7 +158,7 @@ import XSensitive from '@/components/MkTutorialDialog.Sensitive.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import * as os from '@/os.js';
 
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 4fb0749931dcd88e47f82100cc6df6d41bfe1385..91f5b86c2db9110195806f187561b2cd44510f0b 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -19,7 +19,7 @@ import { onMounted, shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
-import { version } from '@/config.js';
+import { version } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { confetti } from '@/scripts/confetti.js';
 
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index a51b8785802f69fdb6043f014d44055886a91009..04f5314463afd9e16264409408598c8ab58cafdf 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -85,12 +85,12 @@ 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 '@/config.js';
+import { url as local } from '@@/js/config.js';
 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 '@/scripts/intl-const.js';
+import { versatileLang } from '@@/js/intl-const.js';
 import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/components/MkUrlWarningDialog.vue b/packages/frontend/src/components/MkUrlWarningDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..f2e9b11fd9020ed7c7b2faf033b3f3dffe9deb80
--- /dev/null
+++ b/packages/frontend/src/components/MkUrlWarningDialog.vue
@@ -0,0 +1,131 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="done(true)" @closed="emit('closed')">
+	<div :class="$style.root" class="_gaps">
+		<div class="_gaps_s">
+			<div :class="$style.header">
+				<div :class="$style.icon">
+					<i class="ti ti-alert-triangle"></i>
+				</div>
+				<div :class="$style.title">{{ i18n.ts._externalNavigationWarning.title }}</div>
+			</div>
+			<div><Mfm :text="i18n.tsx._externalNavigationWarning.description({ host: instanceName })"/></div>
+			<div class="_monospace" :class="$style.urlAddress">{{ url }}</div>
+			<div>
+				<MkSwitch v-model="trustThisDomain">{{ i18n.ts._externalNavigationWarning.trustThisDomain }}</MkSwitch>
+			</div>
+		</div>
+		<div :class="$style.buttons">
+			<MkButton data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ i18n.ts.cancel }}</MkButton>
+			<MkButton data-cy-modal-dialog-ok inline primary rounded @click="ok"><i class="ti ti-external-link"></i> {{ i18n.ts.open }}</MkButton>
+		</div>
+	</div>
+</MkModal>
+</template>
+
+<script lang="ts" setup>
+import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from 'vue';
+import { instanceName } from '@@/js/config.js';
+import MkModal from '@/components/MkModal.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import { i18n } from '@/i18n.js';
+import { defaultStore } from '@/store.js';
+
+type Result = string | number | true | null;
+
+const props = defineProps<{
+	url: string;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'done', v: { canceled: true } | { canceled: false, result: Result }): void;
+	(ev: 'closed'): void;
+}>();
+
+const modal = shallowRef<InstanceType<typeof MkModal>>();
+const trustThisDomain = ref(false);
+
+const domain = computed(() => new URL(props.url).hostname);
+
+// overload function を使いたいので lint エラーを無視する
+function done(canceled: true): void;
+function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
+function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
+	emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
+	modal.value?.close();
+}
+
+async function ok() {
+	const result = true;
+	if (!defaultStore.state.trustedDomains.includes(domain.value) && trustThisDomain.value) {
+		await defaultStore.set('trustedDomains', defaultStore.state.trustedDomains.concat(domain.value));
+	}
+	done(false, result);
+}
+
+function cancel() {
+	done(true);
+}
+
+function onKeydown(evt: KeyboardEvent) {
+	if (evt.key === 'Escape') cancel();
+}
+
+onMounted(() => {
+	document.addEventListener('keydown', onKeydown);
+});
+
+onBeforeUnmount(() => {
+	document.removeEventListener('keydown', onKeydown);
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	margin: auto;
+	padding: 32px;
+	width: 100%;
+	min-width: 320px;
+	max-width: 480px;
+	box-sizing: border-box;
+	background: var(--panel);
+	border-radius: 16px;
+}
+
+.header {
+	display: flex;
+	align-items: center;
+	gap: 0.75em;
+}
+
+.icon {
+	font-size: 18px;
+	color: var(--warn);
+}
+
+.title {
+	font-weight: bold;
+	font-size: 1.1em;
+}
+
+.urlAddress {
+	padding: 10px 14px;
+	border-radius: 8px;
+	border: 1px solid var(--divider);
+	overflow-x: auto;
+	white-space: nowrap;
+}
+
+.buttons {
+	display: flex;
+	gap: 8px;
+	flex-wrap: wrap;
+	justify-content: right;
+}
+</style>
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index e528f04dfc15200d0423206b4ceefbd6b5841564..73cdd9ce00014675f6742f3b36edbd39f0b295bc 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -11,7 +11,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkA :class="$style.name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
 		<p :class="$style.username"><MkAcct :user="user"/></p>
 	</div>
-	<span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
+	<ul v-if="$i && $i.id != user.id" :class="$style.infoBadges">
+		<li v-if="user.isFollowed && user.isFollowing">{{ i18n.ts.mutuals }}</li>
+		<li v-else-if="user.isFollowing">{{ i18n.ts.following }}</li>
+		<li v-else-if="user.isFollowed">{{ i18n.ts.followsYou }}</li>
+		<li v-if="user.isMuted">{{ i18n.ts.muted }}</li>
+		<li v-if="user.isRenoteMuted">{{ i18n.ts.renoteMuted }}</li>
+		<li v-if="user.isBlocking">{{ i18n.ts.blocked }}</li>
+		<li v-if="user.isBlocked && $i.isModerator">{{ i18n.ts.blockingYou }}</li>
+	</ul>
 	<div :class="$style.description">
 		<div v-if="user.description" :class="$style.mfm">
 			<Mfm :text="user.description" :isBlock="true" :author="user"/>
@@ -144,4 +152,30 @@ defineProps<{
 	top: 8px;
 	right: 8px;
 }
+
+.infoBadges {
+	position: absolute;
+	top: 12px;
+	left: 12px;
+
+	display: flex;
+	flex-direction: row;
+
+	padding: 0;
+	margin: 0;
+
+	> * {
+		padding: 4px 8px;
+		color: #fff;
+		background: rgba(0, 0, 0, 0.7);
+		font-size: 0.7em;
+		border-radius: var(--radius-sm);
+		list-style-type: none;
+		margin-left: 0;
+	}
+
+	> :not(:first-child) {
+		margin-left: 8px;
+	}
+}
 </style>
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 7d210a4385cd49c8a8323e8821791911fb76dbaf..a5b48c8ce29249b99a40cda37447a2b6a9725596 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -70,7 +70,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { host as currentHost, hostname } from '@/config.js';
+import { host as currentHost, hostname } from '@@/js/config.js';
 
 const emit = defineEmits<{
 	(ev: 'ok', selected: Misskey.entities.UserDetailed): void;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index 514350c930ff3085fea88e9acad00e6669362ac9..1fb1eda0398913ddc05e5643bcabfacdaede8203 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -137,7 +137,7 @@ import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index 6fb33044680fd38c39591fbddbb8da6b10322bf9..874eff6c796779e8e2c692735b675c3be9817ad7 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <div v-if="instance" :class="$style.root">
 	<div :class="[$style.main, $style.panel]">
-		<img :src="instance.iconUrl || '/apple-touch-icon.png'" alt="" :class="$style.mainIcon"/>
+		<img :src="instance.sidebarLogoUrl || instance.iconUrl || '/apple-touch-icon.png'" alt="" :class="instance.sidebarLogoUrl ? $style.wideIcon : $style.mainIcon"/>
 		<button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
 		<div :class="$style.mainFg">
 			<h1 :class="$style.mainTitle">
@@ -62,7 +62,7 @@ import XSignupDialog from '@/components/MkSignupDialog.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
@@ -126,6 +126,14 @@ function showMenu(ev: MouseEvent) {
 	filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.5));
 }
 
+.wideIcon {
+	min-width: 85px;
+	max-width: 60%;
+	margin-top: -47px;
+	vertical-align: bottom;
+	filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.5));
+}
+
 .mainMenu {
 	position: absolute;
 	top: 16px;
@@ -134,6 +142,7 @@ function showMenu(ev: MouseEvent) {
 	height: 32px;
 	border-radius: var(--radius-sm);
 	font-size: 18px;
+	z-index: 50;
 }
 
 .mainFg {
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index 06879f5917b6a1397f6aee7ff76142b040ecb402..99840bf8d7620615b6caefaac35ac52610c427e6 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -57,6 +57,7 @@ import MkButton from '@/components/MkButton.vue';
 import { widgets as widgetDefs } from '@/widgets/index.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
+import { isLink } from '@@/js/is-link.js';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
@@ -65,13 +66,6 @@ const props = defineProps<{
 	edit: boolean;
 }>();
 
-// This will not be available for now as I don't think this is needed
-// const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes));
-/* if (!notesSearchAvailable) {
-	const wid = widgetDefs.findIndex(widget => widget === 'search');
-	widgetDefs.splice(wid, 1);
-} */
-
 const emit = defineEmits<{
 	(ev: 'updateWidgets', widgets: Widget[]): void;
 	(ev: 'addWidget', widget: Widget): void;
@@ -105,13 +99,6 @@ const updateWidget = (id, data) => {
 
 function onContextmenu(widget: Widget, ev: MouseEvent) {
 	const element = ev.target as HTMLElement | null;
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
 	if (element && isLink(element)) return;
 	if (element && (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(element.tagName) || element.attributes['contenteditable'])) return;
 	if (window.getSelection()?.toString() !== '') return;
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index 303e49de00fa731dfc0604628239b80360e75f74..08906a1205f5349614b73abfff62a529b5049cff 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
 import contains from '@/scripts/contains.js';
 import * as os from '@/os.js';
-import { MenuItem } from '@/types/menu.js';
+import type { MenuItem } from '@/types/menu.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 
@@ -508,10 +508,6 @@ defineExpose({
 .header {
 	--height: 39px;
 
-	&.mini {
-		--height: 32px;
-	}
-
 	display: flex;
 	position: relative;
 	z-index: 1;
@@ -524,6 +520,10 @@ defineExpose({
 	//border-bottom: solid 1px var(--divider);
 	font-size: 90%;
 	font-weight: bold;
+
+	&.mini {
+		--height: 32px;
+	}
 }
 
 .headerButton {
diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue
index e3711b3463f15465f516c62349f2a809625c3d87..1122976436e99c073cde6c42b4dce38c005384bc 100644
--- a/packages/frontend/src/components/MkYouTubePlayer.vue
+++ b/packages/frontend/src/components/MkYouTubePlayer.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import MkWindow from '@/components/MkWindow.vue';
-import { versatileLang } from '@/scripts/intl-const.js';
+import { versatileLang } from '@@/js/intl-const.js';
 import { transformPlayerUrl } from '@/scripts/player-url-transform.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/components/SkApprovalUser.vue b/packages/frontend/src/components/SkApprovalUser.vue
index 20059f139d64e6c6f618d527ae9064ab3492c1ef..1ef0ac5b17dc86767b71668ce0ff9ea8623953b0 100644
--- a/packages/frontend/src/components/SkApprovalUser.vue
+++ b/packages/frontend/src/components/SkApprovalUser.vue
@@ -69,22 +69,10 @@ async function deleteAccount() {
 	});
 	if (confirm.canceled) return;
 
-	const typed = await os.inputText({
-		text: i18n.t('typeToConfirm', { x: props.user.username }),
+	await os.apiWithDialog('admin/decline-user', {
+		userId: props.user.id,
 	});
-	if (typed.canceled) return;
-
-	if (typed.result === props.user.username) {
-		await os.apiWithDialog('admin/delete-account', {
-			userId: props.user.id,
-		});
-		emits('deleted', props.user.id);
-	} else {
-		os.alert({
-			type: 'error',
-			text: 'input not match',
-		});
-	}
+	emits('deleted', props.user.id);
 }
 
 async function approveAccount() {
diff --git a/packages/frontend/src/components/SkFollowingFeedEntry.vue b/packages/frontend/src/components/SkFollowingFeedEntry.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8fa5e014d8310688d750e2cbd9f577efdfd3721b
--- /dev/null
+++ b/packages/frontend/src/components/SkFollowingFeedEntry.vue
@@ -0,0 +1,123 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root" @click="$emit('select', note.user)">
+	<div :class="$style.avatar">
+		<MkAvatar :class="$style.icon" :user="note.user" indictor/>
+	</div>
+	<div :class="$style.contents">
+		<header :class="$style.header">
+			<MkA v-user-preview="note.user.id" :class="$style.headerName" :to="userPage(note.user)">
+				<MkUserName :user="note.user"/>
+			</MkA>
+			<MkA :to="notePage(note)">
+				<MkTime :time="note.createdAt" :class="$style.headerTime" colored/>
+			</MkA>
+		</header>
+		<div>
+			<div v-if="isMuted" :class="[$style.text, $style.muted]">({{ i18n.ts.postFiltered }})</div>
+			<Mfm v-else :class="$style.text" :text="getNoteSummary(note)" :isBlock="true" :plain="true" :nowrap="false" :isNote="true" nyaize="respect" :author="note.user"/>
+		</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+import { getNoteSummary } from '@/scripts/get-note-summary.js';
+import { userPage } from '@/filters/user.js';
+import { notePage } from '@/filters/note.js';
+import { i18n } from '@/i18n.js';
+
+withDefaults(defineProps<{
+	note: Misskey.entities.Note,
+	isMuted: boolean
+}>(), {
+	isMuted: false,
+});
+
+defineEmits<{
+	(event: 'select', user: Misskey.entities.UserLite): void
+}>();
+
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	box-sizing: border-box;
+	padding: 12px 16px;
+	font-size: 0.9em;
+	overflow-wrap: break-word;
+	display: flex;
+	contain: content;
+}
+
+.avatar {
+	align-self: center;
+	flex-shrink: 0;
+	width: 42px;
+	height: 42px;
+	margin-right: 8px;
+}
+
+.contents {
+	flex: 1;
+	min-width: 0;
+}
+
+.header {
+	display: flex;
+	align-items: baseline;
+	justify-content: space-between;
+	white-space: nowrap;
+}
+
+.headerName {
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	min-width: 0;
+	overflow: hidden;
+	font-weight: bold;
+}
+
+.headerTime {
+	margin-left: auto;
+	font-size: 0.9em;
+}
+
+.icon {
+	display: block;
+	width: 100%;
+	height: 100%;
+}
+
+.text {
+	display: flex;
+	width: 100%;
+	overflow: clip;
+	line-height: 1.25em;
+	height: 2.5em;
+}
+
+.muted {
+	font-style: italic;
+}
+
+@container (max-width: 600px) {
+	.root {
+		padding: 16px;
+		font-size: 0.9em;
+	}
+}
+
+@container (max-width: 500px) {
+	.root {
+		padding: 12px;
+		font-size: 0.85em;
+	}
+}
+</style>
diff --git a/packages/frontend/src/components/SkFormula.vue b/packages/frontend/src/components/SkFormula.vue
index 039cef8da82b77b5c521b03f5a2f88d98a89b380..6faf36da55778c3fb4e723f8a75261b1f7e473e3 100644
--- a/packages/frontend/src/components/SkFormula.vue
+++ b/packages/frontend/src/components/SkFormula.vue
@@ -4,26 +4,26 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-	<div v-if="block" :class="$style.block" v-html="renderedFormula"></div>
-	<span v-else v-html="renderedFormula"></span>
+<div v-if="block" :class="$style.block" v-html="renderedFormula"></div>
+<span v-else v-html="renderedFormula"></span>
 </template>
 
 <script lang="ts" setup>
-	import { computed } from 'vue';
-	import katex from 'katex';
-	import 'katex/dist/katex.min.css';
+import { computed } from 'vue';
+import katex from 'katex';
+import 'katex/dist/katex.min.css';
 
-	const props = defineProps<{
+const props = defineProps<{
 		formula: string;
 		block: boolean;
 	}>();
 
-	const renderedFormula = computed(() =>
-		katex.renderToString(props.formula, {
-			throwOnError: false,
-			trust: false,
-			displayMode: props.block,
-		} as any));
+const renderedFormula = computed(() =>
+	katex.renderToString(props.formula, {
+		throwOnError: false,
+		trust: false,
+		displayMode: props.block,
+	}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/SkInstanceTicker.vue b/packages/frontend/src/components/SkInstanceTicker.vue
index 9cfc3326981fb58722b1f5c3bd99116b09a87c1f..eb987d9c779eeb8bedc6edee364dc9a47069703a 100644
--- a/packages/frontend/src/components/SkInstanceTicker.vue
+++ b/packages/frontend/src/components/SkInstanceTicker.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed } from 'vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { instance as Instance } from '@/instance.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index b02d902482582ceec95b83ee93e0efd4acf764fc..bda528214425b6f1762ced800827d8f6a208fe90 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -65,7 +65,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
 			<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" :isBlock="true" :author="appearNote.user" :nyaize="'respect'"/>
+					<Mfm
+						v-if="appearNote.cw != ''"
+						:text="appearNote.cw"
+						:author="appearNote.user"
+						:nyaize="'respect'"
+						:enableEmojiMenu="true"
+						:enableEmojiMenuReaction="true"
+						:isBlock="true"
+					/>
 					<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
 				</p>
 				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
@@ -96,7 +104,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div v-if="appearNote.files && appearNote.files.length > 0">
 						<MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/>
 					</div>
-					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
+					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll" @click.stop/>
 					<div v-if="isEnabledUrlPreview">
 						<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
 					</div>
@@ -149,7 +157,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<i class="ph-heart ph-bold ph-lg"></i>
 				</button>
 				<button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()" @click.stop>
-					<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--eventReactionHeart);"></i>
+					<i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i>
 					<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
 					<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 					<i v-else class="ph-smiley ph-bold ph-lg"></i>
@@ -193,6 +201,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
+import { isLink } from '@@/js/is-link.js';
 import SkNoteSub from '@/components/SkNoteSub.vue';
 import SkNoteHeader from '@/components/SkNoteHeader.vue';
 import SkNoteSimple from '@/components/SkNoteSimple.vue';
@@ -224,13 +233,13 @@ 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 type { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
-import { shouldCollapsed } from '@/scripts/collapsed.js';
 import { useRouter } from '@/router/supplier.js';
 import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
-import { host } from '@/config.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';
@@ -565,7 +574,8 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -589,7 +599,8 @@ function quote() {
 	} else {
 		os.post({
 			renote: appearNote.value,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -742,16 +753,6 @@ function onContextmenu(ev: MouseEvent): void {
 		return;
 	}
 
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
-		if (el.tagName === 'AUDIO') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
-
 	if (ev.target && isLink(ev.target as HTMLElement)) return;
 	if (window.getSelection()?.toString() !== '') return;
 
@@ -1153,7 +1154,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 	z-index: 2;
 	width: 100%;
 	height: 64px;
-	//background: linear-gradient(0deg, var(--panel), var(--X15));
+	//background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 
 	&:hover > .collapsedLabel {
 		background: var(--panelHighlight);
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index cca6c7a40c86e15157734e7c6c2089a6191988ae..7907da6c9416df148f24589f21c2fbcec274b16b 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -78,7 +78,15 @@ 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" :isBlock="true" :author="appearNote.user" :nyaize="'respect'"/>
+				<Mfm
+					v-if="appearNote.cw != ''"
+					:text="appearNote.cw"
+					:author="appearNote.user"
+					:nyaize="'respect'"
+					:enableEmojiMenu="true"
+					:enableEmojiMenuReaction="true"
+					:isBlock="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">
@@ -108,7 +116,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div v-if="appearNote.files && appearNote.files.length > 0">
 					<MkMediaList ref="galleryEl" :mediaList="appearNote.files"/>
 				</div>
-				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
+				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :local="!appearNote.user.host" :class="$style.poll"/>
 				<div v-if="isEnabledUrlPreview">
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
 				</div>
@@ -157,7 +165,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<i class="ph-heart ph-bold ph-lg"></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-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i>
 				<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i>
 				<i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
 				<i v-else class="ph-smiley ph-bold ph-lg"></i>
@@ -235,6 +243,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shallowRef, watch } from 'vue';
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
+import { isLink } from '@@/js/is-link.js';
 import SkNoteSub from '@/components/SkNoteSub.vue';
 import SkNoteSimple from '@/components/SkNoteSimple.vue';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
@@ -258,7 +267,7 @@ 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 '@/config.js';
+import { host } from '@@/js/config.js';
 import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js';
 import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
@@ -559,7 +568,8 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -583,7 +593,8 @@ function quote() {
 	} else {
 		os.post({
 			renote: appearNote.value,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
 				userId: $i?.id,
@@ -710,14 +721,6 @@ function toggleReact() {
 }
 
 function onContextmenu(ev: MouseEvent): void {
-	const isLink = (el: HTMLElement): boolean => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-		return false;
-	};
-
 	if (ev.target && isLink(ev.target as HTMLElement)) return;
 	if (window.getSelection()?.toString() !== '') return;
 
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index c2986b252453efa63c9529b46c7d11ed471d15ea..fac35191b909012b34ce27451c6df1d6f993d214 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -353,7 +353,8 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
@@ -377,7 +378,8 @@ function quote() {
 	} else {
 		os.post({
 			renote: appearNote.value,
-		}).then(() => {
+		}).then((cancelled) => {
+			if (cancelled) return;
 			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
diff --git a/packages/frontend/src/components/SkUserRecentNotes.vue b/packages/frontend/src/components/SkUserRecentNotes.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2cdb4b6586cf41e2b5a7691d55cffada51ee34f1
--- /dev/null
+++ b/packages/frontend/src/components/SkUserRecentNotes.vue
@@ -0,0 +1,110 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkPullToRefresh :refresher="() => reload()">
+	<div v-if="user" :class="$style.userInfo">
+		<MkUserInfo :class="$style.userInfo" class="user" :user="user"/>
+		<MkNotes :noGap="true" :pagination="pagination"/>
+	</div>
+	<div v-else-if="loadError" :class="$style.panel">{{ loadError }}</div>
+	<MkLoading v-else-if="userId"/>
+</MkPullToRefresh>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref, Ref, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import MkLoading from '@/components/global/MkLoading.vue';
+import MkNotes from '@/components/MkNotes.vue';
+import MkUserInfo from '@/components/MkUserInfo.vue';
+import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
+import { Paging } from '@/components/MkPagination.vue';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+
+const props = defineProps<{
+	userId: string;
+	withNonPublic: boolean;
+	withQuotes: boolean;
+	withReplies: boolean;
+	withBots: boolean;
+	onlyFiles: boolean;
+}>();
+
+const loadError: Ref<string | null> = ref(null);
+const user: Ref<Misskey.entities.UserDetailed | null> = ref(null);
+
+const pagination: Paging<'users/notes'> = {
+	endpoint: 'users/notes' as const,
+	limit: 10,
+	params: computed(() => ({
+		userId: props.userId,
+		withNonPublic: props.withNonPublic,
+		withRenotes: false,
+		withQuotes: props.withQuotes,
+		withReplies: props.withReplies,
+		withRepliesToSelf: props.withReplies,
+		withFiles: props.onlyFiles,
+		allowPartial: true,
+	})),
+};
+
+defineExpose({
+	reload,
+	user,
+});
+
+async function reload(): Promise<void> {
+	loadError.value = null;
+	user.value = null;
+
+	await Promise
+		.all([
+			// We need a User entity, but the pagination returns only UserLite.
+			// An additional request is needed to "upgrade" the object.
+			misskeyApi('users/show', { userId: props.userId }),
+
+			// Wait for 1 second to match the animation effects in MkHorizontalSwipe, MkPullToRefresh, and MkPagination.
+			// Otherwise, the page appears to load "backwards".
+			new Promise(resolve => setTimeout(resolve, 1000)),
+		])
+		.then(([u]) => user.value = u)
+		.catch(error => {
+			console.error('Error fetching user info', error);
+
+			loadError.value =
+				typeof(error) === 'string'
+					? String(error)
+					: JSON.stringify(error);
+		});
+}
+
+watch(() => props.userId, async () => {
+	await reload();
+});
+
+onMounted(async () => {
+	await reload();
+});
+
+</script>
+
+<style lang="scss" module>
+
+.panel {
+	background: var(--panel);
+}
+
+.userInfo {
+	margin-bottom: 12px;
+}
+
+@container (min-width: 451px) {
+	.userInfo {
+		margin-bottom: 24px;
+	}
+}
+
+</style>
diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue
index 164606e1f9495b59fe5bbb9dd8a82dca71f3fd62..f5546edf1e928396bb37ce8484c8d3a462ad00f5 100644
--- a/packages/frontend/src/components/form/link.vue
+++ b/packages/frontend/src/components/form/link.vue
@@ -60,18 +60,18 @@ const props = defineProps<{
 	width: 100%;
 	box-sizing: border-box;
 	padding: 10px 14px;
-	background: var(--buttonBg);
+	background: var(--folderHeaderBg);
 	border-radius: var(--radius-sm);
 	font-size: 0.9em;
 
 	&:hover {
 		text-decoration: none;
-		background: var(--buttonHoverBg);
+		background: var(--folderHeaderHoverBg);
 	}
 
 	&.active {
 		color: var(--accent);
-		background: var(--buttonHoverBg);
+		background: var(--folderHeaderHoverBg);
 	}
 }
 
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index e0303dbb2734a456a7cf8bff75a58b56fe7bd1aa..23f049ebb43c3ab607f03500076987f50c49f0d6 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -17,7 +17,7 @@ export type MkABehavior = 'window' | 'browser' | null;
 import { computed, inject, shallowRef } from 'vue';
 import * as os from '@/os.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { useRouter } from '@/router/supplier.js';
 
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index bbcb070803850d7c734d8b8f907e2e348aa50173..9a1ac3aca2d7d79f7d88fc82c11cb47261cf0f03 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>
@@ -17,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
 import { toUnicode } from 'punycode/';
-import { host as hostRaw } from '@/config.js';
+import { host as hostRaw } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
 
 defineProps<{
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index bee1d9ca4c10037171ea8a9b36b6446a02024bdb..1238e6ef231047f8c470dbb352689e3455d38141 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { url as local, host } from '@/config.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';
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index de62fe12a97a4ce03c5013c52857327235e3f377..1f78b068a2ee986d2db3a5b8ded1fa4ee59a7d1e 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -26,12 +26,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template v-if="showDecoration">
 		<img
 			v-for="decoration in decorations ?? user.avatarDecorations"
-			:class="[$style.decoration]"
+			:class="[$style.decoration, { [$style.decorationBlink]: decoration.blink }]"
 			:src="getDecorationUrl(decoration)"
 			:style="{
 				rotate: getDecorationAngle(decoration),
 				scale: getDecorationScale(decoration),
 				translate: getDecorationOffset(decoration),
+				zIndex: getDecorationZIndex(decoration),
 			}"
 			alt=""
 		>
@@ -42,10 +43,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { watch, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
+import { extractAvgColorFromBlurhash } from '@@/js/extract-avg-color-from-blurhash.js';
 import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
 import MkA from './MkA.vue';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash.js';
 import { acct, userPage } from '@/filters/user.js';
 import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue';
 import { defaultStore } from '@/store.js';
@@ -60,7 +61,7 @@ const props = withDefaults(defineProps<{
 	link?: boolean;
 	preview?: boolean;
 	indicator?: boolean;
-	decorations?: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>[];
+	decorations?: (Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'> & { blink?: boolean; })[];
 	forceShowDecoration?: boolean;
 }>(), {
 	target: null,
@@ -113,6 +114,10 @@ function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['ava
 	return offsetX === 0 && offsetY === 0 ? undefined : `${offsetX * 100}% ${offsetY * 100}%`;
 }
 
+function getDecorationZIndex(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+	return decoration.showBelow ? '-1' : undefined;
+}
+
 const color = ref<string | undefined>();
 
 watch(() => props.user.avatarBlurhash, () => {
@@ -159,6 +164,7 @@ watch(() => props.user.avatarBlurhash, () => {
 	flex-shrink: 0;
 	border-radius: 100%; // sharkey: controlled by square avatars setting!
 	line-height: 16px;
+	z-index: 0; // sharkey: starts stacking context to help with showing decorations behind the avatar
 }
 
 .inner {
@@ -330,4 +336,17 @@ watch(() => props.user.avatarBlurhash, () => {
 	width: 200%;
 	pointer-events: none;
 }
+
+.decorationBlink {
+	animation: blink 1s infinite;
+}
+
+@keyframes blink {
+	0%, 100% {
+		filter: brightness(2);
+	}
+	50% {
+		filter: brightness(1);
+	}
+}
 </style>
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
index 7c4957d77fac1c7261335b7eb7d087143b42ccc3..473d444c1606cfa492e775aa9b31d439224cfdba 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 4cacc7b292eb8982e3f44333c10b8ec994eb7c76..fbc716016cdcd4aad0c072da9c3e646751ea3e83 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;
@@ -86,7 +87,10 @@ const errored = ref(url.value == null);
 function onClick(ev: MouseEvent) {
 	if (props.menu) {
 		ev.stopPropagation();
-		os.popupMenu([{
+
+		const menuItems: MenuItem[] = [];
+
+		menuItems.push({
 			type: 'label',
 			text: `:${props.name}:`,
 		}, {
@@ -96,14 +100,20 @@ function onClick(ev: MouseEvent) {
 				copyToClipboard(`:${props.name}:`);
 				os.success();
 			},
-		}, ...(props.menuReaction && react ? [{
-			text: i18n.ts.doReaction,
-			icon: 'ph-smiley ph-bold ph-lg',
-			action: () => {
-				react(`:${props.name}:`);
-				sound.playMisskeySfx('reaction');
-			},
-		}] : []), {
+		});
+
+		if (props.menuReaction && react) {
+			menuItems.push({
+				text: i18n.ts.doReaction,
+				icon: 'ph-smiley ph-bold ph-lg',
+				action: () => {
+					react(`:${props.name}:`);
+					sound.playMisskeySfx('reaction');
+				},
+			});
+		}
+
+		menuItems.push({
 			text: i18n.ts.info,
 			icon: 'ti ti-info-circle',
 			action: async () => {
@@ -115,7 +125,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 c485305376730932656cba26668f30ad21e5a1b4..bd9b1d665a9021161a74bfe7477096f87ff5c245 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -10,13 +10,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, inject } from 'vue';
-import { char2fluentEmojiFilePath, char2twemojiFilePath, char2tossfaceFilePath } from '@/scripts/emoji-base.js';
+import { colorizeEmoji, getEmojiName } from '@@/js/emojilist.js';
+import { char2fluentEmojiFilePath, char2twemojiFilePath, char2tossfaceFilePath } from '@@/js/emoji-base.js';
 import { defaultStore } from '@/store.js';
-import { colorizeEmoji, getEmojiName } from '@/scripts/emojilist.js';
 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;
@@ -40,7 +41,10 @@ function computeTitle(event: PointerEvent): void {
 function onClick(ev: MouseEvent) {
 	if (props.menu) {
 		ev.stopPropagation();
-		os.popupMenu([{
+
+		const menuItems: MenuItem[] = [];
+
+		menuItems.push({
 			type: 'label',
 			text: props.emoji,
 		}, {
@@ -50,14 +54,20 @@ function onClick(ev: MouseEvent) {
 				copyToClipboard(props.emoji);
 				os.success();
 			},
-		}, ...(props.menuReaction && react ? [{
-			text: i18n.ts.doReaction,
-			icon: 'ph-smiley ph-bold ph-lg',
-			action: () => {
-				react(props.emoji);
-				sound.playMisskeySfx('reaction');
-			},
-		}] : [])], ev.currentTarget ?? ev.target);
+		});
+
+		if (props.menuReaction && react) {
+			menuItems.push({
+				text: i18n.ts.doReaction,
+				icon: 'ph-smiley ph-bold ph-lg',
+				action: () => {
+					react(props.emoji);
+					sound.playMisskeySfx('reaction');
+				},
+			});
+		}
+
+		os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
 	}
 }
 </script>
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 730351f79534b9f2e5531b0e6ea461f7ad27c49a..1daf7a29cb6073a910343edec37e89b276b0fb7c 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 97%
rename from packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
rename to packages/frontend/src/components/global/MkMfm.ts
index a3a2b9f319ee7337e011c7b2a50228e67dc266f2..9bf9f4a87238e1388128333aee437b3356071c18 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMfm.ts
@@ -18,10 +18,15 @@ 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 '@/config.js';
+import { host } from '@@/js/config.js';
 import { defaultStore } from '@/store.js';
-import { nyaize as doNyaize } from '@/scripts/nyaize.js';
-import { safeParseFloat } from '@/scripts/safe-parse.js';
+
+function safeParseFloat(str: unknown): number | null {
+	if (typeof str !== 'string' || str === '') return null;
+	const num = parseFloat(str);
+	if (isNaN(num)) return null;
+	return num;
+}
 
 const QUOTE_STYLE = `
 display: block;
@@ -58,8 +63,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 	provide('linkNavigationBehavior', props.linkNavigationBehavior);
 
 	const isNote = props.isNote ?? true;
-	const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author.speakAsCat : false : false : false;
-
+	const shouldNyaize = props.nyaize === 'respect' && props.author?.isCat && props.author?.speakAsCat && !defaultStore.state.disableCatSpeak;
 	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 	if (props.text == null || props.text === '') return;
 
@@ -93,7 +97,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 			case 'text': {
 				let text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
 				if (!disableNyaize && shouldNyaize) {
-					text = doNyaize(text);
+					text = Misskey.nyaize(text);
 				}
 
 				if (!props.plain) {
@@ -341,14 +345,14 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 							const child = token.children[0];
 							let text = child.type === 'text' ? child.props.text : '';
 							if (!disableNyaize && shouldNyaize) {
-								text = doNyaize(text);
+								text = Misskey.nyaize(text);
 							}
 							return h('ruby', {}, [text.split(' ')[0], h('rt', text.split(' ')[1])]);
 						} else {
 							const rt = token.children.at(-1)!;
 							let text = rt.type === 'text' ? rt.props.text : '';
 							if (!disableNyaize && shouldNyaize) {
-								text = doNyaize(text);
+								text = Misskey.nyaize(text);
 							}
 							return h('ruby', {}, [...genEl(token.children.slice(0, token.children.length - 1), scale), h('rt', text.trim())]);
 						}
@@ -460,7 +464,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 			}
 
 			case 'emojiCode': {
-				// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 				if (props.author?.host == null) {
 					return [h(MkCustomEmoji, {
 						key: Math.random(),
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 95ac1020132afe4118cdf84fdd23ef18550eb744..bb20f54fa4cf8cb7b7a76d0f97a1a2c7d961a619 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
 import tinycolor from 'tinycolor2';
 import XTabs, { Tab } from './MkPageHeader.tabs.vue';
-import { scrollToTop } from '@/scripts/scroll.js';
+import { scrollToTop } from '@@/js/scroll.js';
 import { globalEvents } from '@/events.js';
 import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
 import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index b12dc8cb31ad864b5249a694a5baf1bf00e6d0b9..72993991cef1135f33c579b03cd9ee598c368f02 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		ref="bodyEl"
 		:data-sticky-container-header-height="headerHeight"
 		:data-sticky-container-footer-height="footerHeight"
+		style="position: relative; z-index: 0;"
 	>
 		<slot></slot>
 	</div>
@@ -24,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue';
 
-import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const.js';
+import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
 
 const rootEl = shallowRef<HTMLElement>();
 const headerEl = shallowRef<HTMLElement>();
@@ -83,14 +84,14 @@ onMounted(() => {
 	if (headerEl.value != null) {
 		headerEl.value.style.position = 'sticky';
 		headerEl.value.style.top = 'var(--stickyTop, 0)';
-		headerEl.value.style.zIndex = '1000';
+		headerEl.value.style.zIndex = '1';
 		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';
+		footerEl.value.style.zIndex = '1';
 		observer.observe(footerEl.value);
 	}
 });
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index ffd4a849a2ed86bf4b9f56015ea7c78e692bc1dc..ccf7f200b592a566fb4e44802bc1e9dedcb00dcd 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -8,7 +8,7 @@ import { expect } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import MkTime from './MkTime.vue';
 import { i18n } from '@/i18n.js';
-import { dateTimeFormat } from '@/scripts/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 const now = new Date('2023-04-01T00:00:00.000Z');
 const future = new Date('2024-04-01T00:00:00.000Z');
 const oneHourAgo = new Date(now.getTime() - 3600000);
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 027b226f3fb0e8249f7965654463d1215be22a74..50bec990a1eee7beb940219aaecb9f8c8161c17d 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import isChromatic from 'chromatic/isChromatic';
 import { onMounted, onUnmounted, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
-import { dateTimeFormat } from '@/scripts/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 
 const props = withDefaults(defineProps<{
 	time: Date | string | number | null;
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 15595ba51576f01e0905f9d286761ddf26b30126..8cca47c1dbdc0db88a22c21e73b8bc40f2f02aa4 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target"
 	:behavior="props.navigationBehavior"
 	@contextmenu.stop="() => {}"
+	@click.prevent="self ? true : warningExternalWebsite(props.url)"
 	@click.stop
 >
 	<template v-if="!self">
@@ -28,12 +29,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
 import { toUnicode as decodePunycode } from 'punycode/';
-import { url as local } from '@/config.js';
+import { url as local } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
-import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
 import { isEnabledUrlPreview } from '@/instance.js';
 import { MkABehavior } from '@/components/global/MkA.vue';
+import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
+
+function safeURIDecode(str: string): string {
+	try {
+		return decodeURIComponent(str);
+	} catch {
+		return str;
+	}
+}
 
 const props = withDefaults(defineProps<{
 	url: string;
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 44d8d59941b3fac3379c28c2422f4e9cd747ee48..b36625ed1b47254dad6dec8995e9ba4305db0ec5 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/config.ts b/packages/frontend/src/config.ts
deleted file mode 100644
index e3922a0cd5a7f1f609d572420231aa7950ed726c..0000000000000000000000000000000000000000
--- a/packages/frontend/src/config.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { miLocalStorage } from '@/local-storage.js';
-
-const address = new URL(document.querySelector<HTMLMetaElement>('meta[property="instance_url"]')?.content || location.href);
-const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
-
-export const host = address.host;
-export const hostname = address.hostname;
-export const url = address.origin;
-export const apiUrl = location.origin + '/api';
-export const wsOrigin = location.origin;
-export const lang = miLocalStorage.getItem('lang') ?? 'en-US';
-export const langs = _LANGS_;
-const preParseLocale = miLocalStorage.getItem('locale');
-export let locale = preParseLocale ? JSON.parse(preParseLocale) : null;
-export const version = _VERSION_;
-export const instanceName = siteName === 'Sharkey' || siteName == null ? host : siteName;
-export const ui = miLocalStorage.getItem('ui');
-export const debug = miLocalStorage.getItem('debug') === 'true';
-
-export function updateLocale(newLocale): void {
-	locale = newLocale;
-}
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
index 9da3582e1a616dc4fbb34b197a82a9fed2658007..0d03282cee22c8f4fced75f2d4eb5eb756bbe7a7 100644
--- a/packages/frontend/src/custom-emojis.ts
+++ b/packages/frontend/src/custom-emojis.ts
@@ -6,7 +6,6 @@
 import { shallowRef, computed, markRaw, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useStream } from '@/stream.js';
 import { get, set } from '@/scripts/idb-proxy.js';
 
 const storageCache = await get('emojis');
@@ -29,23 +28,20 @@ watch(customEmojis, emojis => {
 	}
 }, { immediate: true });
 
-// TODO: ここら辺副作用なのでいい感じにする
-const stream = useStream();
-
-stream.on('emojiAdded', emojiData => {
-	customEmojis.value = [emojiData.emoji, ...customEmojis.value];
+export function addCustomEmoji(emoji: Misskey.entities.EmojiSimple) {
+	customEmojis.value = [emoji, ...customEmojis.value];
 	set('emojis', customEmojis.value);
-});
+}
 
-stream.on('emojiUpdated', emojiData => {
-	customEmojis.value = customEmojis.value.map(item => emojiData.emojis.find(search => search.name === item.name) as Misskey.entities.EmojiSimple ?? item);
+export function updateCustomEmojis(emojis: Misskey.entities.EmojiSimple[]) {
+	customEmojis.value = customEmojis.value.map(item => emojis.find(search => search.name === item.name) ?? item);
 	set('emojis', customEmojis.value);
-});
+}
 
-stream.on('emojiDeleted', emojiData => {
-	customEmojis.value = customEmojis.value.filter(item => !emojiData.emojis.some(search => search.name === item.name));
+export function removeCustomEmojis(emojis: Misskey.entities.EmojiSimple[]) {
+	customEmojis.value = customEmojis.value.filter(item => !emojis.some(search => search.name === item.name));
 	set('emojis', customEmojis.value);
-});
+}
 
 export async function fetchCustomEmojis(force = false) {
 	const now = Date.now();
diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts
index f200f242ed23621d44a5d91a697b4039269762b0..615dd99fa8d4d384520b81eb78efc03762d3e293 100644
--- a/packages/frontend/src/directives/follow-append.ts
+++ b/packages/frontend/src/directives/follow-append.ts
@@ -4,7 +4,7 @@
  */
 
 import { Directive } from 'vue';
-import { getScrollContainer, getScrollPosition } from '@/scripts/scroll.js';
+import { getScrollContainer, getScrollPosition } from '@@/js/scroll.js';
 
 export default {
 	mounted(src, binding, vn) {
diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts
index 2ffe93e868a2cc2905a34411e366cdee2c4640cc..d13d1a5e428ac29c474500fb6d5b2a00aad76cda 100644
--- a/packages/frontend/src/filters/date.ts
+++ b/packages/frontend/src/filters/date.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { dateTimeFormat } from '@/scripts/intl-const.js';
+import { dateTimeFormat } from '@@/js/intl-const.js';
 
 export default (d: Date | number | undefined) => dateTimeFormat.format(d);
 export const dateString = (d: string) => dateTimeFormat.format(new Date(d));
diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts
index 2e7cc60ff43dc4dff5937087a30bc6633fab65a9..10fb64deb4d703b1303aae7c11c5706fb4cb46cc 100644
--- a/packages/frontend/src/filters/number.ts
+++ b/packages/frontend/src/filters/number.ts
@@ -3,6 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { numberFormat } from '@/scripts/intl-const.js';
+import { numberFormat } from '@@/js/intl-const.js';
 
 export default n => n == null ? 'N/A' : numberFormat.format(n);
diff --git a/packages/frontend/src/filters/user.ts b/packages/frontend/src/filters/user.ts
index a87766764daed628c39e4b17dff106d8c330ec80..d9bc31676464e1c09da2b5d0115e15c5170f9682 100644
--- a/packages/frontend/src/filters/user.ts
+++ b/packages/frontend/src/filters/user.ts
@@ -4,7 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 
 export const acct = (user: Misskey.Acct) => {
 	return Misskey.acct.toString(user);
diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts
index 10d6adbcd0ca9d203c748505aa108a8fc401ccc9..6ad503b0898a53004660609444b91ddaba9ea160 100644
--- a/packages/frontend/src/i18n.ts
+++ b/packages/frontend/src/i18n.ts
@@ -4,11 +4,11 @@
  */
 
 import { markRaw } from 'vue';
+import { I18n } from '@@/js/i18n.js';
 import type { Locale } from '../../../locales/index.js';
-import { locale } from '@/config.js';
-import { I18n } from '@/scripts/i18n.js';
+import { locale } from '@@/js/config.js';
 
-export const i18n = markRaw(new I18n<Locale>(locale));
+export const i18n = markRaw(new I18n<Locale>(locale, _DEV_));
 
 export function updateI18n(newLocale: Locale) {
 	i18n.locale = newLocale;
diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html
index 733116b75f26e4c21344f1df860f9d69bd716402..55d6b6cffde8789cb20f839f422a8386661a8f1e 100644
--- a/packages/frontend/src/index.html
+++ b/packages/frontend/src/index.html
@@ -17,12 +17,12 @@
 	<meta
 		http-equiv="Content-Security-Policy"
 		content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
-			worker-src 'self';
-			script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh;
+			worker-src 'self' blob:;
+			script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com https://esm.sh https://cdn.jsdelivr.net;
 			style-src 'self' 'unsafe-inline';
-			img-src 'self' data: blob: www.google.com xn--931a.moe launcher.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 activitypub.software secure.gravatar.com avatars.githubusercontent.com;
+			img-src 'self' data: blob: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 activitypub.software secure.gravatar.com avatars.githubusercontent.com;
 			media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
-			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.listenbrainz.org;
+			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com https://api.listenbrainz.org https://api.friendlycaptcha.com;
 			frame-src *;"
 	/>
 	<meta property="og:site_name" content="[DEV BUILD] Misskey" />
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index 6847321d6c40d992191eef107ba740e2cf905998..71cb42b30c45f0840f5594ab97c43217321b51e5 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -7,7 +7,7 @@ import { computed, reactive } from 'vue';
 import * as Misskey from 'misskey-js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { miLocalStorage } from '@/local-storage.js';
-import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js';
+import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@@/js/const.js';
 
 // TODO: 他のタブと永続化されたstateを同期
 
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index 2b2f59edb35c08971da4a9014174267966c9d0d5..89c0a4b8496184d3bb53f8563ca7767fc53296be 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-type Keys =
+export type Keys =
 	'v' |
 	'lastVersion' |
 	'instance' |
@@ -39,12 +39,22 @@ type Keys =
 	`aiscript:${string}` |
 	'lastEmojisFetchedAt' | // DEPRECATED, stored in indexeddb (13.9.0~)
 	'emojis' | // DEPRECATED, stored in indexeddb (13.9.0~);
-	`channelLastReadedAt:${string}`
+	`channelLastReadedAt:${string}` |
+	`idbfallback::${string}`
+
+// セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう)
+//const safeSessionStorage = new Map<Keys, string>();
 
 export const miLocalStorage = {
-	getItem: (key: Keys): string | null => window.localStorage.getItem(key),
-	setItem: (key: Keys, value: string): void => window.localStorage.setItem(key, value),
-	removeItem: (key: Keys): void => window.localStorage.removeItem(key),
+	getItem: (key: Keys): string | null => {
+		return window.localStorage.getItem(key);
+	},
+	setItem: (key: Keys, value: string): void => {
+		window.localStorage.setItem(key, value);
+	},
+	removeItem: (key: Keys): void => {
+		window.localStorage.removeItem(key);
+	},
 	getItemAsJson: (key: Keys): any | undefined => {
 		const item = miLocalStorage.getItem(key);
 		if (item === null) {
@@ -52,5 +62,7 @@ export const miLocalStorage = {
 		}
 		return JSON.parse(item);
 	},
-	setItemAsJson: (key: Keys, value: any): void => window.localStorage.setItem(key, JSON.stringify(value)),
+	setItemAsJson: (key: Keys, value: any): void => {
+		miLocalStorage.setItem(key, JSON.stringify(value));
+	},
 };
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index ccd7034968ce9a3b7d5cd18094dd229696f8cd81..6f236dc89e79ddcb0aa79bf2f60bce0632882ca1 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -12,7 +12,7 @@ import { openInstanceMenu } from '@/ui/_common_/common.js';
 import { lookup } from '@/scripts/lookup.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { ui } from '@/config.js';
+import { ui } from '@@/js/config.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 
 export const navbarItemDef = reactive({
@@ -41,7 +41,7 @@ export const navbarItemDef = reactive({
 	followRequests: {
 		title: i18n.ts.followRequests,
 		icon: 'ti ti-user-plus',
-		show: computed(() => $i != null && $i.isLocked),
+		show: computed(() => $i != null && ($i.isLocked || $i.hasPendingReceivedFollowRequest || $i.hasPendingSentFollowRequest)),
 		indicated: computed(() => $i != null && $i.hasPendingReceivedFollowRequest),
 		to: '/my/follow-requests',
 	},
@@ -68,6 +68,12 @@ export const navbarItemDef = reactive({
 			lookup();
 		},
 	},
+	following: {
+		title: i18n.ts.following,
+		icon: 'ph-user-check ph-bold ph-lg',
+		show: computed(() => $i != null && !$i.movedTo),
+		to: '/following-feed',
+	},
 	lists: {
 		title: i18n.ts.lists,
 		icon: 'ti ti-list',
@@ -126,7 +132,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 6a8ea09ed6af93d1bbef30fbfbec04f88f41c48b..25f853453a0dd67fbaec57742d325c453b9e9d8f 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/nirax.ts
@@ -7,7 +7,14 @@
 
 import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
 import { EventEmitter } from 'eventemitter3';
-import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
+
+function safeURIDecode(str: string): string {
+	try {
+		return decodeURIComponent(str);
+	} catch {
+		return str;
+	}
+}
 
 interface RouteDefBase {
 	path: string;
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index f6f4d62d50f5657452abf76cc9fadaae610c9878..e73aa77a3cc157cefeefa42e1b6d8a2e6a188e91 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -22,7 +22,7 @@ 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 type { 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';
@@ -245,6 +245,7 @@ export function confirm(props: {
 	text?: string;
 	okText?: string;
 	cancelText?: string;
+	plain?: boolean;
 }): Promise<{ canceled: boolean }> {
 	return new Promise(resolve => {
 		const { dispose } = popup(MkDialog, {
@@ -691,7 +692,7 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
 	}));
 }
 
-export function post(props: Record<string, any> = {}): Promise<void> {
+export function post(props: Record<string, any> = {}): Promise<void | boolean> {
 	pleaseLogin(undefined, (props.initialText || props.initialNote ? {
 		type: 'share',
 		params: {
@@ -709,8 +710,8 @@ export function post(props: Record<string, any> = {}): Promise<void> {
 		//       複数のpost formを開いたときに場合によってはエラーになる
 		//       もちろん複数のpost formを開けること自体Misskeyサイドのバグなのだが
 		const { dispose } = popup(MkPostFormDialog, props, {
-			closed: () => {
-				resolve();
+			closed: (cancelled) => {
+				resolve(cancelled);
 				dispose();
 			},
 		});
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index dbc113a7664d2c45a035497a13886df03776944b..83f596b870c6bcedcc6e38a161be19b2197135de 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -29,7 +29,7 @@ import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkLink from '@/components/MkLink.vue';
-import { version } from '@/config.js';
+import { version } from '@@/js/config.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/about-sharkey.vue b/packages/frontend/src/pages/about-sharkey.vue
index 938786949c8178616b0020a82498189d304544c5..7ebae62a7b2ef5a2109c4edb705ef82e8642c029 100644
--- a/packages/frontend/src/pages/about-sharkey.vue
+++ b/packages/frontend/src/pages/about-sharkey.vue
@@ -185,7 +185,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { nextTick, onBeforeUnmount, ref, shallowRef, computed } from 'vue';
-import { version } from '@/config.js';
+import { version } from '@@/js/config.js';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue
index c378c0a0b8d29802ca4067c3296445b7c8029969..ca070c65a4f39af6d5224a082f82dfee5794dc3f 100644
--- a/packages/frontend/src/pages/about.overview.vue
+++ b/packages/frontend/src/pages/about.overview.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div class="_gaps_m">
 	<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
 		<div style="overflow: clip;">
-			<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
+			<img :src="instance.sidebarLogoUrl ?? instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
 			<div :class="$style.bannerName">
 				<b>{{ instance.name ?? host }}</b>
 			</div>
@@ -116,6 +116,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</FormSection>
 	</FormSuspense>
 
+	<FormSection v-if="sponsors[0].length > 0">
+		<template #label>Our lovely Sponsors</template>
+		<div :class="$style.contributors">
+			<span
+				v-for="sponsor in sponsors[0]"
+				:key="sponsor"
+				style="margin-bottom: 0.5rem;"
+			>
+				<a :href="sponsor.website || sponsor.profile" target="_blank" :class="$style.contributor">
+					<img :src="sponsor.image || `https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=${sponsor.name}`" :class="$style.contributorAvatar">
+					<span :class="$style.contributorUsername">{{ sponsor.name }}</span>
+				</a>
+			</span>
+		</div>
+	</FormSection>
+
 	<FormSection>
 		<template #label>Well-known resources</template>
 		<div class="_gaps_s">
@@ -130,8 +146,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import sanitizeHtml from '@/scripts/sanitize-html.js';
-import { host, version } from '@/config.js';
+import { host, version } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import number from '@/filters/number.js';
@@ -144,7 +161,10 @@ import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkLink from '@/components/MkLink.vue';
 
+const sponsors = ref([]);
+
 const initStats = () => misskeyApi('stats', {});
+await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.push(res.sponsor_data));
 </script>
 
 <style lang="scss" module>
@@ -160,7 +180,7 @@ const initStats = () => misskeyApi('stats', {});
 .bannerIcon {
 	display: block;
 	margin: 16px auto 0 auto;
-	height: 64px;
+	max-height: 96px;
 	border-radius: var(--radius-sm);;
 }
 
@@ -207,4 +227,37 @@ const initStats = () => misskeyApi('stats', {});
 .ruleText {
 	padding-top: 6px;
 }
+
+.contributors {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+	grid-gap: 12px;
+}
+
+.contributor {
+	display: flex;
+	align-items: center;
+	padding: 12px;
+	background: var(--buttonBg);
+	border-radius: var(--radius-sm);
+
+	&:hover {
+		text-decoration: none;
+		background: var(--buttonHoverBg);
+	}
+
+	&.active {
+		color: var(--accent);
+		background: var(--buttonHoverBg);
+	}
+}
+
+.contributorAvatar {
+	width: 30px;
+	border-radius: var(--radius-full);
+}
+
+.contributorUsername {
+	margin-left: 12px;
+}
 </style>
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index d8311186ab84676851af70999185278b9d3bf81c..60f6be51d42dc95770d50192c7c2c0d93937dca0 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 187ec66b423a5c1cda81de3469502909331a8fb7..22e16effe0272fde1e06a7c69ac0736ddf6266e8 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</div>
 				</div>
 
-				<MkInfo v-if="['instance.actor', 'relay.actor'].includes(user.username)">{{ i18n.ts.isSystemAccount }}</MkInfo>
+				<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
 
 				<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
 
@@ -79,11 +79,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<FormSection>
 					<div class="_gaps">
 						<MkSwitch v-model="silenced" @update:modelValue="toggleSilence">{{ i18n.ts.silence }}</MkSwitch>
-						<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
+						<MkSwitch v-if="!isSystem" v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
 						<MkSwitch v-model="markedAsNSFW" @update:modelValue="toggleNSFW">{{ i18n.ts.markAsNSFW }}</MkSwitch>
 
 						<div>
-							<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
+							<MkButton v-if="user.host == null && !isSystem" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
 						</div>
 
 						<MkFolder>
@@ -114,7 +114,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
 							<MkButton v-if="iAmModerator" inline danger @click="deleteAllFiles"><i class="ph-cloud ph-bold ph-lg"></i> {{ i18n.ts.deleteAllFiles }}</MkButton>
 						</div>
-						<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
+						<MkButton v-if="$i.isAdmin && !isSystem" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
 					</div>
 				</FormSection>
 			</div>
@@ -138,7 +138,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 
 			<div v-else-if="tab === 'announcements'" class="_gaps">
-				<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
+				<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts._announcement.new }}</MkButton>
 
 				<MkPagination :pagination="announcementsPagination">
 					<template #default="{ items }">
@@ -208,7 +208,7 @@ 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 '@/config.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';
@@ -227,15 +227,16 @@ const tab = ref(props.initialTab);
 const chartSrc = ref('per-user-notes');
 const user = ref<null | Misskey.entities.UserDetailed>();
 const init = ref<ReturnType<typeof createFetcher>>();
-const info = ref<any>();
+const info = ref<Misskey.entities.AdminShowUserResponse | null>(null);
 const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
-const ap = ref<any>(null);
+const ap = ref<Misskey.entities.ApGetResponse | null>(null);
 const moderator = ref(false);
 const silenced = ref(false);
 const approved = ref(false);
 const suspended = ref(false);
 const markedAsNSFW = ref(false);
 const moderationNote = ref('');
+const isSystem = computed(() => info.value?.isSystem ?? false);
 const filesPagination = {
 	endpoint: 'admin/drive/files' as const,
 	limit: 10,
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index 61dc4f854921bf43acb0466c1440f149ba9a5f66..2a71e3efab3baa6a318d4a4fbc137398c1e68020 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, onMounted, onUnmounted, ref, shallowRef, watch, nextTick } from 'vue';
 import tinycolor from 'tinycolor2';
 import { popupMenu } from '@/os.js';
-import { scrollToTop } from '@/scripts/scroll.js';
+import { scrollToTop } from '@@/js/scroll.js';
 import MkButton from '@/components/MkButton.vue';
 import { globalEvents } from '@/events.js';
 import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index bd442ccc691a4b4dcf0b0d39b854c52168847f42..6c8901b10b9363fdbb8e20edee35e1b24b40facb 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>
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index b9e09c8d03a6c7be47441bc00bba2d462ab484fb..fd37311b21c4291e6c3580adc7d0763fd9a9fcc8 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -29,8 +29,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--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>
@@ -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 73c5e1919f41af20678e745ad0e39743846688b9..644436cde6d6d6a90f0602c37655eb4f26fd51e1 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -4,145 +4,166 @@ 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 === 'fc'" #suffix>FriendlyCaptcha</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="fc">FriendlyCaptcha</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 === 'fc'">
+			<MkInput v-model="botProtectionForm.state.fcSiteKey">
+				<template #prefix><i class="ti ti-key"></i></template>
+				<template #label>{{ i18n.ts.hcaptchaSiteKey }}</template>
+			</MkInput>
+			<MkInput v-model="botProtectionForm.state.fcSecretKey">
+				<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="fc" :sitekey="botProtectionForm.state.fcSiteKey"/>
+			</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';
 
 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.enableFC
+						? 'fc'
+						: 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,
+	fcSiteKey: meta.fcSiteKey,
+	fcSecretKey: meta.fcSecretKey,
+}, 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,
+		enableFC: state.provider === 'fc',
+		fcSiteKey: state.fcSiteKey,
+		fcSecretKey: state.fcSecretKey,
 	});
-}
+	fetchInstance(true);
+});
 </script>
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 2e14aef0b946e63c7d14206d4b5ec8c98f91a03c..d3d52002fe325352ef23b808e8fade39767561ac 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -37,6 +37,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</template>
 					</MkInput>
 
+					<MkInput v-model="sidebarLogoUrl" type="url">
+						<template #prefix><i class="ti ti-link"></i></template>
+						<template #label>{{ i18n.ts._serverSettings.sidebarLogoUrl }}</template>
+						<template #caption>
+							<div>{{ i18n.ts._serverSettings.sidebarLogoDescription }}</div>
+							<div>({{ i18n.ts._serverSettings.sidebarLogoUsageExample }})</div>
+						</template>
+					</MkInput>
+
 					<MkInput v-model="bannerUrl" type="url">
 						<template #prefix><i class="ti ti-link"></i></template>
 						<template #label>{{ i18n.ts.bannerUrl }}</template>
@@ -125,9 +134,10 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 import MkColorInput from '@/components/MkColorInput.vue';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 
 const iconUrl = ref<string | null>(null);
+const sidebarLogoUrl = ref<string | null>(null);
 const app192IconUrl = ref<string | null>(null);
 const app512IconUrl = ref<string | null>(null);
 const bannerUrl = ref<string | null>(null);
@@ -146,6 +156,7 @@ const manifestJsonOverride = ref<string>('{}');
 async function init() {
 	const meta = await misskeyApi('admin/meta');
 	iconUrl.value = meta.iconUrl;
+	sidebarLogoUrl.value = meta.sidebarLogoUrl;
 	app192IconUrl.value = meta.app192IconUrl;
 	app512IconUrl.value = meta.app512IconUrl;
 	bannerUrl.value = meta.bannerUrl;
@@ -165,6 +176,7 @@ async function init() {
 function save() {
 	os.apiWithDialog('admin/update-meta', {
 		iconUrl: iconUrl.value,
+		sidebarLogoUrl: sidebarLogoUrl.value,
 		app192IconUrl: app192IconUrl.value,
 		app512IconUrl: app512IconUrl.value,
 		bannerUrl: bannerUrl.value,
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index 4a858887f353a0c0774e39ef25d36df4383fad3f..ddfe5ae81f7b8573b1b7ecb7d1d2368c81ab9aee 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -100,7 +100,7 @@ async function init() {
 
 async function testEmail() {
 	const { canceled, result: destination } = await os.inputText({
-		title: i18n.ts.destination,
+		title: i18n.ts.emailDestination,
 		type: 'email',
 		default: instance.maintainerEmail ?? '',
 		placeholder: 'test@example.com',
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index e4308e6030a0c0dfd2f7e4beaf328332d41b4b94..50e2c2dd5149c0ed5b43eaff46e88adb49f5d145 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,6 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkSwitch v-model="deeplIsPro">
 						<template #label>Pro account</template>
 					</MkSwitch>
+
 					<MkSwitch v-model="deeplFreeMode">
 						<template #label>{{ i18n.ts.deeplFreeMode }}</template>
 					</MkSwitch>
@@ -27,17 +28,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #label>DeepLX-JS URL</template>
 						<template #caption>{{ i18n.ts.deeplFreeModeDescription }}</template>
 					</MkInput>
+
+					<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>
 
@@ -48,12 +44,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);
@@ -68,7 +64,7 @@ async function init() {
 	deeplFreeInstance.value = meta.deeplFreeInstance;
 }
 
-function save() {
+function save_deepl() {
 	os.apiWithDialog('admin/update-meta', {
 		deeplAuthKey: deeplAuthKey.value,
 		deeplIsPro: deeplIsPro.value,
@@ -88,10 +84,3 @@ definePageMetadata(() => ({
 	icon: 'ph-arrow-square-out ph-bold ph-lg',
 }));
 </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/index.vue b/packages/frontend/src/pages/admin/index.vue
index f547bedacb12d358921871b0b61dde16cfcb1349..e8d123060aeae4d8ecc804f8ca20348b7c80a481 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -63,7 +63,7 @@ const view = ref(null);
 const el = ref<HTMLDivElement | null>(null);
 const pageProps = ref({});
 const noMaintainerInformation = computed(() => isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail));
-const noBotProtection = computed(() => !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile && !instance.enableMcaptcha);
+const noBotProtection = computed(() => !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile && !instance.enableMcaptcha && !instance.enableFC);
 const noEmailServer = computed(() => !instance.enableEmail);
 const noInquiryUrl = computed(() => isEmpty(instance.inquiryUrl));
 const thereIsUnresolvedAbuseReport = ref(false);
@@ -214,16 +214,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: 'ph-arrow-square-out ph-bold ph-lg',
 		text: i18n.ts.externalServices,
@@ -235,10 +225,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,
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 e090616b26a2437fd37b4d1640113ffed7c40eeb..0000000000000000000000000000000000000000
--- 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 6297b9a1828e470e13fb80c0532cc48cdee328c8..bbcf2a6f77dbc6ff0694404accb4874fa12c207f 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -10,70 +10,130 @@ 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>
 					</MkSwitch>
 
-					<MkSwitch v-model="emailRequiredForSignup">
+					<MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup">
 						<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
 					</MkSwitch>
 
-					<MkSwitch v-model="approvalRequiredForSignup">
+					<MkSwitch v-model="approvalRequiredForSignup" @change="onChange_approvalRequiredForSignup">
 						<template #label>{{ i18n.ts.approvalRequiredForSignup }}</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>
-
-					<MkTextarea v-if="bubbleTimelineEnabled" v-model="bubbleTimeline">
+					<MkFolder v-if="bubbleTimelineEnabled">
+						<template #icon><i class="ph-drop ph-bold ph-lg"></i></template>
 						<template #label>Bubble timeline</template>
-						<template #caption>Choose which instances should be displayed in the bubble.</template>
-					</MkTextarea>
 
-					<MkInput v-model="inquiryUrl" type="url">
+						<div class="_gaps">
+							<MkTextarea v-model="bubbleTimeline">
+								<template #caption>Choose which instances should be displayed in the bubble.</template>
+							</MkTextarea>
+							<MkButton primary @click="save_bubbleTimeline">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
+
+					<MkFolder>
 						<template #prefix><i class="ti ti-link"></i></template>
-						<template #label>{{ i18n.ts._serverSettings.inquiryUrl }}</template>
-						<template #caption>{{ i18n.ts._serverSettings.inquiryUrlDescription }}</template>
-					</MkInput>
+						<template #label>{{ i18n.ts.trustedLinkUrlPatterns }}</template>
+
+						<div class="_gaps">
+							<MkTextarea v-model="trustedLinkUrlPatterns">
+								<template #caption>{{ i18n.ts.trustedLinkUrlPatternsDescription }}</template>
+							</MkTextarea>
+							<MkButton primary @click="save_trustedLinkUrlPatterns">{{ i18n.ts.save }}</MkButton>
+						</div>
+					</MkFolder>
 
-					<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-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>
@@ -92,6 +152,7 @@ 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);
@@ -102,9 +163,10 @@ const prohibitedWords = ref<string>('');
 const hiddenTags = ref<string>('');
 const preservedUsernames = ref<string>('');
 const bubbleTimeline = ref<string>('');
-const tosUrl = ref<string | null>(null);
-const privacyPolicyUrl = ref<string | null>(null);
-const inquiryUrl = ref<string | null>(null);
+const trustedLinkUrlPatterns = ref<string>('');
+const blockedHosts = ref<string>('');
+const silencedHosts = ref<string>('');
+const mediaSilencedHosts = ref<string>('');
 
 async function init() {
 	const meta = await misskeyApi('admin/meta');
@@ -115,26 +177,105 @@ async function init() {
 	prohibitedWords.value = meta.prohibitedWords.join('\n');
 	hiddenTags.value = meta.hiddenTags.join('\n');
 	preservedUsernames.value = meta.preservedUsernames.join('\n');
-	tosUrl.value = meta.tosUrl;
-	privacyPolicyUrl.value = meta.privacyPolicyUrl;
 	bubbleTimeline.value = meta.bubbleInstances.join('\n');
 	bubbleTimelineEnabled.value = meta.policies.btlAvailable;
-	inquiryUrl.value = meta.inquiryUrl;
+	trustedLinkUrlPatterns.value = meta.trustedLinkUrlPatterns.join('\n');
+	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 onChange_approvalRequiredForSignup(value: boolean) {
+	os.apiWithDialog('admin/update-meta', {
+		approvalRequiredForSignup: value,
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_bubbleTimeline() {
+	os.apiWithDialog('admin/update-meta', {
+		bubbleInstances: bubbleTimeline.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_trustedLinkUrlPatterns() {
+	os.apiWithDialog('admin/update-meta', {
+		trustedLinkUrlPatterns: trustedLinkUrlPatterns.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
 }
 
-function save() {
+function save_preservedUsernames() {
+	os.apiWithDialog('admin/update-meta', {
+		preservedUsernames: preservedUsernames.value.split('\n'),
+	}).then(() => {
+		fetchInstance(true);
+	});
+}
+
+function save_sensitiveWords() {
 	os.apiWithDialog('admin/update-meta', {
-		disableRegistration: !enableRegistration.value,
-		emailRequiredForSignup: emailRequiredForSignup.value,
-		approvalRequiredForSignup: approvalRequiredForSignup.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_hiddenTags() {
+	os.apiWithDialog('admin/update-meta', {
 		hiddenTags: hiddenTags.value.split('\n'),
-		preservedUsernames: preservedUsernames.value.split('\n'),
-		bubbleInstances: bubbleTimeline.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);
 	});
@@ -147,10 +288,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 f6f276de53a54bfa85a35a2f40ce379b704b028c..6c81155c51ea1ef9172c1432721859804745421b 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -23,10 +23,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 					'markSensitiveDriveFile',
 					'resetPassword',
 					'suspendRemoteInstance',
+					'setRemoteInstanceNSFW',
+					'unsetRemoteInstanceNSFW',
+					'rejectRemoteInstanceReports',
+					'acceptRemoteInstanceReports',
 				].includes(log.type),
 				[$style.logRed]: [
 					'suspend',
 					'approve',
+					'decline',
 					'deleteRole',
 					'deleteGlobalAnnouncement',
 					'deleteUserAnnouncement',
@@ -47,6 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-if="log.type === 'updateUserNote'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'suspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'approve'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+		<span v-else-if="log.type === 'decline'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-arrow-right"></i> {{ log.info.roleName }}</span>
@@ -61,6 +67,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-else-if="log.type === 'unmarkSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
 		<span v-else-if="log.type === 'suspendRemoteInstance'">: {{ log.info.host }}</span>
 		<span v-else-if="log.type === 'unsuspendRemoteInstance'">: {{ log.info.host }}</span>
+		<span v-else-if="log.type === 'setRemoteInstanceNSFW'">: {{ log.info.host }}</span>
+		<span v-else-if="log.type === 'unsetRemoteInstanceNSFW'">: {{ log.info.host }}</span>
+		<span v-else-if="log.type === 'rejectRemoteInstanceReports'">: {{ log.info.host }}</span>
+		<span v-else-if="log.type === 'acceptRemoteInstanceReports'">: {{ log.info.host }}</span>
 		<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
 		<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
 		<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
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 a92034f2d7c601c091bb69255c56e41bd98d132e..0000000000000000000000000000000000000000
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ /dev/null
@@ -1,113 +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="enableAchievements">
-						<template #label>{{ i18n.ts.enableAchievements }}</template>
-						<template #caption>{{ i18n.ts.turnOffAchievements}}</template>
-					</MkSwitch>
-				</div>
-
-				<div class="_panel" style="padding: 16px;">
-					<MkSwitch v-model="enableBotTrending">
-						<template #label>{{ i18n.ts.enableBotTrending }}</template>
-						<template #caption>{{ i18n.ts.turnOffBotTrending }}</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 enableAchievements = ref<boolean>(false);
-const enableBotTrending = 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;
-	enableAchievements.value = meta.enableAchievements;
-	enableBotTrending.value = meta.enableBotTrending;
-	enableIdenticonGeneration.value = meta.enableIdenticonGeneration;
-	enableChartsForRemoteUser.value = meta.enableChartsForRemoteUser;
-	enableChartsForFederatedInstances.value = meta.enableChartsForFederatedInstances;
-}
-
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		enableServerMachineStats: enableServerMachineStats.value,
-		enableAchievements: enableAchievements.value,
-		enableBotTrending: enableBotTrending.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 0000000000000000000000000000000000000000..584cd3e4d954fad9cc2b13cb84963899fe29c14f
--- /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 d4c83f21b68571001245fb69034b25b9f4b774be..4bbb9210afa6264bdfc0ba413d9f5a2cbcd04f29 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,
 			},
diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue
index a09db2a6d52805ca1e54a26a4ae9b16850f401cd..292e2e1dbc9f94f098eac66e5214508348bc357e 100644
--- a/packages/frontend/src/pages/admin/overview.instances.vue
+++ b/packages/frontend/src/pages/admin/overview.instances.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as Misskey from 'misskey-js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index c7478f252a269ccc1028aeed4d96fba7e4b09786..fb190f53258bc180dda4755bc6cc89801b39e7b7 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -36,7 +36,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
+import * as Misskey from 'misskey-js';
 import XChart from './overview.queue.chart.vue';
+import type { ApQueueDomain } from '@/pages/admin/queue.vue';
 import number from '@/filters/number.js';
 import { useStream } from '@/stream.js';
 
@@ -52,10 +54,10 @@ const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
 const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
 
 const props = defineProps<{
-	domain: string;
+	domain: ApQueueDomain;
 }>();
 
-const onStats = (stats) => {
+function onStats(stats: Misskey.entities.QueueStats) {
 	activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
 	active.value = stats[props.domain].active;
 	delayed.value = stats[props.domain].delayed;
@@ -65,13 +67,13 @@ const onStats = (stats) => {
 	chartActive.value.pushData(stats[props.domain].active);
 	chartDelayed.value.pushData(stats[props.domain].delayed);
 	chartWaiting.value.pushData(stats[props.domain].waiting);
-};
+}
 
-const onStatsLog = (statsLog) => {
-	const dataProcess = [];
-	const dataActive = [];
-	const dataDelayed = [];
-	const dataWaiting = [];
+function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
+	const dataProcess: Misskey.entities.QueueStats[ApQueueDomain]['activeSincePrevTick'][] = [];
+	const dataActive: Misskey.entities.QueueStats[ApQueueDomain]['active'][] = [];
+	const dataDelayed: Misskey.entities.QueueStats[ApQueueDomain]['delayed'][] = [];
+	const dataWaiting: Misskey.entities.QueueStats[ApQueueDomain]['waiting'][] = [];
 
 	for (const stats of [...statsLog].reverse()) {
 		dataProcess.push(stats[props.domain].activeSincePrevTick);
@@ -84,7 +86,7 @@ const onStatsLog = (statsLog) => {
 	chartActive.value.setData(dataActive);
 	chartDelayed.value.setData(dataDelayed);
 	chartWaiting.value.setData(dataWaiting);
-};
+}
 
 onMounted(() => {
 	connection.on('stats', onStats);
diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue
index 408be88d4792282d93439bcebe11b047dfb0644c..8c9d7a8197a23a6e749959b9aef34326c04e4d46 100644
--- a/packages/frontend/src/pages/admin/overview.users.vue
+++ b/packages/frontend/src/pages/admin/overview.users.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as Misskey from 'misskey-js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import { defaultStore } from '@/store.js';
 
@@ -47,14 +47,14 @@ useInterval(fetch, 1000 * 60, {
 .root {
 	&:global {
 		> .users {
-			.chart-move {
-				transition: transform 1s ease;
-			}
-
 			display: grid;
 			grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
 			grid-gap: 12px;
 
+			.chart-move {
+				transition: transform 1s ease;
+			}
+
 			> .user:hover {
 				text-decoration: none;
 			}
diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7e0a932f82925dec516f80a6996c25e053b3b566
--- /dev/null
+++ b/packages/frontend/src/pages/admin/performance.vue
@@ -0,0 +1,193 @@
+<!--
+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="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 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_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 81db9f1da9c213a4b75ec7fff909c163892c50a8..0000000000000000000000000000000000000000
--- 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 8d3fe35320e31917340bf530cd9b87e96e560484..960a263a8628fedf82d10755d6065fbeaf75be29 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -49,7 +49,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
+import * as Misskey from 'misskey-js';
 import XChart from './queue.chart.chart.vue';
+import type { ApQueueDomain } from '@/pages/admin/queue.vue';
 import number from '@/filters/number.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
@@ -62,17 +64,17 @@ const activeSincePrevTick = ref(0);
 const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
-const jobs = ref<(string | number)[][]>([]);
+const jobs = ref<Misskey.Endpoints[`admin/queue/${ApQueueDomain}-delayed`]['res']>([]);
 const chartProcess = shallowRef<InstanceType<typeof XChart>>();
 const chartActive = shallowRef<InstanceType<typeof XChart>>();
 const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
 const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
 
 const props = defineProps<{
-	domain: string;
+	domain: ApQueueDomain;
 }>();
 
-const onStats = (stats) => {
+function onStats(stats: Misskey.entities.QueueStats) {
 	activeSincePrevTick.value = stats[props.domain].activeSincePrevTick;
 	active.value = stats[props.domain].active;
 	delayed.value = stats[props.domain].delayed;
@@ -82,13 +84,13 @@ const onStats = (stats) => {
 	chartActive.value.pushData(stats[props.domain].active);
 	chartDelayed.value.pushData(stats[props.domain].delayed);
 	chartWaiting.value.pushData(stats[props.domain].waiting);
-};
+}
 
-const onStatsLog = (statsLog) => {
-	const dataProcess = [];
-	const dataActive = [];
-	const dataDelayed = [];
-	const dataWaiting = [];
+function onStatsLog(statsLog: Misskey.entities.QueueStatsLog) {
+	const dataProcess: Misskey.entities.QueueStats[ApQueueDomain]['activeSincePrevTick'][] = [];
+	const dataActive: Misskey.entities.QueueStats[ApQueueDomain]['active'][] = [];
+	const dataDelayed: Misskey.entities.QueueStats[ApQueueDomain]['delayed'][] = [];
+	const dataWaiting: Misskey.entities.QueueStats[ApQueueDomain]['waiting'][] = [];
 
 	for (const stats of [...statsLog].reverse()) {
 		dataProcess.push(stats[props.domain].activeSincePrevTick);
@@ -101,14 +103,12 @@ const onStatsLog = (statsLog) => {
 	chartActive.value.setData(dataActive);
 	chartDelayed.value.setData(dataDelayed);
 	chartWaiting.value.setData(dataWaiting);
-};
+}
 
 onMounted(() => {
-	if (props.domain === 'inbox' || props.domain === 'deliver') {
-		misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => {
-			jobs.value = result;
-		});
-	}
+	misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => {
+		jobs.value = result;
+	});
 
 	connection.on('stats', onStats);
 	connection.on('statsLog', onStatsLog);
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 8d77d927d7bfc04310794648d1611f40af7f385e..512039242e304f0933e4afc3677d61575b22e6fe 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -16,16 +16,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, type Ref } from 'vue';
 import XQueue from './queue.chart.vue';
 import XHeader from './_header_.vue';
 import * as os from '@/os.js';
-import * as config from '@/config.js';
+import * as config from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 
-const tab = ref('deliver');
+export type ApQueueDomain = 'deliver' | 'inbox';
+
+const tab: Ref<ApQueueDomain> = ref('deliver');
 
 function clear() {
 	os.confirm({
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 8200244cd79b915de4bbd15e0e03d821562766b8..1763db2323fc3b252a8d48924322d2a0a1e77354 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -630,6 +630,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>
@@ -648,7 +748,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkRange from '@/components/MkRange.vue';
 import FormSlot from '@/components/form/slot.vue';
 import { i18n } from '@/i18n.js';
-import { ROLE_POLICIES } from '@/const.js';
+import { ROLE_POLICIES } from '@@/js/const.js';
 import { instance } from '@/instance.js';
 import { deepClone } from '@/scripts/clone.js';
 
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 0a8bd0e898e5ebc3819f7137ff4a8258d0f13062..00a25446ab64060d62613e5eb5b0443a62a78c2a 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>
@@ -233,7 +236,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>
@@ -259,6 +300,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';
@@ -273,7 +315,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 '@/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 2e80622d7a146f4b324b5ca2cf4d3c23f6b30033..4358821092533adf216e4b02de0cad44ddd96937 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -7,76 +7,71 @@ 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>
+		<div class="_gaps_m">
+			<XBotProtection/>
 
-					<XBotProtection/>
-				</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>
 
-				<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="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>
 
-					<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>
+				<template v-if="bannedEmailDomainsForm.modified.value" #footer>
+					<MkFormFooter :form="bannedEmailDomainsForm"/>
+				</template>
 
-				<MkFolder>
-					<template #label>Banned Email Domains</template>
+				<div class="_gaps_m">
+					<MkTextarea v-model="bannedEmailDomainsForm.state.bannedEmailDomains">
+						<template #label>Banned Email Domains List</template>
+					</MkTextarea>
+				</div>
+			</MkFolder>
 
-					<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="ipLoggingForm.savedState.enableIpLogging" #suffix>Enabled</template>
+				<template v-else #suffix>Disabled</template>
+				<template v-if="ipLoggingForm.modified.value" #footer>
+					<MkFormFooter :form="ipLoggingForm"/>
+				</template>
 
-				<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">
+					<MkSwitch v-model="ipLoggingForm.state.enableIpLogging">
+						<template #label>Enable</template>
+					</MkSwitch>
+				</div>
+			</MkFolder>
+		</div>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -88,60 +83,55 @@ 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';
+import { useForm } from '@/scripts/use-form.js';
+import MkFormFooter from '@/components/MkFormFooter.vue';
+
+const meta = await misskeyApi('admin/meta');
 
-const enableHcaptcha = ref<boolean>(false);
-const enableMcaptcha = ref<boolean>(false);
-const enableRecaptcha = ref<boolean>(false);
-const enableTurnstile = 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>('');
+const ipLoggingForm = useForm({
+	enableIpLogging: meta.enableIpLogging,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableIpLogging: state.enableIpLogging,
+	});
+	fetchInstance(true);
+});
 
-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;
-	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') || '';
-}
+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);
+});
 
-function save() {
-	os.apiWithDialog('admin/update-meta', {
-		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);
+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/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 90ac259c5328a048b6f98728d833df016791325f..04975dcbf30855bb8b21ca1ca7c72235c8dfdbd7 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -8,178 +8,216 @@ 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>
-
-					<MkInput v-model="donationUrl" type="url">
-						<template #label>{{ i18n.ts.donationUrl }}</template>
-						<template #prefix><i class="ph-link ph-bold ph-lg"></i></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>
+						<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>
 
-							<template v-if="cacheRemoteFiles">
-								<MkSwitch v-model="cacheRemoteSensitiveFiles">
-									<template #label>{{ i18n.ts.cacheRemoteSensitiveFiles }}</template>
-									<template #caption>{{ i18n.ts.cacheRemoteSensitiveFilesDescription }}</template>
-								</MkSwitch>
-							</template>
-						</div>
-					</FormSection>
+							<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>
 
-					<FormSection>
-						<template #label>ServiceWorker</template>
+						<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>
 
-						<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.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>
 
-							<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.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>
 
-								<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.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>
 
-					<FormSection>
-						<template #label>Misskey® Fan-out Timeline Technology™ (FTT)</template>
+						<MkInfo v-if="!instance.providesTarball && !infoForm.state.repositoryUrl" warn>
+							{{ i18n.ts.repositoryUrlOrTarballRequired }}
+						</MkInfo>
 
-						<div class="_gaps_m">
-							<MkSwitch v-model="enableFanoutTimeline">
-								<template #label>{{ i18n.ts.enable }}</template>
-								<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDescription }}</template>
-							</MkSwitch>
+						<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>
 
-							<MkSwitch v-model="enableFanoutTimelineDbFallback">
-								<template #label>{{ i18n.ts._serverSettings.fanoutTimelineDbFallback }}</template>
-								<template #caption>{{ i18n.ts._serverSettings.fanoutTimelineDbFallbackDescription }}</template>
+						<MkInput v-model="infoForm.state.donationUrl" type="url">
+							<template #label>{{ i18n.ts.donationUrl }}</template>
+							<template #prefix><i class="ph-link ph-bold ph-lg"></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>
-							</MkInput>
-
-							<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
-								<template #label>perRemoteUserUserTimelineCacheMax</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="perUserHomeTimelineCacheMax" type="number">
-								<template #label>perUserHomeTimelineCacheMax</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="perUserListTimelineCacheMax" type="number">
-								<template #label>perUserListTimelineCacheMax</template>
+						</template>
+					</div>
+				</MkFolder>
+
+				<MkFolder>
+					<template #icon><i class="ph-faders ph-bold ph-lg ti-fw"></i></template>
+					<template #label>{{ i18n.ts.otherSettings }}</template>
+					<template v-if="otherForm.modified.value" #footer>
+						<MkFormFooter :form="otherForm"/>
+					</template>
+
+					<div class="_gaps">
+						<MkSwitch v-model="otherForm.state.enableAchievements">
+							<template #label>{{ i18n.ts.enableAchievements }}<span v-if="otherForm.modifiedStates.enableAchievements" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.turnOffAchievements}}</template>
+						</MkSwitch>
+
+						<MkSwitch v-model="otherForm.state.enableBotTrending">
+							<template #label>{{ i18n.ts.enableBotTrending }}<span v-if="otherForm.modifiedStates.enableBotTrending" class="_modified">{{ i18n.ts.modified }}</span></template>
+							<template #caption>{{ i18n.ts.turnOffBotTrending }}</template>
+						</MkSwitch>
+					</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>
 
@@ -193,18 +231,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>
@@ -216,9 +287,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';
@@ -226,99 +295,136 @@ 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 donationUrl = 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;
-	donationUrl.value = meta.donationUrl;
-	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 ?? '',
+	donationUrl: meta.donationUrl ?? '',
+}, 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,
+		donationUrl: state.donationUrl,
+	});
+	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,
-		donationUrl: donationUrl.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 otherForm = useForm({
+	enableAchievements: meta.enableAchievements,
+	enableBotTrending: meta.enableBotTrending,
+}, async (state) => {
+	await os.apiWithDialog('admin/update-meta', {
+		enableAchievements: state.enableAchievements,
+		enableBotTrending: state.enableBotTrending,
+	});
+	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(() => []);
@@ -330,11 +436,6 @@ 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);
diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue
index 0c07122af33a4032f9f5bef0a4a3ae0f0cfbb477..4e767fba16bfd03c7414440dcee61f93ab43cae6 100644
--- a/packages/frontend/src/pages/admin/system-webhook.item.vue
+++ b/packages/frontend/src/pages/admin/system-webhook.item.vue
@@ -4,8 +4,9 @@ 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 #icon>
 		<i v-if="!entity.isActive" class="ti ti-player-pause"/>
 		<i v-else-if="entity.latestStatus === null" class="ti ti-circle"/>
 		<i
@@ -14,23 +15,38 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:style="{ color: 'var(--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">
+	</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 +70,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);
-	}
-}
 </style>
diff --git a/packages/frontend/src/pages/admin/system-webhook.vue b/packages/frontend/src/pages/admin/system-webhook.vue
index 7a40eec94494402105317836c6323f6b472b5148..c59abda24a21b12267067584dadda368191fd18d 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/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index e947ec9ba5dbe7206b3ce6cf13bfdbe92ee857bd..e57e212b60a143dd00d118b7667ba25efe737587 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="800">
 		<div ref="rootEl">
 			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, watch, ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkTimeline from '@/components/MkTimeline.vue';
-import { scroll } from '@/scripts/scroll.js';
+import { scroll } from '@@/js/scroll.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index b9f45e219c110598fccf92a9728d9e36c1df3a34..75a44802f8b9468bb8d07249bb3d629c7cb96361 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<h1>{{ i18n.ts._auth.denied }}</h1>
 			</div>
 			<div v-if="state == 'accepted' && session">
-				<h1>{{ session.app.isAuthorized ? i18n.ts['already-authorized'] : i18n.ts.allowed }}</h1>
+				<h1>{{ session.app.isAuthorized ? i18n.ts['already-authorized'] : i18n.ts._auth.allowed }}</h1>
 				<p v-if="session.app.callbackUrl">
 					{{ i18n.ts._auth.callback }}
 					<MkEllipsis/>
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index ad9ec3c4eecec5f4c20b2b2fd3be48793a950e88..b377314856a5e7c4828bcbc746b7bde16b20bc26 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -12,19 +12,31 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #label>{{ avatarDecoration.name }}</template>
 				<template #caption>{{ avatarDecoration.description }}</template>
 
-				<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 _buttons">
-						<MkButton class="button" inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-						<MkButton v-if="avatarDecoration.id != null" class="button" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+				<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>
 			</MkFolder>
@@ -39,6 +51,7 @@ 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';
@@ -47,6 +60,8 @@ 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),
@@ -99,3 +114,55 @@ definePageMetadata(() => ({
 	icon: 'ti ti-sparkles',
 }));
 </script>
+
+<style lang="scss" module>
+.editorRoot {
+	container: editor / inline-size;
+}
+
+.editorWrapper {
+	display: grid;
+	grid-template-columns: 1fr;
+	grid-template-rows: auto auto;
+	gap: var(--margin);
+}
+
+.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;
+	}
+}
+
+@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;
+	}
+}
+</style>
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index e922599642846699bf0825f9a9be63199afa1695..790e16e47103cdde906af6740139b68013314d02 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -82,7 +82,7 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import MkNotes from '@/components/MkNotes.vue';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { favoritedChannelsCache } from '@/cache.js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -341,7 +341,7 @@ definePageMetadata(() => ({
 	left: 0;
 	width: 100%;
 	height: 64px;
-	background: linear-gradient(0deg, var(--panel), var(--X15));
+	background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 }
 
 .bannerStatus {
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 506d906683f5e654b9b9b2fac6327cbbd4390605..52b852ad17441d2358f6a304b10314e8f7c8f585 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -39,11 +39,13 @@ import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import MkButton from '@/components/MkButton.vue';
 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,
@@ -127,21 +129,41 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 		clipsCache.delete();
 	},
 }, ...(clip.value.isPublic ? [{
-	icon: 'ti ti-link',
-	text: i18n.ts.copyUrl,
-	handler: async (): Promise<void> => {
-		copyToClipboard(`${url}/clips/${clip.value.id}`);
-		os.success();
-	},
-}] : []), ...(clip.value.isPublic && isSupportShare() ? [{
 	icon: 'ti ti-share',
 	text: i18n.ts.share,
-	handler: async (): Promise<void> => {
-		navigator.share({
-			title: clip.value.name,
-			text: clip.value.description,
-			url: `${url}/clips/${clip.value.id}`,
+	handler: (ev: MouseEvent): void => {
+		const menuItems: MenuItem[] = [];
+
+		menuItems.push({
+			icon: 'ti ti-link',
+			text: i18n.ts.copyUrl,
+			action: () => {
+				copyToClipboard(`${url}/clips/${clip.value!.id}`);
+				os.success();
+			},
+		}, {
+			icon: 'ti ti-code',
+			text: i18n.ts.genEmbedCode,
+			action: () => {
+				genEmbedCode('clips', clip.value!.id);
+			},
 		});
+
+		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',
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
index 4ed2a676788f251ddf7015bd4ee23ab3077f68f2..36e95cec2d2c5feb5304fe1239682cd11acd9e1a 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>
@@ -99,12 +99,12 @@ const file = ref<Misskey.entities.DriveFile>();
 const folderHierarchy = computed(() => {
 	if (!file.value) return [i18n.ts.drive];
 	const folderNames = [i18n.ts.drive];
-	
+
 	function get(folder: Misskey.entities.DriveFolder) {
 		if (folder.parent) get(folder.parent);
 		folderNames.push(folder.name);
 	}
-	
+
 	if (file.value.folder) get(file.value.folder);
 	return folderNames;
 });
@@ -313,6 +313,10 @@ onMounted(async () => {
 	padding: .5rem 1rem;
 }
 
+.multiline {
+	white-space: pre-wrap;
+}
+
 .kvEditBtn {
 	text-align: start;
 	display: block;
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 0f0b7e1ea8e67eba6767f643648f9e586c22c571..4db952eac239f24e8139b43f87d744782d7b34db 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -205,8 +205,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { useInterval } from '@/scripts/use-interval.js';
-import { apiUrl } from '@/config.js';
+import { useInterval } from '@@/js/use-interval.js';
+import { apiUrl } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import * as sound from '@/scripts/sound.js';
 import MkRange from '@/components/MkRange.vue';
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index d282ed48107af9be3a3813d47116897913b4eb64..fd6fadd0b37125d37fa22c4d0736f4d61399e991 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(--blur, blur(15px));
+	background: var(--acrylicBg);
+	border-top: solid .5px var(--divider);
+}
+</style>
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 1b277c936a04b8dda4b49fbe30dfe7fe790123a6..1229fcfd4e12ad2f651b1dea3594d2c8a28ab3fd 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -68,7 +68,7 @@ import { Interpreter, Parser, values } from '@syuilo/aiscript';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkAsUi from '@/components/MkAsUi.vue';
@@ -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: 'ph-repeat ph-bold ph-lg ti-fw',
-			action: shareWithNote,
-		},
-		...(isSupportShare() ? [{
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
+		text: i18n.ts.shareWithNote,
+		icon: 'ph-repeat ph-bold ph-lg ti-fw',
+		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() {
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index d50887b2e9f1df7f67044c112b10adc289dcd4dc..400dfdbe6dc97ef51846982565ad3b5a4a2eb9c2 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -5,39 +5,43 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader/></template>
+	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="800">
-		<MkPagination ref="paginationComponent" :pagination="pagination">
-			<template #empty>
-				<div class="_fullinfo">
-					<img :src="infoImageUrl" class="_ghost"/>
-					<div>{{ i18n.ts.noFollowRequests }}</div>
-				</div>
-			</template>
-			<template #default="{items}">
-				<div class="mk-follow-requests">
-					<div v-for="req in items" :key="req.id" class="user _panel">
-						<MkAvatar class="avatar" :user="req.follower" indicator link preview/>
-						<div class="body">
-							<div class="name">
-								<MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA>
-								<p class="acct">@{{ acct(req.follower) }}</p>
-							</div>
-							<div class="commands">
-								<MkButton class="command" rounded primary @click="accept(req.follower)"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
-								<MkButton class="command" rounded danger @click="reject(req.follower)"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div :key="tab" class="_gaps">
+				<MkPagination ref="paginationComponent" :pagination="pagination">
+					<template #empty>
+						<div class="_fullinfo">
+							<img :src="infoImageUrl" class="_ghost"/>
+							<div>{{ i18n.ts.noFollowRequests }}</div>
+						</div>
+					</template>
+					<template #default="{items}">
+						<div class="mk-follow-requests">
+							<div v-for="req in items" :key="req.id" class="user _panel">
+								<MkAvatar class="avatar" :user="displayUser(req)" indicator link preview/>
+								<div class="body">
+									<div class="name">
+										<MkA v-user-preview="displayUser(req).id" class="name" :to="userPage(displayUser(req))"><MkUserName :user="displayUser(req)"/></MkA>
+										<p class="acct">@{{ acct(displayUser(req)) }}</p>
+									</div>
+									<div v-if="tab === 'list'" class="commands">
+										<MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton>
+										<MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton>
+									</div>
+								</div>
 							</div>
 						</div>
-					</div>
-				</div>
-			</template>
-		</MkPagination>
+					</template>
+				</MkPagination>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed } from 'vue';
+import { shallowRef, computed, ref } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
 import { userPage, acct } from '@/filters/user.js';
@@ -45,29 +49,53 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { infoImageUrl } from '@/instance.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import { $i } from '@/account';
 
 const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
-const pagination = {
-	endpoint: 'following/requests/list' as const,
-	limit: 10,
-};
+const pagination = computed(() => tab.value === 'list'
+	? {
+		endpoint: 'following/requests/list' as const,
+		limit: 10,
+	}
+	: {
+		endpoint: 'following/requests/sent' as const,
+		limit: 10,
+	},
+);
 
 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();
 	});
 }
 
+function displayUser(req) {
+	return tab.value === 'list' ? req.follower : req.followee;
+}
+
 const headerActions = computed(() => []);
 
-const headerTabs = computed(() => []);
+const headerTabs = computed(() => [
+	{
+		key: 'list',
+		title: i18n.ts.followRequests,
+		icon: 'ph-envelope ph-bold ph-lg',
+	}, {
+		key: 'sent',
+		title: i18n.ts.pendingFollowRequests,
+		icon: 'ph-paper-plane-tilt ph-bold ph-lg',
+	},
+]);
+
+const tab = ref($i?.isLocked || !$i.hasPendingSentFollowRequest ? 'list' : 'sent');
 
 definePageMetadata(() => ({
 	title: i18n.ts.followRequests,
diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d45f572739217b82ad707f98412f3e2e84617fdf
--- /dev/null
+++ b/packages/frontend/src/pages/following-feed.vue
@@ -0,0 +1,282 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div :class="$style.root">
+	<div :class="$style.header">
+		<MkPageHeader v-model:tab="userList" :tabs="headerTabs" :actions="headerActions" :displayBackButton="true" @update:tab="onChangeTab"/>
+		<MkInfo v-if="showRemoteWarning" :class="$style.remoteWarning" warn closable @close="remoteWarningDismissed = true">{{ i18n.ts.remoteFollowersWarning }}</MkInfo>
+	</div>
+
+	<div ref="noteScroll" :class="$style.notes">
+		<MkHorizontalSwipe v-model:tab="userList" :tabs="headerTabs">
+			<MkPullToRefresh :refresher="() => reloadLatestNotes()">
+				<MkPagination ref="latestNotesPaging" :pagination="latestNotesPagination" @init="onListReady">
+					<template #empty>
+						<div class="_fullinfo">
+							<img :src="infoImageUrl" class="_ghost" :alt="i18n.ts.noNotes" aria-hidden="true"/>
+							<div>{{ i18n.ts.noNotes }}</div>
+						</div>
+					</template>
+
+					<template #default="{ items: notes }">
+						<MkDateSeparatedList v-slot="{ item: note }" :items="notes" :class="$style.panel" :noGap="true">
+							<SkFollowingFeedEntry v-if="!isHardMuted(note)" :isMuted="isSoftMuted(note)" :note="note" @select="userSelected"/>
+						</MkDateSeparatedList>
+					</template>
+				</MkPagination>
+			</MkPullToRefresh>
+		</MkHorizontalSwipe>
+	</div>
+
+	<div v-if="isWideViewport" ref="userScroll" :class="$style.user">
+		<MkHorizontalSwipe v-if="selectedUserId" v-model:tab="userList" :tabs="headerTabs">
+			<SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withBots="withBots" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
+		</MkHorizontalSwipe>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, Ref, ref, shallowRef } from 'vue';
+import * as Misskey from 'misskey-js';
+import { getScrollContainer } from '@@/js/scroll.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { i18n } from '@/i18n.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
+import { infoImageUrl } from '@/instance.js';
+import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
+import { Tab } from '@/components/global/MkPageHeader.tabs.vue';
+import { PageHeaderItem } from '@/types/page-header.js';
+import SkFollowingFeedEntry from '@/components/SkFollowingFeedEntry.vue';
+import { useRouter } from '@/router/supplier.js';
+import MkPageHeader from '@/components/global/MkPageHeader.vue';
+import { $i } from '@/account.js';
+import { checkWordMute } from '@/scripts/check-word-mute.js';
+import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
+import { useScrollPositionManager } from '@/nirax.js';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { createModel, createOptions, followersTab, followingTab, mutualsTab } from '@/scripts/following-feed-utils.js';
+
+const {
+	userList,
+	withNonPublic,
+	withQuotes,
+	withBots,
+	withReplies,
+	onlyFiles,
+	remoteWarningDismissed,
+} = createModel();
+
+const router = useRouter();
+
+const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>();
+const userScroll = shallowRef<HTMLElement>();
+const noteScroll = shallowRef<HTMLElement>();
+
+const showRemoteWarning = computed(() => userList.value === 'followers' && !remoteWarningDismissed.value);
+
+// We have to disable the per-user feed on small displays, and it must be done through JS instead of CSS.
+// Otherwise, the second column will waste resources in the background.
+const wideViewportQuery = window.matchMedia('(min-width: 750px)');
+const isWideViewport: Ref<boolean> = ref(wideViewportQuery.matches);
+wideViewportQuery.addEventListener('change', () => isWideViewport.value = wideViewportQuery.matches);
+
+const selectedUserId: Ref<string | null> = ref(null);
+
+function userSelected(user: Misskey.entities.UserLite): void {
+	if (isWideViewport.value) {
+		selectedUserId.value = user.id;
+	} else {
+		router.push(`/following-feed/${user.id}`);
+	}
+}
+
+async function reloadLatestNotes() {
+	await latestNotesPaging.value?.reload();
+}
+
+async function reloadUserNotes() {
+	await userRecentNotes.value?.reload();
+}
+
+async function reload() {
+	await Promise.all([
+		reloadLatestNotes(),
+		reloadUserNotes(),
+	]);
+}
+
+async function onListReady(): Promise<void> {
+	if (!selectedUserId.value && latestNotesPaging.value?.items.size) {
+		// This looks messy, but actually just gets the first user ID.
+		const selectedNote = latestNotesPaging.value.items.values().next().value;
+
+		// We know this to be non-null because of the size check above.
+		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+		selectedUserId.value = selectedNote!.userId;
+	}
+}
+
+async function onChangeTab(): Promise<void> {
+	selectedUserId.value = null;
+}
+
+function isSoftMuted(note: Misskey.entities.Note): boolean {
+	return isMuted(note, $i?.mutedWords);
+}
+
+function isHardMuted(note: Misskey.entities.Note): boolean {
+	return isMuted(note, $i?.hardMutedWords);
+}
+
+// Match the typing used by Misskey
+type Mutes = (string | string[])[] | null | undefined;
+
+// Adapted from MkNote.ts
+function isMuted(note: Misskey.entities.Note, mutes: Mutes): boolean {
+	return checkMute(note, mutes)
+		|| checkMute(note.reply, mutes)
+		|| checkMute(note.renote, mutes);
+}
+
+// Adapted from check-word-mute.ts
+function checkMute(note: Misskey.entities.Note | undefined | null, mutes: Mutes): boolean {
+	if (!note) {
+		return false;
+	}
+
+	if (!mutes || mutes.length < 1) {
+		return false;
+	}
+
+	return checkWordMute(note, $i, mutes);
+}
+
+const latestNotesPaging = shallowRef<InstanceType<typeof MkPagination>>();
+
+const latestNotesPagination: Paging<'notes/following'> = {
+	endpoint: 'notes/following' as const,
+	limit: 20,
+	params: computed(() => ({
+		list: userList.value,
+		filesOnly: onlyFiles.value,
+		includeNonPublic: withNonPublic.value,
+		includeReplies: withReplies.value,
+		includeQuotes: withQuotes.value,
+		includeBots: withBots.value,
+	})),
+};
+
+const headerActions: PageHeaderItem[] = [
+	{
+		icon: 'ti ti-refresh',
+		text: i18n.ts.reload,
+		handler: () => reload(),
+	},
+	createOptions(),
+];
+
+const headerTabs = computed(() => [
+	{
+		key: followingTab,
+		icon: 'ph-user-check ph-bold ph-lg',
+		title: i18n.ts.following,
+	} satisfies Tab,
+	{
+		key: mutualsTab,
+		icon: 'ph-user-switch ph-bold ph-lg',
+		title: i18n.ts.mutuals,
+	} satisfies Tab,
+	{
+		key: followersTab,
+		icon: 'ph-user ph-bold ph-lg',
+		title: i18n.ts.followers,
+	} satisfies Tab,
+]);
+
+useScrollPositionManager(() => getScrollContainer(userScroll.value ?? null), router);
+useScrollPositionManager(() => getScrollContainer(noteScroll.value ?? null), router);
+definePageMetadata(() => ({
+	title: i18n.ts.following,
+	icon: 'ph-user-check ph-bold ph-lg',
+}));
+
+</script>
+
+<style lang="scss" module>
+//This inspection complains about duplicate "height" properties, but this is needed because "dvh" units are not supported in all browsers.
+//The earlier "vh" provide a "close enough" approximation for older browsers.
+//noinspection CssOverwrittenProperties
+.root {
+	display: grid;
+	grid-template-columns: min-content 1fr min-content;
+	grid-template-rows: min-content 1fr;
+	grid-template-areas:
+		"header header header"
+		"lm notes rm";
+	gap: 12px;
+
+	height: 100vh;
+	height: 100dvh;
+
+	// The universal layout inserts a "spacer" thing that causes a stray scroll bar.
+	// We have to create fake "space" for it to "roll up" and back into the viewport, which removes the scrollbar.
+	margin-bottom: calc(-1 * var(--minBottomSpacing));
+
+	// Some "just in case" backup properties.
+	// These should not be needed, but help to maintain the layout if the above trick ever stops working.
+	overflow: hidden;
+	position: sticky;
+	top: 0;
+}
+
+.header {
+	grid-area: header;
+}
+
+.notes {
+	grid-area: notes;
+	overflow-y: auto;
+}
+
+.user {
+	grid-area: user;
+	overflow-y: auto;
+}
+
+.remoteWarning {
+	margin: 12px 12px 0 12px;
+}
+
+.userInfo {
+	margin-bottom: 12px;
+}
+
+@media (min-width: 750px) {
+	.root {
+		grid-template-columns: min-content 4fr 6fr min-content;
+		grid-template-rows: min-content 1fr;
+		grid-template-areas:
+			"header header header header"
+			"lm notes user rm";
+		gap: 24px;
+	}
+
+	.remoteWarning {
+		margin: 24px 24px 0 24px;
+	}
+
+	.userInfo {
+		margin-bottom: 24px;
+	}
+}
+
+.panel {
+	background: var(--panel);
+}
+</style>
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index 913758ba7e969d53d72f32ceeee2425bd599dd5d..6ed119c0c4cab9821900a13f87e64b8b70588fcf 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -72,7 +72,7 @@ import MkContainer from '@/components/MkContainer.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { defaultStore } from '@/store.js';
@@ -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 });
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 4ff26197d8427d7ccfe06d763a795bb4fb384e38..a5e6e5ac338fd5f363413ab31b44d8b0b6c385b6 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -43,12 +43,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 								{{ i18n.ts._delivery._type[suspensionState] }}
 							</template>
 						</MkKeyValue>
-						<MkButton v-if="suspensionState === 'none'" :disabled="!instance" danger @click="stopDelivery">{{ i18n.ts._delivery.stop }}</MkButton>
-						<MkButton v-if="suspensionState !== 'none'" :disabled="!instance" @click="resumeDelivery">{{ i18n.ts._delivery.resume }}</MkButton>
-						<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
-						<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
-						<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">Mark as NSFW</MkSwitch>
-						<MkSwitch v-model="isMediaSilenced" :disabled="!meta || !instance" @update:modelValue="toggleMediaSilenced">{{ i18n.ts.mediaSilenceThisInstance }}</MkSwitch>
+						<div class="_buttons">
+							<MkButton inline :disabled="!instance" danger @click="deleteAllFiles">{{ i18n.ts.deleteAllFiles }}</MkButton>
+							<MkButton inline :disabled="!instance" danger @click="severAllFollowRelations">{{ i18n.ts.severAllFollowRelations }}</MkButton>
+						</div>
+						<MkSwitch v-model="isSuspended" :disabled="!instance" @update:modelValue="toggleSuspended">{{ i18n.ts._delivery.stop }}</MkSwitch>
+						<MkInfo v-if="isBaseBlocked" warn>{{ i18n.ts.blockedByBase }}</MkInfo>
+						<MkSwitch v-model="isBlocked" :disabled="!meta || !instance || isBaseBlocked" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
+						<MkInfo v-if="isBaseSilenced" warn>{{ i18n.ts.silencedByBase }}</MkInfo>
+						<MkSwitch v-model="isSilenced" :disabled="!meta || !instance || isBaseSilenced" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
+						<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">{{ i18n.ts.markInstanceAsNSFW }}</MkSwitch>
+						<MkSwitch v-model="rejectReports" :disabled="!instance" @update:modelValue="toggleRejectReports">{{ i18n.ts.rejectReports }}</MkSwitch>
+						<MkInfo v-if="isBaseMediaSilenced" warn>{{ i18n.ts.mediaSilencedByBase }}</MkInfo>
+						<MkSwitch v-model="isMediaSilenced" :disabled="!meta || !instance || isBaseMediaSilenced" @update:modelValue="toggleMediaSilenced">{{ i18n.ts.mediaSilenceThisInstance }}</MkSwitch>
 						<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
 						<MkTextarea v-model="moderationNote" manualSave>
 							<template #label>{{ i18n.ts.moderationNote }}</template>
@@ -123,6 +130,36 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</MkA>
 				</MkPagination>
 			</div>
+			<div v-else-if="tab === 'following'" key="following" class="_gaps_m">
+				<MkPagination v-slot="{items}" :pagination="followingPagination">
+					<div class="follow-relations-list">
+						<div v-for="followRelationship in items" :key="followRelationship.id" class="follow-relation">
+							<MkA v-tooltip.mfm="`Last posted: ${dateString(followRelationship.followee.updatedAt)}`" :to="`/admin/user/${followRelationship.followee.id}`" class="user">
+								<MkUserCardMini :user="followRelationship.followee" :withChart="false"/>
+							</MkA>
+							<span class="arrow">→</span>
+							<MkA v-tooltip.mfm="`Last posted: ${dateString(followRelationship.follower.updatedAt)}`" :to="`/admin/user/${followRelationship.follower.id}`" class="user">
+								<MkUserCardMini :user="followRelationship.follower" :withChart="false"/>
+							</MkA>
+						</div>
+					</div>
+				</MkPagination>
+			</div>
+			<div v-else-if="tab === 'followers'" key="followers" class="_gaps_m">
+				<MkPagination v-slot="{items}" :pagination="followersPagination">
+					<div class="follow-relations-list">
+						<div v-for="followRelationship in items" :key="followRelationship.id" class="follow-relation">
+							<MkA v-tooltip.mfm="`Last posted: ${dateString(followRelationship.followee.updatedAt)}`" :to="`/admin/user/${followRelationship.followee.id}`" class="user">
+								<MkUserCardMini :user="followRelationship.followee" :withChart="false"/>
+							</MkA>
+							<span class="arrow">←</span>
+							<MkA v-tooltip.mfm="`Last posted: ${dateString(followRelationship.follower.updatedAt)}`" :to="`/admin/user/${followRelationship.follower.id}`" class="user">
+								<MkUserCardMini :user="followRelationship.follower" :withChart="false"/>
+							</MkA>
+						</div>
+					</div>
+				</MkPagination>
+			</div>
 			<div v-else-if="tab === 'raw'" key="raw" class="_gaps_m">
 				<MkObjectView tall :value="instance">
 				</MkObjectView>
@@ -135,7 +172,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkChart from '@/components/MkChart.vue';
+import MkChart, { type ChartSrc } from '@/components/MkChart.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
 import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/MkLink.vue';
@@ -151,11 +188,13 @@ import { iAmModerator, iAmAdmin } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination from '@/components/MkPagination.vue';
+import MkPagination, { type Paging } from '@/components/MkPagination.vue';
 import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 import { dateString } from '@/filters/date.js';
 import MkTextarea from '@/components/MkTextarea.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { $i } from '@/account.js';
 
 const props = defineProps<{
 	host: string;
@@ -163,19 +202,36 @@ const props = defineProps<{
 
 const tab = ref('overview');
 
-const chartSrc = ref('instance-requests');
+const chartSrc = ref<ChartSrc>('instance-requests');
 const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
 const instance = ref<Misskey.entities.FederationInstance | null>(null);
 const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'>('none');
+const isSuspended = ref(false);
 const isBlocked = ref(false);
 const isSilenced = ref(false);
 const isNSFW = ref(false);
+const rejectReports = ref(false);
 const isMediaSilenced = ref(false);
 const faviconUrl = ref<string | null>(null);
 const moderationNote = ref('');
 
+const baseDomains = computed(() => {
+	const domains: string[] = [];
+
+	const parts = props.host.toLowerCase().split('.');
+	for (let s = 1; s < parts.length; s++) {
+		const domain = parts.slice(s).join('.');
+		domains.push(domain);
+	}
+
+	return domains;
+});
+const isBaseBlocked = computed(() => meta.value && baseDomains.value.some(d => meta.value?.blockedHosts.includes(d)));
+const isBaseSilenced = computed(() => meta.value && baseDomains.value.some(d => meta.value?.silencedHosts.includes(d)));
+const isBaseMediaSilenced = computed(() => meta.value && baseDomains.value.some(d => meta.value?.mediaSilencedHosts.includes(d)));
+
 const usersPagination = {
-	endpoint: iAmModerator ? 'admin/show-users' : 'users' as const,
+	endpoint: iAmModerator ? 'admin/show-users' : 'users',
 	limit: 10,
 	params: {
 		sort: '+updatedAt',
@@ -183,11 +239,34 @@ const usersPagination = {
 		hostname: props.host,
 	},
 	offsetMode: true,
+} satisfies Paging;
+
+const followingPagination = {
+	endpoint: 'federation/following' as const,
+	limit: 10,
+	params: {
+		host: props.host,
+		includeFollower: true,
+	},
+	offsetMode: false,
 };
 
-watch(moderationNote, async () => {
-	await misskeyApi('admin/federation/update-instance', { host: instance.value.host, moderationNote: moderationNote.value });
-});
+const followersPagination = {
+	endpoint: 'federation/followers' as const,
+	limit: 10,
+	params: {
+		host: props.host,
+		includeFollower: true,
+	},
+	offsetMode: false,
+};
+
+if (iAmModerator) {
+	watch(moderationNote, async () => {
+		if (instance.value == null) return;
+		await misskeyApi('admin/federation/update-instance', { host: instance.value.host, moderationNote: moderationNote.value });
+	});
+}
 
 async function fetch(): Promise<void> {
 	if (iAmAdmin) {
@@ -197,15 +276,18 @@ async function fetch(): Promise<void> {
 		host: props.host,
 	});
 	suspensionState.value = instance.value?.suspensionState ?? 'none';
+	isSuspended.value = suspensionState.value !== 'none';
 	isBlocked.value = instance.value?.isBlocked ?? false;
 	isSilenced.value = instance.value?.isSilenced ?? false;
 	isNSFW.value = instance.value?.isNSFW ?? false;
+	rejectReports.value = instance.value?.rejectReports ?? false;
 	isMediaSilenced.value = instance.value?.isMediaSilenced ?? false;
 	faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
 	moderationNote.value = instance.value?.moderationNote ?? '';
 }
 
 async function toggleBlock(): Promise<void> {
+	if (!iAmAdmin) return;
 	if (!meta.value) throw new Error('No meta?');
 	if (!instance.value) throw new Error('No instance?');
 	const { host } = instance.value;
@@ -215,6 +297,7 @@ async function toggleBlock(): Promise<void> {
 }
 
 async function toggleSilenced(): Promise<void> {
+	if (!iAmAdmin) return;
 	if (!meta.value) throw new Error('No meta?');
 	if (!instance.value) throw new Error('No instance?');
 	const { host } = instance.value;
@@ -225,6 +308,7 @@ async function toggleSilenced(): Promise<void> {
 }
 
 async function toggleMediaSilenced(): Promise<void> {
+	if (!iAmAdmin) return;
 	if (!meta.value) throw new Error('No meta?');
 	if (!instance.value) throw new Error('No instance?');
 	const { host } = instance.value;
@@ -234,33 +318,36 @@ async function toggleMediaSilenced(): Promise<void> {
 	});
 }
 
-async function stopDelivery(): Promise<void> {
+async function toggleSuspended(): Promise<void> {
+	if (!iAmModerator) return;
 	if (!instance.value) throw new Error('No instance?');
-	suspensionState.value = 'manuallySuspended';
+	suspensionState.value = isSuspended.value ? 'manuallySuspended' : 'none';
 	await misskeyApi('admin/federation/update-instance', {
 		host: instance.value.host,
-		isSuspended: true,
+		isSuspended: isSuspended.value,
 	});
 }
 
-async function resumeDelivery(): Promise<void> {
+async function toggleNSFW(): Promise<void> {
+	if (!iAmModerator) return;
 	if (!instance.value) throw new Error('No instance?');
-	suspensionState.value = 'none';
 	await misskeyApi('admin/federation/update-instance', {
 		host: instance.value.host,
-		isSuspended: false,
+		isNSFW: isNSFW.value,
 	});
 }
 
-async function toggleNSFW(): Promise<void> {
+async function toggleRejectReports(): Promise<void> {
+	if (!iAmModerator) return;
 	if (!instance.value) throw new Error('No instance?');
 	await misskeyApi('admin/federation/update-instance', {
 		host: instance.value.host,
-		isNSFW: isNSFW.value,
+		rejectReports: rejectReports.value,
 	});
 }
 
 function refreshMetadata(): void {
+	if (!iAmModerator) return;
 	if (!instance.value) throw new Error('No instance?');
 	misskeyApi('admin/federation/refresh-remote-instance-metadata', {
 		host: instance.value.host,
@@ -270,6 +357,50 @@ function refreshMetadata(): void {
 	});
 }
 
+async function deleteAllFiles(): Promise<void> {
+	if (!iAmModerator) return;
+	if (!instance.value) throw new Error('No instance?');
+
+	const confirm = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.deleteAllFilesConfirm,
+	});
+	if (confirm.canceled) return;
+
+	await Promise.all([
+		misskeyApi('admin/federation/delete-all-files', {
+			host: instance.value.host,
+		}),
+		os.alert({
+			text: i18n.ts.deleteAllFilesQueued,
+		}),
+	]);
+}
+
+async function severAllFollowRelations(): Promise<void> {
+	if (!iAmModerator) return;
+	if (!instance.value) throw new Error('No instance?');
+
+	const confirm = await os.confirm({
+		type: 'warning',
+		text: i18n.tsx.severAllFollowRelationsConfirm({
+			instanceName: instance.value.name ?? instance.value.host,
+			followingCount: instance.value.followingCount,
+			followersCount: instance.value.followersCount,
+		}),
+	});
+	if (confirm.canceled) return;
+
+	await Promise.all([
+		misskeyApi('admin/federation/remove-all-following', {
+			host: instance.value.host,
+		}),
+		os.alert({
+			text: i18n.tsx.severAllFollowRelationsQueued({ host: instance.value.host }),
+		}),
+	]);
+}
+
 fetch();
 
 const headerActions = computed(() => [{
@@ -292,12 +423,28 @@ const headerTabs = computed(() => [{
 	key: 'users',
 	title: i18n.ts.users,
 	icon: 'ti ti-users',
-}, {
+}, ...getFollowingTabs(), {
 	key: 'raw',
 	title: 'Raw',
 	icon: 'ti ti-code',
 }]);
 
+function getFollowingTabs() {
+	if (!$i) return [];
+	return [
+		{
+			key: 'following',
+			title: i18n.ts.following,
+			icon: 'ti ti-arrow-right',
+		},
+		{
+			key: 'followers',
+			title: i18n.ts.followers,
+			icon: 'ti ti-arrow-left',
+		},
+	];
+}
+
 definePageMetadata(() => ({
 	title: props.host,
 	icon: 'ti ti-server',
@@ -334,4 +481,31 @@ definePageMetadata(() => ({
 		}
 	}
 }
+
+.follow-relations-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+
+  .follow-relation {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    flex-wrap: nowrap;
+    justify-content: space-between;
+
+    .user {
+      flex: 1;
+      max-width: 45%;
+      flex-shrink: 0;
+      overflow: hidden;
+      text-overflow: ellipsis;
+    }
+
+    .arrow {
+      font-size: 1.5em;
+      flex-shrink: 0;
+    }
+  }
+}
 </style>
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index a2ceb222feeac22490ca9f03ecabc28189ef7688..5f195693ccc8a23f12aa9a3044c45b33bce69da1 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -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,
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index 28f583829653684e0053c8757ded31b24652dba4..bd93fc8369659ef96d04c9ee915e24a6a7a84302 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -30,7 +30,7 @@ import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { notificationTypes } from '@/const.js';
+import { notificationTypes } from '@@/js/const.js';
 
 const tab = ref('all');
 const includeTypes = ref<string[] | null>(null);
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index 6bc3c0908a4998a10474cc65f8d8fbea618ebf94..ac9f3e74013d87e8e6325cd7081dbd8a1ef8534d 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -69,6 +69,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
+import { url } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { selectFile } from '@/scripts/select-file.js';
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index fc8627a772c10ad3d6e9c50e01962848b9d1bf86..12b70fa64f41a62ba4469f058935a5f6768b1dac 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -104,7 +104,7 @@ import XPage from '@/components/page/page.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import MkMediaImage from '@/components/MkMediaImage.vue';
 import MkImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 import MkFollowButton from '@/components/MkFollowButton.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._pages.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 });
@@ -433,13 +446,12 @@ definePageMetadata(() => ({
 		.pageBannerTitleUser {
 			--height: 32px;
 			flex-shrink: 0;
+			line-height: var(--height);
 
 			.avatar {
 				height: var(--height);
 				width: var(--height);
 			}
-
-			line-height: var(--height);
 		}
 
 		.pageBannerTitleSubActions {
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index bac1d2bb700752e7bfc2f142d9c2266acec87412..8dcb8fa477a898a72f331e8f3adbefdb099d21c4 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
 
 			<FormSection v-if="keys">
-				<template #label>{{ i18n.ts.keys }}</template>
+				<template #label>{{ i18n.ts._registry.keys }}</template>
 				<div class="_gaps_s">
 					<FormLink v-for="key in keys" :to="`/registry/value/${props.domain}/${scope.join('/')}/${key[0]}`" class="_monospace">{{ key[0] }}<template #suffix>{{ key[1].toUpperCase() }}</template></FormLink>
 				</div>
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 7d9cefa5c9f44e7d2b91806967ac4289c0b910f3..54e66f6e169bf685c1216e06884cf11937a7d17f 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -149,9 +149,9 @@ import MkButton from '@/components/MkButton.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import { deepClone } from '@/scripts/clone.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { signinRequired } from '@/account.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { userPage } from '@/filters/user.js';
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 31c0003130ca9a6c8c26e238d889e0e0f2880cc5..08bb3cb76cea203f05b7e579b17c215ff854d62e 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -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();
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
index 97a793753d8b18598e30513b90798fd6ecd29535..10ea3717abbaf895e48cdd2eb610f6b65d367d0e 100644
--- a/packages/frontend/src/pages/reversi/game.vue
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -20,9 +20,9 @@ import { useStream } from '@/stream.js';
 import { signinRequired } from '@/account.js';
 import { useRouter } from '@/router/supplier.js';
 import * as os from '@/os.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 
 const $i = signinRequired();
 
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
index 51a03e441801df034807441aca575e3780ed9432..d823861b4ac634e73c3f5fa41cc69aafb8afb7d4 100644
--- a/packages/frontend/src/pages/reversi/index.vue
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -117,7 +117,7 @@ import { $i } from '@/account.js';
 import MkPagination from '@/components/MkPagination.vue';
 import { useRouter } from '@/router/supplier.js';
 import * as os from '@/os.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
 import * as sound from '@/scripts/sound.js';
 
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index 45edbc5da284b91f8a12c9235134b0f7826fdb66..9ee3fd7f01b2db7d1488e23e93d067d967f71d70 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
+	<template #header><MkPageHeader v-model:tab="tab" :displayBackButton="true" :tabs="headerTabs"/></template>
 	<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
 		<div :class="$style.root">
 			<img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/>
@@ -43,7 +43,7 @@ import MkUserList from '@/components/MkUserList.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkTimeline from '@/components/MkTimeline.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { serverErrorImageUrl, infoImageUrl } from '@/instance.js';
 
 const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index 9aaa8ff9c633cc589ec93d663a76b4ec23404ea5..155d8b82d71cbc25e0f87fab4b38c4d34052d063 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';
@@ -61,6 +80,7 @@ const logs = ref<any[]>([]);
 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 +91,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 +180,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',
@@ -192,4 +234,39 @@ definePageMetadata(() => ({
 		}
 	}
 }
+
+.uiInspector {
+	display: grid;
+	gap: 8px;
+	padding: 16px;
+}
+
+.uiInspectorUnShown {
+	color: var(--fgTransparent);
+}
+
+.uiInspectorType {
+	display: inline-block;
+	border: hidden;
+	border-radius: 10px;
+	background-color: var(--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/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
index 0767fa7864c6b5493a7bf7a00a0bf08d0ce882f1..9f7852a71d74e057345bf65150aaa8edfee21207 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@click="emit('click')"
 >
 	<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/>
+	<MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH, offsetX, offsetY, showBelow }]" 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>
 </div>
 </template>
@@ -32,6 +32,7 @@ const props = defineProps<{
 	flipH?: boolean;
 	offsetX?: number;
 	offsetY?: number;
+	showBelow?: boolean;
 }>();
 
 const emit = defineEmits<{
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
index ce1d4e48d829801d93968870ee1e87060113ac31..4ec4610279a583d08c5666530219fc7c7fdef88b 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
@@ -29,6 +29,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkRange v-model="offsetY" continuousUpdate :min="-0.25" :max="0.25" :step="0.025" :textConverter="(v) => `${Math.floor(v * 100)}%`">
 					<template #label>Y {{ i18n.ts.position }}</template>
 				</MkRange>
+				<MkSwitch v-model="showBelow">
+					<template #label>{{ i18n.ts.showBelowAvatar }}</template>
+				</MkSwitch>
 				<MkSwitch v-model="flipH">
 					<template #label>{{ i18n.ts.flip }}</template>
 				</MkSwitch>
@@ -71,12 +74,14 @@ const emit = defineEmits<{
 		flipH: boolean;
 		offsetX: number;
 		offsetY: number;
+		showBelow: boolean;
 	}): void;
 	(ev: 'update', payload: {
 		angle: number;
 		flipH: boolean;
 		offsetX: number;
 		offsetY: number;
+		showBelow: boolean;
 	}): void;
 	(ev: 'detach'): void;
 }>();
@@ -87,6 +92,7 @@ const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIn
 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);
 const offsetY = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetY : null) ?? 0);
+const showBelow = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].showBelow : null) ?? false);
 
 const decorationsForPreview = computed(() => {
 	const decoration = {
@@ -96,6 +102,8 @@ const decorationsForPreview = computed(() => {
 		flipH: flipH.value,
 		offsetX: offsetX.value,
 		offsetY: offsetY.value,
+		showBelow: showBelow.value,
+		blink: true,
 	};
 	const decorations = [...$i.avatarDecorations];
 	if (props.usingIndex != null) {
@@ -116,6 +124,7 @@ async function update() {
 		flipH: flipH.value,
 		offsetX: offsetX.value,
 		offsetY: offsetY.value,
+		showBelow: showBelow.value,
 	});
 	dialog.value.close();
 }
@@ -126,6 +135,7 @@ async function attach() {
 		flipH: flipH.value,
 		offsetX: offsetX.value,
 		offsetY: offsetY.value,
+		showBelow: showBelow.value,
 	});
 	dialog.value.close();
 }
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue
index 77229d3349d04c5fa331f67076083877411ff40f..5324a6b7f74d3d6ccb663ca93fbf421385458820 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.vue
@@ -21,6 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					:flipH="avatarDecoration.flipH"
 					:offsetX="avatarDecoration.offsetX"
 					:offsetY="avatarDecoration.offsetY"
+					:showBelow="avatarDecoration.showBelow"
 					:active="true"
 					@click="openDecoration(avatarDecoration, i)"
 				/>
@@ -78,6 +79,7 @@ function openDecoration(avatarDecoration, index?: number) {
 				flipH: payload.flipH,
 				offsetX: payload.offsetX,
 				offsetY: payload.offsetY,
+				showBelow: payload.showBelow,
 			};
 			const update = [...$i.avatarDecorations, decoration];
 			await os.apiWithDialog('i/update', {
@@ -92,6 +94,7 @@ function openDecoration(avatarDecoration, index?: number) {
 				flipH: payload.flipH,
 				offsetX: payload.offsetX,
 				offsetY: payload.offsetY,
+				showBelow: payload.showBelow,
 			};
 			const update = [...$i.avatarDecorations];
 			update[index] = decoration;
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 3f7db1b779dd44832a13973d5ecb8dfdf9998d8a..432295aa7f1fe6216066a45c9a616f9ecaa0a11a 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<option v-for="x in sortOptions" :key="x.value" :value="x.value">{{ x.displayName }}</option>
 	</MkSelect>
 	<div v-if="!fetching">
-		<MkPagination v-slot="{items}" :pagination="pagination">
+		<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination">
 			<div class="_gaps">
 				<div
 					v-for="file in items" :key="file.id"
@@ -48,8 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch, type StyleValue } from 'vue';
+import { computed, ref, shallowRef, watch, type StyleValue } from 'vue';
 import tinycolor from 'tinycolor2';
+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 MkPagination from '@/components/MkPagination.vue';
@@ -58,13 +60,16 @@ import { i18n } from '@/i18n.js';
 import bytes from '@/filters/bytes.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkSelect from '@/components/MkSelect.vue';
-import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+
+const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
 const sortMode = ref('+size');
 const pagination = {
 	endpoint: 'drive/files' as const,
 	limit: 10,
 	params: computed(() => ({ sort: sortMode.value })),
+	offsetMode: true,
 };
 
 const sortOptions = [
@@ -109,6 +114,46 @@ function genUsageBar(fsize: number): StyleValue {
 	};
 }
 
+async function deleteFile(file: Misskey.entities.DriveFile) {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }),
+	});
+
+	if (canceled) return;
+	misskeyApi('drive/files/delete', {
+		fileId: file.id,
+	});
+
+	if (paginationComponent.value) {
+		paginationComponent.value.items.delete(file.id);
+	}
+}
+
+function getDriveFileMenu(file: Misskey.entities.DriveFile): MenuItem[] {
+	const menuItems: MenuItem[] = [];
+
+	menuItems.push({
+		text: i18n.ts.copyUrl,
+		icon: 'ti ti-link',
+		action: () => copyToClipboard(file.url),
+	}, {
+		type: 'a',
+		href: file.url,
+		target: '_blank',
+		text: i18n.ts.download,
+		icon: 'ti ti-download',
+		download: file.name,
+	}, { type: 'divider' }, {
+		text: i18n.ts.delete,
+		icon: 'ti ti-trash',
+		danger: true,
+		action: () => deleteFile(file),
+	});
+
+	return menuItems;
+}
+
 function onClick(ev: MouseEvent, file) {
 	os.popupMenu(getDriveFileMenu(file), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
 }
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index fa0963784432c432233b04c4e576954faa43bf0a..c1e325863176bcf730f39c4e7d274ce1e4a4cfb2 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #label>{{ i18n.ts.keepOriginalFilename }}</template>
 				<template #caption>{{ i18n.ts.keepOriginalFilenameDescription }}</template>
 			</MkSwitch>
-			<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
+			<MkSwitch v-model="defaultSensitive" @update:modelValue="saveProfile()">
 				<template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
 			</MkSwitch>
 		</div>
@@ -80,7 +80,7 @@ const fetching = ref(true);
 const usage = ref<number | null>(null);
 const capacity = ref<number | null>(null);
 const uploadFolder = ref<Misskey.entities.DriveFolder | null>(null);
-const alwaysMarkNsfw = ref($i.alwaysMarkNsfw);
+const defaultSensitive = ref($i.defaultSensitive);
 
 const meterStyle = computed(() => {
 	if (!capacity.value || !usage.value) return {};
@@ -127,14 +127,14 @@ function chooseUploadFolder() {
 
 function saveProfile() {
 	misskeyApi('i/update', {
-		alwaysMarkNsfw: !!alwaysMarkNsfw.value,
+		defaultSensitive: !!defaultSensitive.value,
 	}).catch(err => {
 		os.alert({
 			type: 'error',
 			title: i18n.ts.error,
 			text: err.message,
 		});
-		alwaysMarkNsfw.value = true;
+		defaultSensitive.value = true;
 	});
 }
 
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index 1df723c850366ddaeb3d0241d3bd01d8ea5e7c6a..bb919c6c5395e4dab506da97a623fc396b08f3b0 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -124,10 +124,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>
@@ -140,7 +143,7 @@ import MkRadios from '@/components/MkRadios.vue';
 import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
 import FromSlot from '@/components/form/slot.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';
@@ -159,7 +162,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);
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 637c1b24b9fc88ffc8cf3685b1edf0f40f94c77d..b42c2f15030d422c6a59cc124e5d07c728f2b1e8 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -14,16 +14,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkLink url="https://crowdin.com/project/misskey">Crowdin</MkLink>
 				</template>
 			</I18n>
+			<!--
+			<br />
+			<I18n :src="i18n.ts.i18nInfoSharkey" tag="span">
+				<template #link>
+					<MkLink url="https://crowdin.com/project/misskey">INSERT THINGY</MkLink>
+				</template>
+			</I18n>
+			-->
 		</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>
@@ -68,6 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
 				<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
 				<MkSwitch v-model="showTickerOnReplies">{{ i18n.ts.showTickerOnReplies }}</MkSwitch>
+				<MkSwitch v-model="disableCatSpeak">{{ i18n.ts.disableCatSpeak }}</MkSwitch>
 				<MkSelect v-model="searchEngine" placeholder="Other">
 					<template #label>{{ i18n.ts.searchEngine }}</template>
 					<option
@@ -169,6 +171,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
 			</MkRadios>
 
+			<MkSwitch v-model="notificationClickable">{{ i18n.ts.allowClickingNotifications }}</MkSwitch>
+
 			<MkButton @click="testNotification">{{ i18n.ts._notification.checkNotificationBehavior }}</MkButton>
 		</div>
 	</FormSection>
@@ -186,12 +190,19 @@ 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="oneko">{{ i18n.ts.oneko }}</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>
@@ -230,11 +241,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
 				<MkSwitch v-model="keepScreenOn">{{ i18n.ts.keepScreenOn }}</MkSwitch>
 				<MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch>
-				<MkSwitch v-model="showBots">{{ i18n.ts.showBots }}</MkSwitch>
 				<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
 				<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
 				<MkSwitch v-model="alwaysConfirmFollow">{{ i18n.ts.alwaysConfirmFollow }}</MkSwitch>
 				<MkSwitch v-model="confirmWhenRevealingSensitiveMedia">{{ i18n.ts.confirmWhenRevealingSensitiveMedia }}</MkSwitch>
+				<MkSwitch v-model="warnExternalUrl">{{ i18n.ts.warnExternalUrl }}</MkSwitch>
 			</div>
 			<MkSelect v-model="serverDisconnectedBehavior">
 				<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@@ -306,6 +317,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">
@@ -325,6 +342,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, reactive, 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';
@@ -336,12 +354,11 @@ 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 '@/config.js';
 import { searchEngineMap } from '@/scripts/search-engine-map.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';
@@ -356,16 +373,6 @@ const cornerRadius = ref(miLocalStorage.getItem('cornerRadius'));
 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'));
@@ -376,14 +383,6 @@ const limitWidthOfReaction = computed(defaultStore.makeGetterSetter('limitWidthO
 const collapseRenotes = computed(defaultStore.makeGetterSetter('collapseRenotes'));
 const collapseNotesRepliedTo = computed(defaultStore.makeGetterSetter('collapseNotesRepliedTo'));
 const clickToOpen = computed(defaultStore.makeGetterSetter('clickToOpen'));
-// copied from src/pages/timeline.vue
-const showBots = computed<boolean>({
-	get: () => defaultStore.reactiveState.tl.value.filter.withBots,
-	set: (newValue) => {
-		const out = deepMerge({ filter: { withBots: newValue } }, defaultStore.state.tl);
-		defaultStore.set('tl', out);
-	},
-});
 const collapseFiles = computed(defaultStore.makeGetterSetter('collapseFiles'));
 const autoloadConversation = computed(defaultStore.makeGetterSetter('autoloadConversation'));
 const reduceAnimation = computed(defaultStore.makeGetterSetter('animation', v => !v, v => !v));
@@ -395,11 +394,12 @@ 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 oneko = computed(defaultStore.makeGetterSetter('oneko'));
 const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
+const disableCatSpeak = computed(defaultStore.makeGetterSetter('disableCatSpeak'));
 const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
 const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
 const enableFaviconNotificationDot = computed(defaultStore.makeGetterSetter('enableFaviconNotificationDot'));
@@ -417,6 +417,7 @@ const showAvatarDecorations = computed(defaultStore.makeGetterSetter('showAvatar
 const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
 const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
 const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
+const notificationClickable = computed(defaultStore.makeGetterSetter('notificationClickable'));
 const keepScreenOn = computed(defaultStore.makeGetterSetter('keepScreenOn'));
 const disableStreamingTimeline = computed(defaultStore.makeGetterSetter('disableStreamingTimeline'));
 const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroupedNotifications'));
@@ -435,6 +436,7 @@ const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('u
 const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow'));
 const confirmWhenRevealingSensitiveMedia = computed(defaultStore.makeGetterSetter('confirmWhenRevealingSensitiveMedia'));
 const contextMenu = computed(defaultStore.makeGetterSetter('contextMenu'));
+const warnExternalUrl = computed(defaultStore.makeGetterSetter('warnExternalUrl'));
 
 watch(lang, () => {
 	miLocalStorage.setItem('lang', lang.value as string);
@@ -496,8 +498,9 @@ watch([
 	alwaysConfirmFollow,
 	confirmWhenRevealingSensitiveMedia,
 	contextMenu,
+	warnExternalUrl,
 ], 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 9c02b604c07c493af2d21ad21c7aa75ab423ccbb..e000c608fe37783db2256d9ac1b55b9a4beff639 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -60,7 +60,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">
@@ -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="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>
@@ -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="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>
@@ -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="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>
@@ -123,7 +123,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/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index 7f8460e316409cf33cbc998369885ba96a36286f..a0e6cad9c8d4484b32f463bac4de2b50f8b06eb9 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(() => []);
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index a861f6ee0d227116267ac3bcfa007e6f066487f8..491102fece49475bbf47871734ec5c4c7c62af5e 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -69,12 +69,12 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
-import { notificationTypes } from '@/const.js';
+import { notificationTypes } from '@@/js/const.js';
 
 const $i = signinRequired();
 
-const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted'];
-const notificationTypesWithoutSender = ['achievementEarned'];
+const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] as const satisfies (typeof notificationTypes[number])[];
+const notificationTypesWithoutSender = ['achievementEarned'] as const satisfies (typeof notificationTypes[number])[];
 
 const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
 const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 86bbae431d93c2f7e1c5ddab6e5b49860b66d292..eb9d158c1e08a494427fc6a74d9cd2fc2bed85a4 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -62,8 +62,8 @@ 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>
 				</div>
 			</MkFolder>
@@ -109,13 +109,13 @@ 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 devMode = computed(defaultStore.makeGetterSetter('devMode'));
 const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
 
@@ -143,16 +143,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',
@@ -178,9 +168,9 @@ const exportData = () => {
 };
 
 watch([
-	enableCondensedLineForAcct,
+    enableCondensedLine,
 ], async () => {
-	await reloadAsk();
+    await reloadAsk();
 });
 
 const headerActions = computed(() => []);
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index f9fd494ce95f272ac12a9d973c2a7c1ea3c4beb9..8036ef5555bc0230bd78d15bb587d8f0f2d581d5 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 '@/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',
@@ -77,9 +76,10 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'enableFaviconNotificationDot',
 	'imageNewTab',
 	'dataSaver',
+	'disableCatSpeak',
 	'disableShowingAnimatedImages',
 	'emojiStyle',
-	'disableDrawer',
+	'menuStyle',
 	'useBlurEffectForModal',
 	'useBlurEffect',
 	'showFixedPostForm',
@@ -91,7 +91,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'emojiPickerScale',
 	'emojiPickerWidth',
 	'emojiPickerHeight',
-	'emojiPickerUseDrawerForMobile',
+	'emojiPickerStyle',
 	'defaultSideView',
 	'menuDisplay',
 	'reportError',
@@ -109,7 +109,6 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'mediaListWithOneImageAppearance',
 	'notificationPosition',
 	'notificationStackAxis',
-	'enableCondensedLineForAcct',
 	'keepScreenOn',
 	'defaultWithReplies',
 	'disableStreamingTimeline',
@@ -210,15 +209,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 }),
 		});
 	}
 
@@ -247,8 +246,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,
 			});
 		}
 
@@ -259,7 +258,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 ?? '',
 			});
 		}
@@ -285,8 +284,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;
 
@@ -345,7 +344,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;
 
@@ -357,8 +356,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;
 
@@ -373,8 +372,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;
 
@@ -393,15 +392,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 }),
 		});
 	}
 
@@ -409,8 +408,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;
 
@@ -422,25 +421,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,
@@ -462,7 +461,7 @@ onUnmounted(() => {
 });
 
 definePageMetadata(() => ({
-	title: ts.preferencesBackups,
+	title: i18n.ts.preferencesBackups,
 	icon: 'ti ti-device-floppy',
 }));
 </script>
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 6cc19db127b01f45a1293c3db5b83c13ae61e6d6..c94cd512f3a56f5d465cfdb3698d787e6b06d9e8 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -94,15 +94,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<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-if="profile.isCat" v-model="profile.speakAsCat">{{ i18n.ts.flagSpeakAsCat }}<template #caption>{{ i18n.ts.flagSpeakAsCatDescription }}</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>
@@ -112,6 +110,16 @@ 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-if="profile.isCat" v-model="profile.speakAsCat">{{ i18n.ts.flagSpeakAsCat }}<template #caption>{{ i18n.ts.flagSpeakAsCatDescription }}</template></MkSwitch>
+			<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
+		</div>
+	</MkFolder>
 </div>
 </template>
 
@@ -153,6 +161,7 @@ const setMaxBirthDate = () => {
 const profile = reactive({
 	name: $i.name,
 	description: $i.description,
+	followedMessage: $i.followedMessage,
 	location: $i.location,
 	birthday: $i.birthday,
 	listenbrainz: $i.listenbrainz,
@@ -209,6 +218,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,
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index ad07a6b5392a841dfdfb54a7dabcfbda89bf6699..e7aef55a53a765b1e7106e304b36a2213432db47 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(() => {
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 058ef69c35e3745243dbd5d15dcc63794bd1b9ab..adeaf8550cd3600e513192435e66d458863b9db1 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(--fgTransparentWeak);
+}
+</style>
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index 9b77392872fbfee9ee137ee83002ae335cba9c74..0d261b1af39275d4178eb4a5ddbacdeb078d8c81 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="800">
 		<MkNotes ref="notes" class="" :pagination="pagination"/>
 	</MkSpacer>
@@ -28,6 +28,7 @@ import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
+import { genEmbedCode } from '@/scripts/get-embed-code.js';
 
 const props = defineProps<{
 	tag: string;
@@ -51,7 +52,19 @@ async function post() {
 	notes.value?.pagingComponent?.reload();
 }
 
-const headerActions = computed(() => []);
+const headerActions = computed(() => [{
+	icon: 'ti ti-dots',
+	label: i18n.ts.more,
+	handler: (ev: MouseEvent) => {
+		os.popupMenu([{
+			text: i18n.ts.genEmbedCode,
+			icon: 'ti ti-code',
+			action: () => {
+				genEmbedCode('tags', props.tag);
+			},
+		}], ev.currentTarget ?? ev.target);
+	}
+}]);
 
 const headerTabs = computed(() => []);
 
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 1cdccdc244b7ca35ff68db9833db1247f707ac1b..2fa6eb81ba7f13f020caeec4977762ea1a1f0784 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -79,6 +79,8 @@ import tinycolor from 'tinycolor2';
 import { v4 as uuid } from 'uuid';
 import JSON5 from 'json5';
 
+import lightTheme from '@@/themes/_light.json5';
+import darkTheme from '@@/themes/_dark.json5';
 import MkButton from '@/components/MkButton.vue';
 import MkCodeEditor from '@/components/MkCodeEditor.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -86,9 +88,7 @@ import MkFolder from '@/components/MkFolder.vue';
 
 import { $i } from '@/account.js';
 import { Theme, applyTheme } from '@/scripts/theme.js';
-import lightTheme from '@/themes/_light.json5';
-import darkTheme from '@/themes/_dark.json5';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { ColdDeviceStorage, defaultStore } from '@/store.js';
 import { addTheme } from '@/theme-store.js';
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 20d8abccf6e2772e63c209456e0d2f64f0035629..60974da971f95569a9a08fa3cff7c8c2ab1b5cca 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div :class="$style.tl">
 					<MkTimeline
 						ref="tlComponent"
-						:key="src + withRenotes + withReplies + onlyFiles"
+						:key="src + withRenotes + withBots + withReplies + onlyFiles"
 						:src="src.split(':')[0]"
 						:list="src.split(':')[1]"
 						:withRenotes="withRenotes"
@@ -41,7 +41,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
 import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
-import { scroll } from '@/scripts/scroll.js';
+import { scroll } from '@@/js/scroll.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
@@ -51,13 +51,16 @@ 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';
+import { useRouter } from '@/router/supplier.js';
 
 provide('shouldOmitHeaderTitle', true);
 
+const router = useRouter();
+
 const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
 const rootEl = shallowRef<HTMLElement>();
 
@@ -195,7 +198,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',
@@ -264,16 +267,28 @@ 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, {
+					text: i18n.ts.showBots,
+					ref: withBots,
+				});
+
+				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,
@@ -282,7 +297,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);
 			},
 		},
 	];
@@ -309,6 +326,11 @@ const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserList
 	icon: basicTimelineIconClass(tl),
 	iconOnly: true,
 })), {
+	icon: 'ph-user-check ph-bold ph-lg',
+	title: i18n.ts.following,
+	iconOnly: true,
+	onClick: () => router.push('/following-feed'),
+}, {
 	icon: 'ti ti-list',
 	title: i18n.ts.lists,
 	iconOnly: true,
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 6566263c010f6fc834ba67d372e5e5802078570c..396e6eb14a0ee469c229eb5499e25eee95904cab 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<template #header><MkPageHeader :actions="headerActions" :displayBackButton="true" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="800">
 		<div ref="rootEl">
 			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, watch, ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkTimeline from '@/components/MkTimeline.vue';
-import { scroll } from '@/scripts/scroll.js';
+import { scroll } from '@@/js/scroll.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index b997fe1c3f1ac34512f252f055605581d6ee43a1..279f301d78f9f66cb5e580d984739dd14517d7bd 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -30,7 +30,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 								</button>
 							</div>
 						</div>
-						<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
+						<ul v-if="$i && $i.id != user.id" :class="$style.infoBadges">
+							<li v-if="user.isFollowed && user.isFollowing">{{ i18n.ts.mutuals }}</li>
+							<li v-else-if="user.isFollowing">{{ i18n.ts.following }}</li>
+							<li v-else-if="user.isFollowed">{{ i18n.ts.followsYou }}</li>
+							<li v-if="user.isMuted">{{ i18n.ts.muted }}</li>
+							<li v-if="user.isRenoteMuted">{{ i18n.ts.renoteMuted }}</li>
+							<li v-if="user.isBlocking">{{ i18n.ts.blocked }}</li>
+							<li v-if="user.isBlocked && $i.isModerator">{{ i18n.ts.blockingYou }}</li>
+						</ul>
 						<div class="actions">
 							<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
 							<MkFollowButton v-if="$i?.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
@@ -46,6 +54,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<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">
+						<div style="border: solid 1px var(--love); border-radius: 6px; background: color-mix(in srgb, var(--love), transparent 90%); padding: 6px 8px;">
+							<Mfm :text="user.followedMessage" :author="user"/>
+						</div>
+					</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}`">
@@ -120,19 +133,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 
 			<div class="contents _gaps">
-				<div v-if="user.pinnedNotes.length > 0" class="_gaps">
-					<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
-				</div>
-				<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
+				<MkInfo v-if="user.pinnedNotes.length === 0 && $i?.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
 				<template v-if="narrow">
 					<MkLazy>
-						<XFiles :key="user.id" :user="user"/>
+						<XFiles :key="user.id" :user="user" :collapsed="true"/>
 					</MkLazy>
 					<MkLazy>
-						<XActivity :key="user.id" :user="user"/>
+						<XActivity :key="user.id" :user="user" :collapsed="true"/>
 					</MkLazy>
 					<MkLazy>
-						<XListenBrainz v-if="user.listenbrainz && listenbrainzdata" :key="user.id" :user="user"/>
+						<XListenBrainz v-if="user.listenbrainz && listenbrainzdata" :key="user.id" :user="user" :collapsed="true"/>
 					</MkLazy>
 				</template>
 				<!-- <div v-if="!disableNotes">
@@ -142,14 +152,27 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div> -->
 				<MkStickyContainer>
 					<template #header>
+						<!-- You can't use v-if on these, as MkTab first *deletes* and replaces all children with native HTML elements. -->
+						<!-- Instead, we add a "no notes" placeholder and default to null (all notes) if there's nothing pinned. -->
+						<!-- It also converts all comments into text! -->
 						<MkTab v-model="noteview" :class="$style.tab">
+							<option value="pinned">{{ i18n.ts.pinnedOnly }}</option>
 							<option :value="null">{{ i18n.ts.notes }}</option>
 							<option value="all">{{ i18n.ts.all }}</option>
 							<option value="files">{{ i18n.ts.withFiles }}</option>
 						</MkTab>
 					</template>
 					<MkLazy>
-						<MkNotes :class="$style.tl" :noGap="true" :pagination="AllPagination"/>
+						<div v-if="noteview === 'pinned'" class="_gaps">
+							<div v-if="user.pinnedNotes.length < 1" class="_fullinfo">
+								<img :src="infoImageUrl" class="_ghost" aria-hidden="true" :alt="i18n.ts.noNotes"/>
+								<div>{{ i18n.ts.noNotes }}</div>
+							</div>
+							<div v-else class="_panel">
+								<MkNote v-for="note of user.pinnedNotes" :key="note.id" class="note" :class="$style.pinnedNote" :note="note" :pinned="true"/>
+							</div>
+						</div>
+						<MkNotes v-else :class="$style.tl" :noGap="true" :pagination="AllPagination"/>
 					</MkLazy>
 				</MkStickyContainer>
 			</div>
@@ -175,7 +198,7 @@ import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkButton from '@/components/MkButton.vue';
-import { getScrollPosition } from '@/scripts/scroll.js';
+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';
@@ -189,11 +212,12 @@ 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 { infoImageUrl } from '@/instance.js';
 
 const MkNote = defineAsyncComponent(() =>
-	(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
-	(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
-	null
+	defaultStore.state.noteDesign === 'sharkey'
+		? import('@/components/SkNote.vue')
+		: import('@/components/MkNote.vue'),
 );
 
 function calcAge(birthdate: string): number {
@@ -213,7 +237,7 @@ function calcAge(birthdate: string): number {
 
 const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
 const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
-const XListenBrainz = defineAsyncComponent(() => import("./index.listenbrainz.vue"));
+const XListenBrainz = defineAsyncComponent(() => import('./index.listenbrainz.vue'));
 //const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
 
 const props = withDefaults(defineProps<{
@@ -245,7 +269,7 @@ if (props.user.listenbrainz) {
 			const response = await fetch(`https://api.listenbrainz.org/1/user/${props.user.listenbrainz}/playing-now`, {
 				method: 'GET',
 				headers: {
-					'Content-Type': 'application/json'
+					'Content-Type': 'application/json',
 				},
 			});
 			const data = await response.json();
@@ -262,11 +286,11 @@ const background = computed(() => {
 	if (props.user.backgroundUrl == null) return {};
 	if (defaultStore.state.disableShowingAnimatedImages) {
 		return {
-			'--backgroundImageStatic': `url('${getStaticImageUrl(props.user.backgroundUrl)}')`
+			'--backgroundImageStatic': `url('${getStaticImageUrl(props.user.backgroundUrl)}')`,
 		};
 	} else {
 		return {
-			'--backgroundImageStatic': `url('${props.user.backgroundUrl}')`
+			'--backgroundImageStatic': `url('${props.user.backgroundUrl}')`,
 		};
 	}
 });
@@ -279,7 +303,7 @@ const pagination = {
 	endpoint: 'users/featured-notes' as const,
 	limit: 10,
 	params: computed(() => ({
-		userId: props.user.id
+		userId: props.user.id,
 	})),
 };
 
@@ -445,17 +469,6 @@ onUnmounted(() => {
 						background: linear-gradient(transparent, rgba(#000, 0.7));
 					}
 
-					> .followed {
-						position: absolute;
-						top: 12px;
-						left: 12px;
-						padding: 4px 8px;
-						color: #fff;
-						background: rgba(0, 0, 0, 0.7);
-						font-size: 0.7em;
-						border-radius: var(--radius-sm);
-					}
-
 					> .actions {
 						position: absolute;
 						top: 12px;
@@ -552,6 +565,11 @@ onUnmounted(() => {
 					filter: drop-shadow(1px 1px 3px rgba(#000, 0.2));
 				}
 
+				> .followedMessage {
+					padding: 24px 24px 0 154px;
+					font-size: 0.9em;
+				}
+
 				> .roles {
 					padding: 24px 24px 0 154px;
 					font-size: 0.95em;
@@ -734,6 +752,10 @@ onUnmounted(() => {
 					margin: auto;
 				}
 
+				> .followedMessage {
+					padding: 16px 16px 0 16px;
+				}
+
 				> .roles {
 					padding: 16px 16px 0 16px;
 					justify-content: center;
@@ -780,7 +802,7 @@ onUnmounted(() => {
 }
 
 .tab {
-	margin: calc(var(--margin) / 2) 0;
+	margin-bottom: calc(var(--margin) / 2);
 	padding: calc(var(--margin) / 2) 0;
 	background: color-mix(in srgb, var(--bg) 65%, transparent);
 	backdrop-filter: var(--blur, blur(15px));
@@ -797,4 +819,34 @@ onUnmounted(() => {
 	margin-left: 4px;
 	color: var(--success);
 }
+
+.pinnedNote:not(:last-child) {
+	border-bottom: solid 0.5px var(--divider);
+}
+
+.infoBadges {
+	position: absolute;
+	top: 12px;
+	left: 12px;
+
+	display: flex;
+	flex-direction: row;
+
+	padding: 0;
+	margin: 0;
+
+	> * {
+		padding: 4px 8px;
+		color: #fff;
+		background: rgba(0, 0, 0, 0.7);
+		font-size: 0.7em;
+		border-radius: var(--radius-sm);
+		list-style-type: none;
+		margin-left: 0;
+	}
+
+	> :not(:first-child) {
+		margin-left: 8px;
+	}
+}
 </style>
diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue
index 45bc35067b2f7021192ada44c997d54397f0c1f0..ba94b5c822c5d584f571261cbd929d88c543564a 100644
--- a/packages/frontend/src/pages/user/index.activity.vue
+++ b/packages/frontend/src/pages/user/index.activity.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkContainer>
+<MkContainer :foldable="true" :expanded="!collapsed">
 	<template #icon><i class="ti ti-chart-line"></i></template>
 	<template #header>{{ i18n.ts.activity }}</template>
 	<template #func="{ buttonStyleClass }">
@@ -30,8 +30,10 @@ import { i18n } from '@/i18n.js';
 const props = withDefaults(defineProps<{
 	user: Misskey.entities.User;
 	limit?: number;
+	collapsed?: boolean;
 }>(), {
 	limit: 50,
+	collapsed: false,
 });
 
 const chartSrc = ref('per-user-notes');
diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.files.vue
index 1430ae1296f02c04c8cbcc183035c9f3528711e2..23fd4ca23e82d47a31715eeda8380fa6410128e2 100644
--- a/packages/frontend/src/pages/user/index.files.vue
+++ b/packages/frontend/src/pages/user/index.files.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkContainer :max-height="300" :foldable="true">
+<MkContainer :max-height="300" :foldable="true" :expanded="!collapsed">
 	<template #icon><i class="ti ti-photo"></i></template>
 	<template #header>{{ i18n.ts.files }}</template>
 	<div :class="$style.root">
@@ -43,9 +43,12 @@ import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
 	user: Misskey.entities.UserDetailed;
-}>();
+	collapsed?: boolean;
+}>(), {
+	collapsed: false,
+});
 
 const fetching = ref(true);
 const files = ref<{
diff --git a/packages/frontend/src/pages/user/index.listenbrainz.vue b/packages/frontend/src/pages/user/index.listenbrainz.vue
index 18092d9d875a0c4691cd12b814d42f460ccb4f39..1c9ef8dd226a6eb42053cfab5bd8389b9ae572ce 100644
--- a/packages/frontend/src/pages/user/index.listenbrainz.vue
+++ b/packages/frontend/src/pages/user/index.listenbrainz.vue
@@ -4,44 +4,43 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-	<MkContainer :foldable="true">
-		<template #header
-			><i
-				class="ph-headphones ph-bold ph-lg"
-				style="margin-right: 0.5em"
-			></i
-			>Music</template
-		>
+<MkContainer :foldable="true" :expanded="!collapsed">
+	<template #header>
+		<i
+			class="ph-headphones ph-bold ph-lg"
+			style="margin-right: 0.5em"
+		></i>Music
+	</template>
 
-		<div style="padding: 8px">
-			<div class="flex">
-				<a :href="listenbrainz.musicbrainzurl">
-					<img class="image" :src="listenbrainz.img" :alt="listenbrainz.title" />
-					<div class="flex flex-col items-start">
-						<p class="text-sm font-bold">Now Playing: {{ listenbrainz.title }}</p>
-						<p class="text-xs font-medium">{{ listenbrainz.artist }}</p>
-					</div>
-				</a>
-				<a :href="listenbrainz.listenbrainzurl">
-					<div class="playicon">
-						<i class="ph-play ph-bold ph-lg"></i>
-					</div>
-				</a>
-			</div>
+	<div style="padding: 8px">
+		<div class="flex">
+			<a :href="listenbrainz.musicbrainzurl">
+				<img class="image" :src="listenbrainz.img" :alt="listenbrainz.title"/>
+				<div class="flex flex-col items-start">
+					<p class="text-sm font-bold">Now Playing: {{ listenbrainz.title }}</p>
+					<p class="text-xs font-medium">{{ listenbrainz.artist }}</p>
+				</div>
+			</a>
+			<a :href="listenbrainz.listenbrainzurl">
+				<div class="playicon">
+					<i class="ph-play ph-bold ph-lg"></i>
+				</div>
+			</a>
 		</div>
-	</MkContainer>
+	</div>
+</MkContainer>
 </template>
 
 <script lang="ts" setup>
-/* eslint-disable no-mixed-spaces-and-tabs */
-import {} from "vue";
-import * as misskey from "misskey-js";
-import MkContainer from "@/components/MkContainer.vue";
+import * as misskey from 'misskey-js';
+import MkContainer from '@/components/MkContainer.vue';
 const props = withDefaults(
 	defineProps<{
-		user: misskey.entities.User;
-	}>(),
-	{},
+		user: misskey.entities.UserDetailed;
+		collapsed?: boolean;
+	}>(), {
+		collapsed: false,
+	},
 );
 const listenbrainz = { title: '', artist: '', lastlisten: '', img: '', musicbrainzurl: '', listenbrainzurl: '' };
 if (props.user.listenbrainz) {
@@ -49,12 +48,12 @@ if (props.user.listenbrainz) {
 		const response = await fetch(`https://api.listenbrainz.org/1/metadata/lookup/?artist_name=${artist}&recording_name=${title}`, {
 			method: 'GET',
 			headers: {
-				'Content-Type': 'application/json'
+				'Content-Type': 'application/json',
 			},
 		});
 		const data = await response.json();
 		if (!data.recording_name) {
-		return null;
+			return null;
 		}
 		const titler: string = data.recording_name;
 		const artistr: string = data.artist_credit_name;
@@ -64,35 +63,33 @@ if (props.user.listenbrainz) {
 		return [titler, artistr, img, musicbrainzurl, listenbrainzurl];
 	};
 	const response = await fetch(`https://api.listenbrainz.org/1/user/${props.user.listenbrainz}/playing-now`, {
-        method: 'GET',
-        headers: {
-			'Content-Type': 'application/json'
-        },
-    });
-    const data = await response.json();
+		method: 'GET',
+		headers: {
+			'Content-Type': 'application/json',
+		},
+	});
+	const data = await response.json();
 	if (data.payload.listens && data.payload.listens.length !== 0) {
-      const title: string = data.payload.listens[0].track_metadata.track_name;
-      const artist: string = data.payload.listens[0].track_metadata.artist_name;
-      const lastlisten: string = data.payload.listens[0].playing_now;
-      const img: string = 'https://coverartarchive.org/img/big_logo.svg';
-      await getLMData(title, artist).then((data) => {
-        if (!data) {
-          listenbrainz.title = title;
-		  listenbrainz.img = img;
-		  listenbrainz.artist = artist;
-		  listenbrainz.lastlisten = lastlisten;
-		  return;
-        } else {
-          listenbrainz.title = data[0];
-		  listenbrainz.img = data[2];
-		  listenbrainz.artist = data[1];
-		  listenbrainz.lastlisten = lastlisten;
-		  listenbrainz.musicbrainzurl = data[3];
-		  listenbrainz.listenbrainzurl = data[4];
-          return;
-        }
-      });
-    }
+		const title: string = data.payload.listens[0].track_metadata.track_name;
+		const artist: string = data.payload.listens[0].track_metadata.artist_name;
+		const lastlisten: string = data.payload.listens[0].playing_now;
+		const img = 'https://coverartarchive.org/img/big_logo.svg';
+		await getLMData(title, artist).then((lmData) => {
+			if (!lmData) {
+				listenbrainz.title = title;
+				listenbrainz.img = img;
+				listenbrainz.artist = artist;
+				listenbrainz.lastlisten = lastlisten;
+			} else {
+				listenbrainz.title = lmData[0];
+				listenbrainz.img = lmData[2];
+				listenbrainz.artist = lmData[1];
+				listenbrainz.lastlisten = lastlisten;
+				listenbrainz.musicbrainzurl = lmData[3];
+				listenbrainz.listenbrainzurl = lmData[4];
+			}
+		});
+	}
 }
 </script>
 
@@ -119,8 +116,7 @@ if (props.user.listenbrainz) {
 }
 .text-sm {
 	font-size: 0.875rem;
-	margin: 0;
-	margin-bottom: 0.3rem;
+	margin: 0 0 0.3rem;
 }
 .font-bold {
 	font-weight: 700;
diff --git a/packages/frontend/src/pages/user/recent-notes.vue b/packages/frontend/src/pages/user/recent-notes.vue
new file mode 100644
index 0000000000000000000000000000000000000000..c16a1c5fed91c68fffead4f1a9d7f30d7cf8f830
--- /dev/null
+++ b/packages/frontend/src/pages/user/recent-notes.vue
@@ -0,0 +1,66 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer ref="userScroll">
+	<template #header>
+		<MkPageHeader :actions="headerActions" :displayBackButton="true"/>
+	</template>
+	<SkUserRecentNotes ref="userRecentNotes" :userId="userId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withBots="withBots" :withReplies="withReplies" :onlyFiles="onlyFiles"/>
+</MkStickyContainer>
+</template>
+
+<script setup lang="ts">
+
+import { computed, shallowRef } from 'vue';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { i18n } from '@/i18n.js';
+import { PageHeaderItem } from '@/types/page-header.js';
+import MkPageHeader from '@/components/global/MkPageHeader.vue';
+import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue';
+import { acct } from '@/filters/user.js';
+import { createModel, createOptions } from '@/scripts/following-feed-utils.js';
+import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
+
+defineProps<{
+	userId: string;
+}>();
+
+const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>();
+const user = computed(() => userRecentNotes.value?.user);
+
+const {
+	withNonPublic,
+	withQuotes,
+	withBots,
+	withReplies,
+	onlyFiles,
+} = createModel();
+
+const headerActions: PageHeaderItem[] = [
+	{
+		icon: 'ti ti-refresh',
+		text: i18n.ts.reload,
+		handler: () => userRecentNotes.value?.reload(),
+	},
+	createOptions(),
+];
+
+// Based on user/index.vue
+definePageMetadata(() => ({
+	title: i18n.ts.user,
+	icon: 'ti ti-user',
+	...user.value ? {
+		title: user.value.name ? ` (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: `@${acct(user.value)}`,
+		userName: user.value,
+		avatar: user.value,
+		path: `/@${user.value.username}`,
+		share: {
+			title: user.value.name,
+		},
+	} : {},
+}));
+</script>
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 31911649ac8170238da4825462624bd9761c9f13..5a41100bf1c842256f80fdae10553ebfec98fdd1 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
-import { host, version } from '@/config.js';
+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';
diff --git a/packages/frontend/src/pages/welcome.timeline.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue
index 252b1a2955c49f029b8f9c944556653d08ef5def..ee8d4e1d625b80aee822353ecf50f1ae10b2e12d 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), var(--X15));
+		background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0));
 	}
 }
 
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 045f424cdaedbec86ab3cfa9c9c6c8a9c13d777e..16d558cc91626cc8068ef6c66ea7b7c501383505 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -24,7 +24,7 @@ import * as Misskey from 'misskey-js';
 import { onUpdated, ref, shallowRef } from 'vue';
 import XNote from '@/pages/welcome.timeline.note.vue';
 import { misskeyApiGet } from '@/scripts/misskey-api.js';
-import { getScrollContainer } from '@/scripts/scroll.js';
+import { getScrollContainer } from '@@/js/scroll.js';
 
 const notes = ref<Misskey.entities.Note[]>([]);
 const isScrolling = ref(false);
diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue
index 915fe35025ea72885c01c0547a85927b2c7b948e..38d257506c48062378280223fc78598fca943605 100644
--- a/packages/frontend/src/pages/welcome.vue
+++ b/packages/frontend/src/pages/welcome.vue
@@ -15,7 +15,7 @@ import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XSetup from './welcome.setup.vue';
 import XEntrance from './welcome.entrance.a.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { fetchInstance } from '@/instance.js';
 
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 81233a5a5e451447948ad9bc7c636165b7477086..c0034d414ce679aa993728439313bae5f10cab3f 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -6,8 +6,10 @@
 import { ref } from 'vue';
 import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
 import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
-import { inputText } from '@/os.js';
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
 import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
+import { warningExternalWebsite } from '@/scripts/warning-external-website.js';
 
 const parser = new Parser();
 const pluginContexts = new Map<string, Interpreter>();
@@ -92,7 +94,7 @@ function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record<s
 		}),
 		'Plugin:open_url': values.FN_NATIVE(([url]) => {
 			utils.assertString(url);
-			window.open(url.value, '_blank', 'noopener');
+			warningExternalWebsite(url.value);
 		}),
 		'Plugin:config': values.OBJ(config),
 	};
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts
index 14110d1f9bd0cbb9494d5692fa9e548d2818f1b3..686ac6920acfa2d528c154c9e34105897bb8d2cb 100644
--- a/packages/frontend/src/router/definition.ts
+++ b/packages/frontend/src/router/definition.ts
@@ -3,15 +3,14 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
-import type { RouteDef } from '@/nirax.js';
-import { IRouter, Router } from '@/nirax.js';
+import { AsyncComponentLoader, defineAsyncComponent } from 'vue';
+import type { IRouter, RouteDef } from '@/nirax.js';
+import { Router } from '@/nirax.js';
 import { $i, iAmModerator } from '@/account.js';
 import MkLoading from '@/pages/_loading_.vue';
 import MkError from '@/pages/_error_.vue';
-import { setMainRouter } from '@/router/main.js';
 
-const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
+export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
 	loader: loader,
 	loadingComponent: MkLoading,
 	errorComponent: MkError,
@@ -227,6 +226,14 @@ const routes: RouteDef[] = [{
 	path: '/explore',
 	component: page(() => import('@/pages/explore.vue')),
 	hash: 'initialTab',
+}, {
+	path: '/following-feed',
+	component: page(() => import('@/pages/following-feed.vue')),
+	loginRequired: true,
+}, {
+	path: '/following-feed/:userId',
+	component: page(() => import('@/pages/user/recent-notes.vue')),
+	loginRequired: true,
 }, {
 	path: '/search',
 	component: page(() => import('@/pages/search.vue')),
@@ -463,22 +470,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',
@@ -601,36 +600,6 @@ const routes: RouteDef[] = [{
 	component: page(() => import('@/pages/not-found.vue')),
 }];
 
-function createRouterImpl(path: string): IRouter {
+export function createMainRouter(path: string): IRouter {
 	return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue')));
 }
-
-/**
- * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
- * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能)
- */
-export function setupRouter(app: App) {
-	app.provide('routerFactory', createRouterImpl);
-
-	const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);
-
-	window.addEventListener('popstate', (event) => {
-		mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
-	});
-
-	mainRouter.addListener('push', ctx => {
-		window.history.pushState({ key: ctx.key }, '', ctx.path);
-	});
-
-	mainRouter.addListener('same', () => {
-		window.scroll({ top: 0, behavior: 'smooth' });
-	});
-
-	mainRouter.addListener('replace', ctx => {
-		window.history.replaceState({ key: ctx.key }, '', ctx.path);
-	});
-
-	mainRouter.init();
-
-	setMainRouter(mainRouter);
-}
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
index 7a3fde131ed840139b459b4ad1148a7bb8e367d8..709c508741b414dc323061f1dbccc4c4571a1801 100644
--- a/packages/frontend/src/router/main.ts
+++ b/packages/frontend/src/router/main.ts
@@ -3,10 +3,41 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { ShallowRef } from 'vue';
 import { EventEmitter } from 'eventemitter3';
 import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
 
+import type { App, ShallowRef } from 'vue';
+
+/**
+ * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
+ * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能)
+ */
+export function setupRouter(app: App, routerFactory: ((path: string) => IRouter)): void {
+	app.provide('routerFactory', routerFactory);
+
+	const mainRouter = routerFactory(location.pathname + location.search + location.hash);
+
+	window.addEventListener('popstate', (event) => {
+		mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
+	});
+
+	mainRouter.addListener('push', ctx => {
+		window.history.pushState({ key: ctx.key }, '', ctx.path);
+	});
+
+	mainRouter.addListener('same', () => {
+		window.scroll({ top: 0, behavior: 'smooth' });
+	});
+
+	mainRouter.addListener('replace', ctx => {
+		window.history.replaceState({ key: ctx.key }, '', ctx.path);
+	});
+
+	mainRouter.init();
+
+	setMainRouter(mainRouter);
+}
+
 function getMainRouter(): IRouter {
 	const router = mainRouterHolder;
 	if (!router) {
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 98a0c61752cb66c446dd5b552f18795e218e4901..46aed49330f79750f55f3cf98d0190b14ab0603d 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -4,13 +4,13 @@
  */
 
 import { utils, values } from '@syuilo/aiscript';
+import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i } from '@/account.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { customEmojis } from '@/custom-emojis.js';
-import { url, lang } from '@/config.js';
-import { nyaize } from '@/scripts/nyaize.js';
+import { url, lang } from '@@/js/config.js';
 
 export function aiScriptReadline(q: string): Promise<string> {
 	return new Promise(ok => {
@@ -87,7 +87,7 @@ export function createAiScriptEnv(opts) {
 		}),
 		'Mk:nyaize': values.FN_NATIVE(([text]) => {
 			utils.assertString(text);
-			return values.STR(nyaize(text.value));
+			return values.STR(Misskey.nyaize(text.value));
 		}),
 	};
 }
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index fa3fcac2e79a992e82e3c2ffd58e4123da4712ee..2b386bebb81aa1f79b390b7157768aaeb8e49f72 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-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts
index 8fc857f84fce06babd2417ad944cc96222e96322..c3c3f419a978c064f94107e1de0421813dba6add 100644
--- a/packages/frontend/src/scripts/check-reaction-permissions.ts
+++ b/packages/frontend/src/scripts/check-reaction-permissions.ts
@@ -4,7 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { UnicodeEmojiDef } from './emojilist.js';
+import { UnicodeEmojiDef } from '@@/js/emojilist.js';
 
 export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
 	if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする;
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index e94027d3022a516e4e08d28b3a1c9743347b380b..6710d9826e8d6ce643d03d9293af56f164842dc4 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -3,17 +3,17 @@
  * 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';
+import lightTheme from '@@/themes/_light.json5';
+import darkTheme from '@@/themes/_dark.json5';
 import { unique } from './array.js';
 import { deepClone } from './clone.js';
 import { deepMerge } from './merge.js';
 import type { HighlighterCore, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki/core';
 import { ColdDeviceStorage } from '@/store.js';
-import lightTheme from '@/themes/_light.json5';
-import darkTheme from '@/themes/_dark.json5';
 
 let _highlighter: HighlighterCore | null = null;
 
@@ -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/focus.ts b/packages/frontend/src/scripts/focus.ts
index eb2da5ad8665c47ebe6c7d55b7c8f9684a79c407..81278b17ea6de36820ae97f5ae45412fc2ab556a 100644
--- a/packages/frontend/src/scripts/focus.ts
+++ b/packages/frontend/src/scripts/focus.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@/scripts/scroll.js';
+import { getScrollPosition, getScrollContainer, getStickyBottom, getStickyTop } from '@@/js/scroll.js';
 import { getElementOrNull, getNodeOrNull } from '@/scripts/get-dom-node-or-null.js';
 
 type MaybeHTMLElement = EventTarget | Node | Element | HTMLElement;
diff --git a/packages/frontend/src/scripts/following-feed-utils.ts b/packages/frontend/src/scripts/following-feed-utils.ts
new file mode 100644
index 0000000000000000000000000000000000000000..064d6b72e35cf7f6adcfb1de470ab55fac9dfe71
--- /dev/null
+++ b/packages/frontend/src/scripts/following-feed-utils.ts
@@ -0,0 +1,118 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { computed } from 'vue';
+import { defaultStore } from '@/store.js';
+import { deepMerge } from '@/scripts/merge.js';
+import { PageHeaderItem } from '@/types/page-header.js';
+import { i18n } from '@/i18n.js';
+import { popupMenu } from '@/os.js';
+
+export const followingTab = 'following' as const;
+export const mutualsTab = 'mutuals' as const;
+export const followersTab = 'followers' as const;
+export type FollowingFeedTab = typeof followingTab | typeof mutualsTab | typeof followersTab;
+
+export function createOptions(): PageHeaderItem {
+	const {
+		userList,
+		withNonPublic,
+		withQuotes,
+		withBots,
+		withReplies,
+		onlyFiles,
+	} = createModel();
+
+	return {
+		icon: 'ti ti-dots',
+		text: i18n.ts.options,
+		handler: ev =>
+			popupMenu([
+				{
+					type: 'switch',
+					text: i18n.ts.showNonPublicNotes,
+					ref: withNonPublic,
+					disabled: userList.value === 'followers',
+				},
+				{
+					type: 'switch',
+					text: i18n.ts.showQuotes,
+					ref: withQuotes,
+				},
+				{
+					type: 'switch',
+					text: i18n.ts.showBots,
+					ref: withBots,
+				},
+				{
+					type: 'switch',
+					text: i18n.ts.showReplies,
+					ref: withReplies,
+					disabled: onlyFiles,
+				},
+				{
+					type: 'divider',
+				},
+				{
+					type: 'switch',
+					text: i18n.ts.fileAttachedOnly,
+					ref: onlyFiles,
+					disabled: withReplies,
+				},
+			], ev.currentTarget ?? ev.target),
+	};
+}
+
+export function createModel() {
+	const userList = computed({
+		get: () => defaultStore.reactiveState.followingFeed.value.userList,
+		set: value => saveFollowingFilter('userList', value),
+	});
+
+	const withNonPublic = computed({
+		get: () => {
+			if (userList.value === 'followers') return false;
+			return defaultStore.reactiveState.followingFeed.value.withNonPublic;
+		},
+		set: value => saveFollowingFilter('withNonPublic', value),
+	});
+	const withQuotes = computed({
+		get: () => defaultStore.reactiveState.followingFeed.value.withQuotes,
+		set: value => saveFollowingFilter('withQuotes', value),
+	});
+	const withBots = computed({
+		get: () => defaultStore.reactiveState.followingFeed.value.withBots,
+		set: value => saveFollowingFilter('withBots', value),
+	});
+	const withReplies = computed({
+		get: () => defaultStore.reactiveState.followingFeed.value.withReplies,
+		set: value => saveFollowingFilter('withReplies', value),
+	});
+	const onlyFiles = computed({
+		get: () => defaultStore.reactiveState.followingFeed.value.onlyFiles,
+		set: value => saveFollowingFilter('onlyFiles', value),
+	});
+
+	const remoteWarningDismissed = computed({
+		get: () => defaultStore.reactiveState.followingFeed.value.remoteWarningDismissed,
+		set: value => saveFollowingFilter('remoteWarningDismissed', value),
+	});
+
+	return {
+		userList,
+		withNonPublic,
+		withQuotes,
+		withBots,
+		withReplies,
+		onlyFiles,
+		remoteWarningDismissed,
+	};
+}
+
+// Based on timeline.saveTlFilter()
+function saveFollowingFilter<Key extends keyof typeof defaultStore.state.followingFeed>(key: Key, value: (typeof defaultStore.state.followingFeed)[Key]) {
+	const out = deepMerge({ [key]: value }, defaultStore.state.followingFeed);
+	return defaultStore.set('followingFeed', out);
+}
diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts
index 60884d08d39b933a0eb83e8b82cd7e1568543fbb..a85ee01e26e55f4a7d36cd89e6fc215777b5019b 100644
--- a/packages/frontend/src/scripts/gen-search-query.ts
+++ b/packages/frontend/src/scripts/gen-search-query.ts
@@ -4,7 +4,7 @@
  */
 
 import * as Misskey from 'misskey-js';
-import { host as localHost } from '@/config.js';
+import { host as localHost } from '@@/js/config.js';
 
 export async function genSearchQuery(v: any, q: string) {
 	let host: string;
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index 108648d640ad0defe1a53f80987715042a3a0ff5..c8ab9238d3b72996938d36790c6d872417be6e47 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-embed-code.ts b/packages/frontend/src/scripts/get-embed-code.ts
new file mode 100644
index 0000000000000000000000000000000000000000..158ab9c7f81e2d13e4273e47284c1da8b58470be
--- /dev/null
+++ b/packages/frontend/src/scripts/get-embed-code.ts
@@ -0,0 +1,87 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+import { defineAsyncComponent } from 'vue';
+import { v4 as uuid } from 'uuid';
+import type { EmbedParams, EmbeddableEntity } from '@@/js/embed-page.js';
+import { url } from '@@/js/config.js';
+import * as os from '@/os.js';
+import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
+import { defaultEmbedParams, embedRouteWithScrollbar } from '@@/js/embed-page.js';
+
+const MOBILE_THRESHOLD = 500;
+
+/**
+ * パラメータを正規化する(埋め込みコード作成用)
+ * @param params パラメータ
+ * @returns 正規化されたパラメータ
+ */
+export function normalizeEmbedParams(params: EmbedParams): Record<string, string> {
+	// paramsのvalueをすべてstringに変換。undefinedやnullはプロパティごと消す
+	const normalizedParams: Record<string, string> = {};
+	for (const key in params) {
+		// デフォルトの値と同じならparamsに含めない
+		if (params[key] == null || params[key] === defaultEmbedParams[key]) {
+			continue;
+		}
+		switch (typeof params[key]) {
+			case 'number':
+				normalizedParams[key] = params[key].toString();
+				break;
+			case 'boolean':
+				normalizedParams[key] = params[key] ? 'true' : 'false';
+				break;
+			default:
+				normalizedParams[key] = params[key];
+				break;
+		}
+	}
+	return normalizedParams;
+}
+
+/**
+ * 埋め込みコードを生成(iframe IDの発番もやる)
+ */
+export function getEmbedCode(path: string, params?: EmbedParams): string {
+	const iframeId = 'v1_' + uuid(); // 将来embed.jsのバージョンが上がったとき用にv1_を付けておく
+
+	let paramString = '';
+	if (params) {
+		const searchParams = new URLSearchParams(normalizeEmbedParams(params));
+		paramString = searchParams.toString() === '' ? '' : '?' + searchParams.toString();
+	}
+
+	const iframeCode = [
+		`<iframe src="${url + path + paramString}" data-misskey-embed-id="${iframeId}" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" style="border: none; width: 100%; max-width: 500px; height: 300px; color-scheme: light dark;"></iframe>`,
+		`<script defer src="${url}/embed.js"></script>`,
+	];
+	return iframeCode.join('\n');
+}
+
+/**
+ * 埋め込みコードを生成してコピーする(カスタマイズ機能つき)
+ *
+ * カスタマイズ機能がいらない場合(事前にパラメータを指定する場合)は getEmbedCode を直接使ってください
+ */
+export function genEmbedCode(entity: EmbeddableEntity, id: string, params?: EmbedParams) {
+	const _params = { ...params };
+
+	if (embedRouteWithScrollbar.includes(entity) && _params.maxHeight == null) {
+		_params.maxHeight = 700;
+	}
+
+	// PCじゃない場合はコードカスタマイズ画面を出さずにそのままコピー
+	if (window.innerWidth < MOBILE_THRESHOLD) {
+		copyToClipboard(getEmbedCode(`/embed/${entity}/${id}`, _params));
+		os.success();
+	} else {
+		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkEmbedCodeGenDialog.vue')), {
+			entity,
+			id,
+			params: _params,
+		}, {
+			closed: () => dispose(),
+		});
+	}
+}
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index a7ec4ce6d7735acd81a7c5183de681ad522d830b..4f3fb656651694e6fc10fa73c162c3ccf92ae33a 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -12,15 +12,16 @@ import { instance } from '@/instance.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { url } from '@/config.js';
+import { url } from '@@/js/config.js';
 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';
+import { genEmbedCode } from '@/scripts/get-embed-code.js';
 
 export async function getNoteClipMenu(props: {
 	note: Misskey.entities.Note;
@@ -66,6 +67,11 @@ export async function getNoteClipMenu(props: {
 							});
 							if (props.currentClip?.id === clip.id) props.isDeleted.value = true;
 						}
+					} else if (err.id === 'f0dba960-ff73-4615-8df4-d6ac5d9dc118') {
+						os.alert({
+							type: 'error',
+							text: i18n.ts.clipNoteLimitExceeded,
+						});
 					} else {
 						os.alert({
 							type: 'error',
@@ -93,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,
 				},
@@ -162,6 +170,19 @@ export function getCopyNoteOriginLinkMenu(note: misskey.entities.Note, text: str
 	};
 }
 
+function getNoteEmbedCodeMenu(note: Misskey.entities.Note, text: string): MenuItem | undefined {
+	if (note.url != null || note.uri != null) return undefined;
+	if (['specified', 'followers'].includes(note.visibility)) return undefined;
+
+	return {
+		icon: 'ti ti-code',
+		text,
+		action: (): void => {
+			genEmbedCode('notes', note.id);
+		},
+	};
+}
+
 export function getNoteMenu(props: {
 	note: Misskey.entities.Note;
 	translation: Ref<Misskey.entities.NotesTranslateResponse | null>;
@@ -267,7 +288,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,
@@ -298,170 +319,184 @@ 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(
 				getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
-			: undefined,
-			(appearNote.url || appearNote.uri) ? {
+			);
+			menuItems.push({
 				icon: 'ti ti-external-link',
 				text: i18n.ts.showOnRemote,
 				action: () => {
 					window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
 				},
-			} : undefined,
-			...(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: 'ph-pencil-simple ph-bold ph-lg',
 					text: i18n.ts.edit,
 					action: edit,
-				} : undefined,
-				{
+				});
+				menuItems.push({
 					icon: 'ti ti-edit',
 					text: i18n.ts.deleteAndEdit,
 					danger: true,
 					action: delEdit,
-				},
-				{
-					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,
@@ -469,38 +504,45 @@ export function getNoteMenu(props: {
 			icon: 'ti ti-copy',
 			text: i18n.ts.copyContent,
 			action: copyContent,
-		}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)
-		, (appearNote.url || appearNote.uri) ?
-			getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
-		: undefined,
-		(appearNote.url || appearNote.uri) ? {
-			icon: 'ti ti-external-link',
-			text: i18n.ts.showOnRemote,
-			action: () => {
-				window.open(appearNote.url ?? appearNote.uri, '_blank', 'noopener');
-			},
-		} : undefined]
-			.filter(x => x !== undefined);
+		}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink));
+
+		if (appearNote.url || appearNote.uri) {
+			menuItems.push(
+				getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
+			);
+			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 = () => {
@@ -511,7 +553,7 @@ export function getNoteMenu(props: {
 	};
 
 	return {
-		menu,
+		menu: menuItems,
 		cleanup,
 	};
 }
diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts
index 6fd9947ac1e75eff85eb7790d926f3c88955b432..58d486bf9b166d4d3798413cde389f78d6a2e1ac 100644
--- a/packages/frontend/src/scripts/get-note-summary.ts
+++ b/packages/frontend/src/scripts/get-note-summary.ts
@@ -27,13 +27,13 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
 
 	// 本文
 	if (note.cw != null) {
-		summary += note.cw;
-	} else {
-		summary += note.text ? note.text : '';
+		summary += `CW: ${note.cw}`;
+	} else if (note.text) {
+		summary += note.text;
 	}
 
 	// ファイルが添付されているとき
-	if ((note.files || []).length !== 0) {
+	if (note.files && note.files.length !== 0) {
 		summary += ` (${i18n.tsx.withNFiles({ n: note.files.length })})`;
 	}
 
@@ -44,7 +44,7 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
 
 	// 返信のとき
 	if (note.replyId) {
-		if (note.reply) {
+		if (note.reply && !note.cw) {
 			summary += `\n\nRE: ${getNoteSummary(note.reply)}`;
 		} else {
 			summary += '\n\nRE: ...';
@@ -53,7 +53,7 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
 
 	// Renoteのとき
 	if (note.renoteId) {
-		if (note.renote) {
+		if (note.renote && !note.cw) {
 			summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
 		} else {
 			summary += '\n\nRN: ...';
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 33f16a68aa618d4e8c6c8ae9b3b03b0324d1b20c..d15279d6333935a4e77431bde1b9622bae44faae 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -8,7 +8,7 @@ import { defineAsyncComponent, ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
-import { host, url } from '@/config.js';
+import { host, url } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore, userActions } from '@/store.js';
@@ -17,7 +17,8 @@ import { notesSearchAvailable, canSearchNonLocalNotes } from '@/scripts/check-pe
 import { IRouter } from '@/nirax.js';
 import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
 import { mainRouter } from '@/router/main.js';
-import { MenuItem } from '@/types/menu.js';
+import { genEmbedCode } from '@/scripts/get-embed-code.js';
+import type { MenuItem } from '@/types/menu.js';
 
 export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
 	const meId = $i ? $i.id : null;
@@ -147,123 +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');
-		},
-	}] : []), {
+	});
+
+	if (user.host != null && user.url != null) {
+		menuItems.push({
+			icon: 'ti ti-external-link',
+			text: i18n.ts.showOnRemote,
+			action: () => {
+				if (user.url == null) return;
+				window.open(user.url, '_blank', 'noopener');
+			},
+		});
+	} 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,
@@ -301,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,
@@ -316,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,
@@ -327,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,
@@ -339,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/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts
index 6b511f2a5fc78ed8be89a74a393014984746f16f..20f51660c725ddc15a79b50d80a675ea0559477e 100644
--- a/packages/frontend/src/scripts/idb-proxy.ts
+++ b/packages/frontend/src/scripts/idb-proxy.ts
@@ -10,10 +10,11 @@ import {
 	set as iset,
 	del as idel,
 } from 'idb-keyval';
+import { miLocalStorage } from '@/local-storage.js';
 
-const fallbackName = (key: string) => `idbfallback::${key}`;
+const PREFIX = 'idbfallback::';
 
-let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && window.indexedDB.open) : true;
+let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && typeof window.indexedDB.open === 'function') : true;
 
 // iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。
 // バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと
@@ -38,15 +39,15 @@ if (idbAvailable) {
 
 export async function get(key: string) {
 	if (idbAvailable) return iget(key);
-	return JSON.parse(window.localStorage.getItem(fallbackName(key)));
+	return miLocalStorage.getItemAsJson(`${PREFIX}${key}`);
 }
 
 export async function set(key: string, val: any) {
 	if (idbAvailable) return iset(key, val);
-	return window.localStorage.setItem(fallbackName(key), JSON.stringify(val));
+	return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val);
 }
 
 export async function del(key: string) {
 	if (idbAvailable) return idel(key);
-	return window.localStorage.removeItem(fallbackName(key));
+	return miLocalStorage.removeItem(`${PREFIX}${key}`);
 }
diff --git a/packages/frontend/src/scripts/initialize-sw.ts b/packages/frontend/src/scripts/initialize-sw.ts
index 1517e4e1e8fa7be0ea821ce84f7d2af2d77380bb..867ebf19edadd4cacc8dbf33944e7d84aad54e70 100644
--- a/packages/frontend/src/scripts/initialize-sw.ts
+++ b/packages/frontend/src/scripts/initialize-sw.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { lang } from '@/config.js';
+import { lang } from '@@/js/config.js';
 
 export async function initializeSw() {
 	if (!('serviceWorker' in navigator)) return;
diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/scripts/intl-const.ts
index aaa4f0a86edf4898662d1a3f6caeaea88f057c4c..385f59ec39c068f213d9151ed7c38289f85a0b12 100644
--- a/packages/frontend/src/scripts/intl-const.ts
+++ b/packages/frontend/src/scripts/intl-const.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { lang } from '@/config.js';
+import { lang } from '@@/js/config.js';
 
 export const versatileLang = (lang ?? 'ja-JP').replace('ja-KS', 'ja-JP');
 
diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts
index 099a22163af44a0e8c2e6c3444bda13e2706fada..78eba35eaddb941518276539fb9286df400e0fa6 100644
--- a/packages/frontend/src/scripts/media-proxy.ts
+++ b/packages/frontend/src/scripts/media-proxy.ts
@@ -3,51 +3,32 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { query } from '@/scripts/url.js';
-import { url } from '@/config.js';
+import { MediaProxy } from '@@/js/media-proxy.js';
+import { url } from '@@/js/config.js';
 import { instance } from '@/instance.js';
 
-export function getProxiedImageUrl(imageUrl: string, type?: 'preview' | 'emoji' | 'avatar', mustOrigin = false, noFallback = false): string {
-	const localProxy = `${url}/proxy`;
+let _mediaProxy: MediaProxy | null = null;
 
-	if (imageUrl.startsWith(instance.mediaProxy + '/') || imageUrl.startsWith('/proxy/') || imageUrl.startsWith(localProxy + '/')) {
-		// もう既にproxyっぽそうだったらurlを取り出す
-		imageUrl = (new URL(imageUrl)).searchParams.get('url') ?? imageUrl;
+export function getProxiedImageUrl(...args: Parameters<MediaProxy['getProxiedImageUrl']>): string {
+	if (_mediaProxy == null) {
+		_mediaProxy = new MediaProxy(instance, url);
 	}
 
-	return `${mustOrigin ? localProxy : instance.mediaProxy}/${
-		type === 'preview' ? 'preview.webp'
-		: 'image.webp'
-	}?${query({
-		url: imageUrl,
-		...(!noFallback ? { 'fallback': '1' } : {}),
-		...(type ? { [type]: '1' } : {}),
-		...(mustOrigin ? { origin: '1' } : {}),
-	})}`;
+	return _mediaProxy.getProxiedImageUrl(...args);
 }
 
-export function getProxiedImageUrlNullable(imageUrl: string | null | undefined, type?: 'preview'): string | null {
-	if (imageUrl == null) return null;
-	return getProxiedImageUrl(imageUrl, type);
-}
-
-export function getStaticImageUrl(baseUrl: string): string {
-	const u = baseUrl.startsWith('http') ? new URL(baseUrl) : new URL(baseUrl, url);
-
-	if (u.href.startsWith(`${url}/emoji/`)) {
-		// もう既にemojiっぽそうだったらsearchParams付けるだけ
-		u.searchParams.set('static', '1');
-		return u.href;
+export function getProxiedImageUrlNullable(...args: Parameters<MediaProxy['getProxiedImageUrlNullable']>): string | null {
+	if (_mediaProxy == null) {
+		_mediaProxy = new MediaProxy(instance, url);
 	}
 
-	if (u.href.startsWith(instance.mediaProxy + '/')) {
-		// もう既にproxyっぽそうだったらsearchParams付けるだけ
-		u.searchParams.set('static', '1');
-		return u.href;
+	return _mediaProxy.getProxiedImageUrlNullable(...args);
+}
+
+export function getStaticImageUrl(...args: Parameters<MediaProxy['getStaticImageUrl']>): string {
+	if (_mediaProxy == null) {
+		_mediaProxy = new MediaProxy(instance, url);
 	}
 
-	return `${instance.mediaProxy}/static.webp?${query({
-		url: u.href,
-		static: '1',
-	})}`;
+	return _mediaProxy.getStaticImageUrl(...args);
 }
diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts
index 63acf9d3debcec849f0bbd0280b0746891d44e4a..2911469cddb9d671721c93dc5c9f4f312c29e596 100644
--- a/packages/frontend/src/scripts/mfm-function-picker.ts
+++ b/packages/frontend/src/scripts/mfm-function-picker.ts
@@ -6,7 +6,7 @@
 import { Ref, nextTick } from 'vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { MFM_TAGS } from '@/const.js';
+import { MFM_TAGS } from '@@/js/const.js';
 import type { MenuItem } from '@/types/menu.js';
 
 /**
diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts
index 49fb6f9e59c670f6ac0d7972b55fee911ce4ca35..1b1159fd015a8f3e67697d9dc7f43d193b531176 100644
--- a/packages/frontend/src/scripts/misskey-api.ts
+++ b/packages/frontend/src/scripts/misskey-api.ts
@@ -5,7 +5,7 @@
 
 import * as Misskey from 'misskey-js';
 import { ref } from 'vue';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { $i } from '@/account.js';
 export const pendingApiRequestsCount = ref(0);
 
diff --git a/packages/frontend/src/scripts/player-url-transform.ts b/packages/frontend/src/scripts/player-url-transform.ts
index 53b2a9e44151857adfcd262a7e8065ebbf3b2303..39c6df65004a996c2ccd55de7947901333dace95 100644
--- a/packages/frontend/src/scripts/player-url-transform.ts
+++ b/packages/frontend/src/scripts/player-url-transform.ts
@@ -2,7 +2,7 @@
  * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
-import { hostname } from '@/config.js';
+import { hostname } from '@@/js/config.js';
 
 export function transformPlayerUrl(url: string): string {
 	const urlObj = new URL(url);
diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/scripts/popout.ts
index 1caa2dfc21017074e4233f2176e312e94d6a5f3a..5b141222e84f2e5f5f272964bd73843b82464ad0 100644
--- a/packages/frontend/src/scripts/popout.ts
+++ b/packages/frontend/src/scripts/popout.ts
@@ -3,8 +3,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { appendQuery } from './url.js';
-import * as config from '@/config.js';
+import { appendQuery } from '@@/js/url.js';
+import * as config from '@@/js/config.js';
 
 export function popout(path: string, w?: HTMLElement) {
 	let url = path.startsWith('http://') || path.startsWith('https://') ? path : config.url + path;
diff --git a/packages/frontend/src/scripts/post-message.ts b/packages/frontend/src/scripts/post-message.ts
index 31a9ac1ad9dca54f33892827884254d48bee010b..11b6f52ddd0db4fb7d6f4e0589422ed6babd27a8 100644
--- a/packages/frontend/src/scripts/post-message.ts
+++ b/packages/frontend/src/scripts/post-message.ts
@@ -18,7 +18,7 @@ export type MiPostMessageEvent = {
  * 親フレームにイベントを送信
  */
 export function postMessageToParentWindow(type: PostMessageEventType, payload?: any): void {
-	window.postMessage({
+	window.parent.postMessage({
 		type,
 		payload,
 	}, '*');
diff --git a/packages/frontend/src/scripts/reload-ask.ts b/packages/frontend/src/scripts/reload-ask.ts
new file mode 100644
index 0000000000000000000000000000000000000000..733d91b85ab42625e51e0004a63a16ba9d0cde52
--- /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/safe-parse.ts b/packages/frontend/src/scripts/safe-parse.ts
deleted file mode 100644
index 6bfcef6c362c208d4b3dbc4cbb3eb8a84e9c73d7..0000000000000000000000000000000000000000
--- a/packages/frontend/src/scripts/safe-parse.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export function safeParseFloat(str: unknown): number | null {
-	if (typeof str !== 'string' || str === '') return null;
-	const num = parseFloat(str);
-	if (isNaN(num)) return null;
-	return num;
-}
diff --git a/packages/frontend/src/scripts/safe-uri-decode.ts b/packages/frontend/src/scripts/safe-uri-decode.ts
deleted file mode 100644
index 0edf4e9eba0f32f6bded54812f4a71afb74a1379..0000000000000000000000000000000000000000
--- a/packages/frontend/src/scripts/safe-uri-decode.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export function safeURIDecode(str: string): string {
-	try {
-		return decodeURIComponent(str);
-	} catch {
-		return str;
-	}
-}
diff --git a/packages/frontend/src/scripts/show-system-account-dialog.ts b/packages/frontend/src/scripts/show-system-account-dialog.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3c28d901fc5ba507c9c97a4eeeb93f4edd512f71
--- /dev/null
+++ b/packages/frontend/src/scripts/show-system-account-dialog.ts
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+
+export function showSystemAccountDialog(): Promise<void> {
+	return os.alert({
+		type: 'error',
+		title: i18n.ts.systemAccountTitle,
+		text: i18n.ts.systemAccountDescription,
+	});
+}
diff --git a/packages/frontend/src/scripts/stream-mock.ts b/packages/frontend/src/scripts/stream-mock.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cb0e607fcb6ad6a57e10a735ea8259696ef5ae9e
--- /dev/null
+++ b/packages/frontend/src/scripts/stream-mock.ts
@@ -0,0 +1,81 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { EventEmitter } from 'eventemitter3';
+import * as Misskey from 'misskey-js';
+import type { Channels, StreamEvents, IStream, IChannelConnection } from 'misskey-js';
+
+type AnyOf<T extends Record<any, any>> = T[keyof T];
+type OmitFirst<T extends any[]> = T extends [any, ...infer R] ? R : never;
+
+/**
+ * Websocket無効化時に使うStreamのモック(なにもしない)
+ */
+export class StreamMock extends EventEmitter<StreamEvents> implements IStream {
+	public readonly state = 'initializing';
+
+	constructor(...args: ConstructorParameters<typeof Misskey.Stream>) {
+		super();
+		// do nothing
+	}
+
+	public useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnectionMock<Channels[C]> {
+		return new ChannelConnectionMock(this, channel, name);
+	}
+
+	public removeSharedConnection(connection: any): void {
+		// do nothing
+	}
+
+	public removeSharedConnectionPool(pool: any): void {
+		// do nothing
+	}
+
+	public disconnectToChannel(): void {
+		// do nothing
+	}
+
+	public send(typeOrPayload: string): void
+	public send(typeOrPayload: string, payload: any): void
+	public send(typeOrPayload: Record<string, any> | any[]): void
+	public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void {
+		// do nothing
+	}
+
+	public ping(): void {
+		// do nothing
+	}
+
+	public heartbeat(): void {
+		// do nothing
+	}
+
+	public close(): void {
+		// do nothing
+	}
+}
+
+class ChannelConnectionMock<Channel extends AnyOf<Channels> = any> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
+	public id = '';
+	public name?: string; // for debug
+	public inCount = 0; // for debug
+	public outCount = 0; // for debug
+	public channel: string;
+
+	constructor(stream: IStream, ...args: OmitFirst<ConstructorParameters<typeof Misskey.ChannelConnection<Channel>>>) {
+		super();
+
+		this.channel = args[0];
+		this.name = args[1];
+	}
+
+	public send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void {
+		// do nothing
+	}
+
+	public dispose(): void {
+		// do nothing
+	}
+}
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index e59643b09cf4bedab6c4ccbe769e9be87341d895..bd3cddde67962a09319ea8bd3e8c97ee6a9c311d 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -5,11 +5,11 @@
 
 import { ref } from 'vue';
 import tinycolor from 'tinycolor2';
+import lightTheme from '@@/themes/_light.json5';
+import darkTheme from '@@/themes/_dark.json5';
 import { deepClone } from './clone.js';
 import type { BundledTheme } from 'shiki/themes';
 import { globalEvents } from '@/events.js';
-import lightTheme from '@/themes/_light.json5';
-import darkTheme from '@/themes/_dark.json5';
 import { miLocalStorage } from '@/local-storage.js';
 
 export type Theme = {
@@ -54,7 +54,7 @@ export const getBuiltinThemes = () => Promise.all(
 		'd-u0',
 		'rosepine',
 		'rosepine-dawn',
-	].map(name => import(`@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
+	].map(name => import(`@@/themes/${name}.json5`).then(({ default: _default }): Theme => _default)),
 );
 
 export const getBuiltinThemesRef = () => {
@@ -78,6 +78,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);
 
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index 3e947183c9eaa370c22377983bcaf077625e831c..22dce609c692b3de69158eb6bb64fde3b1a1b2b4 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -9,10 +9,11 @@ import { v4 as uuid } from 'uuid';
 import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
 import { getCompressionConfig } from './upload/compress-config.js';
 import { defaultStore } from '@/store.js';
-import { apiUrl } from '@/config.js';
+import { apiUrl } from '@@/js/config.js';
 import { $i } from '@/account.js';
 import { alert } from '@/os.js';
 import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
 
 type Uploading = {
 	id: string;
@@ -39,6 +40,15 @@ export function uploadFile(
 
 	if (folder && typeof folder === 'object') folder = folder.id;
 
+	if (file.size > instance.maxFileSize) {
+		alert({
+			type: 'error',
+			title: i18n.ts.failedToUpload,
+			text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
+		});
+		return Promise.reject();
+	}
+
 	return new Promise((resolve, reject) => {
 		const id = uuid();
 
diff --git a/packages/frontend/src/scripts/use-form.ts b/packages/frontend/src/scripts/use-form.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0d505fe4668baf668261bee95809afe2ff72f003
--- /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/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index 1b3626bff5f7a035de42ca348490a5da749d410c..89aa023f23250a2f940ffa1bf4f49007670612db 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -13,16 +13,16 @@ import { misskeyApi } from './misskey-api.js';
 export function useNoteCapture(props: {
 	rootEl: ShallowRef<HTMLElement | undefined>;
 	note: Ref<Misskey.entities.Note>;
-	pureNote: Ref<Misskey.entities.Note>;
+	pureNote?: Ref<Misskey.entities.Note>;
 	isDeletedRef: Ref<boolean>;
-	onReplyCallback: (replyNote: Misskey.entities.Note) => void | undefined;
-	onDeleteCallback: (id: Misskey.entities.Note['id']) => void | undefined;
+	onReplyCallback?: (replyNote: Misskey.entities.Note) => void | Promise<void>;
+	onDeleteCallback?: (id: Misskey.entities.Note['id']) => void | Promise<void>;
 }) {
 	const note = props.note;
 	const pureNote = props.pureNote !== undefined ? props.pureNote : props.note;
 	const connection = $i ? useStream() : null;
 
-	async function onStreamNoteUpdated(noteData): void {
+	async function onStreamNoteUpdated(noteData): Promise<void> {
 		const { type, id, body } = noteData;
 
 		if ((id !== note.value.id) && (id !== pureNote.value.id)) return;
@@ -39,7 +39,7 @@ export function useNoteCapture(props: {
 
 					await props.onReplyCallback(replyNote);
 				} catch { /* empty */ }
-				
+
 				break;
 			}
 
@@ -81,7 +81,7 @@ export function useNoteCapture(props: {
 			case 'pollVoted': {
 				const choice = body.choice;
 
-				const choices = [...note.value.poll.choices];
+				const choices = [...note.value.poll!.choices];
 				choices[choice] = {
 					...choices[choice],
 					votes: choices[choice].votes + 1,
@@ -90,7 +90,7 @@ export function useNoteCapture(props: {
 					} : {}),
 				};
 
-				note.value.poll.choices = choices;
+				note.value.poll!.choices = choices;
 				break;
 			}
 
@@ -106,7 +106,7 @@ export function useNoteCapture(props: {
 					const editedNote = await misskeyApi('notes/show', {
 						noteId: id,
 					});
-					
+
 					const keys = new Set<string>();
 					Object.keys(editedNote)
 						.concat(Object.keys(note.value))
diff --git a/packages/frontend/src/scripts/warning-external-website.ts b/packages/frontend/src/scripts/warning-external-website.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67158c64385d30cd4b194a957acdf792a5b87c67
--- /dev/null
+++ b/packages/frontend/src/scripts/warning-external-website.ts
@@ -0,0 +1,52 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { instance } from '@/instance.js';
+import { defaultStore } from '@/store.js';
+import * as os from '@/os.js';
+import MkUrlWarningDialog from '@/components/MkUrlWarningDialog.vue';
+
+const extractDomain = /^(https?:\/\/|\/\/)?([^@/\s]+@)?(www\.)?([^:/\s]+)/i;
+const isRegExp = /^\/(.+)\/(.*)$/;
+
+export async function warningExternalWebsite(url: string) {
+	const domain = extractDomain.exec(url)?.[4];
+
+	if (!domain) return false;
+
+	const isTrustedByInstance = instance.trustedLinkUrlPatterns.some(expression => {
+		const r = isRegExp.exec(expression);
+
+		if (r) {
+			return new RegExp(r[1], r[2]).test(url);
+		} else if (expression.includes(' ')) {
+			return expression.split(' ').every(keyword => url.includes(keyword));
+		} else {
+			return domain.endsWith(expression);
+		}
+	});
+
+	const isTrustedByUser = defaultStore.reactiveState.trustedDomains.value.includes(domain);
+	const isDisabledByUser = !defaultStore.reactiveState.warnExternalUrl.value;
+
+	if (!isTrustedByInstance && !isTrustedByUser && !isDisabledByUser) {
+		const confirm = await new Promise<{ canceled: boolean }>(resolve => {
+			const { dispose } = os.popup(MkUrlWarningDialog, {
+				url,
+			}, {
+				done: result => {
+					resolve(result ?? { canceled: true });
+				},
+				closed: () => dispose(),
+			});
+		});
+
+		if (confirm.canceled) return false;
+ 
+		return window.open(url, '_blank', 'nofollow noopener popup=false');
+	}
+
+	return window.open(url, '_blank', 'nofollow noopener popup=false');
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index dda320dbacc7188e6fbc8481acbb7c76c94fe2d3..fd84ad2e4d579931a6a8f83b68e58fa204eb04fe 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -5,11 +5,14 @@
 
 import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { hemisphere } from '@@/js/intl-const.js';
+import lightTheme from '@@/themes/l-cherry.json5';
+import darkTheme from '@@/themes/d-ice.json5';
 import { miLocalStorage } from './local-storage.js';
 import { searchEngineMap } from './scripts/search-engine-map.js';
 import type { SoundType } from '@/scripts/sound.js';
+import type { FollowingFeedTab } from '@/scripts/following-feed-utils.js';
 import { Storage } from '@/pizzax.js';
-import { hemisphere } from '@/scripts/intl-const.js';
 
 interface PostFormAction {
 	title: string,
@@ -165,6 +168,14 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: 'public' as 'public' | 'home' | 'followers',
 	},
+	trustedDomains: {
+		where: 'account',
+		default: [] as string[],
+	},
+	warnExternalUrl: {
+		where: 'account',
+		default: true,
+	},
 
 	menu: {
 		where: 'deviceAccount',
@@ -231,6 +242,18 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'deviceAccount',
 		default: [] as Misskey.entities.UserList[],
 	},
+	followingFeed: {
+		where: 'account',
+		default: {
+			withNonPublic: false,
+			withQuotes: false,
+			withBots: true,
+			withReplies: false,
+			onlyFiles: false,
+			userList: 'following' as FollowingFeedTab,
+			remoteWarningDismissed: false,
+		},
+	},
 
 	overridedDeviceKind: {
 		where: 'device',
@@ -288,13 +311,17 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: window.matchMedia('(prefers-reduced-motion)').matches,
 	},
+	disableCatSpeak: {
+		where: 'account',
+		default: false,
+	},
 	emojiStyle: {
 		where: 'device',
 		default: 'twemoji', // twemoji / fluentEmoji / native
 	},
-	disableDrawer: {
+	menuStyle: {
 		where: 'device',
-		default: false,
+		default: 'auto' as 'auto' | 'popup' | 'drawer',
 	},
 	useBlurEffectForModal: {
 		where: 'device',
@@ -356,9 +383,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',
@@ -456,10 +483,14 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: 'horizontal' as 'vertical' | 'horizontal',
 	},
-	enableCondensedLineForAcct: {
+	notificationClickable: {
 		where: 'device',
 		default: false,
 	},
+	enableCondensedLine: {
+		where: 'device',
+		default: true,
+	},
 	additionalUnicodeEmojiIndexes: {
 		where: 'device',
 		default: {} as Record<string, Record<string, string[]>>,
@@ -524,10 +555,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
-  contextMenu: {
+	contextMenu: {
 		where: 'device',
 		default: 'app' as 'app' | 'appWithShift' | 'native',
-  },
+	},
 
 	sound_masterVolume: {
 		where: 'device',
@@ -586,8 +617,6 @@ interface Watcher {
 /**
  * 常にメモリにロードしておく必要がないような設定情報を保管するストレージ(非リアクティブ)
  */
-import lightTheme from '@/themes/l-cherry.json5';
-import darkTheme from '@/themes/d-ice.json5';
 
 export class ColdDeviceStorage {
 	public static default = {
@@ -624,7 +653,7 @@ export class ColdDeviceStorage {
 	public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void {
 		// 呼び出し側のバグ等で undefined が来ることがある
 		// undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視
-		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+
 		if (value === undefined) {
 			console.error(`attempt to store undefined value for key '${key}'`);
 			return;
diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts
index 0d5bd78b09af845f20d1a99c3803833f9013c711..e63dac951c0033d8bdc45d37b198af06af3dac32 100644
--- a/packages/frontend/src/stream.ts
+++ b/packages/frontend/src/stream.ts
@@ -6,18 +6,21 @@
 import * as Misskey from 'misskey-js';
 import { markRaw } from 'vue';
 import { $i } from '@/account.js';
-import { wsOrigin } from '@/config.js';
+import { wsOrigin } from '@@/js/config.js';
+// TODO: No WebsocketモードでStreamMockが使えそう
+//import { StreamMock } from '@/scripts/stream-mock.js';
 
 // heart beat interval in ms
 const HEART_BEAT_INTERVAL = 1000 * 60;
 
-let stream: Misskey.Stream | null = null;
-let timeoutHeartBeat: ReturnType<typeof setTimeout> | null = null;
+let stream: Misskey.IStream | null = null;
+let timeoutHeartBeat: number | null = null;
 let lastHeartbeatCall = 0;
 
-export function useStream(): Misskey.Stream {
+export function useStream(): Misskey.IStream {
 	if (stream) return stream;
 
+	// TODO: No Websocketモードもここで判定
 	stream = markRaw(new Misskey.Stream(wsOrigin, $i ? {
 		token: $i.token,
 	} : null));
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index 62ba7a08d55e45bce8e6f4d5b54cbd3f2875b8c4..d990a706b3ca2dffa0cbae7e63940702df669203 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -33,21 +33,14 @@
 	--minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px)));
 	--minBottomSpacing: var(--minBottomSpacingMobile);
 
+	//--ad: rgb(255 169 0 / 10%);
+
 	@media (max-width: 500px) {
 		--margin: var(--marginHalf);
 	}
 
 	--avatar: 48px;
 	--thread-width: 2px;
-
-	//--ad: rgb(255 169 0 / 10%);
-	--eventFollow: #36aed2;
-	--eventRenote: #36d298;
-	--eventReply: #007aff;
-	--eventReactionHeart: #dd2e44;
-	--eventReaction: #e99a0b;
-	--eventAchievement: #cb9a11;
-	--eventOther: #88a6b7;
 }
 
 html.radius-misskey {
@@ -289,11 +282,11 @@ rt {
 	background: var(--accent);
 
 	&:not(:disabled):hover {
-		background: var(--X8);
+		background: hsl(from var(--accent) h s calc(l + 5));
 	}
 
 	&:not(:disabled):active {
-		background: var(--X9);
+		background: hsl(from var(--accent) h s calc(l - 5));
 	}
 }
 
@@ -303,11 +296,11 @@ rt {
 	background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
 
 	&:not(:disabled):hover {
-		background: linear-gradient(90deg, var(--X8), var(--X8));
+		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 	}
 
 	&:not(:disabled):active {
-		background: linear-gradient(90deg, var(--X8), var(--X8));
+		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 	}
 }
 
@@ -412,6 +405,16 @@ rt {
 	vertical-align: top;
 }
 
+._modified {
+	margin-left: 0.7em;
+	font-size: 65%;
+	padding: 2px 3px;
+	color: var(--warn);
+	border: solid 1px var(--warn);
+	border-radius: 4px;
+	vertical-align: top;
+}
+
 ._table {
 	> ._row {
 		display: flex;
@@ -481,7 +484,7 @@ rt {
 	--fg: #693410;
 }
 
-html[data-color-mode=dark] ._woodenFrame {
+html[data-color-scheme=dark] ._woodenFrame {
 	--bg: #1d0c02;
 	--fg: #F1E8DC;
 	--panel: #192320;
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index 17079b3ddc705e9aa08127e1b8e9a9155f6d730c..8355ae3061580fb92c142b69904d2015385ad144 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -7,7 +7,7 @@ import { defineAsyncComponent } from 'vue';
 import type { MenuItem } from '@/types/menu.js';
 import * as os from '@/os.js';
 import { instance } from '@/instance.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 
@@ -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,50 +92,80 @@ 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.donationUrl) ? {
-		type: 'a',
-		text: i18n.ts.donation,
-		icon: 'ph-hand-coins ph-bold ph-lg',
-		href: instance.donationUrl,
-		target: '_blank',
-	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl && !instance.donationUrl) ? 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.donationUrl) {
+		menuItems.push({
+			type: 'a',
+			text: i18n.ts.donation,
+			icon: 'ph-hand-coins ph-bold ph-lg',
+			href: instance.donationUrl,
+			target: '_blank',
+		});
+	}
+
+	if (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl && !instance.donationUrl) {
+		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,
 		icon: 'sk-icons sk-shark sk-icons-lg',
 		to: '/about-sharkey',
-	}], 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 442b6479dd29c6af2a50789cab1023516a29b8ac..a8ff2a4c8d1f75473339a56f49afdc23a26e3af8 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -30,7 +30,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:enterFromClass="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''"
 	:leaveToClass="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''"
 >
-	<div v-for="notification in notifications" :key="notification.id" :class="$style.notification">
+	<div
+		v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{
+			pointerEvents: getPointerEvents()
+		}"
+	>
 		<XNotification :notification="notification"/>
 	</div>
 </TransitionGroup>
@@ -39,7 +43,7 @@ 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>
 
@@ -101,6 +105,10 @@ if ($i) {
 		swInject();
 	}
 }
+
+function getPointerEvents() {
+	return defaultStore.state.notificationClickable ? undefined : 'none';
+}
 </script>
 
 <style lang="scss" module>
@@ -122,7 +130,6 @@ if ($i) {
 	position: fixed;
 	z-index: 3900000;
 	padding: 0 var(--margin);
-	pointer-events: none;
 	display: flex;
 
 	&.notificationsPosition_leftTop {
@@ -263,10 +270,6 @@ if ($i) {
 	font-size: 14px;
 	pointer-events: none;
 	user-select: none;
-
-	> span {
-		animation: dev-ticker-blink 2s infinite;
-	}
 }
 
 #devTicker {
@@ -280,9 +283,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 a3f9d6cf2cc5a6f58a6206f43024aeba5c461c96..f3244b5697d69dd2193e63d72ee327402b7f8b18 100644
--- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
+++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div :class="$style.top">
 		<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
 		<button class="_button" :class="$style.instance" @click="openInstanceMenu">
-			<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
+			<img :src="instance.sidebarLogoUrl || instance.iconUrl || '/apple-touch-icon.png'" alt="" :class="instance.sidebarLogoUrl ? $style.wideInstanceIcon : $style.instanceIcon"/>
 		</button>
 	</div>
 	<div :class="$style.middle">
@@ -82,6 +82,8 @@ function more() {
 
 <style lang="scss" module>
 .root {
+	--nav-bg-transparent: color(from var(--navBg) srgb r g b / 0.5);
+
 	display: flex;
 	flex-direction: column;
 }
@@ -91,7 +93,7 @@ function more() {
 	top: 0;
 	z-index: 1;
 	padding: 20px 0;
-	background: var(--X14);
+	background: var(--nav-bg-transparent);
 	-webkit-backdrop-filter: var(--blur, blur(8px));
 	backdrop-filter: var(--blur, blur(8px));
 }
@@ -121,11 +123,18 @@ function more() {
 	aspect-ratio: 1;
 }
 
+.wideInstanceIcon {
+	display: inline-block;
+	min-width: 38px;
+	max-width: 100%;
+	max-height: 80px;
+}
+
 .bottom {
 	position: sticky;
 	bottom: 0;
 	padding: 20px 0;
-	background: var(--X14);
+	background: var(--nav-bg-transparent);
 	-webkit-backdrop-filter: var(--blur, blur(8px));
 	backdrop-filter: var(--blur, blur(8px));
 }
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 1f1fd37def98a6019635b38d2e7b87b232936f1f..17690df4121cc22026678ad683a24c1edc80d69e 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.top">
 			<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
 			<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
-				<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
+				<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || '/apple-touch-icon.png'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon"/>
 			</button>
 		</div>
 		<div :class="$style.middle">
@@ -111,6 +111,7 @@ function more(ev: MouseEvent) {
 .root {
 	--nav-width: 250px;
 	--nav-icon-only-width: 80px;
+	--nav-bg-transparent: color(from var(--navBg) srgb r g b / 0.5);
 
 	flex: 0 0 var(--nav-width);
 	width: var(--nav-width);
@@ -144,7 +145,7 @@ function more(ev: MouseEvent) {
 		top: 0;
 		z-index: 1;
 		padding: 20px 0;
-		background: var(--X14);
+		background: var(--nav-bg-transparent);
 		-webkit-backdrop-filter: var(--blur, blur(8px));
 		backdrop-filter: var(--blur, blur(8px));
 	}
@@ -183,11 +184,18 @@ function more(ev: MouseEvent) {
 		aspect-ratio: 1;
 	}
 
+	.wideInstanceIcon {
+		display: inline-block;
+		min-width: 38px;
+		max-width: 100%;
+		max-height: 80px;
+	}
+
 	.bottom {
 		position: sticky;
 		bottom: 0;
 		padding-top: 20px;
-		background: var(--X14);
+		background: var(--nav-bg-transparent);
 		-webkit-backdrop-filter: var(--blur, blur(8px));
 		backdrop-filter: var(--blur, blur(8px));
 	}
@@ -378,7 +386,7 @@ function more(ev: MouseEvent) {
 		top: 0;
 		z-index: 1;
 		padding: 20px 0;
-		background: var(--X14);
+		background: var(--nav-bg-transparent);
 		-webkit-backdrop-filter: var(--blur, blur(8px));
 		backdrop-filter: var(--blur, blur(8px));
 	}
@@ -408,7 +416,7 @@ function more(ev: MouseEvent) {
 		position: sticky;
 		bottom: 0;
 		padding-top: 20px;
-		background: var(--X14);
+		background: var(--nav-bg-transparent);
 		-webkit-backdrop-filter: var(--blur, blur(8px));
 		backdrop-filter: var(--blur, blur(8px));
 	}
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index 8dad6666235daac237d61f9b00e6b511087e0366..e234bb3a33a0393cad69fecbd24786ad33956625 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -35,7 +35,7 @@ import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MarqueeText from '@/components/MkMarquee.vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
 const props = defineProps<{
diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue
index 6e1d06eec1f99e620334bd66e055c4f9bda66eee..550fc39b001bf57145da603baa7ca3844b020279 100644
--- a/packages/frontend/src/ui/_common_/statusbar-rss.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MarqueeText from '@/components/MkMarquee.vue';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { shuffle } from '@/scripts/shuffle.js';
 
 const props = defineProps<{
diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
index 67f8b109c484a6d15855b510867852e0cd05e64e..078b595dca744b2bb830746cf4c9fa2fc238fb15 100644
--- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
@@ -35,7 +35,7 @@ import { ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MarqueeText from '@/components/MkMarquee.vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { getNoteSummary } from '@/scripts/get-note-summary.js';
 import { notePage } from '@/filters/note.js';
 
diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue
index 872c69810c750011736e1571ca79e260d081cdcb..690366307b1c5a711ff34a80a1579d58a8ee38b6 100644
--- a/packages/frontend/src/ui/_common_/statusbars.vue
+++ b/packages/frontend/src/ui/_common_/statusbars.vue
@@ -40,6 +40,14 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue')
 	--nameMargin: 10px;
 	font-size: 0.85em;
 
+	display: flex;
+	vertical-align: bottom;
+	width: 100%;
+	line-height: var(--height);
+	height: var(--height);
+	overflow: clip;
+	contain: strict;
+
 	&.verySmall {
 		--nameMargin: 7px;
 		--height: 16px;
@@ -64,14 +72,6 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue')
 		font-size: 0.9em;
 	}
 
-	display: flex;
-	vertical-align: bottom;
-	width: 100%;
-	line-height: var(--height);
-	height: var(--height);
-	overflow: clip;
-	contain: strict;
-
 	&.black {
 		background: #000;
 		color: #fff;
diff --git a/packages/frontend/src/ui/_common_/upload.vue b/packages/frontend/src/ui/_common_/upload.vue
index 244bac6f102eadcc8b7e17c424b564be9762d953..96030c7897c9286c4dc16589432608bcd6ac7f32 100644
--- a/packages/frontend/src/ui/_common_/upload.vue
+++ b/packages/frontend/src/ui/_common_/upload.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div class="top">
 				<p class="name"><MkLoading :em="true"/>{{ ctx.name }}</p>
 				<p class="status">
-					<span v-if="ctx.progressValue === undefined" class="initing">{{ i18n.ts.waiting }}<MkEllipsis/></span>
+					<span v-if="ctx.progressValue === undefined" class="initing">{{ i18n.ts.uploading }}</span>
 					<span v-if="ctx.progressValue !== undefined" class="kb">{{ String(Math.floor(ctx.progressValue / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i> / {{ String(Math.floor(ctx.progressMax / 1024)).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,') }}<i>KB</i></span>
 					<span v-if="ctx.progressValue !== undefined" class="percentage">{{ Math.floor((ctx.progressValue / ctx.progressMax) * 100) }}</span>
 				</p>
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index d49df8e8ac651eb44b94988d4d86e56933e7356e..96cc24c9b9c21621b2fcacb62239c70436fa6911 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div class="divider"></div>
 	<div class="about">
 		<button v-click-anime class="item _button" @click="openInstanceMenu">
-			<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
+			<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" :class="{ wideIcon: instance.sidebarLogoUrl && !iconOnly }" class="_ghost" />
 		</button>
 	</div>
 	<!--<MisskeyLogo class="misskey"/>-->
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent, computed, watch, ref, shallowRef } from 'vue';
 import { openInstanceMenu } from './_common_/common.js';
-// import { host } from '@/config.js';
+// import { host } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { navbarItemDef } from '@/navbar.js';
 import { openAccountMenu as openAccountMenu_, $i } from '@/account.js';
@@ -66,7 +66,6 @@ import { i18n } from '@/i18n.js';
 const WINDOW_THRESHOLD = 1400;
 
 const menu = ref(defaultStore.state.menu);
-const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
 const otherNavItemIndicated = computed<boolean>(() => {
 	for (const def in navbarItemDef) {
 		if (menu.value.includes(def)) continue;
@@ -81,10 +80,12 @@ const iconOnly = ref(false);
 const settingsWindowed = ref(false);
 
 function calcViewState() {
-	iconOnly.value = (window.innerWidth <= WINDOW_THRESHOLD) || (menuDisplay.value === 'sideIcon');
+	iconOnly.value = (window.innerWidth <= WINDOW_THRESHOLD) || (defaultStore.state.menuDisplay === 'sideIcon');
 	settingsWindowed.value = (window.innerWidth > WINDOW_THRESHOLD);
 }
 
+calcViewState();
+
 function more(ev: MouseEvent) {
 	const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
 		src: ev.currentTarget ?? ev.target,
@@ -166,7 +167,6 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 		top: 0;
 		z-index: 1;
 		padding: 16px 0;
-		background: var(--bg);
 
 		> .button {
 			min-width: 0;
@@ -180,12 +180,15 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 
 		> .item {
 			display: block;
-			width: 32px;
 			margin: 0 auto;
 
 			img {
 				display: block;
-				width: 100%;
+				width: 32px;
+				&.wideIcon {
+					width: 80%;
+					margin: 0 auto;
+				}
 			}
 		}
 	}
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index ce5a22a61d4665f235f8fe75e9dafaca1be0ca1f..31bb1ddc14928a4db1a41ec235d70498e9ab2ed6 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, onMounted, provide, ref, computed, shallowRef } from 'vue';
 import XSidebar from './classic.sidebar.vue';
 import XCommon from './_common_/common.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { StickySidebar } from '@/scripts/sticky-sidebar.js';
 import * as os from '@/os.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
@@ -57,6 +57,8 @@ import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { mainRouter } from '@/router/main.js';
+import { isLink } from '@@/js/is-link.js';
+
 const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 
@@ -104,12 +106,6 @@ function top() {
 }
 
 function onContextmenu(ev: MouseEvent) {
-	const isLink = (el: HTMLElement) => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-	};
 	if (isLink(ev.target)) return;
 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
 	if (window.getSelection().toString() !== '') return;
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 44f1af5f8fdde0fa8c486f50cb9f206c3ca36b4a..cd4e256056332e717829b6e294b3cb0ffdd7e73d 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -118,7 +118,7 @@ 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';
+import type { MenuItem } from '@/types/menu.js';
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
 const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
 
@@ -451,6 +451,7 @@ body {
 
 	&:active {
 		color: var(--accent);
+		background: hsl(from var(--panel) h s calc(l - 2));
 	}
 }
 
@@ -460,12 +461,12 @@ body {
 	color: var(--fgOnAccent);
 
 	&:hover {
-		background: linear-gradient(90deg, var(--X8), var(--X8));
+		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		color: var(--fgOnAccent);
 	}
 
 	&:active {
-		background: linear-gradient(90deg, var(--X8), var(--X8));
+		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		color: var(--fgOnAccent);
 	}
 }
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 987bd4db557e4a7888ec38719f634bb359ac89f6..a41639e71c46cf551c06f992086c6e9b9b0e8388 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 518df7a6fcd530c9764ccf435b6f0a12b33708b6..661d45b110354b6fc783e0dedf950d007d4840a7 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 5fed70fc9055763e45531d682e39eab195fa5310..5ed3aa754f377f1294736e566ac2dcc7502fb17e 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -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) {
@@ -324,11 +343,11 @@ function onDrop(ev) {
 
 		> .body {
 			background: transparent !important;
+			scrollbar-color: var(--scrollbarHandle) transparent;
 
 			&::-webkit-scrollbar-track {
 				background: transparent;
 			}
-			scrollbar-color: var(--scrollbarHandle) transparent;
 		}
 	}
 
@@ -338,11 +357,11 @@ function onDrop(ev) {
 		> .body {
 			background: var(--bg) !important;
 			overflow-y: scroll !important;
+			scrollbar-color: var(--scrollbarHandle) transparent;
 
 			&::-webkit-scrollbar-track {
 				background: inherit;
 			}
-			scrollbar-color: var(--scrollbarHandle) transparent;
 		}
 	}
 }
@@ -423,10 +442,10 @@ function onDrop(ev) {
 	box-sizing: border-box;
 	container-type: size;
 	background-color: var(--bg);
+	scrollbar-color: var(--scrollbarHandle) var(--panel);
 
 	&::-webkit-scrollbar-track {
 		background: var(--panel);
 	}
-	scrollbar-color: var(--scrollbarHandle) var(--panel);
 }
 </style>
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index a0e318f7eb7c629065970673dea86cdb5d345241..8762fb0cce1ed444dc7a0adc8940ba1e87775b3c 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/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index 79c967191709a6e7fb635c4a1d31cbe490a79f37..f8c712c3713d7b2933493a2591dec1addf747c49 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -26,7 +26,8 @@ import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { useScrollPositionManager } from '@/nirax.js';
-import { getScrollContainer } from '@/scripts/scroll.js';
+import { getScrollContainer } from '@@/js/scroll.js';
+import { isLink } from '@@/js/is-link.js';
 import { mainRouter } from '@/router/main.js';
 
 defineProps<{
@@ -52,12 +53,6 @@ function back() {
 function onContextmenu(ev: MouseEvent) {
 	if (!ev.target) return;
 
-	const isLink = (el: HTMLElement) => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-	};
 	if (isLink(ev.target as HTMLElement)) return;
 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes((ev.target as HTMLElement).tagName) || (ev.target as HTMLElement).attributes['contenteditable']) return;
 	if (window.getSelection()?.toString() !== '') return;
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index a375e9c574f82276f5fac5d8f77fe93d49df84f1..beb4237978c3b48440a77827fbc1feb6b0b525b7 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 17afa12551cbbda7e1d4136cbfe3c6e3e049831e..8315f7fca572bb88a341f67e55b26df974851e65 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -115,29 +115,41 @@ 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,
+	});
+
+	return menuItems;
+});
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index db5eb19c2079e680ce6219fac5a45489ab1c2f44..9e41c48c5bcf972c8e78ac1262df12fb8b89700c 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import { mainRouter } from '@/router/main.js';
 
 const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index d2b5d8cc42152de90bd4b9242a4e8dc2dfb9fc32..2fdaca775bc1d49463e7844730b62c50e2de7a5e 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
-import { instanceName } from '@/config.js';
+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,9 +108,10 @@ 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 '@/const.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'));
@@ -195,12 +196,6 @@ onMounted(() => {
 });
 
 const onContextmenu = (ev) => {
-	const isLink = (el: HTMLElement) => {
-		if (el.tagName === 'A') return true;
-		if (el.parentElement) {
-			return isLink(el.parentElement);
-		}
-	};
 	if (isLink(ev.target)) return;
 	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
 	if (window.getSelection()?.toString() !== '') return;
@@ -423,10 +418,12 @@ $widgets-hide-threshold: 1090px;
 	color: var(--fg);
 
 	&:hover {
+		background: var(--panelHighlight);
 		color: var(--accent);
 	}
 
 	&:active {
+		background: hsl(from var(--panel) h s calc(l - 2));
 		color: var(--accent);
 	}
 }
@@ -437,12 +434,12 @@ $widgets-hide-threshold: 1090px;
 	color: var(--fgOnAccent);
 
 	&:hover {
-		background: linear-gradient(90deg, var(--X8), var(--X8));
+		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		color: var(--fgOnAccent);
 	}
 
 	&:active {
-		background: linear-gradient(90deg, var(--X8), var(--X8));
+		background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5)));
 		color: var(--fgOnAccent);
 	}
 }
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index ccc8d8fd973efeac84ebeed9ad26e6f7e8a36245..510b2a4342b3ccf2f05c8a1a0eda12cbb4aae8a4 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, provide, ref, computed } from 'vue';
 import XCommon from './_common_/common.vue';
-import { instanceName } from '@/config.js';
+import { instanceName } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { instance } from '@/instance.js';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 47502b2af1feae601323484c5476743e86a49829..ac13d7822f6f56ae85d3f71dd1a89b2214e07c67 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
-import { instanceName, ui } from '@/config.js';
+import { instanceName, ui } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { mainRouter } from '@/router/main.js';
 
diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
index 49fd103d37eb60f8ce8ed72ff2f7bcac741b6ab8..bcfaaf00ab4ff10060ae21a5c0af7131bee5370a 100644
--- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
+++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
@@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import { useInterval } from '@@/js/use-interval.js';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { infoImageUrl } from '@/instance.js';
 import { $i } from '@/account.js';
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index 19843f3949c582d5b9195173564fa5df270877eb..e4ac1acfc7b4c9049833c93b2d61ff4ae7206e26 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -42,7 +42,7 @@ import { ref } from 'vue';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import { i18n } from '@/i18n.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 
 const name = 'calendar';
 
diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue
index 2ea1253c2b42b354786ea9d93a6672da223b0bde..e91a77beab330e53159497e229f2599d16cca26c 100644
--- a/packages/frontend/src/widgets/WidgetFederation.vue
+++ b/packages/frontend/src/widgets/WidgetFederation.vue
@@ -32,7 +32,7 @@ import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
 import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 import { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index 76ccdb397119a7e5516763a49008fd34b6e825e2..d090372b9a26a9cd31317a21d9e0322288655795 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -26,7 +26,7 @@ import MkContainer from '@/components/MkContainer.vue';
 import MkTagCloud from '@/components/MkTagCloud.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
 const name = 'instanceCloud';
diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
index ce8b0dd60254c275704989ea0be42960d76f09ae..014cf01a5d7210e40892c8b30790243272d266cb 100644
--- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
-import { host } from '@/config.js';
+import { host } from '@@/js/config.js';
 import { instance } from '@/instance.js';
 
 const name = 'instanceInfo';
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index b69152b4feaa3be66ca57a2dfb46c68e0d8753b6..ee89beb944c00b11af20ec1455e2dfa50445f362 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header>{{ i18n.ts._widgets.memo }}</template>
 
 	<div :class="$style.root">
-		<textarea v-model="text" :style="`height: ${widgetProps.height}px;`" :class="$style.textarea" :placeholder="i18n.ts.placeholder" @input="onChange"></textarea>
+		<textarea v-model="text" :style="`height: ${widgetProps.height}px;`" :class="$style.textarea" @input="onChange"></textarea>
 		<button :class="$style.save" :disabled="!changed" class="_buttonPrimary" @click="saveMemo">{{ i18n.ts.save }}</button>
 	</div>
 </MkContainer>
diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
index 5c89a06c62062dd03f92c41d4ce768d09b3a1049..d56ee96ac1751e374eb1cbaefa35e1a03f5a062c 100644
--- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue
+++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
@@ -18,7 +18,7 @@ import { ref } from 'vue';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 import number from '@/filters/number.js';
 
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index e5758662cc49479c8ef5b79ff5b8f8bbb7d1a2ce..511777a5709c427bc8e453833283aefa6b3da6e4 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -28,9 +28,9 @@ import * as Misskey from 'misskey-js';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
-import { url as base } from '@/config.js';
+import { url as base } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { infoImageUrl } from '@/instance.js';
 
 const name = 'rss';
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 16306ef5ba9b39ca8fed919115d266aba9a1f576..b393ecd74b9d6f0bdf2c95d742cd19f85f20b60b 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -34,8 +34,8 @@ import MarqueeText from '@/components/MkMarquee.vue';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import { shuffle } from '@/scripts/shuffle.js';
-import { url as base } from '@/config.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { url as base } from '@@/js/config.js';
+import { useInterval } from '@@/js/use-interval.js';
 
 const name = 'rssTicker';
 
diff --git a/packages/frontend/src/widgets/WidgetSearch.vue b/packages/frontend/src/widgets/WidgetSearch.vue
index 294c97e2932d0ae065c12409653025d5ccafd191..1a328be7ce2ae6c212bdf1f009435388c02679c2 100644
--- a/packages/frontend/src/widgets/WidgetSearch.vue
+++ b/packages/frontend/src/widgets/WidgetSearch.vue
@@ -24,6 +24,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as os from '@/os.js';
 import { useRouter } from '@/router/supplier.js';
 import { GetFormResultType } from '@/scripts/form.js';
+import { notesSearchAvailable } from '@/scripts/check-permissions.js';
 
 const name = 'search';
 
@@ -71,7 +72,7 @@ function options(ev) {
 			{
 				type: 'button',
 				icon: 'ph-image ph-bold ph-lg',
-				text: 'With Images',
+				text: `With Images ${filetype.value === 'image' ? '✔' : ''}`,
 				action: () => {
 					filetype.value = 'image';
 				},
@@ -79,7 +80,7 @@ function options(ev) {
 			{
 				type: 'button',
 				icon: 'ph-music-notes-simple ph-bold ph-lg',
-				text: 'With Audios',
+				text: `With Audios ${filetype.value === 'audio' ? '✔' : ''}`,
 				action: () => {
 					filetype.value = 'audio';
 				},
@@ -87,12 +88,19 @@ function options(ev) {
 			{
 				type: 'button',
 				icon: 'ph-video ph-bold ph-lg',
-				text: 'With Videos',
+				text: `With Videos ${filetype.value === 'video' ? '✔' : ''}`,
 				action: () => {
 					filetype.value = 'video';
 				},
 			}],
-	}], ev.currentTarget ?? ev.target);
+	},
+	...(filetype.value ? [{
+			text: 'Clear Filter',
+			icon: 'ti ti-trash',
+			action: () => {
+				filetype.value = null;
+			},
+	}] : [])], ev.currentTarget ?? ev.target);
 }
 
 async function search() {
@@ -128,6 +136,14 @@ async function search() {
 		return;
 	}
 
+	if (!notesSearchAvailable) {
+		os.alert({
+			type: 'warning',
+			text: i18n.ts.notesSearchNotAvailable,
+		});
+		return;
+	}
+
 	notePagination.value = {
 		endpoint: 'notes/search',
 		limit: 10,
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index b8efd3bda9fc7808ab0a5bfe339da3b34d444b22..3fea1d70534429af206a158e11e926fe0fe505e8 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -23,7 +23,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import { GetFormResultType } from '@/scripts/form.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 
 const name = 'slideshow';
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index d02f9b8e224abdba1a8005ae620b1b1f4ff1b193..a4685fd1fc6ca7fffcc2f0b7742f83f2343d8129 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 4299181a273f93c23758e6cd3325bf12df0c1377..a41db513e8d1eeca371bd557d5bef3d87657b1a5 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -31,7 +31,7 @@ import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
 import { misskeyApiGet } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 
diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue
index d9f4dc49ea87aeeb621e6e111470383f3c4d184c..72391d622eeb3b470f74bd2362c23acd3478d48f 100644
--- a/packages/frontend/src/widgets/WidgetUserList.vue
+++ b/packages/frontend/src/widgets/WidgetUserList.vue
@@ -31,7 +31,7 @@ import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/scripts/misskey-api.js';
-import { useInterval } from '@/scripts/use-interval.js';
+import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 import MkButton from '@/components/MkButton.vue';
 
diff --git a/packages/frontend/src/widgets/server-metric/cpu-mem.vue b/packages/frontend/src/widgets/server-metric/cpu-mem.vue
index 27d32342073526dd6c9769566ce4c30a9de13060..469075e2c49c9a15593ed8e9238aedb9bf205462 100644
--- a/packages/frontend/src/widgets/server-metric/cpu-mem.vue
+++ b/packages/frontend/src/widgets/server-metric/cpu-mem.vue
@@ -138,7 +138,7 @@ function onStats(connStats: Misskey.entities.ServerStats) {
 }
 
 function onStatsLog(statsLog: Misskey.entities.ServerStatsLog) {
-	for (const revStats of statsLog.reverse()) {
+	for (const revStats of statsLog.toReversed()) {
 		onStats(revStats);
 	}
 }
diff --git a/packages/frontend/src/widgets/server-metric/net.vue b/packages/frontend/src/widgets/server-metric/net.vue
index d46aaa5f69372aa6ec312c4da07cb45d0fd75409..d78494b8d26642c7cd29e33773a28ddbc0e58d5e 100644
--- a/packages/frontend/src/widgets/server-metric/net.vue
+++ b/packages/frontend/src/widgets/server-metric/net.vue
@@ -111,7 +111,7 @@ function onStats(connStats: Misskey.entities.ServerStats) {
 }
 
 function onStatsLog(statsLog: Misskey.entities.ServerStatsLog) {
-	for (const revStats of statsLog.reverse()) {
+	for (const revStats of statsLog.toReversed()) {
 		onStats(revStats);
 	}
 }
diff --git a/packages/frontend/test/emoji.test.ts b/packages/frontend/test/emoji.test.ts
index 9a2989b37348794c5347efab6eb85d4cc23cb4f5..cf686efd0d127651a1aaadc142ce0be00087547e 100644
--- a/packages/frontend/test/emoji.test.ts
+++ b/packages/frontend/test/emoji.test.ts
@@ -6,7 +6,7 @@
 import { describe, test, assert, afterEach } from 'vitest';
 import { render, cleanup, type RenderResult } from '@testing-library/vue';
 import { defaultStoreState } from './init.js';
-import { getEmojiName } from '@/scripts/emojilist.js';
+import { getEmojiName } from '@@/js/emojilist.js';
 import { components } from '@/components/index.js';
 import { directives } from '@/directives/index.js';
 import MkEmoji from '@/components/global/MkEmoji.vue';
diff --git a/packages/frontend/test/i18n.test.ts b/packages/frontend/test/i18n.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..149815d39c76f42674a9875cad37f777a17b6f8a
--- /dev/null
+++ b/packages/frontend/test/i18n.test.ts
@@ -0,0 +1,56 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { describe, expect, it } from 'vitest';
+import { I18n } from '../../frontend-shared/js/i18n.js'; // @@で参照できなかったので
+import { ParameterizedString } from '../../../locales/index.js';
+
+/* eslint "sharkey/locale":"off" */
+
+// TODO: このテストはfrontend-sharedに移動する
+
+describe('i18n', () => {
+	it('t', () => {
+		const i18n = new I18n({
+			foo: 'foo',
+			bar: {
+				baz: 'baz',
+				qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
+				quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
+			},
+		});
+
+		expect(i18n.t('foo')).toBe('foo');
+		expect(i18n.t('bar.baz')).toBe('baz');
+		expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
+		expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
+	});
+	it('ts', () => {
+		const i18n = new I18n({
+			foo: 'foo',
+			bar: {
+				baz: 'baz',
+				qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
+				quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
+			},
+		});
+
+		expect(i18n.ts.foo).toBe('foo');
+		expect(i18n.ts.bar.baz).toBe('baz');
+	});
+	it('tsx', () => {
+		const i18n = new I18n({
+			foo: 'foo',
+			bar: {
+				baz: 'baz',
+				qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
+				quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
+			},
+		});
+
+		expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
+		expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
+	});
+});
diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts
index a0b56b7221bbadb5cd63b7af51e5bbf425072ad2..32a5a1c55811931b67d6e5c85de8cb6fd6b6f709 100644
--- a/packages/frontend/test/scroll.test.ts
+++ b/packages/frontend/test/scroll.test.ts
@@ -5,7 +5,7 @@
 
 import { describe, test, assert, afterEach } from 'vitest';
 import { Window } from 'happy-dom';
-import { onScrollBottom, onScrollTop } from '@/scripts/scroll.js';
+import { onScrollBottom, onScrollTop } from '@@/js/scroll.js';
 
 describe('Scroll', () => {
 	describe('onScrollTop', () => {
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
index fe4d20289492776252989f792ada957b8d70ce47..bbf9d653cf349296c9dbd9db73f885adedf84d09 100644
--- a/packages/frontend/tsconfig.json
+++ b/packages/frontend/tsconfig.json
@@ -21,9 +21,11 @@
 		"allowSyntheticDefaultImports": true,
 		"isolatedModules": true,
 		"useDefineForClassFields": true,
+		"skipLibCheck": true,
 		"baseUrl": ".",
 		"paths": {
-			"@/*": ["./src/*"]
+			"@/*": ["./src/*"],
+			"@@/*": ["../frontend-shared/*"]
 		},
 		"typeRoots": [
 			"./@types",
@@ -44,10 +46,14 @@
 	},
 	"compileOnSave": false,
 	"include": [
-		"./**/*.ts",
-		"./**/*.vue"
+		"./src/**/*.ts",
+		"./src/**/*.vue",
+		"./test/**/*.ts",
+		"./test/**/*.vue",
+		"./@types/**/*.ts"
 	],
 	"exclude": [
+		"node_modules",
 		".storybook/**/*"
 	]
 }
diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts
index 07cf3b4a69e06c3e946b59ca31e8efc1298463bf..d588f83138e8c88bde2507260a140e74686310fe 100644
--- a/packages/frontend/vite.config.local-dev.ts
+++ b/packages/frontend/vite.config.local-dev.ts
@@ -15,6 +15,7 @@ const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8'))
 
 const httpUrl = `http://localhost:${port}/`;
 const websocketUrl = `ws://localhost:${port}/`;
+const embedUrl = `http://localhost:5174/`;
 
 // activitypubリクエストはProxyを通し、それ以外はViteの開発サーバーを返す
 function varyHandler(req: IncomingMessage) {
@@ -51,6 +52,12 @@ const devConfig: UserConfig = {
 				ws: true,
 			},
 			'/favicon.ico': httpUrl,
+			'/robots.txt': httpUrl,
+			'/embed.js': httpUrl,
+			'/embed': {
+				target: embedUrl,
+				ws: true,
+			},
 			'/identicon': {
 				target: httpUrl,
 				rewrite(path) {
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 674fdbf6801bc2ba826771f2b4c0190e393f1f5d..f8bd433335d1bfb70ee5487734eb7640413256aa 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -2,13 +2,13 @@ import path from 'path';
 import pluginReplace from '@rollup/plugin-replace';
 import pluginVue from '@vitejs/plugin-vue';
 import { type UserConfig, defineConfig } from 'vite';
-
+import { localesVersion } from '../../locales/version.js';
 import locales from '../../locales/index.js';
 import meta from '../../package.json';
 import packageInfo from './package.json' with { type: 'json' };
 import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name.js';
 import pluginJson5 from './vite.json5.js';
-import { pluginReplaceIcons } from './vite.replaceIcons.ts';
+import { pluginReplaceIcons } from './vite.replaceIcons.js';
 
 const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue', '.wasm'];
 
@@ -66,6 +66,9 @@ export function getConfig(): UserConfig {
 
 		server: {
 			port: 5173,
+			headers: { // なんか効かない
+				'X-Frame-Options': 'DENY',
+			},
 		},
 
 		plugins: [
@@ -89,6 +92,7 @@ export function getConfig(): UserConfig {
 			extensions,
 			alias: {
 				'@/': __dirname + '/src/',
+				'@@/': __dirname + '/../frontend-shared/',
 				'/client-assets/': __dirname + '/assets/',
 				'/static-assets/': __dirname + '/../backend/assets/',
 				'/fluent-emojis/': __dirname + '/../../fluent-emojis/dist/',
@@ -110,6 +114,7 @@ export function getConfig(): UserConfig {
 		define: {
 			_VERSION_: JSON.stringify(meta.version),
 			_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
+			_LANGS_VERSION_: JSON.stringify(localesVersion),
 			_ENV_: JSON.stringify(process.env.NODE_ENV),
 			_DEV_: process.env.NODE_ENV !== 'production',
 			_PERF_PREFIX_: JSON.stringify('Misskey:'),
@@ -151,7 +156,7 @@ export function getConfig(): UserConfig {
 				},
 			},
 			cssCodeSplit: true,
-			outDir: __dirname + '/../../built/_vite_',
+			outDir: __dirname + '/../../built/_frontend_vite_',
 			assetsDir: '.',
 			emptyOutDir: false,
 			sourcemap: process.env.NODE_ENV === 'development',
diff --git a/packages/frontend/vite.replaceIcons.ts b/packages/frontend/vite.replaceIcons.ts
index 92ac568ef34e9d2db1b4cf82a2f4d79c8cc77efe..ae9a8f81d62009c01de5abc3d96376686af01a6c 100644
--- a/packages/frontend/vite.replaceIcons.ts
+++ b/packages/frontend/vite.replaceIcons.ts
@@ -144,6 +144,7 @@ export function pluginReplaceIcons() {
 					'ti ti-api': 'ph-key ph-bold ph-lg',
 					'ti ti-app-window': 'ph-app-window ph-bold ph-lg',
 					'ti ti-apple': 'ph-orange-slice ph-bold ph-lg',
+					'ti ti-archive': 'ph-archive ph-bold ph-lg',
 					'ti ti-arrow-back-up': 'ph-arrow-u-up-left ph-bold ph-lg',
 					'ti ti-arrow-bar-to-down': 'ph-arrow-line-down ph-bold ph-lg',
 					'ti ti-arrow-big-right': 'ph-arrow-fat-right ph-bold ph-lg',
@@ -214,6 +215,7 @@ export function pluginReplaceIcons() {
 					'ti ti-device-mobile': 'ph-device-mobile ph-bold ph-lg',
 					'ti ti-device-tablet': 'ph-device-tablet ph-bold ph-lg',
 					'ti ti-device-tv': 'ph-television ph-bold ph-lg',
+					'ti ti-device-usb': 'ph-usb ph-bold ph-lg',
 					'ti ti-devices': 'ph-devices ph-bold ph-lg',
 					'ti ti-dice': 'ph ph-dice-five ph-bold ph-lg',
 					'ti ti-dice-5': 'ph ph-dice-five ph-bold ph-lg',
@@ -262,6 +264,7 @@ export function pluginReplaceIcons() {
 					'ti ti-list-search': 'ph-list ph-bold ph-lg-search',
 					'ti ti-lock': 'ph-lock ph-bold ph-lg',
 					'ti ti-lock-open': 'ph-lock-open ph-bold ph-lg',
+					'ti ti-lock-star': 'ph-shield-star ph-bold ph-lg',
 					'ti ti-mail': 'ph-envelope ph-bold ph-lg',
 					'ti ti-map-pin': 'ph-map-pin ph-bold ph-lg',
 					'ti ti-maximize': 'ph-frame-corners ph-bold ph-lg',
@@ -269,7 +272,9 @@ export function pluginReplaceIcons() {
 					'ti ti-menu': 'ph-list ph-bold ph-lg',
 					'ti ti-menu-2': 'ph-list ph-bold ph-lg',
 					'ti ti-message': 'ph-envelope ph-bold ph-lg',
+					'ti ti-message-exclamation': 'ph-exclamation ph-bold ph-lg',
 					'ti ti-message-off': 'ph-bell-slash ph-bold ph-lg',
+					'ti ti-message-x': 'ph-prohibit ph-bold ph-lg',
 					'ti ti-messages': 'ph-envelope ph-bold ph-lg',
 					'ti ti-messages-off': 'ph-envelope-open ph-bold ph-lg',
 					'ti ti-minimize': 'ph-arrows-in-simple ph-bold ph-lg',
@@ -344,7 +349,7 @@ export function pluginReplaceIcons() {
 					'ti ti-universe': 'ph-rocket-launch ph-bold ph-lg',
 					'ti ti-upload': 'ph-upload ph-bold ph-lg',
 					'ti ti-user': 'ph-user ph-bold ph-lg',
-					'ti ti-user-check': 'ph-check ph-bold ph-lg',
+					'ti ti-user-check': 'ph-user-check ph-bold ph-lg',
 					'ti ti-user-circle': 'ph-user-circle ph-bold ph-lg',
 					'ti ti-user-edit': 'ph-user-list ph-bold ph-lg',
 					'ti ti-user-exclamation': 'ph-warning-circle ph-bold ph-lg',
@@ -352,6 +357,7 @@ export function pluginReplaceIcons() {
 					'ti ti-user-plus': 'ph-user-plus ph-bold ph-lg',
 					'ti ti-user-search': 'ph-user-circle ph-bold ph-lg',
 					'ti ti-user-shield': 'ph-newspaper-clipping ph-bold ph-lg',
+					'ti ti-user-star': 'ph-user-focus ph-bold ph-lg',
 					'ti ti-users': 'ph-users ph-bold ph-lg',
 					'ti ti-video': 'ph-video ph-bold ph-lg',
 					'ti ti-volume': 'ph-speaker-high ph-bold ph-lg',
@@ -361,6 +367,7 @@ export function pluginReplaceIcons() {
 					'ti ti-window-maximize': 'ph-frame-corners ph-bold ph-lg',
 					'ti ti-world': 'ph-globe-hemisphere-west ph-bold ph-lg',
 					'ti ti-world-download': 'ph-cloud-arrow-down ph-bold ph-lg',
+					'ti ti-world-cog': 'ph-gear-six ph-bold ph-lg',
 					'ti ti-world-search': 'ph-binoculars ph-bold ph-lg',
 					'ti ti-world-upload': 'ph-cloud-arrow-up ph-bold ph-lg',
 					'ti ti-world-x': 'ph-planet ph-bold ph-lg',
diff --git a/packages/megalodon/package.json b/packages/megalodon/package.json
index dbb5686af26eb58c9d2d3d57a7e0650c77ae3885..bd9e4fc2844d1006dd85c0fac6d94e83c3e7a272 100644
--- a/packages/megalodon/package.json
+++ b/packages/megalodon/package.json
@@ -6,7 +6,6 @@
   "typings": "./lib/src/index.d.ts",
   "scripts": {
     "build": "tsc -p ./",
-    "lint": "eslint --ext .js,.ts src --cache",
     "doc": "typedoc --out ../docs ./src",
     "test": "NODE_ENV=test jest -u --maxWorkers=3"
   },
@@ -63,7 +62,7 @@
     "@types/parse-link-header": "^2.0.3",
     "@types/uuid": "^9.0.7",
     "@types/ws": "^8.5.10",
-    "axios": "1.6.0",
+    "axios": "1.7.4",
     "dayjs": "^1.11.10",
     "form-data": "^4.0.0",
     "https-proxy-agent": "^7.0.2",
@@ -73,7 +72,7 @@
     "socks-proxy-agent": "^8.0.2",
     "typescript": "5.1.6",
     "uuid": "^9.0.1",
-    "ws": "8.14.2"
+    "ws": "8.17.1"
   },
   "devDependencies": {
     "@typescript-eslint/eslint-plugin": "^6.12.0",
diff --git a/packages/megalodon/tsconfig.json b/packages/megalodon/tsconfig.json
index b2b4a984b1a68869fafee116ccdc489c8d96745b..6327d7c31db10affb8fa8b29d1d62bf8d6fecac6 100644
--- a/packages/megalodon/tsconfig.json
+++ b/packages/megalodon/tsconfig.json
@@ -34,6 +34,7 @@
     "noUnusedParameters": true,               /* Report errors on unused parameters. */
     "noImplicitReturns": true,                /* Report error when not all code paths in function return a value. */
     "noFallthroughCasesInSwitch": true,       /* Report errors for fallthrough cases in switch statement. */
+		"skipLibCheck": true,
 
     /* Module Resolution Options */
     "moduleResolution": "node",               /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
diff --git a/packages/misskey-bubble-game/build.js b/packages/misskey-bubble-game/build.js
index e626c97a5967d6c20facd1cd107f46f28451a63a..a80b71646f43800c48d502dc828142c4e0bd367c 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 86c21a22a3a78a7619a698d3d1c9200868e3e3a3..749971a72b581d8b08cb143a5f7711e468781eb4 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,
 	{
@@ -24,4 +25,13 @@ export default [
 			},
 		},
 	},
+	{
+		ignores: [
+			"**/lib/",
+			"**/temp/",
+			"**/built/",
+			"**/coverage/",
+			"**/node_modules/",
+		]
+	},
 ];
diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json
index acd6ab9dd2d0c67f6aa13908a11e0bf893ff9c1c..2b280f17fc3626bbe468456bbaf9e330b5ca1f03 100644
--- a/packages/misskey-bubble-game/package.json
+++ b/packages/misskey-bubble-game/package.json
@@ -17,7 +17,7 @@
 	"scripts": {
 		"build": "node ./build.js",
 		"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
-		"eslint": "eslint './**/*.{js,jsx,ts,tsx}' --cache",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
 		"typecheck": "tsc --noEmit",
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
diff --git a/packages/misskey-bubble-game/src/game.ts b/packages/misskey-bubble-game/src/game.ts
index 3bce4b1dcfe8dd2e15ad8645889dcf039c5d9959..7f230e39cb8f511a9d02c1a31de598892025da0d 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-bubble-game/tsconfig.json b/packages/misskey-bubble-game/tsconfig.json
index 6e34e332e000246724fd39812527733960a20185..f467951ef6f6168a713b0d6665bd66ba43c9111d 100644
--- a/packages/misskey-bubble-game/tsconfig.json
+++ b/packages/misskey-bubble-game/tsconfig.json
@@ -15,6 +15,7 @@
 		"experimentalDecorators": true,
 		"noImplicitReturns": true,
 		"esModuleInterop": true,
+		"skipLibCheck": true,
 		"typeRoots": [
 			"./node_modules/@types"
 		],
diff --git a/packages/misskey-js/eslint.config.js b/packages/misskey-js/eslint.config.js
index d8173f30e9ac53c6dfe8a1e379aa8cfc9b815fe8..f33c4c4d2516a8bb5db39fdd8568349f946d3069 100644
--- a/packages/misskey-js/eslint.config.js
+++ b/packages/misskey-js/eslint.config.js
@@ -26,4 +26,13 @@ export default [
 			},
 		},
 	},
+	{
+		ignores: [
+			"**/lib/",
+			"**/temp/",
+			"**/built/",
+			"**/coverage/",
+			"**/node_modules/",
+		]
+	},
 ];
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 87fda5568eca67598eab813ea6476e5c99eab5f2..5af1a4112f8e95a2e5152ad610599142d91ed8fb 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -133,6 +133,9 @@ type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations
 // @public (undocumented)
 type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
 
@@ -367,6 +370,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'];
 
@@ -566,7 +572,7 @@ type Channel = components['schemas']['Channel'];
 // Warning: (ae-forgotten-export) The symbol "AnyOf" needs to be exported by the entry point index.d.ts
 //
 // @public (undocumented)
-export abstract class ChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> {
+export abstract class ChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
     constructor(stream: Stream, channel: string, name?: string);
     // (undocumented)
     channel: string;
@@ -699,7 +705,7 @@ export type Channels = {
     };
     hashtag: {
         params: {
-            q?: string;
+            q: string[][];
         };
         events: {
             note: (payload: Note) => void;
@@ -1188,6 +1194,10 @@ export type Endpoints = Overwrite<Endpoints_2, {
         req: SigninRequest;
         res: SigninResponse;
     };
+    'signin-with-passkey': {
+        req: SigninWithPasskeyRequest;
+        res: SigninWithPasskeyResponse;
+    };
     'admin/roles/create': {
         req: Overwrite<AdminRolesCreateRequest, {
             policies: PartialRolePolicyOverride;
@@ -1219,6 +1229,8 @@ declare namespace entities {
         SignupPendingRequest,
         SignupPendingResponse,
         SigninRequest,
+        SigninWithPasskeyRequest,
+        SigninWithPasskeyResponse,
         SigninResponse,
         PartialRolePolicyOverride,
         EmptyRequest,
@@ -1319,6 +1331,7 @@ declare namespace entities {
         AdminUnsilenceUserRequest,
         AdminSuspendUserRequest,
         AdminApproveUserRequest,
+        AdminDeclineUserRequest,
         AdminUnsuspendUserRequest,
         AdminUpdateMetaRequest,
         AdminDeleteAccountRequest,
@@ -1344,6 +1357,7 @@ declare namespace entities {
         AdminSystemWebhookShowResponse,
         AdminSystemWebhookUpdateRequest,
         AdminSystemWebhookUpdateResponse,
+        AdminSystemWebhookTestRequest,
         AnnouncementsRequest,
         AnnouncementsResponse,
         AnnouncementsShowRequest,
@@ -1502,6 +1516,8 @@ declare namespace entities {
         FollowingRequestsCancelResponse,
         FollowingRequestsListRequest,
         FollowingRequestsListResponse,
+        FollowingRequestsSentRequest,
+        FollowingRequestsSentResponse,
         FollowingRequestsRejectRequest,
         GalleryFeaturedRequest,
         GalleryFeaturedResponse,
@@ -1605,6 +1621,7 @@ declare namespace entities {
         IWebhooksShowResponse,
         IWebhooksUpdateRequest,
         IWebhooksDeleteRequest,
+        IWebhooksTestRequest,
         InviteCreateResponse,
         InviteDeleteRequest,
         InviteListRequest,
@@ -1642,6 +1659,8 @@ declare namespace entities {
         NotesFavoritesDeleteRequest,
         NotesFeaturedRequest,
         NotesFeaturedResponse,
+        NotesFollowingRequest,
+        NotesFollowingResponse,
         NotesGlobalTimelineRequest,
         NotesGlobalTimelineResponse,
         NotesBubbleTimelineRequest,
@@ -1655,6 +1674,7 @@ declare namespace entities {
         NotesPollsRecommendationRequest,
         NotesPollsRecommendationResponse,
         NotesPollsVoteRequest,
+        NotesPollsRefreshRequest,
         NotesReactionsRequest,
         NotesReactionsResponse,
         NotesReactionsCreateRequest,
@@ -2021,6 +2041,12 @@ type FollowingRequestsListResponse = operations['following___requests___list']['
 // @public (undocumented)
 type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type FollowingRequestsSentRequest = operations['following___requests___sent']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type FollowingRequestsSentResponse = operations['following___requests___sent']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type FollowingUpdateAllRequest = operations['following___update-all']['requestBody']['content']['application/json'];
 
@@ -2165,6 +2191,24 @@ type IAuthorizedAppsResponse = operations['i___authorized-apps']['responses']['2
 // @public (undocumented)
 type IChangePasswordRequest = operations['i___change-password']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+export interface IChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> {
+    // (undocumented)
+    channel: string;
+    // (undocumented)
+    dispose(): void;
+    // (undocumented)
+    id: string;
+    // (undocumented)
+    inCount: number;
+    // (undocumented)
+    name?: string;
+    // (undocumented)
+    outCount: number;
+    // (undocumented)
+    send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void;
+}
+
 // @public (undocumented)
 type IClaimAchievementRequest = operations['i___claim-achievement']['requestBody']['content']['application/json'];
 
@@ -2333,6 +2377,40 @@ type ISigninHistoryResponse = operations['i___signin-history']['responses']['200
 // @public (undocumented)
 function isPureRenote(note: Note): note is PureRenote;
 
+// @public (undocumented)
+export interface IStream extends EventEmitter<StreamEvents> {
+    // (undocumented)
+    close(): void;
+    // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    disconnectToChannel(connection: NonSharedConnection): void;
+    // (undocumented)
+    heartbeat(): void;
+    // (undocumented)
+    ping(): void;
+    // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    removeSharedConnection(connection: SharedConnection): void;
+    // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts
+    //
+    // (undocumented)
+    removeSharedConnectionPool(pool: Pool): void;
+    // (undocumented)
+    send(typeOrPayload: string): void;
+    // (undocumented)
+    send(typeOrPayload: string, payload: unknown): void;
+    // (undocumented)
+    send(typeOrPayload: Record<string, unknown> | unknown[]): void;
+    // (undocumented)
+    send(typeOrPayload: string | Record<string, unknown> | unknown[], payload?: unknown): void;
+    // (undocumented)
+    state: 'initializing' | 'reconnecting' | 'connected';
+    // (undocumented)
+    useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): IChannelConnection<Channels[C]>;
+}
+
 // @public (undocumented)
 type IUnpinRequest = operations['i___unpin']['requestBody']['content']['application/json'];
 
@@ -2369,6 +2447,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'];
 
@@ -2411,6 +2492,9 @@ type ModerationLog = {
 } | {
     type: 'approve';
     info: ModerationLogPayloads['approve'];
+} | {
+    type: 'decline';
+    info: ModerationLogPayloads['decline'];
 } | {
     type: 'suspend';
     info: ModerationLogPayloads['suspend'];
@@ -2648,6 +2732,12 @@ type NotesFeaturedRequest = operations['notes___featured']['requestBody']['conte
 // @public (undocumented)
 type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type NotesFollowingRequest = operations['notes___following']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type NotesFollowingResponse = operations['notes___following']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json'];
 
@@ -2681,6 +2771,9 @@ type NotesPollsRecommendationRequest = operations['notes___polls___recommendatio
 // @public (undocumented)
 type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type NotesPollsRefreshRequest = operations['notes___polls___refresh']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json'];
 
@@ -2783,6 +2876,9 @@ type NotificationsCreateRequest = operations['notifications___create']['requestB
 // @public (undocumented)
 export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned", "edited"];
 
+// @public (undocumented)
+export function nyaize(text: string): string;
+
 // @public (undocumented)
 type Page = components['schemas']['Page'];
 
@@ -2841,7 +2937,7 @@ type PartialRolePolicyOverride = Partial<{
 }>;
 
 // @public (undocumented)
-export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
+export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
 
 // @public (undocumented)
 type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
@@ -3042,6 +3138,19 @@ type SigninResponse = {
     i: string;
 };
 
+// @public (undocumented)
+type SigninWithPasskeyRequest = {
+    credential?: object;
+    context?: string;
+};
+
+// @public (undocumented)
+type SigninWithPasskeyResponse = {
+    option?: object;
+    context?: string;
+    signinResponse?: SigninResponse;
+};
+
 // @public (undocumented)
 type SignupPendingRequest = {
     code: string;
@@ -3076,10 +3185,8 @@ type SponsorsRequest = operations['sponsors']['requestBody']['content']['applica
 // @public (undocumented)
 type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];
 
-// Warning: (ae-forgotten-export) The symbol "StreamEvents" needs to be exported by the entry point index.d.ts
-//
 // @public (undocumented)
-export class Stream extends EventEmitter<StreamEvents> {
+export class Stream extends EventEmitter<StreamEvents> implements IStream {
     constructor(origin: string, user: {
         token: string;
     } | null, options?: {
@@ -3087,20 +3194,14 @@ export class Stream extends EventEmitter<StreamEvents> {
     });
     // (undocumented)
     close(): void;
-    // Warning: (ae-forgotten-export) The symbol "NonSharedConnection" needs to be exported by the entry point index.d.ts
-    //
     // (undocumented)
     disconnectToChannel(connection: NonSharedConnection): void;
     // (undocumented)
     heartbeat(): void;
     // (undocumented)
     ping(): void;
-    // Warning: (ae-forgotten-export) The symbol "SharedConnection" needs to be exported by the entry point index.d.ts
-    //
     // (undocumented)
     removeSharedConnection(connection: SharedConnection): void;
-    // Warning: (ae-forgotten-export) The symbol "Pool" needs to be exported by the entry point index.d.ts
-    //
     // (undocumented)
     removeSharedConnectionPool(pool: Pool): void;
     // (undocumented)
@@ -3115,6 +3216,14 @@ export class Stream extends EventEmitter<StreamEvents> {
     useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): ChannelConnection<Channels[C]>;
 }
 
+// Warning: (ae-forgotten-export) The symbol "BroadcastEvents" needs to be exported by the entry point index.d.ts
+//
+// @public (undocumented)
+export type StreamEvents = {
+    _connected_: void;
+    _disconnected_: void;
+} & BroadcastEvents;
+
 // Warning: (ae-forgotten-export) The symbol "SwitchCase" needs to be exported by the entry point index.d.ts
 // Warning: (ae-forgotten-export) The symbol "IsCaseMatched" needs to be exported by the entry point index.d.ts
 // Warning: (ae-forgotten-export) The symbol "GetCaseResult" 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 f9bd0a4e5d06e0bde402d824578fbb482a46d2bf..1d31c732374941b812cccc96ed04180c6d6efecd 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 --cache"
 	},
 	"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/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts
index 4ae00a4522db6ccfa8adb3608e8cc789d27080ad..88f2ae9ee96e6251727923ccb16f1fdc32108d27 100644
--- a/packages/misskey-js/generator/src/generator.ts
+++ b/packages/misskey-js/generator/src/generator.ts
@@ -96,15 +96,11 @@ async function generateEndpoints(
 				endpoint.request = req;
 
 				const reqType = new EndpointReqMediaType(path, req);
-				endpointReqMediaTypesSet.add(reqType.getMediaType());
-				endpointReqMediaTypes.push(reqType);
-			} else {
-				endpointReqMediaTypesSet.add('application/json');
-				endpointReqMediaTypes.push(new EndpointReqMediaType(path, undefined, 'application/json'));
+				if (reqType.getMediaType() !== 'application/json') {
+					endpointReqMediaTypesSet.add(reqType.getMediaType());
+					endpointReqMediaTypes.push(reqType);
+				}
 			}
-		} else {
-			endpointReqMediaTypesSet.add('application/json');
-			endpointReqMediaTypes.push(new EndpointReqMediaType(path, undefined, 'application/json'));
 		}
 
 		if (operation.responses && isResponseObject(operation.responses['200']) && operation.responses['200'].content) {
@@ -158,16 +154,19 @@ async function generateEndpoints(
 	endpointOutputLine.push('');
 
 	function generateEndpointReqMediaTypesType() {
-		return `Record<keyof Endpoints, ${[...endpointReqMediaTypesSet].map((t) => `'${t}'`).join(' | ')}>`;
+		return `{ [K in keyof Endpoints]?: ${[...endpointReqMediaTypesSet].map((t) => `'${t}'`).join(' | ')}; }`;
 	}
 
-	endpointOutputLine.push(`export const endpointReqTypes: ${generateEndpointReqMediaTypesType()} = {`);
+	endpointOutputLine.push(`/**
+ * NOTE: The content-type for all endpoints not listed here is application/json.
+ */`);
+	endpointOutputLine.push('export const endpointReqTypes = {');
 
 	endpointOutputLine.push(
 		...endpointReqMediaTypes.map(it => '\t' + it.toLine()),
 	);
 
-	endpointOutputLine.push('};');
+	endpointOutputLine.push(`} as const satisfies ${generateEndpointReqMediaTypesType()};`);
 	endpointOutputLine.push('');
 
 	await writeFile(endpointOutputPath, endpointOutputLine.join('\n'));
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 5ba4b93411acda005554b38de71dda78cf6f2016..05cda465745e6c44669f3c7ebd3bb4daf4c4c6a6 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.9.1",
 	"description": "Misskey SDK for JavaScript",
 	"license": "MIT",
 	"main": "./built/index.js",
@@ -22,7 +22,7 @@
 		"tsd": "tsd",
 		"api": "pnpm api-extractor run --local --verbose",
 		"api-prod": "pnpm api-extractor run --verbose",
-		"eslint": "eslint './**/*.{js,jsx,ts,tsx}' --cache",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
 		"typecheck": "tsc --noEmit",
 		"lint": "pnpm typecheck && pnpm eslint",
 		"jest": "jest --coverage --detectOpenHandles",
@@ -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,11 +46,11 @@
 		"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": [
diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts
index ea1df57f3d77cf10564b949a2f235d9025de9eac..ed1282957fbe3e613c767e0bc078bbb8138a9407 100644
--- a/packages/misskey-js/src/api.ts
+++ b/packages/misskey-js/src/api.ts
@@ -56,6 +56,10 @@ export class APIClient {
 		return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
 	}
 
+	private assertSpecialEpReqType(ep: keyof Endpoints): ep is keyof typeof endpointReqTypes {
+		return ep in endpointReqTypes;
+	}
+
 	public request<E extends keyof Endpoints, P extends Endpoints[E]['req']>(
 		endpoint: E,
 		params: P = {} as P,
@@ -63,9 +67,12 @@ export class APIClient {
 	): Promise<SwitchCaseResponseType<E, P>> {
 		return new Promise((resolve, reject) => {
 			let mediaType = 'application/json';
-			if (endpoint in endpointReqTypes) {
+			// (autogenがバグったときのため、念の為nullチェックも行う)
+			// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+			if (this.assertSpecialEpReqType(endpoint) && endpointReqTypes[endpoint] != null) {
 				mediaType = endpointReqTypes[endpoint];
 			}
+
 			let payload: FormData | string = '{}';
 
 			if (mediaType === 'application/json') {
@@ -100,7 +107,7 @@ export class APIClient {
 				method: 'POST',
 				body: payload,
 				headers: {
-					'Content-Type': endpointReqTypes[endpoint],
+					'Content-Type': mediaType,
 				},
 				credentials: 'omit',
 				cache: 'no-cache',
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index 5ee4194db213b0a0122bcf9f36152d666c9f92d0..4c3f2e157859965c04a9a3951e6f63d01b91aaea 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -5,6 +5,8 @@ import {
 	PartialRolePolicyOverride,
 	SigninRequest,
 	SigninResponse,
+	SigninWithPasskeyRequest,
+	SigninWithPasskeyResponse,
 	SignupPendingRequest,
 	SignupPendingResponse,
 	SignupRequest,
@@ -82,6 +84,10 @@ export type Endpoints = Overwrite<
 			req: SigninRequest;
 			res: SigninResponse;
 		},
+		'signin-with-passkey': {
+			req: SigninWithPasskeyRequest;
+			res: SigninWithPasskeyResponse;
+		}
 		'admin/roles/create': {
 			req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>;
 			res: AdminRolesCreateResponse;
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index c13485621b5709b631aac1e96ba079547bbbc4ad..3fa67b79903f28923ef48358eec547d688d4b24c 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -812,6 +812,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user*
+     */
+    request<E extends 'admin/decline-user', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -1015,6 +1026,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.
      * 
@@ -1867,7 +1890,7 @@ declare module '../api.js' {
     /**
      * No description provided.
      * 
-     * **Credential required**: *No*
+     * **Credential required**: *Yes* / **Permission**: *read:account*
      */
     request<E extends 'federation/followers', P extends Endpoints[E]['req']>(
       endpoint: E,
@@ -1878,7 +1901,7 @@ declare module '../api.js' {
     /**
      * No description provided.
      * 
-     * **Credential required**: *No*
+     * **Credential required**: *Yes* / **Permission**: *read:account*
      */
     request<E extends 'federation/following', P extends Endpoints[E]['req']>(
       endpoint: E,
@@ -2029,6 +2052,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:following*
+     */
+    request<E extends 'following/requests/sent', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2909,6 +2943,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.
      * 
@@ -3174,6 +3220,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    request<E extends 'notes/following', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -3251,6 +3308,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:federation*
+     */
+    request<E extends 'notes/polls/refresh', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -4258,7 +4326,7 @@ declare module '../api.js' {
     ): Promise<SwitchCaseResponseType<E, P>>;
 
     /**
-     * Get Sharkey GH Sponsors
+     * Get Sharkey Sponsors or Instance Sponsors
      * 
      * **Credential required**: *No*
      */
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 628825d5044c7f0dae683a8aafe0e833a80583b8..609c9f99d8a3d73bb80436d57bd307ce25677854 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -97,6 +97,7 @@ import type {
 	AdminUnsilenceUserRequest,
 	AdminSuspendUserRequest,
 	AdminApproveUserRequest,
+	AdminDeclineUserRequest,
 	AdminUnsuspendUserRequest,
 	AdminUpdateMetaRequest,
 	AdminDeleteAccountRequest,
@@ -122,6 +123,7 @@ import type {
 	AdminSystemWebhookShowResponse,
 	AdminSystemWebhookUpdateRequest,
 	AdminSystemWebhookUpdateResponse,
+	AdminSystemWebhookTestRequest,
 	AnnouncementsRequest,
 	AnnouncementsResponse,
 	AnnouncementsShowRequest,
@@ -280,6 +282,8 @@ import type {
 	FollowingRequestsCancelResponse,
 	FollowingRequestsListRequest,
 	FollowingRequestsListResponse,
+	FollowingRequestsSentRequest,
+	FollowingRequestsSentResponse,
 	FollowingRequestsRejectRequest,
 	GalleryFeaturedRequest,
 	GalleryFeaturedResponse,
@@ -383,6 +387,7 @@ import type {
 	IWebhooksShowResponse,
 	IWebhooksUpdateRequest,
 	IWebhooksDeleteRequest,
+	IWebhooksTestRequest,
 	InviteCreateResponse,
 	InviteDeleteRequest,
 	InviteListRequest,
@@ -420,6 +425,8 @@ import type {
 	NotesFavoritesDeleteRequest,
 	NotesFeaturedRequest,
 	NotesFeaturedResponse,
+	NotesFollowingRequest,
+	NotesFollowingResponse,
 	NotesGlobalTimelineRequest,
 	NotesGlobalTimelineResponse,
 	NotesBubbleTimelineRequest,
@@ -433,6 +440,7 @@ import type {
 	NotesPollsRecommendationRequest,
 	NotesPollsRecommendationResponse,
 	NotesPollsVoteRequest,
+	NotesPollsRefreshRequest,
 	NotesReactionsRequest,
 	NotesReactionsResponse,
 	NotesReactionsCreateRequest,
@@ -662,6 +670,7 @@ export type Endpoints = {
 	'admin/unsilence-user': { req: AdminUnsilenceUserRequest; res: EmptyResponse };
 	'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse };
 	'admin/approve-user': { req: AdminApproveUserRequest; res: EmptyResponse };
+	'admin/decline-user': { req: AdminDeclineUserRequest; res: EmptyResponse };
 	'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
 	'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
 	'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse };
@@ -680,6 +689,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 };
@@ -772,6 +782,7 @@ export type Endpoints = {
 	'following/requests/accept': { req: FollowingRequestsAcceptRequest; res: EmptyResponse };
 	'following/requests/cancel': { req: FollowingRequestsCancelRequest; res: FollowingRequestsCancelResponse };
 	'following/requests/list': { req: FollowingRequestsListRequest; res: FollowingRequestsListResponse };
+	'following/requests/sent': { req: FollowingRequestsSentRequest; res: FollowingRequestsSentResponse };
 	'following/requests/reject': { req: FollowingRequestsRejectRequest; res: EmptyResponse };
 	'gallery/featured': { req: GalleryFeaturedRequest; res: GalleryFeaturedResponse };
 	'gallery/popular': { req: EmptyRequest; res: GalleryPopularResponse };
@@ -849,6 +860,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 };
@@ -873,6 +885,7 @@ export type Endpoints = {
 	'notes/favorites/create': { req: NotesFavoritesCreateRequest; res: EmptyResponse };
 	'notes/favorites/delete': { req: NotesFavoritesDeleteRequest; res: EmptyResponse };
 	'notes/featured': { req: NotesFeaturedRequest; res: NotesFeaturedResponse };
+	'notes/following': { req: NotesFollowingRequest; res: NotesFollowingResponse };
 	'notes/global-timeline': { req: NotesGlobalTimelineRequest; res: NotesGlobalTimelineResponse };
 	'notes/bubble-timeline': { req: NotesBubbleTimelineRequest; res: NotesBubbleTimelineResponse };
 	'notes/hybrid-timeline': { req: NotesHybridTimelineRequest; res: NotesHybridTimelineResponse };
@@ -880,6 +893,7 @@ export type Endpoints = {
 	'notes/mentions': { req: NotesMentionsRequest; res: NotesMentionsResponse };
 	'notes/polls/recommendation': { req: NotesPollsRecommendationRequest; res: NotesPollsRecommendationResponse };
 	'notes/polls/vote': { req: NotesPollsVoteRequest; res: EmptyResponse };
+	'notes/polls/refresh': { req: NotesPollsRefreshRequest; res: EmptyResponse };
 	'notes/reactions': { req: NotesReactionsRequest; res: NotesReactionsResponse };
 	'notes/reactions/create': { req: NotesReactionsCreateRequest; res: EmptyResponse };
 	'notes/reactions/delete': { req: NotesReactionsDeleteRequest; res: EmptyResponse };
@@ -983,397 +997,9 @@ export type Endpoints = {
 	'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse };
 }
 
-export const endpointReqTypes: Record<keyof Endpoints, 'application/json' | 'multipart/form-data'> = {
-	'admin/meta': 'application/json',
-	'admin/abuse-user-reports': 'application/json',
-	'admin/abuse-report/notification-recipient/list': 'application/json',
-	'admin/abuse-report/notification-recipient/show': 'application/json',
-	'admin/abuse-report/notification-recipient/create': 'application/json',
-	'admin/abuse-report/notification-recipient/update': 'application/json',
-	'admin/abuse-report/notification-recipient/delete': 'application/json',
-	'admin/accounts/create': 'application/json',
-	'admin/accounts/delete': 'application/json',
-	'admin/accounts/find-by-email': 'application/json',
-	'admin/ad/create': 'application/json',
-	'admin/ad/delete': 'application/json',
-	'admin/ad/list': 'application/json',
-	'admin/ad/update': 'application/json',
-	'admin/announcements/create': 'application/json',
-	'admin/announcements/delete': 'application/json',
-	'admin/announcements/list': 'application/json',
-	'admin/announcements/update': 'application/json',
-	'admin/avatar-decorations/create': 'application/json',
-	'admin/avatar-decorations/delete': 'application/json',
-	'admin/avatar-decorations/list': 'application/json',
-	'admin/avatar-decorations/update': 'application/json',
-	'admin/delete-all-files-of-a-user': 'application/json',
-	'admin/unset-user-avatar': 'application/json',
-	'admin/unset-user-banner': 'application/json',
-	'admin/drive/clean-remote-files': 'application/json',
-	'admin/drive/cleanup': 'application/json',
-	'admin/drive/files': 'application/json',
-	'admin/drive/show-file': 'application/json',
-	'admin/emoji/add-aliases-bulk': 'application/json',
-	'admin/emoji/add': 'application/json',
-	'admin/emoji/copy': 'application/json',
-	'admin/emoji/delete-bulk': 'application/json',
-	'admin/emoji/delete': 'application/json',
-	'admin/emoji/import-zip': 'application/json',
-	'admin/emoji/list-remote': 'application/json',
-	'admin/emoji/list': 'application/json',
-	'admin/emoji/remove-aliases-bulk': 'application/json',
-	'admin/emoji/set-aliases-bulk': 'application/json',
-	'admin/emoji/set-category-bulk': 'application/json',
-	'admin/emoji/set-license-bulk': 'application/json',
-	'admin/emoji/update': 'application/json',
-	'admin/federation/delete-all-files': 'application/json',
-	'admin/federation/refresh-remote-instance-metadata': 'application/json',
-	'admin/federation/remove-all-following': 'application/json',
-	'admin/federation/update-instance': 'application/json',
-	'admin/get-index-stats': 'application/json',
-	'admin/get-table-stats': 'application/json',
-	'admin/get-user-ips': 'application/json',
-	'admin/invite/create': 'application/json',
-	'admin/invite/list': 'application/json',
-	'admin/promo/create': 'application/json',
-	'admin/queue/clear': 'application/json',
-	'admin/queue/deliver-delayed': 'application/json',
-	'admin/queue/inbox-delayed': 'application/json',
-	'admin/queue/promote': 'application/json',
-	'admin/queue/stats': 'application/json',
-	'admin/relays/add': 'application/json',
-	'admin/relays/list': 'application/json',
-	'admin/relays/remove': 'application/json',
-	'admin/reset-password': 'application/json',
-	'admin/resolve-abuse-user-report': 'application/json',
-	'admin/send-email': 'application/json',
-	'admin/server-info': 'application/json',
-	'admin/show-moderation-logs': 'application/json',
-	'admin/show-user': 'application/json',
-	'admin/show-users': 'application/json',
-	'admin/nsfw-user': 'application/json',
-	'admin/unnsfw-user': 'application/json',
-	'admin/silence-user': 'application/json',
-	'admin/unsilence-user': 'application/json',
-	'admin/suspend-user': 'application/json',
-	'admin/approve-user': 'application/json',
-	'admin/unsuspend-user': 'application/json',
-	'admin/update-meta': 'application/json',
-	'admin/delete-account': 'application/json',
-	'admin/update-user-note': 'application/json',
-	'admin/roles/create': 'application/json',
-	'admin/roles/delete': 'application/json',
-	'admin/roles/list': 'application/json',
-	'admin/roles/show': 'application/json',
-	'admin/roles/update': 'application/json',
-	'admin/roles/assign': 'application/json',
-	'admin/roles/unassign': 'application/json',
-	'admin/roles/update-default-policies': 'application/json',
-	'admin/roles/users': 'application/json',
-	'admin/system-webhook/create': 'application/json',
-	'admin/system-webhook/delete': 'application/json',
-	'admin/system-webhook/list': 'application/json',
-	'admin/system-webhook/show': 'application/json',
-	'admin/system-webhook/update': 'application/json',
-	'announcements': 'application/json',
-	'announcements/show': 'application/json',
-	'antennas/create': 'application/json',
-	'antennas/delete': 'application/json',
-	'antennas/list': 'application/json',
-	'antennas/notes': 'application/json',
-	'antennas/show': 'application/json',
-	'antennas/update': 'application/json',
-	'ap/get': 'application/json',
-	'ap/show': 'application/json',
-	'app/create': 'application/json',
-	'app/show': 'application/json',
-	'auth/accept': 'application/json',
-	'auth/session/generate': 'application/json',
-	'auth/session/show': 'application/json',
-	'auth/session/userkey': 'application/json',
-	'blocking/create': 'application/json',
-	'blocking/delete': 'application/json',
-	'blocking/list': 'application/json',
-	'channels/create': 'application/json',
-	'channels/featured': 'application/json',
-	'channels/follow': 'application/json',
-	'channels/followed': 'application/json',
-	'channels/owned': 'application/json',
-	'channels/show': 'application/json',
-	'channels/timeline': 'application/json',
-	'channels/unfollow': 'application/json',
-	'channels/update': 'application/json',
-	'channels/favorite': 'application/json',
-	'channels/unfavorite': 'application/json',
-	'channels/my-favorites': 'application/json',
-	'channels/search': 'application/json',
-	'charts/active-users': 'application/json',
-	'charts/ap-request': 'application/json',
-	'charts/drive': 'application/json',
-	'charts/federation': 'application/json',
-	'charts/instance': 'application/json',
-	'charts/notes': 'application/json',
-	'charts/user/drive': 'application/json',
-	'charts/user/following': 'application/json',
-	'charts/user/notes': 'application/json',
-	'charts/user/pv': 'application/json',
-	'charts/user/reactions': 'application/json',
-	'charts/users': 'application/json',
-	'clips/add-note': 'application/json',
-	'clips/remove-note': 'application/json',
-	'clips/create': 'application/json',
-	'clips/delete': 'application/json',
-	'clips/list': 'application/json',
-	'clips/notes': 'application/json',
-	'clips/show': 'application/json',
-	'clips/update': 'application/json',
-	'clips/favorite': 'application/json',
-	'clips/unfavorite': 'application/json',
-	'clips/my-favorites': 'application/json',
-	'drive': 'application/json',
-	'drive/files': 'application/json',
-	'drive/files/attached-notes': 'application/json',
-	'drive/files/check-existence': 'application/json',
+/**
+ * NOTE: The content-type for all endpoints not listed here is application/json.
+ */
+export const endpointReqTypes = {
 	'drive/files/create': 'multipart/form-data',
-	'drive/files/delete': 'application/json',
-	'drive/files/find-by-hash': 'application/json',
-	'drive/files/find': 'application/json',
-	'drive/files/show': 'application/json',
-	'drive/files/update': 'application/json',
-	'drive/files/upload-from-url': 'application/json',
-	'drive/folders': 'application/json',
-	'drive/folders/create': 'application/json',
-	'drive/folders/delete': 'application/json',
-	'drive/folders/find': 'application/json',
-	'drive/folders/show': 'application/json',
-	'drive/folders/update': 'application/json',
-	'drive/stream': 'application/json',
-	'email-address/available': 'application/json',
-	'endpoint': 'application/json',
-	'endpoints': 'application/json',
-	'export-custom-emojis': 'application/json',
-	'federation/followers': 'application/json',
-	'federation/following': 'application/json',
-	'federation/instances': 'application/json',
-	'federation/show-instance': 'application/json',
-	'federation/update-remote-user': 'application/json',
-	'federation/users': 'application/json',
-	'federation/stats': 'application/json',
-	'following/create': 'application/json',
-	'following/delete': 'application/json',
-	'following/update': 'application/json',
-	'following/update-all': 'application/json',
-	'following/invalidate': 'application/json',
-	'following/requests/accept': 'application/json',
-	'following/requests/cancel': 'application/json',
-	'following/requests/list': 'application/json',
-	'following/requests/reject': 'application/json',
-	'gallery/featured': 'application/json',
-	'gallery/popular': 'application/json',
-	'gallery/posts': 'application/json',
-	'gallery/posts/create': 'application/json',
-	'gallery/posts/delete': 'application/json',
-	'gallery/posts/like': 'application/json',
-	'gallery/posts/show': 'application/json',
-	'gallery/posts/unlike': 'application/json',
-	'gallery/posts/update': 'application/json',
-	'get-online-users-count': 'application/json',
-	'get-avatar-decorations': 'application/json',
-	'hashtags/list': 'application/json',
-	'hashtags/search': 'application/json',
-	'hashtags/show': 'application/json',
-	'hashtags/trend': 'application/json',
-	'hashtags/users': 'application/json',
-	'i': 'application/json',
-	'i/2fa/done': 'application/json',
-	'i/2fa/key-done': 'application/json',
-	'i/2fa/password-less': 'application/json',
-	'i/2fa/register-key': 'application/json',
-	'i/2fa/register': 'application/json',
-	'i/2fa/update-key': 'application/json',
-	'i/2fa/remove-key': 'application/json',
-	'i/2fa/unregister': 'application/json',
-	'i/apps': 'application/json',
-	'i/authorized-apps': 'application/json',
-	'i/claim-achievement': 'application/json',
-	'i/change-password': 'application/json',
-	'i/delete-account': 'application/json',
-	'i/export-data': 'application/json',
-	'i/export-blocking': 'application/json',
-	'i/export-following': 'application/json',
-	'i/export-mute': 'application/json',
-	'i/export-notes': 'application/json',
-	'i/export-clips': 'application/json',
-	'i/export-favorites': 'application/json',
-	'i/export-user-lists': 'application/json',
-	'i/export-antennas': 'application/json',
-	'i/favorites': 'application/json',
-	'i/gallery/likes': 'application/json',
-	'i/gallery/posts': 'application/json',
-	'i/import-blocking': 'application/json',
-	'i/import-following': 'application/json',
-	'i/import-notes': 'application/json',
-	'i/import-muting': 'application/json',
-	'i/import-user-lists': 'application/json',
-	'i/import-antennas': 'application/json',
-	'i/notifications': 'application/json',
-	'i/notifications-grouped': 'application/json',
-	'i/page-likes': 'application/json',
-	'i/pages': 'application/json',
-	'i/pin': 'application/json',
-	'i/read-all-unread-notes': 'application/json',
-	'i/read-announcement': 'application/json',
-	'i/regenerate-token': 'application/json',
-	'i/registry/get-all': 'application/json',
-	'i/registry/get-unsecure': 'application/json',
-	'i/registry/get-detail': 'application/json',
-	'i/registry/get': 'application/json',
-	'i/registry/keys-with-type': 'application/json',
-	'i/registry/keys': 'application/json',
-	'i/registry/remove': 'application/json',
-	'i/registry/scopes-with-domain': 'application/json',
-	'i/registry/set': 'application/json',
-	'i/revoke-token': 'application/json',
-	'i/signin-history': 'application/json',
-	'i/unpin': 'application/json',
-	'i/update-email': 'application/json',
-	'i/update': 'application/json',
-	'i/move': 'application/json',
-	'i/webhooks/create': 'application/json',
-	'i/webhooks/list': 'application/json',
-	'i/webhooks/show': 'application/json',
-	'i/webhooks/update': 'application/json',
-	'i/webhooks/delete': 'application/json',
-	'invite/create': 'application/json',
-	'invite/delete': 'application/json',
-	'invite/list': 'application/json',
-	'invite/limit': 'application/json',
-	'meta': 'application/json',
-	'emojis': 'application/json',
-	'emoji': 'application/json',
-	'miauth/gen-token': 'application/json',
-	'mute/create': 'application/json',
-	'mute/delete': 'application/json',
-	'mute/list': 'application/json',
-	'renote-mute/create': 'application/json',
-	'renote-mute/delete': 'application/json',
-	'renote-mute/list': 'application/json',
-	'my/apps': 'application/json',
-	'notes': 'application/json',
-	'notes/children': 'application/json',
-	'notes/clips': 'application/json',
-	'notes/conversation': 'application/json',
-	'notes/create': 'application/json',
-	'notes/delete': 'application/json',
-	'notes/favorites/create': 'application/json',
-	'notes/favorites/delete': 'application/json',
-	'notes/featured': 'application/json',
-	'notes/global-timeline': 'application/json',
-	'notes/bubble-timeline': 'application/json',
-	'notes/hybrid-timeline': 'application/json',
-	'notes/local-timeline': 'application/json',
-	'notes/mentions': 'application/json',
-	'notes/polls/recommendation': 'application/json',
-	'notes/polls/vote': 'application/json',
-	'notes/reactions': 'application/json',
-	'notes/reactions/create': 'application/json',
-	'notes/reactions/delete': 'application/json',
-	'notes/like': 'application/json',
-	'notes/renotes': 'application/json',
-	'notes/replies': 'application/json',
-	'notes/search-by-tag': 'application/json',
-	'notes/search': 'application/json',
-	'notes/show': 'application/json',
-	'notes/state': 'application/json',
-	'notes/thread-muting/create': 'application/json',
-	'notes/thread-muting/delete': 'application/json',
-	'notes/timeline': 'application/json',
-	'notes/translate': 'application/json',
-	'notes/unrenote': 'application/json',
-	'notes/user-list-timeline': 'application/json',
-	'notes/edit': 'application/json',
-	'notes/versions': 'application/json',
-	'notifications/create': 'application/json',
-	'notifications/flush': 'application/json',
-	'notifications/mark-all-as-read': 'application/json',
-	'notifications/test-notification': 'application/json',
-	'page-push': 'application/json',
-	'pages/create': 'application/json',
-	'pages/delete': 'application/json',
-	'pages/featured': 'application/json',
-	'pages/like': 'application/json',
-	'pages/show': 'application/json',
-	'pages/unlike': 'application/json',
-	'pages/update': 'application/json',
-	'flash/create': 'application/json',
-	'flash/delete': 'application/json',
-	'flash/featured': 'application/json',
-	'flash/like': 'application/json',
-	'flash/show': 'application/json',
-	'flash/unlike': 'application/json',
-	'flash/update': 'application/json',
-	'flash/my': 'application/json',
-	'flash/my-likes': 'application/json',
-	'ping': 'application/json',
-	'pinned-users': 'application/json',
-	'promo/read': 'application/json',
-	'roles/list': 'application/json',
-	'roles/show': 'application/json',
-	'roles/users': 'application/json',
-	'roles/notes': 'application/json',
-	'request-reset-password': 'application/json',
-	'reset-db': 'application/json',
-	'reset-password': 'application/json',
-	'server-info': 'application/json',
-	'stats': 'application/json',
-	'sw/show-registration': 'application/json',
-	'sw/update-registration': 'application/json',
-	'sw/register': 'application/json',
-	'sw/unregister': 'application/json',
-	'test': 'application/json',
-	'username/available': 'application/json',
-	'users': 'application/json',
-	'users/clips': 'application/json',
-	'users/followers': 'application/json',
-	'users/following': 'application/json',
-	'users/gallery/posts': 'application/json',
-	'users/get-frequently-replied-users': 'application/json',
-	'users/featured-notes': 'application/json',
-	'users/lists/create': 'application/json',
-	'users/lists/delete': 'application/json',
-	'users/lists/list': 'application/json',
-	'users/lists/pull': 'application/json',
-	'users/lists/push': 'application/json',
-	'users/lists/show': 'application/json',
-	'users/lists/favorite': 'application/json',
-	'users/lists/unfavorite': 'application/json',
-	'users/lists/update': 'application/json',
-	'users/lists/create-from-public': 'application/json',
-	'users/lists/update-membership': 'application/json',
-	'users/lists/get-memberships': 'application/json',
-	'users/notes': 'application/json',
-	'users/pages': 'application/json',
-	'users/flashs': 'application/json',
-	'users/reactions': 'application/json',
-	'users/recommendation': 'application/json',
-	'users/relation': 'application/json',
-	'users/report-abuse': 'application/json',
-	'users/search-by-username-and-host': 'application/json',
-	'users/search': 'application/json',
-	'users/show': 'application/json',
-	'users/achievements': 'application/json',
-	'users/update-memo': 'application/json',
-	'fetch-rss': 'application/json',
-	'fetch-external-resources': 'application/json',
-	'retention': 'application/json',
-	'sponsors': 'application/json',
-	'bubble-game/register': 'application/json',
-	'bubble-game/ranking': 'application/json',
-	'reversi/cancel-match': 'application/json',
-	'reversi/games': 'application/json',
-	'reversi/match': 'application/json',
-	'reversi/invitations': 'application/json',
-	'reversi/show-game': 'application/json',
-	'reversi/surrender': 'application/json',
-	'reversi/verify': 'application/json',
-};
+} as const satisfies { [K in keyof Endpoints]?: 'multipart/form-data'; };
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 0228ad47e60e6a3e1c10b1f707f3fbed95c162c3..999dd4dd545254b50f4618919ba836ee4c56bb8d 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -100,6 +100,7 @@ export type AdminSilenceUserRequest = operations['admin___silence-user']['reques
 export type AdminUnsilenceUserRequest = operations['admin___unsilence-user']['requestBody']['content']['application/json'];
 export type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json'];
 export type AdminApproveUserRequest = operations['admin___approve-user']['requestBody']['content']['application/json'];
+export type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
 export type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json'];
 export type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json'];
 export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
@@ -125,6 +126,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'];
@@ -283,6 +285,8 @@ export type FollowingRequestsCancelRequest = operations['following___requests___
 export type FollowingRequestsCancelResponse = operations['following___requests___cancel']['responses']['200']['content']['application/json'];
 export type FollowingRequestsListRequest = operations['following___requests___list']['requestBody']['content']['application/json'];
 export type FollowingRequestsListResponse = operations['following___requests___list']['responses']['200']['content']['application/json'];
+export type FollowingRequestsSentRequest = operations['following___requests___sent']['requestBody']['content']['application/json'];
+export type FollowingRequestsSentResponse = operations['following___requests___sent']['responses']['200']['content']['application/json'];
 export type FollowingRequestsRejectRequest = operations['following___requests___reject']['requestBody']['content']['application/json'];
 export type GalleryFeaturedRequest = operations['gallery___featured']['requestBody']['content']['application/json'];
 export type GalleryFeaturedResponse = operations['gallery___featured']['responses']['200']['content']['application/json'];
@@ -386,6 +390,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'];
@@ -423,6 +428,8 @@ export type NotesFavoritesCreateRequest = operations['notes___favorites___create
 export type NotesFavoritesDeleteRequest = operations['notes___favorites___delete']['requestBody']['content']['application/json'];
 export type NotesFeaturedRequest = operations['notes___featured']['requestBody']['content']['application/json'];
 export type NotesFeaturedResponse = operations['notes___featured']['responses']['200']['content']['application/json'];
+export type NotesFollowingRequest = operations['notes___following']['requestBody']['content']['application/json'];
+export type NotesFollowingResponse = operations['notes___following']['responses']['200']['content']['application/json'];
 export type NotesGlobalTimelineRequest = operations['notes___global-timeline']['requestBody']['content']['application/json'];
 export type NotesGlobalTimelineResponse = operations['notes___global-timeline']['responses']['200']['content']['application/json'];
 export type NotesBubbleTimelineRequest = operations['notes___bubble-timeline']['requestBody']['content']['application/json'];
@@ -436,6 +443,7 @@ export type NotesMentionsResponse = operations['notes___mentions']['responses'][
 export type NotesPollsRecommendationRequest = operations['notes___polls___recommendation']['requestBody']['content']['application/json'];
 export type NotesPollsRecommendationResponse = operations['notes___polls___recommendation']['responses']['200']['content']['application/json'];
 export type NotesPollsVoteRequest = operations['notes___polls___vote']['requestBody']['content']['application/json'];
+export type NotesPollsRefreshRequest = operations['notes___polls___refresh']['requestBody']['content']['application/json'];
 export type NotesReactionsRequest = operations['notes___reactions']['requestBody']['content']['application/json'];
 export type NotesReactionsResponse = operations['notes___reactions']['responses']['200']['content']['application/json'];
 export type NotesReactionsCreateRequest = operations['notes___reactions___create']['requestBody']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 562c4981909a544728764f74eaf622d129add204..e275e4b475c88935f5884d1942b3f60f6a4fd5b1 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -675,6 +675,15 @@ export type paths = {
      */
     post: operations['admin___approve-user'];
   };
+  '/admin/decline-user': {
+    /**
+     * admin/decline-user
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user*
+     */
+    post: operations['admin___decline-user'];
+  };
   '/admin/unsuspend-user': {
     /**
      * admin/unsuspend-user
@@ -842,6 +851,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
@@ -1626,7 +1645,7 @@ export type paths = {
      * federation/followers
      * @description No description provided.
      *
-     * **Credential required**: *No*
+     * **Credential required**: *Yes* / **Permission**: *read:account*
      */
     post: operations['federation___followers'];
   };
@@ -1635,7 +1654,7 @@ export type paths = {
      * federation/following
      * @description No description provided.
      *
-     * **Credential required**: *No*
+     * **Credential required**: *Yes* / **Permission**: *read:account*
      */
     post: operations['federation___following'];
   };
@@ -1770,6 +1789,15 @@ export type paths = {
      */
     post: operations['following___requests___list'];
   };
+  '/following/requests/sent': {
+    /**
+     * following/requests/sent
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:following*
+     */
+    post: operations['following___requests___sent'];
+  };
   '/following/requests/reject': {
     /**
      * following/requests/reject
@@ -2510,6 +2538,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
@@ -2748,6 +2786,22 @@ export type paths = {
      */
     post: operations['notes___featured'];
   };
+  '/notes/following': {
+    /**
+     * notes/following
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    get: operations['notes___following'];
+    /**
+     * notes/following
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    post: operations['notes___following'];
+  };
   '/notes/global-timeline': {
     /**
      * notes/global-timeline
@@ -2811,6 +2865,15 @@ export type paths = {
      */
     post: operations['notes___polls___vote'];
   };
+  '/notes/polls/refresh': {
+    /**
+     * notes/polls/refresh
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:federation*
+     */
+    post: operations['notes___polls___refresh'];
+  };
   '/notes/reactions': {
     /**
      * notes/reactions
@@ -3673,7 +3736,7 @@ export type paths = {
   '/sponsors': {
     /**
      * sponsors
-     * @description Get Sharkey GH Sponsors
+     * @description Get Sharkey Sponsors or Instance Sponsors
      *
      * **Credential required**: *No*
      */
@@ -3814,16 +3877,19 @@ export type components = {
           url: string;
           offsetX?: number;
           offsetY?: number;
+          showBelow?: boolean;
         }[];
       /** @default false */
       isAdmin?: boolean;
       /** @default false */
       isModerator?: boolean;
-      isSilenced: boolean;
+      /** @default false */
+      isSystem?: boolean;
       noindex: boolean;
       isBot?: boolean;
       isCat?: boolean;
       speakAsCat?: boolean;
+      isSilenced: boolean;
       instance?: {
         name: string | null;
         softwareName: string | null;
@@ -3864,6 +3930,7 @@ export type components = {
       backgroundUrl: string | null;
       backgroundBlurhash: string | null;
       isLocked: boolean;
+      isSilenced: boolean;
       /** @example false */
       isSuspended: boolean;
       /** @example Hi masters, I am Ai! */
@@ -3872,7 +3939,7 @@ export type components = {
       /** @example 2018-03-12 */
       birthday: string | null;
       /** @example Steve */
-      ListenBrainz: string | null;
+      listenbrainz: string | null;
       /** @example ja-JP */
       lang: string | null;
       fields: {
@@ -3899,6 +3966,7 @@ export type components = {
       /** @default false */
       securityKeys: boolean;
       roles: components['schemas']['RoleLite'][];
+      followedMessage?: string | null;
       memo: string | null;
       moderationNote?: string;
       isFollowing?: boolean;
@@ -3920,11 +3988,13 @@ export type components = {
       bannerId: string | null;
       /** Format: id */
       backgroundId: string | null;
+      followedMessage: string | null;
       isModerator: boolean | null;
       isAdmin: boolean | null;
       injectFeaturedNote: boolean;
       receiveAnnouncementEmail: boolean;
       alwaysMarkNsfw: boolean;
+      defaultSensitive: boolean;
       autoSensitive: boolean;
       carefulBot: boolean;
       autoAcceptFollowed: boolean;
@@ -3943,6 +4013,7 @@ export type components = {
       hasUnreadChannel: boolean;
       hasUnreadNotification: boolean;
       hasPendingReceivedFollowRequest: boolean;
+      hasPendingSentFollowRequest: boolean;
       unreadNotificationsCount: number;
       mutedWords: string[][];
       hardMutedWords: string[][];
@@ -4359,7 +4430,7 @@ export type components = {
       user: components['schemas']['UserLite'];
       /** Format: id */
       userId: string;
-    } | {
+    } | ({
       /** Format: id */
       id: string;
       /** Format: date-time */
@@ -4369,7 +4440,8 @@ export type components = {
       user: components['schemas']['UserLite'];
       /** Format: id */
       userId: string;
-    } | {
+      message: string | null;
+    }) | {
       /** Format: id */
       id: string;
       /** Format: date-time */
@@ -4377,15 +4449,27 @@ export type components = {
       /** @enum {string} */
       type: 'roleAssigned';
       role: components['schemas']['Role'];
-    } | {
+    } | ({
       /** Format: id */
       id: string;
       /** Format: date-time */
       createdAt: string;
       /** @enum {string} */
       type: 'achievementEarned';
-      achievement: string;
-    } | {
+      /** @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 */
@@ -4393,9 +4477,9 @@ export type components = {
       /** @enum {string} */
       type: 'app';
       body: string;
-      header: string;
-      icon: string;
-    } | {
+      header: string | null;
+      icon: string | null;
+    }) | {
       /** Format: id */
       id: string;
       /** Format: date-time */
@@ -4753,6 +4837,7 @@ export type components = {
       /** Format: date-time */
       latestRequestReceivedAt: string | null;
       isNSFW: boolean;
+      rejectReports: boolean;
       moderationNote?: string | null;
     };
     GalleryPost: {
@@ -4946,6 +5031,11 @@ export type components = {
       userEachUserListsLimit: number;
       rateLimitFactor: number;
       avatarDecorationLimit: number;
+      canImportAntennas: boolean;
+      canImportBlocking: boolean;
+      canImportFollowing: boolean;
+      canImportMuting: boolean;
+      canImportUserLists: boolean;
     };
     ReversiGameLite: {
       /** Format: id */
@@ -5052,6 +5142,8 @@ export type components = {
       recaptchaSiteKey: string | null;
       enableTurnstile: boolean;
       turnstileSiteKey: string | null;
+      enableFC: boolean;
+      fcSiteKey: string | null;
       enableAchievements: boolean | null;
       swPublickey: string | null;
       /** @default /assets/ai.png */
@@ -5061,7 +5153,13 @@ export type components = {
       infoImageUrl: string | null;
       notFoundImageUrl: string | null;
       iconUrl: string | null;
+      sidebarLogoUrl: string | null;
       maxNoteTextLength: number;
+      maxRemoteNoteTextLength: number;
+      maxCwLength: number;
+      maxRemoteCwLength: number;
+      maxAltTextLength: number;
+      maxRemoteAltTextLength: number;
       ads: {
           /**
            * Format: id
@@ -5096,6 +5194,8 @@ export type components = {
        * @enum {string}
        */
       noteSearchableScope: 'local' | 'global';
+      trustedLinkUrlPatterns: string[];
+      maxFileSize: number;
     };
     MetaDetailedOnly: {
       features?: {
@@ -5183,6 +5283,8 @@ export type operations = {
             recaptchaSiteKey: string | null;
             enableTurnstile: boolean;
             turnstileSiteKey: string | null;
+            enableFC: boolean;
+            fcSiteKey: string | null;
             swPublickey: string | null;
             /** @default /assets/ai.png */
             mascotImageUrl: string | null;
@@ -5193,10 +5295,11 @@ export type operations = {
             iconUrl: string | null;
             app192IconUrl: string | null;
             app512IconUrl: string | null;
+            sidebarLogoUrl: string | null;
             enableEmail: boolean;
             enableServiceWorker: boolean;
             translatorAvailable: boolean;
-            silencedHosts?: string[];
+            silencedHosts: string[];
             mediaSilencedHosts: string[];
             pinnedUsers: string[];
             hiddenTags: string[];
@@ -5210,6 +5313,7 @@ export type operations = {
             mcaptchaSecretKey: string | null;
             recaptchaSecretKey: string | null;
             turnstileSecretKey: string | null;
+            fcSecretKey: string | null;
             sensitiveMediaDetection: string;
             sensitiveMediaDetectionSensitivity: string;
             setSensitiveFlagAutomatically: boolean;
@@ -5256,6 +5360,7 @@ export type operations = {
             perRemoteUserUserTimelineCacheMax: number;
             perUserHomeTimelineCacheMax: number;
             perUserListTimelineCacheMax: number;
+            enableReactionsBuffering: boolean;
             notesPerOneAd: number;
             backgroundImageUrl: string | null;
             deeplAuthKey: string | null;
@@ -5291,6 +5396,9 @@ export type operations = {
             urlPreviewRequireContentLength: boolean;
             urlPreviewUserAgent: string | null;
             urlPreviewSummaryProxyUrl: string | null;
+            trustedLinkUrlPatterns: string[];
+            federation: string;
+            federationHosts: string[];
           };
         };
       };
@@ -7950,6 +8058,7 @@ export type operations = {
           host: string;
           isSuspended?: boolean;
           isNSFW?: boolean;
+          rejectReports?: boolean;
           moderationNote?: string;
         };
       };
@@ -8375,7 +8484,7 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': ((string | number)[])[];
+          'application/json': [string, number][];
         };
       };
       /** @description Client error */
@@ -8421,7 +8530,7 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': ((string | number)[])[];
+          'application/json': [string, number][];
         };
       };
       /** @description Client error */
@@ -9055,6 +9164,8 @@ export type operations = {
           'application/json': {
             email: string | null;
             emailVerified: boolean;
+            approved: boolean;
+            followedMessage: string | null;
             autoAcceptFollowed: boolean;
             noCrawle: boolean;
             preventAiLearning: boolean;
@@ -9194,6 +9305,7 @@ export type operations = {
               }]>;
             };
             isModerator: boolean;
+            isSystem: boolean;
             isSilenced: boolean;
             isSuspended: boolean;
             isHibernated: boolean;
@@ -9629,6 +9741,58 @@ export type operations = {
       };
     };
   };
+  /**
+   * admin/decline-user
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:decline-user*
+   */
+  'admin___decline-user': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId: 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/unsuspend-user
    * @description No description provided.
@@ -9706,6 +9870,7 @@ export type operations = {
           iconUrl?: string | null;
           app192IconUrl?: string | null;
           app512IconUrl?: string | null;
+          sidebarLogoUrl?: string | null;
           backgroundImageUrl?: string | null;
           logoImageUrl?: string | null;
           name?: string | null;
@@ -9731,6 +9896,9 @@ export type operations = {
           enableTurnstile?: boolean;
           turnstileSiteKey?: string | null;
           turnstileSecretKey?: string | null;
+          enableFC?: boolean;
+          fcSiteKey?: string | null;
+          fcSecretKey?: string | null;
           /** @enum {string} */
           sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote';
           /** @enum {string} */
@@ -9800,6 +9968,7 @@ export type operations = {
           perRemoteUserUserTimelineCacheMax?: number;
           perUserHomeTimelineCacheMax?: number;
           perUserListTimelineCacheMax?: number;
+          enableReactionsBuffering?: boolean;
           notesPerOneAd?: number;
           silencedHosts?: string[] | null;
           mediaSilencedHosts?: string[] | null;
@@ -9811,6 +9980,10 @@ export type operations = {
           urlPreviewRequireContentLength?: boolean;
           urlPreviewUserAgent?: string | null;
           urlPreviewSummaryProxyUrl?: string | null;
+          trustedLinkUrlPatterns?: string[] | null;
+          /** @enum {string} */
+          federation?: 'all' | 'none' | 'specified';
+          federationHosts?: string[];
         };
       };
     };
@@ -10752,6 +10925,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';
+          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.
@@ -15325,7 +15563,7 @@ export type operations = {
    * federation/followers
    * @description No description provided.
    *
-   * **Credential required**: *No*
+   * **Credential required**: *Yes* / **Permission**: *read:account*
    */
   federation___followers: {
     requestBody: {
@@ -15338,6 +15576,10 @@ export type operations = {
           untilId?: string;
           /** @default 10 */
           limit?: number;
+          /** @default false */
+          includeFollower?: boolean;
+          /** @default true */
+          includeFollowee?: boolean;
         };
       };
     };
@@ -15384,7 +15626,7 @@ export type operations = {
    * federation/following
    * @description No description provided.
    *
-   * **Credential required**: *No*
+   * **Credential required**: *Yes* / **Permission**: *read:account*
    */
   federation___following: {
     requestBody: {
@@ -15397,6 +15639,10 @@ export type operations = {
           untilId?: string;
           /** @default 10 */
           limit?: number;
+          /** @default false */
+          includeFollower?: boolean;
+          /** @default true */
+          includeFollowee?: boolean;
         };
       };
     };
@@ -16207,6 +16453,69 @@ export type operations = {
       };
     };
   };
+  /**
+   * following/requests/sent
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:following*
+   */
+  following___requests___sent: {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          sinceId?: string;
+          /** Format: misskey:id */
+          untilId?: string;
+          /** @default 10 */
+          limit?: number;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': {
+              /** Format: id */
+              id: string;
+              follower: components['schemas']['UserLite'];
+              followee: components['schemas']['UserLite'];
+            }[];
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
   /**
    * following/requests/reject
    * @description No description provided.
@@ -19029,8 +19338,8 @@ export type operations = {
           untilId?: string;
           /** @default true */
           markAsRead?: boolean;
-          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
-          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
         };
       };
     };
@@ -19097,8 +19406,8 @@ export type operations = {
           untilId?: string;
           /** @default true */
           markAsRead?: boolean;
-          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
-          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
         };
       };
     };
@@ -20210,6 +20519,7 @@ export type operations = {
         'application/json': {
           name?: string | null;
           description?: string | null;
+          followedMessage?: string | null;
           location?: string | null;
           birthday?: string | null;
           listenbrainz?: string | null;
@@ -20224,6 +20534,7 @@ export type operations = {
               flipH?: boolean | null;
               offsetX?: number | null;
               offsetY?: number | null;
+              showBelow?: boolean | null;
             })[];
           /** Format: misskey:id */
           bannerId?: string | null;
@@ -20248,6 +20559,7 @@ export type operations = {
           injectFeaturedNote?: boolean;
           receiveAnnouncementEmail?: boolean;
           alwaysMarkNsfw?: boolean;
+          defaultSensitive?: boolean;
           autoSensitive?: boolean;
           /** @enum {string} */
           followingVisibility?: 'public' | 'followers' | 'private';
@@ -20801,6 +21113,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' | 'edited';
+          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.
@@ -22173,6 +22550,81 @@ export type operations = {
       };
     };
   };
+  /**
+   * notes/following
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:account*
+   */
+  notes___following: {
+    requestBody: {
+      content: {
+        'application/json': {
+          /**
+           * @default following
+           * @enum {string}
+           */
+          list?: 'following' | 'followers' | 'mutuals';
+          /** @default false */
+          filesOnly?: boolean;
+          /** @default false */
+          includeNonPublic?: boolean;
+          /** @default false */
+          includeReplies?: boolean;
+          /** @default false */
+          includeQuotes?: boolean;
+          /** @default true */
+          includeBots?: boolean;
+          /** @default 10 */
+          limit?: number;
+          /** Format: misskey:id */
+          sinceId?: string;
+          /** Format: misskey:id */
+          untilId?: string;
+          sinceDate?: number;
+          untilDate?: number;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['Note'][];
+        };
+      };
+      /** @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'];
+        };
+      };
+    };
+  };
   /**
    * notes/global-timeline
    * @description No description provided.
@@ -22623,6 +23075,58 @@ export type operations = {
       };
     };
   };
+  /**
+   * notes/polls/refresh
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:federation*
+   */
+  notes___polls___refresh: {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          noteId: 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'];
+        };
+      };
+    };
+  };
   /**
    * notes/reactions
    * @description No description provided.
@@ -27055,7 +27559,15 @@ export type operations = {
           /** @default false */
           withReplies?: boolean;
           /** @default true */
+          withRepliesToSelf?: boolean;
+          /** @default true */
+          withQuotes?: boolean;
+          /** @default true */
           withRenotes?: boolean;
+          /** @default true */
+          withBots?: boolean;
+          /** @default true */
+          withNonPublic?: boolean;
           /** @default false */
           withChannelNotes?: boolean;
           /** @default 10 */
@@ -27988,7 +28500,7 @@ export type operations = {
   };
   /**
    * sponsors
-   * @description Get Sharkey GH Sponsors
+   * @description Get Sharkey Sponsors or Instance Sponsors
    *
    * **Credential required**: *No*
    */
@@ -27998,6 +28510,8 @@ export type operations = {
         'application/json': {
           /** @default false */
           forceUpdate?: boolean;
+          /** @default false */
+          instance?: boolean;
         };
       };
     };
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 890b43639d150a75a44d85bafa874ef5f062a27f..c99b8f55707c9d8cc6845a5237afffc68c770add 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -78,6 +78,7 @@ export const permissions = [
 	'read:admin:show-user',
 	'write:admin:suspend-user',
 	'write:admin:approve-user',
+	'write:admin:decline-user',
 	'write:admin:nsfw-user',
 	'write:admin:unnsfw-user',
 	'write:admin:silence-user',
@@ -204,6 +205,11 @@ export type ModerationLogPayloads = {
 		userUsername: string;
 		userHost: string | null;
 	};
+	decline: {
+		userId: string;
+		userUsername: string;
+		userHost: string | null;
+	};
 	unsuspend: {
 		userId: string;
 		userUsername: string;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 6a405f81a5514f8092cb61448c161078001b5255..f85574d9d5036b2c04ad7fce68b5c712af135c4b 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -50,6 +50,9 @@ export type ModerationLog = {
 } | {
 	type: 'approve';
 	info: ModerationLogPayloads['approve'];
+} | {
+	type: 'decline';
+	info: ModerationLogPayloads['decline'];
 } | {
 	type: 'suspend';
 	info: ModerationLogPayloads['suspend'];
@@ -274,6 +277,17 @@ export type SigninRequest = {
 	token?: string;
 };
 
+export type SigninWithPasskeyRequest = {
+	credential?: object;
+	context?: string;
+};
+
+export type SigninWithPasskeyResponse = {
+	option?: object;
+	context?: string;
+	signinResponse?: SigninResponse;
+};
+
 export type SigninResponse = {
 	id: User['id'],
 	i: string,
diff --git a/packages/misskey-js/src/index.ts b/packages/misskey-js/src/index.ts
index ace9738e6a19ffd6d2e6a253dbc54455ee09df54..e4c9364aa1c2a0a184e45061020d75ce14fb6692 100644
--- a/packages/misskey-js/src/index.ts
+++ b/packages/misskey-js/src/index.ts
@@ -1,15 +1,6 @@
-import { type Endpoints } from './api.types.js';
 import Stream, { Connection } from './streaming.js';
-import { type Channels } from './streaming.types.js';
-import { type Acct } from './acct.js';
 import * as consts from './consts.js';
 
-export type {
-	Endpoints,
-	Channels,
-	Acct,
-};
-
 export {
 	Stream,
 	Connection as ChannelConnection,
@@ -31,4 +22,21 @@ import * as api from './api.js';
 import * as entities from './entities.js';
 import * as acct from './acct.js';
 import * as note from './note.js';
-export { api, entities, acct, note };
+import { nyaize } from './nyaize.js';
+export { api, entities, acct, note, nyaize };
+
+//#region standalone types
+import type { Endpoints } from './api.types.js';
+import type { StreamEvents, IStream, IChannelConnection } from './streaming.js';
+import type { Channels } from './streaming.types.js';
+import type { Acct } from './acct.js';
+
+export type {
+	Endpoints,
+	Channels,
+	Acct,
+	StreamEvents,
+	IStream,
+	IChannelConnection,
+};
+//#endregion
diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/misskey-js/src/nyaize.ts
similarity index 81%
rename from packages/frontend/src/scripts/nyaize.ts
rename to packages/misskey-js/src/nyaize.ts
index 5e6fa298d14c80be20568e3b31df07bcc11fbb9d..d5ee54776f4e9f85cb560f745cc7b291fa6cf1a6 100644
--- a/packages/frontend/src/scripts/nyaize.ts
+++ b/packages/misskey-js/src/nyaize.ts
@@ -7,10 +7,10 @@ const koRegex1 = /[나-낳]/g;
 const koRegex2 = /(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm;
 const koRegex3 = /(야(?=\?))|(야$)|(야(?= ))/gm;
 
-function ifAfter(prefix, fn) {
+function ifAfter(prefix: string, fn: (x: string) => string) {
 	const preLen = prefix.length;
 	const regex = new RegExp(prefix, 'i');
-	return (x, pos, string) => {
+	return (x: string, pos: number, string: string) => {
 		return pos > 0 && string.substring(pos - preLen, pos).match(regex) ? fn(x) : x;
 	};
 }
@@ -24,9 +24,9 @@ export function nyaize(text: string): string {
 		.replace(/ing/gi, ifAfter('morn', x => x === 'ING' ? 'YAN' : 'yan'))
 		.replace(/one/gi, ifAfter('every', x => x === 'ONE' ? 'NYAN' : 'nyan'))
 		// ko-KR
-		.replace(koRegex1, match => String.fromCharCode(
+		.replace(koRegex1, match => !isNaN(match.charCodeAt(0)) ? String.fromCharCode(
 			match.charCodeAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0),
-		))
+		) : match)
 		.replace(koRegex2, '다냥')
 		.replace(koRegex3, '냥');
 }
diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts
index d1d131cfc134d95aa34416dff09817bd989d8ed9..ffb46c77f6c179002023228d804f5a3d78247291 100644
--- a/packages/misskey-js/src/streaming.ts
+++ b/packages/misskey-js/src/streaming.ts
@@ -17,16 +17,32 @@ export function urlQuery(obj: Record<string, string | number | boolean | undefin
 
 type AnyOf<T extends Record<PropertyKey, unknown>> = T[keyof T];
 
-type StreamEvents = {
+export type StreamEvents = {
 	_connected_: void;
 	_disconnected_: void;
 } & BroadcastEvents;
 
+export interface IStream extends EventEmitter<StreamEvents> {
+	state: 'initializing' | 'reconnecting' | 'connected';
+
+	useChannel<C extends keyof Channels>(channel: C, params?: Channels[C]['params'], name?: string): IChannelConnection<Channels[C]>;
+	removeSharedConnection(connection: SharedConnection): void;
+	removeSharedConnectionPool(pool: Pool): void;
+	disconnectToChannel(connection: NonSharedConnection): void;
+	send(typeOrPayload: string): void;
+	send(typeOrPayload: string, payload: unknown): void;
+	send(typeOrPayload: Record<string, unknown> | unknown[]): void;
+	send(typeOrPayload: string | Record<string, unknown> | unknown[], payload?: unknown): void;
+	ping(): void;
+	heartbeat(): void;
+	close(): void;
+}
+
 /**
  * Misskey stream connection
  */
 // eslint-disable-next-line import/no-default-export
-export default class Stream extends EventEmitter<StreamEvents> {
+export default class Stream extends EventEmitter<StreamEvents> implements IStream {
 	private stream: _ReconnectingWebsocket.default;
 	public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
 	private sharedConnectionPools: Pool[] = [];
@@ -277,7 +293,18 @@ class Pool {
 	}
 }
 
-export abstract class Connection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> {
+export interface IChannelConnection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> {
+	id: string;
+	name?: string;
+	inCount: number;
+	outCount: number;
+	channel: string;
+
+	send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void;
+	dispose(): void;
+}
+
+export abstract class Connection<Channel extends AnyOf<Channels> = AnyOf<Channels>> extends EventEmitter<Channel['events']> implements IChannelConnection<Channel> {
 	public channel: string;
 	protected stream: Stream;
 	public abstract id: string;
diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts
index 7455bdcd7fccec9adf97e8cafbf328efa308f8a8..186db05d618da43b62042cd271b5147a76d4d5a0 100644
--- a/packages/misskey-js/src/streaming.types.ts
+++ b/packages/misskey-js/src/streaming.types.ts
@@ -140,7 +140,7 @@ export type Channels = {
 	};
 	hashtag: {
 		params: {
-			q?: string;
+			q: string[][];
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -249,7 +249,7 @@ export type Channels = {
 	}
 };
 
-export type NoteUpdatedEvent = {
+export type NoteUpdatedEvent = { id: Note['id'] } & ({
 	type: 'reacted';
 	body: {
 		reaction: string;
@@ -280,7 +280,7 @@ export type NoteUpdatedEvent = {
 		choice: number;
 		userId: User['id'];
 	};
-};
+});
 
 export type BroadcastEvents = {
 	noteUpdated: (payload: NoteUpdatedEvent) => void;
diff --git a/packages/frontend/test/nyaize.test.ts b/packages/misskey-js/test/nyaize.test.ts
similarity index 74%
rename from packages/frontend/test/nyaize.test.ts
rename to packages/misskey-js/test/nyaize.test.ts
index 28b90c315244cd75706f225532a28a5b1b6b07e9..24b19bc2b90dbe471e61c83e5cf5079b9e5f362d 100644
--- a/packages/frontend/test/nyaize.test.ts
+++ b/packages/misskey-js/test/nyaize.test.ts
@@ -3,15 +3,14 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { describe, test, assert, afterEach } from 'vitest';
-import { nyaize } from '@/scripts/nyaize.js';
+import { nyaize } from '../src/nyaize.js';
 
 function runTests(cases) {
-    for (const c of cases) {
-        const [input,expected] = c;
-        const got = nyaize(input);
-        assert.strictEqual(got, expected);
-    }
+	for (const c of cases) {
+		const [input,expected] = c;
+		const got = nyaize(input);
+		expect(got).toEqual(expected);
+	}
 }
 
 describe('nyaize', () => {
diff --git a/packages/misskey-js/tsconfig.json b/packages/misskey-js/tsconfig.json
index f7bbc4730454e937f210a032cfcd90f978b445a8..95128b8fab18b0aafe6676921bb5a35d55040e93 100644
--- a/packages/misskey-js/tsconfig.json
+++ b/packages/misskey-js/tsconfig.json
@@ -16,6 +16,7 @@
 		"noImplicitReturns": true,
 		"esModuleInterop": true,
 		"exactOptionalPropertyTypes": true,
+		"skipLibCheck": true,
 		"typeRoots": [
 			"./node_modules/@types"
 		],
diff --git a/packages/misskey-reversi/build.js b/packages/misskey-reversi/build.js
index e626c97a5967d6c20facd1cd107f46f28451a63a..a80b71646f43800c48d502dc828142c4e0bd367c 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 3f81df714508b46e3d547e601ca7ce3dcd5e007b..8453d9b8e707c0597ae922e58feaa023cf9539f0 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/package.json b/packages/misskey-reversi/package.json
index f32b31d4d9aea2aeac6e7659dc09ff9f8ebb481f..6386f4011881f8f42def158309cb2b94147047b3 100644
--- a/packages/misskey-reversi/package.json
+++ b/packages/misskey-reversi/package.json
@@ -17,7 +17,7 @@
 	"scripts": {
 		"build": "node ./build.js",
 		"watch": "nodemon -w package.json -e json --exec \"node ./build.js --watch\"",
-		"eslint": "eslint './**/*.{js,jsx,ts,tsx}' --cache",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
 		"typecheck": "tsc --noEmit",
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts
index 4afca9898cf092901017afb9f53ec279d3fe0001..35cc44feb4d94c0e4ae9492d71751c8717792b6e 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/misskey-reversi/tsconfig.json b/packages/misskey-reversi/tsconfig.json
index 6e34e332e000246724fd39812527733960a20185..f467951ef6f6168a713b0d6665bd66ba43c9111d 100644
--- a/packages/misskey-reversi/tsconfig.json
+++ b/packages/misskey-reversi/tsconfig.json
@@ -15,6 +15,7 @@
 		"experimentalDecorators": true,
 		"noImplicitReturns": true,
 		"esModuleInterop": true,
+		"skipLibCheck": true,
 		"typeRoots": [
 			"./node_modules/@types"
 		],
diff --git a/packages/sw/build.js b/packages/sw/build.js
index 9522d061e020d5414433c18aff784070f7539153..c9754086643f463c752de907142ca6e676506e73 100644
--- a/packages/sw/build.js
+++ b/packages/sw/build.js
@@ -8,10 +8,11 @@
 import { fileURLToPath } from 'node:url';
 import * as esbuild from 'esbuild';
 import locales from '../../locales/index.js';
-import meta from '../../package.json' with { type: "json" };
+import meta from '../../package.json' with { type: 'json' };
+import { localesVersion } from '../../locales/version.js';
 const watch = process.argv[2]?.includes('watch');
 
-const __dirname = fileURLToPath(new URL('.', import.meta.url))
+const __dirname = fileURLToPath(new URL('.', import.meta.url));
 
 console.log('Starting SW building...');
 
@@ -23,6 +24,7 @@ const buildOptions = {
 		_DEV_: JSON.stringify(process.env.NODE_ENV !== 'production'),
 		_ENV_: JSON.stringify(process.env.NODE_ENV ?? ''), // `NODE_ENV`が`undefined`なとき`JSON.stringify`が`undefined`を返してエラーになってしまうので`??`を使っている
 		_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
+		_LANGS_VERSION_: JSON.stringify(localesVersion),
 		_PERF_PREFIX_: JSON.stringify('Misskey:'),
 		_VERSION_: JSON.stringify(meta.version),
 	},
diff --git a/packages/sw/eslint.config.js b/packages/sw/eslint.config.js
index c62a2eadc6d5f03feef800d0bf4dcefae5441d98..7878eb14b0f83f4bafcfef06883f95e45364cad7 100644
--- a/packages/sw/eslint.config.js
+++ b/packages/sw/eslint.config.js
@@ -12,6 +12,7 @@ export default [
 				require: false,
 				_DEV_: false,
 				_LANGS_: false,
+				_LANGS_VERSION_: false,
 				_VERSION_: false,
 				_ENV_: false,
 				_PERF_PREFIX_: false,
diff --git a/packages/sw/package.json b/packages/sw/package.json
index 081f648f8adb76d1eae03860eda5e68771f69149..ed63a026624a61c83f878f730634159bddf06293 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -5,20 +5,20 @@
 		"watch": "nodemon -w ../../package.json -e json --exec \"node build.js watch\"",
 		"build": "node build.js",
 		"typecheck": "tsc --noEmit",
-		"eslint": "eslint --quiet src/**/*.ts --cache",
+		"eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache",
 		"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/@types/global.d.ts b/packages/sw/src/@types/global.d.ts
index bf63810e6d6e3847f8b7ad3e5d7402aca8c1b66f..334fa815117d84515bb318c981e3272193e6abdc 100644
--- a/packages/sw/src/@types/global.d.ts
+++ b/packages/sw/src/@types/global.d.ts
@@ -7,6 +7,7 @@
 type FIXME = any;
 
 declare const _LANGS_: string[][];
+declare const _LANGS_VERSION_: string;
 declare const _VERSION_: string;
 declare const _ENV_: string;
 declare const _DEV_: boolean;
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 32b12f4b4fa926e984615003c7303d593f6849a8..9c56e338c765927a91a6dca81d8b191d8d93286b 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -41,11 +41,10 @@ export async function createNotification<K extends keyof PushNotificationDataMap
 
 async function composeNotification(data: PushNotificationDataMap[keyof PushNotificationDataMap]): Promise<[string, NotificationOptions] | null> {
 	const i18n = await (swLang.i18n ?? swLang.fetchLocale());
-	const { t } = i18n;
 	switch (data.type) {
 		/*
 		case 'driveFileCreated': // TODO (Server Side)
-			return [t('_notification.fileUploaded'), {
+			return [i18n.ts._notification.fileUploaded, {
 				body: body.name,
 				icon: body.url,
 				data
@@ -58,52 +57,52 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 					const account = await getAccountFromId(data.userId);
 					if (!account) return null;
 					const userDetail = await cli.request('users/show', { userId: data.body.userId }, account.token);
-					return [t('_notification.youWereFollowed'), {
+					return [i18n.ts._notification.youWereFollowed, {
 						body: getUserName(data.body.user),
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('user-plus'),
 						data,
 						actions: userDetail.isFollowing ? [] : [
 							{
 								action: 'follow',
-								title: t('_notification._actions.followBack'),
+								title: i18n.ts._notification._actions.followBack,
 							},
 						],
 					}];
 				}
 
 				case 'mention':
-					return [t('_notification.youGotMention', { name: getUserName(data.body.user) }), {
+					return [i18n.tsx._notification.youGotMention({ name: getUserName(data.body.user) }), {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('at'),
 						data,
 						actions: [
 							{
 								action: 'reply',
-								title: t('_notification._actions.reply'),
+								title: i18n.ts._notification._actions.reply,
 							},
 						],
 					}];
 
 				case 'reply':
-					return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), {
+					return [i18n.tsx._notification.youGotReply({ name: getUserName(data.body.user) }), {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('arrow-back-up'),
 						data,
 						actions: [
 							{
 								action: 'reply',
-								title: t('_notification._actions.reply'),
+								title: i18n.ts._notification._actions.reply,
 							},
 						],
 					}];
 
 				case 'renote':
-					return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), {
+					return [i18n.tsx._notification.youRenoted({ name: getUserName(data.body.user) }), {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('repeat'),
 						data,
 						actions: [
@@ -115,29 +114,29 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 					}];
 
 				case 'quote':
-					return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), {
+					return [i18n.tsx._notification.youGotQuote({ name: getUserName(data.body.user) }), {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('quote'),
 						data,
 						actions: [
 							{
 								action: 'reply',
-								title: t('_notification._actions.reply'),
+								title: i18n.ts._notification._actions.reply,
 							},
 							...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [
 								{
 									action: 'renote',
-									title: t('_notification._actions.renote'),
+									title: i18n.ts._notification._actions.renote,
 								},
 							] : []),
 						],
 					}];
 
 				case 'note':
-					return [t('_notification.newNote') + ': ' + getUserName(data.body.user), {
+					return [i18n.ts._notification.newNote + ': ' + getUserName(data.body.user), {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						data,
 					}];
 
@@ -164,7 +163,7 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 					const tag = `reaction:${data.body.note.id}`;
 					return [`${reaction} ${getUserName(data.body.user)}`, {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						tag,
 						badge,
 						data,
@@ -178,41 +177,60 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 				}
 
 				case 'receiveFollowRequest':
-					return [t('_notification.youReceivedFollowRequest'), {
+					return [i18n.ts._notification.youReceivedFollowRequest, {
 						body: getUserName(data.body.user),
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('user-plus'),
 						data,
 						actions: [
 							{
 								action: 'accept',
-								title: t('accept'),
+								title: i18n.ts.accept,
 							},
 							{
 								action: 'reject',
-								title: t('reject'),
+								title: i18n.ts.reject,
 							},
 						],
 					}];
 
 				case 'followRequestAccepted':
-					return [t('_notification.yourFollowRequestAccepted'), {
+					return [i18n.ts._notification.yourFollowRequestAccepted, {
 						body: getUserName(data.body.user),
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('circle-check'),
 						data,
 					}];
 
 				case 'achievementEarned':
-					return [t('_notification.achievementEarned'), {
-						body: t(`_achievements._types._${data.body.achievement}.title`),
+					return [i18n.ts._notification.achievementEarned, {
+						body: i18n.ts._achievements._types[`_${data.body.achievement}`].title,
 						badge: iconUrl('medal'),
 						data,
 						tag: `achievement:${data.body.achievement}`,
 					}];
 
+				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 [t('_notification.pollEnded'), {
+					return [i18n.ts._notification.pollEnded, {
 						body: data.body.note.text ?? '',
 						badge: iconUrl('chart-arrows'),
 						data,
@@ -226,16 +244,16 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 					}];
 
 				case 'test':
-					return [t('_notification.testNotification'), {
-						body: t('_notification.notificationWillBeDisplayedLikeThis'),
+					return [i18n.ts._notification.testNotification, {
+						body: i18n.ts._notification.notificationWillBeDisplayedLikeThis,
 						badge: iconUrl('bell'),
 						data,
 					}];
 
 				case 'edited':
-					return [t('_notification.edited', { name: getUserName(data.body.user) }), {
+					return [i18n.ts._notification.edited, {
 						body: data.body.note.text ?? '',
-						icon: data.body.user.avatarUrl,
+						icon: data.body.user.avatarUrl ?? undefined,
 						badge: iconUrl('messages'),
 						data,
 					}];
@@ -244,9 +262,9 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 					return null;
 			}
 		case 'unreadAntennaNote':
-			return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), {
+			return [i18n.tsx._notification.unreadAntennaNote({ name: data.body.antenna.name }), {
 				body: `${getUserName(data.body.note.user)}: ${data.body.note.text ?? ''}`,
-				icon: data.body.note.user.avatarUrl,
+				icon: data.body.note.user.avatarUrl ?? undefined,
 				badge: iconUrl('antenna'),
 				tag: `antenna:${data.body.antenna.id}`,
 				data,
@@ -260,7 +278,6 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 export async function createEmptyNotification(): Promise<void> {
 	return new Promise<void>(async res => {
 		const i18n = await (swLang.i18n ?? swLang.fetchLocale());
-		const { t } = i18n;
 
 		await globalThis.registration.showNotification(
 			(new URL(origin)).host,
@@ -272,11 +289,11 @@ export async function createEmptyNotification(): Promise<void> {
 				actions: [
 					{
 						action: 'markAllAsRead',
-						title: t('markAllAsRead'),
+						title: i18n.ts.markAllAsRead,
 					},
 					{
 						action: 'settings',
-						title: t('notificationSettings'),
+						title: i18n.ts.notificationSettings,
 					},
 				],
 				data: {},
diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts
index 19bfe052eea2b0456a1e1f7d48226e2878a34e5b..157dbd005e194982bf5fa9e1175a6c18b77842ed 100644
--- a/packages/sw/src/scripts/get-account-from-id.ts
+++ b/packages/sw/src/scripts/get-account-from-id.ts
@@ -4,9 +4,10 @@
  */
 
 import { get } from 'idb-keyval';
+import * as Misskey from 'misskey-js';
 
-export async function getAccountFromId(id: string): Promise<{ token: string; id: string } | void> {
-	const accounts = await get<{ token: string; id: string }[]>('accounts');
+export async function getAccountFromId(id: string): Promise<Pick<Misskey.entities.SignupResponse, 'id' | 'token'> | undefined> {
+	const accounts = await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts');
 	if (!accounts) {
 		console.log('Accounts are not recorded');
 		return;
diff --git a/packages/sw/src/scripts/i18n.ts b/packages/sw/src/scripts/i18n.ts
deleted file mode 100644
index 77b955dbe837ef3c20ff332def463713702a115d..0000000000000000000000000000000000000000
--- a/packages/sw/src/scripts/i18n.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export type Locale = { [key: string]: string | Locale };
-
-export class I18n<T extends Locale = Locale> {
-	public ts: T;
-
-	constructor(locale: T) {
-		this.ts = locale;
-
-		//#region BIND
-		this.t = this.t.bind(this);
-		//#endregion
-	}
-
-	// string にしているのは、ドット区切りでのパス指定を許可するため
-	// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
-	public t(key: string, args?: Record<string, string>): string {
-		try {
-			let str = key.split('.').reduce<Locale | Locale[keyof Locale]>((o, i) => o[i], this.ts);
-			if (typeof str !== 'string') throw new Error();
-
-			if (args) {
-				for (const [k, v] of Object.entries(args)) {
-					str = str.replace(`{${k}}`, v);
-				}
-			}
-			return str;
-		} catch (err) {
-			console.warn(`missing localization '${key}'`);
-			return key;
-		}
-	}
-}
diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts
index 6fccedd7466ce37e1a3e4e09558c39d60c0a6a0c..41b06515f4fed48acec1cc225417d64401b8c0ac 100644
--- a/packages/sw/src/scripts/lang.ts
+++ b/packages/sw/src/scripts/lang.ts
@@ -7,10 +7,11 @@
  * Language manager for SW
  */
 import { get, set } from 'idb-keyval';
-import { I18n, type Locale } from '@/scripts/i18n.js';
+import { I18n } from '@@/js/i18n.js';
+import type { Locale } from '../../../../locales/index.js';
 
 class SwLang {
-	public cacheName = `mk-cache-${_VERSION_}`;
+	public cacheName = `mk-cache-${_LANGS_VERSION_}`;
 
 	public lang: Promise<string> = get('lang').then(async prelang => {
 		if (!prelang) return 'en-US';
@@ -23,7 +24,7 @@ class SwLang {
 		return this.fetchLocale();
 	}
 
-	public i18n: Promise<I18n> | null = null;
+	public i18n: Promise<I18n<Locale>> | null = null;
 
 	public fetchLocale(): Promise<I18n<Locale>> {
 		return (this.i18n = this._fetch());
@@ -31,7 +32,7 @@ class SwLang {
 
 	private async _fetch(): Promise<I18n<Locale>> {
 		// Service Workerは何度も起動しそのたびにlocaleを読み込むので、CacheStorageを使う
-		const localeUrl = `/assets/locales/${await this.lang}.${_VERSION_}.json`;
+		const localeUrl = `/assets/locales/${await this.lang}.${_LANGS_VERSION_}.json`;
 		let localeRes = await caches.match(localeUrl);
 
 		// _DEV_がtrueの場合は常に最新化
diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts
index 24eea0623146ea5136a554b86a7476e725198f78..8862c6faa5f2330cbb3ab2ca903c9eee438da095 100644
--- a/packages/sw/src/scripts/operations.ts
+++ b/packages/sw/src/scripts/operations.ts
@@ -14,15 +14,22 @@ import { getUrlWithLoginId } from '@/scripts/login-id.js';
 
 export const cli = new Misskey.api.APIClient({ origin, fetch: (...args): Promise<Response> => fetch(...args) });
 
-export async function api<E extends keyof Misskey.Endpoints, O extends Misskey.Endpoints[E]['req']>(endpoint: E, userId?: string, options?: O): Promise<void | ReturnType<typeof cli.request<E, O>>> {
-	let account: { token: string; id: string } | void = undefined;
+export async function api<
+	E extends keyof Misskey.Endpoints,
+	P extends Misskey.Endpoints[E]['req']
+>(endpoint: E, userId?: string, params?: P): Promise<Misskey.api.SwitchCaseResponseType<E, P> | undefined> {
+	let account: Pick<Misskey.entities.SignupResponse, 'id' | 'token'> | undefined;
 
 	if (userId) {
 		account = await getAccountFromId(userId);
 		if (!account) return;
 	}
 
-	return cli.request(endpoint, options, account?.token);
+	return (cli.request as <E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
+		endpoint: E,
+		params: P,
+		credential?: string | null,
+	) => Promise<Misskey.api.SwitchCaseResponseType<E, P>>)(endpoint, params, account?.token);
 }
 
 // mark-all-as-read送出を1秒間隔に制限する
@@ -33,7 +40,7 @@ export function sendMarkAllAsRead(userId: string): Promise<null | undefined | vo
 	return new Promise(resolve => {
 		setTimeout(() => {
 			readBlockingStatus.set(userId, false);
-			api('notifications/mark-all-as-read', userId).then(resolve, resolve);
+			(api('notifications/mark-all-as-read', userId) as Promise<void>).then(resolve, resolve);
 		}, 1000);
 	});
 }
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index 33614429de6d3eef87f5f93aab7e520a4e5dd34d..a7b9f27e2c1f8d19270c0bb551c73c90d5aed188 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -6,7 +6,8 @@
 import { get } from 'idb-keyval';
 import * as Misskey from 'misskey-js';
 import type { PushNotificationDataMap } from '@/types.js';
-import type { I18n, Locale } from '@/scripts/i18n.js';
+import type { I18n } from '@@/js/i18n.js';
+import type { Locale } from '../../../locales/index.js';
 import { createEmptyNotification, createNotification } from '@/scripts/create-notification.js';
 import { swLang } from '@/scripts/lang.js';
 import * as swos from '@/scripts/operations.js';
@@ -30,8 +31,8 @@ globalThis.addEventListener('activate', ev => {
 async function offlineContentHTML() {
 	const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
 	const messages = {
-		title: i18n.ts?._offlineScreen?.title ?? 'Offline - Could not connect to server',
-		header: i18n.ts?._offlineScreen?.header ?? 'Could not connect to server',
+		title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
+		header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
 		reload: i18n.ts?.reload ?? 'Reload',
 	};
 
@@ -162,8 +163,8 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
 					case 'markAllAsRead':
 						await globalThis.registration.getNotifications()
 							.then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close()));
-						await get('accounts').then(accounts => {
-							return Promise.all(accounts.map(async account => {
+						await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts').then(accounts => {
+							return Promise.all((accounts ?? []).map(async account => {
 								await swos.sendMarkAllAsRead(account.id);
 							}));
 						});
diff --git a/packages/sw/tsconfig.json b/packages/sw/tsconfig.json
index 50d4aae19d3edf5f97b5b4ddf093068a34b8e927..112a932e58212c2fe19d502ccf08b2441757c52e 100644
--- a/packages/sw/tsconfig.json
+++ b/packages/sw/tsconfig.json
@@ -2,6 +2,7 @@
 	"compilerOptions": {
 		"allowJs": true,
 		"noEmitOnError": false,
+		"noImplicitAny": false,
 		"noImplicitReturns": true,
 		"noUnusedParameters": false,
 		"noUnusedLocals": true,
@@ -18,9 +19,11 @@
 		"experimentalDecorators": true,
 		"resolveJsonModule": true,
 		"isolatedModules": true,
+		"skipLibCheck": true,
 		"baseUrl": ".",
 		"paths": {
-			"@/*": ["./src/*"]
+			"@/*": ["./src/*"],
+			"@@/*": ["../frontend-shared/*"]
 		},
 		"typeRoots": [
 			"./node_modules/@types",
@@ -34,5 +37,8 @@
 	"compileOnSave": false,
 	"include": [
 		"./**/*.ts"
+	],
+	"exclude": [
+		"node_modules"
 	]
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 31efc9a89afcab5fe476a8d620f64f82433c7d2a..bda23dfd328074bc7941162c89466c3a955c64a8 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,48 +34,48 @@ 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
     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.30.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:
@@ -86,41 +86,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.0
+        version: 5.0.0
       '@fastify/cookie':
-        specifier: 9.3.1
-        version: 9.3.1
+        specifier: 10.0.0
+        version: 10.0.0
       '@fastify/cors':
-        specifier: 9.0.1
-        version: 9.0.1
+        specifier: 10.0.0
+        version: 10.0.0
       '@fastify/express':
-        specifier: 3.0.0
-        version: 3.0.0
+        specifier: 4.0.0
+        version: 4.0.0
       '@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.0
+        version: 9.0.0
       '@fastify/static':
-        specifier: 7.0.4
-        version: 7.0.4
+        specifier: 8.0.0
+        version: 8.0.0
       '@fastify/view':
-        specifier: 9.1.0
-        version: 9.1.0
+        specifier: 10.0.0
+        version: 10.0.0
       '@misskey-dev/sharp-read-bmp':
         specifier: 1.2.0
         version: 1.2.0
@@ -128,17 +128,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.3
+        version: 10.4.3(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.3
+        version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(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.3
+        version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3))
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
@@ -191,11 +191,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.13.2
+        version: 5.13.2
       cacheable-lookup:
         specifier: 7.0.0
         version: 7.0.0
@@ -227,23 +227,23 @@ importers:
         specifier: 0.1.21
         version: 0.1.21
       fast-xml-parser:
-        specifier: ^4.4.0
-        version: 4.4.0
+        specifier: 4.4.1
+        version: 4.4.1
       fastify:
-        specifier: 4.28.1
-        version: 4.28.1
+        specifier: 5.0.0
+        version: 5.0.0
       fastify-multer:
         specifier: ^2.0.3
         version: 2.0.3
       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
@@ -272,14 +272,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
@@ -295,12 +295,15 @@ importers:
       jsrsasign:
         specifier: 11.1.0
         version: 11.1.0
+      juice:
+        specifier: 11.0.0
+        version: 11.0.0
       megalodon:
         specifier: workspace:*
         version: link:../megalodon
       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)
       microformats-parser:
         specifier: 2.0.2
         version: 2.0.2
@@ -326,8 +329,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
       oauth:
         specifier: 0.10.0
         version: 0.10.0
@@ -341,14 +344,14 @@ importers:
         specifier: 0.0.14
         version: 0.0.14
       otpauth:
-        specifier: 9.3.1
-        version: 9.3.1
+        specifier: 9.3.2
+        version: 9.3.2
       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
@@ -368,8 +371,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
@@ -377,8 +380,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
@@ -401,8 +404,8 @@ importers:
         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
@@ -413,8 +416,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
@@ -429,10 +432,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
@@ -538,8 +541,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.3
+        version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)
       '@simplewebauthn/types':
         specifier: 10.0.0
         version: 10.0.0
@@ -565,8 +568,8 @@ importers:
         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
@@ -574,8 +577,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
@@ -598,8 +601,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
@@ -610,8 +613,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/proxy-addr':
         specifier: ^2.0.3
         version: 2.0.3
@@ -634,8 +637,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
@@ -661,14 +664,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.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)
       aws-sdk-client-mock:
         specifier: 4.0.1
         version: 4.0.1
@@ -676,11 +679,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.8.0)(typescript@5.6.2))(eslint@9.8.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
@@ -691,8 +694,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
@@ -703,8 +706,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
@@ -719,13 +722,13 @@ importers:
         version: 2.1.1
       '@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
@@ -736,17 +739,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.10(typescript@5.6.2))
       '@vue/compiler-sfc':
-        specifier: 3.4.37
-        version: 3.4.37
+        specifier: 3.5.10
+        version: 3.5.10
       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
@@ -757,41 +760,41 @@ 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.10.4
+        version: 11.10.4
       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
       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
@@ -805,8 +808,8 @@ importers:
         specifier: 2.2.3
         version: 2.2.3
       katex:
-        specifier: 0.16.9
-        version: 0.16.9
+        specifier: 0.16.10
+        version: 0.16.10
       matter-js:
         specifier: 0.19.0
         version: 0.19.0
@@ -826,14 +829,14 @@ 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
       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
@@ -844,8 +847,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
@@ -859,90 +862,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.10(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.10
+        version: 3.5.10(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.10(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(react@18.3.1)(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(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.3
+        version: 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(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.3
+        version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(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.3
+        version: 8.3.3(storybook@8.3.3(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.10(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.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))
       '@types/estree':
-        specifier: 1.0.5
-        version: 1.0.5
+        specifier: 1.0.6
+        version: 1.0.6
+      '@types/katex':
+        specifier: ^0.16.7
+        version: 0.16.7
       '@types/matter-js':
         specifier: 0.19.7
         version: 0.19.7
@@ -956,8 +959,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
@@ -971,20 +974,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.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)
       '@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.10
+        version: 3.5.10
       acorn:
         specifier: 8.12.1
         version: 8.12.1
@@ -992,14 +995,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.30.0
+        version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.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.8.0)
       fast-glob:
         specifier: 3.3.2
         version: 3.3.2
@@ -1010,17 +1013,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
@@ -1034,32 +1037,232 @@ 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.3
+        version: 8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.3(storybook@8.3.3(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.1.6
+        version: 2.1.6
+      vue-eslint-parser:
+        specifier: 9.4.3
+        version: 9.4.3(eslint@9.8.0)
+      vue-tsc:
+        specifier: 2.1.6
+        version: 2.1.6(typescript@5.6.2)
+
+  packages/frontend-embed:
+    dependencies:
+      '@discordapp/twemoji':
+        specifier: 15.1.0
+        version: 15.1.0
+      '@phosphor-icons/web':
+        specifier: ^2.0.3
+        version: 2.1.1
+      '@rollup/plugin-json':
+        specifier: 6.1.0
+        version: 6.1.0(rollup@4.22.5)
+      '@rollup/plugin-replace':
+        specifier: 5.0.7
+        version: 5.0.7(rollup@4.22.5)
+      '@rollup/pluginutils':
+        specifier: 5.1.2
+        version: 5.1.2(rollup@4.22.5)
+      '@transfem-org/sfm-js':
+        specifier: 0.24.5
+        version: 0.24.5
+      '@twemoji/parser':
+        specifier: 15.1.1
+        version: 15.1.1
+      '@vitejs/plugin-vue':
+        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.10(typescript@5.6.2))
+      '@vue/compiler-sfc':
+        specifier: 3.5.10
+        version: 3.5.10
+      astring:
+        specifier: 1.9.0
+        version: 1.9.0
+      buraha:
+        specifier: 0.0.1
+        version: 0.0.1
+      estree-walker:
+        specifier: 3.0.3
+        version: 3.0.3
+      frontend-shared:
+        specifier: workspace:*
+        version: link:../frontend-shared
+      json5:
+        specifier: 2.2.3
+        version: 2.2.3
+      misskey-js:
+        specifier: workspace:*
+        version: link:../misskey-js
+      punycode:
+        specifier: 2.3.1
+        version: 2.3.1
+      rollup:
+        specifier: 4.22.5
+        version: 4.22.5
+      sass:
+        specifier: 1.79.3
+        version: 1.79.3
+      shiki:
+        specifier: 1.12.0
+        version: 1.12.0
+      tinycolor2:
+        specifier: 1.6.0
+        version: 1.6.0
+      tsc-alias:
+        specifier: 1.8.10
+        version: 1.8.10
+      tsconfig-paths:
+        specifier: 4.2.0
+        version: 4.2.0
+      typescript:
+        specifier: 5.6.2
+        version: 5.6.2
+      uuid:
+        specifier: 10.0.0
+        version: 10.0.0
+      vite:
+        specifier: 5.4.8
+        version: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)
+      vue:
+        specifier: 3.5.10
+        version: 3.5.10(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.5.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))
+      '@types/estree':
+        specifier: 1.0.6
+        version: 1.0.6
+      '@types/micromatch':
+        specifier: 4.0.9
+        version: 4.0.9
+      '@types/node':
+        specifier: 20.14.12
+        version: 20.14.12
+      '@types/punycode':
+        specifier: 2.1.4
+        version: 2.1.4
+      '@types/tinycolor2':
+        specifier: 1.4.6
+        version: 1.4.6
+      '@types/uuid':
+        specifier: 10.0.0
+        version: 10.0.0
+      '@types/ws':
+        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.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.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.79.3)(terser@5.33.0))
+      '@vue/runtime-core':
+        specifier: 3.5.10
+        version: 3.5.10
+      acorn:
+        specifier: 8.12.1
+        version: 8.12.1
+      cross-env:
+        specifier: 7.0.3
+        version: 7.0.3
+      eslint-plugin-import:
+        specifier: 2.30.0
+        version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)
+      eslint-plugin-vue:
+        specifier: 9.28.0
+        version: 9.28.0(eslint@9.8.0)
+      fast-glob:
+        specifier: 3.3.2
+        version: 3.3.2
+      happy-dom:
+        specifier: 10.0.3
+        version: 10.0.3
+      intersection-observer:
+        specifier: 0.12.2
+        version: 0.12.2
+      micromatch:
+        specifier: 4.0.8
+        version: 4.0.8
+      msw:
+        specifier: 2.3.4
+        version: 2.3.4(typescript@5.6.2)
+      nodemon:
+        specifier: 3.1.7
+        version: 3.1.7
+      prettier:
+        specifier: 3.3.3
+        version: 3.3.3
+      start-server-and-test:
+        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)
       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:
+      misskey-js:
+        specifier: workspace:*
+        version: link:../misskey-js
+      vue:
+        specifier: 3.4.37
+        version: 3.4.37(typescript@5.5.4)
+    devDependencies:
+      '@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)
+      '@typescript-eslint/parser':
+        specifier: 7.17.0
+        version: 7.17.0(eslint@9.8.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)
+      typescript:
+        specifier: 5.5.4
+        version: 5.5.4
+      vue-eslint-parser:
+        specifier: 9.4.3
+        version: 9.4.3(eslint@9.8.0)
 
   packages/megalodon:
     dependencies:
@@ -1088,8 +1291,8 @@ importers:
         specifier: ^8.5.10
         version: 8.5.11
       axios:
-        specifier: 1.6.0
-        version: 1.6.0
+        specifier: 1.7.4
+        version: 1.7.4
       dayjs:
         specifier: ^1.11.10
         version: 1.11.10
@@ -1118,8 +1321,8 @@ importers:
         specifier: ^9.0.1
         version: 9.0.1
       ws:
-        specifier: 8.14.2
-        version: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+        specifier: 8.17.1
+        version: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
     devDependencies:
       '@typescript-eslint/eslint-plugin':
         specifier: ^6.12.0
@@ -1147,7 +1350,7 @@ importers:
         version: 3.3.3
       ts-jest:
         specifier: ^29.1.1
-        version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.23.0)(jest@29.7.0(@types/node@20.14.12))(typescript@5.1.6)
+        version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.12))(typescript@5.1.6)
       typedoc:
         specifier: ^0.25.3
         version: 0.25.13(typescript@5.1.6)
@@ -1205,29 +1408,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.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)
       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
@@ -1247,29 +1450,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.8.0)(typescript@5.6.2))(eslint@9.8.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.8.0)(typescript@5.6.2)
       openapi-types:
         specifier: 12.1.3
         version: 12.1.3
@@ -1277,14 +1480,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:
@@ -1320,8 +1523,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
@@ -1331,24 +1534,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.8.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.8.0)(typescript@5.6.2))(eslint@9.8.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}
@@ -1359,17 +1562,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'}
@@ -1571,14 +1766,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'}
@@ -1587,51 +1774,18 @@ 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==}
+  '@babel/helper-environment-visitor@7.24.7':
+    resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==}
     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'}
-
-  '@babel/helper-environment-visitor@7.24.7':
-    resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-function-name@7.23.0':
-    resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-function-name@7.24.7':
-    resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==}
-    engines: {node: '>=6.9.0'}
-
-  '@babel/helper-hoist-variables@7.22.5':
-    resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+  '@babel/helper-function-name@7.24.7':
+    resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==}
     engines: {node: '>=6.9.0'}
 
   '@babel/helper-hoist-variables@7.24.7':
     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'}
@@ -1652,30 +1806,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'}
@@ -1684,14 +1818,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'}
-
   '@babel/helper-split-export-declaration@7.24.7':
     resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==}
     engines: {node: '>=6.9.0'}
@@ -1700,10 +1826,18 @@ packages:
     resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/helper-string-parser@7.25.7':
+    resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/helper-validator-identifier@7.24.7':
     resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==}
     engines: {node: '>=6.9.0'}
 
+  '@babel/helper-validator-identifier@7.25.7':
+    resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==}
+    engines: {node: '>=6.9.0'}
+
   '@babel/helper-validator-option@7.23.5':
     resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
     engines: {node: '>=6.9.0'}
@@ -1712,10 +1846,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'}
@@ -1724,10 +1854,6 @@ packages:
     resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==}
     engines: {node: '>=6.9.0'}
 
-  '@babel/highlight@7.23.4':
-    resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
-    engines: {node: '>=6.9.0'}
-
   '@babel/highlight@7.24.7':
     resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==}
     engines: {node: '>=6.9.0'}
@@ -1737,35 +1863,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.7':
+    resolution: {integrity: sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
 
   '@babel/plugin-syntax-async-generators@7.8.4':
     resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
@@ -1782,40 +1883,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:
@@ -1862,12 +1929,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'}
@@ -1880,487 +1941,150 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
 
-  '@babel/plugin-syntax-unicode-sets-regex@7.18.6':
-    resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
+  '@babel/runtime@7.23.4':
+    resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==}
     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/template@7.22.15':
+    resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
     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.24.0':
+    resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
     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.7':
+    resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==}
     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/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-block-scoping@7.24.7':
-    resolution: {integrity: sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==}
+  '@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-properties@7.24.7':
-    resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==}
+  '@babel/types@7.24.7':
+    resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==}
     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.25.7':
+    resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==}
     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
+  '@base2/pretty-print-object@1.0.1':
+    resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==}
 
-  '@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
+  '@bcoe/v8-coverage@0.2.3':
+    resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
 
-  '@babel/plugin-transform-destructuring@7.24.7':
-    resolution: {integrity: sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==}
-    engines: {node: '>=6.9.0'}
+  '@bull-board/api@6.0.0':
+    resolution: {integrity: sha512-O0IsIwAOU47bPTJnqRO7RtKFQToMvwRebbuPi6M+SG1gXyiqixLg9pycnfXgSeroaT9E7QQ2PsCPW1HO8VORvw==}
     peerDependencies:
-      '@babel/core': ^7.0.0-0
+      '@bull-board/ui': 6.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
+  '@bull-board/fastify@6.0.0':
+    resolution: {integrity: sha512-VrKa5BdxYmXh5fJvlSPSm71b+QA9VVXHyGk6xmI/qAefUQbwd2cWJo+ppqaWSaweXa9ymJc+V4l/un0K4oomVA==}
 
-  '@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
+  '@bull-board/ui@6.0.0':
+    resolution: {integrity: sha512-wAFTlBTJbq5DSWxCzTV+FOyZDVwrXP+G1CQ2BpLG9o9+dpwYxUESx/VxNEDHnyPcy13gm29kB4fSRY+nkelkcQ==}
 
-  '@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
+  '@bundled-es-modules/cookie@2.0.0':
+    resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==}
 
-  '@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
+  '@bundled-es-modules/statuses@1.0.1':
+    resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
 
-  '@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
+  '@bundled-es-modules/tough-cookie@0.1.6':
+    resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==}
 
-  '@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
+  '@canvas/image-data@1.0.0':
+    resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==}
 
-  '@babel/plugin-transform-for-of@7.24.7':
-    resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@colors/colors@1.5.0':
+    resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
+    engines: {node: '>=0.1.90'}
 
-  '@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
+  '@cropper/element-canvas@2.0.0-rc.2':
+    resolution: {integrity: sha512-0aqbJ3ycQM6/yn4T03vw8K/OeTB8C6+Z/jimuavy4UM2CENH9ucSLM4hAG0yYCgghIyv9Zd0unaBmtgW+I5+SQ==}
 
-  '@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
+  '@cropper/element-crosshair@2.0.0-rc.2':
+    resolution: {integrity: sha512-yopINLvaZhL3E2GNienju1zeQ1Cifkn5f/0R7ZabXcAgUI0s2sLzNqL8+2XV2J3DzEzYEIYc+49KmMle04nVWQ==}
 
-  '@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
+  '@cropper/element-grid@2.0.0-rc.2':
+    resolution: {integrity: sha512-PzAfEya6CmIc/o/lcA/NZ1rohszz42wjq2z3E2zq2jMfNDxY/EIoFnGI6+hJrxCAaoKD8UlKOEHQdRQbtnjcMg==}
 
-  '@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
+  '@cropper/element-handle@2.0.0-rc.2':
+    resolution: {integrity: sha512-wOWX4xpryxKcrhnJC2mHebqQQ622UN2oyQoDZcaMzvlwt7nnX3bInF+SFrIj9/aCxtCUYY0oD2gaJkfd6aNJ0g==}
 
-  '@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
+  '@cropper/element-image@2.0.0-rc.2':
+    resolution: {integrity: sha512-RTKnuJrqn1K8FscS11auit2W57AG04mxRNOxBldYs3lKTkwZjzJdQFkZ/Nxu+cwVXT+c6IeEiayNKvu4B7CAQg==}
 
-  '@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
+  '@cropper/element-selection@2.0.0-rc.2':
+    resolution: {integrity: sha512-UIgIHKHz4qNKlm5YRnC/Pu9+VrInm5TSOzkmU8kPt2swUk0WHNRv3ZcOjCQZ2ccTQnAH3FVM3FYDZ8HjRwLcBg==}
 
-  '@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
+  '@cropper/element-shade@2.0.0-rc.2':
+    resolution: {integrity: sha512-vHAGFxlqgflGZWkRYNWNHUY0zsV72YZGmCgtUu4sMrnWLZL/jMGhxmm8zZCe/aB94F829XcQ6uf3BoiApB+7Ng==}
 
-  '@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
+  '@cropper/element-viewer@2.0.0-rc.2':
+    resolution: {integrity: sha512-2z9mIA7ic3enNS4xvq9Gq6hnRZ1tPr0h+lCrOHP55NL4he63lE9oTVJfDx19rL95wUS4VxL2ANvr2BVLNiBM7A==}
 
-  '@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
+  '@cropper/element@2.0.0-rc.2':
+    resolution: {integrity: sha512-4G6lTJblndwzpsb43YKeHiKcocOkDIWystGzbHNbqRysE0U0lYHuRyvV7FW6a9S63wtMFSYuwFxcdUdUcmkF8w==}
 
-  '@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
+  '@cropper/elements@2.0.0-rc.2':
+    resolution: {integrity: sha512-NG5kdqpv7/tGvUfNjJiIHr2Ip431v5t/P5cIXTcYAgt8PRyFJmjx3fatC7NLnP/FUlv+bbzd8PMRI4LY4Gaw3Q==}
 
-  '@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
+  '@cropper/utils@2.0.0-rc.2':
+    resolution: {integrity: sha512-EEivNsyV6BtL496m4Q/IeAC6FGlyKjKIT1qMtwaxtkR+2ZlKnf9O7AdcGpClemIBA+TbwWAzp0UyIvYFtKUZ1Q==}
 
-  '@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
+  '@cypress/request@3.0.5':
+    resolution: {integrity: sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==}
+    engines: {node: '>= 6'}
 
-  '@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
+  '@cypress/xvfb@1.2.4':
+    resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==}
 
-  '@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
+  '@digitalbazaar/http-client@3.4.1':
+    resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==}
+    engines: {node: '>=14.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
+  '@discordapp/twemoji@15.1.0':
+    resolution: {integrity: sha512-QdpV4ifTONAXvDjRrMohausZeGrQ1ac/Ox6togUh6Xl3XKJ/KAaMMuAEi0qsb0wDwoVTSZBll5Y6+N3hB2ktBw==}
 
-  '@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
+  '@emnapi/runtime@1.3.0':
+    resolution: {integrity: sha512-XMBySMuNZs3DM96xcJmLW4EfGnf+uGmFNjzpehMjuX5PLB5j87ar2Zc4e3PVeZ3I5g3tYtAqskB28manlF69Zw==}
 
-  '@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
+  '@esbuild/aix-ppc64@0.19.11':
+    resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [aix]
 
-  '@babel/plugin-transform-parameters@7.24.7':
-    resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
+  '@esbuild/aix-ppc64@0.21.5':
+    resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
+    engines: {node: '>=12'}
+    cpu: [ppc64]
+    os: [aix]
 
-  '@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
+  '@esbuild/aix-ppc64@0.23.0':
+    resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
 
-  '@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
+  '@esbuild/aix-ppc64@0.23.1':
+    resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==}
+    engines: {node: '>=18'}
+    cpu: [ppc64]
+    os: [aix]
 
-  '@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==}
-    engines: {node: '>=6.9.0'}
-
-  '@base2/pretty-print-object@1.0.1':
-    resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==}
-
-  '@bcoe/v8-coverage@0.2.3':
-    resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
-
-  '@bull-board/api@5.21.1':
-    resolution: {integrity: sha512-anzTfhOJ93eraT/GYeyxWpxRMarHwuevn6pPBfdOj0LC2eg98OPnkfdMSjcrpL3qrqsxON0RslS7kuPfCEnX6A==}
-    peerDependencies:
-      '@bull-board/ui': 5.21.1
-
-  '@bull-board/fastify@5.21.1':
-    resolution: {integrity: sha512-We33yolc70SALjDdF3cjEaLn1L/vrw85eFCsrviESaW3dFVIdB+xn0fdqMFK6NnaC0JjBa3Ypfev4Co+eaZ+1A==}
-
-  '@bull-board/ui@5.21.1':
-    resolution: {integrity: sha512-JBDeCqG7j/c3WE0uGMN9snPkRJz9/D6MpTZzyVj7KOxIJwNKPOICNFZbCrCNi7bcJYHDJ2xGTN9OO1mw7i43BQ==}
-
-  '@bundled-es-modules/cookie@2.0.0':
-    resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==}
-
-  '@bundled-es-modules/statuses@1.0.1':
-    resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
-
-  '@bundled-es-modules/tough-cookie@0.1.6':
-    resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==}
-
-  '@canvas/image-data@1.0.0':
-    resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==}
-
-  '@colors/colors@1.5.0':
-    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-crosshair@2.0.0-rc.1':
-    resolution: {integrity: sha512-xfLelqM8EnRZUf7xEE88RWQQx5erUv7jrzni52bAw3/Ua8HXIz3uAMnkrGKOTBj8K4Rv/mNJY8k1DVAEfHY6Lg==}
-
-  '@cropper/element-grid@2.0.0-rc.1':
-    resolution: {integrity: sha512-U/BYPl76upd9sXT+pZTFoQzUqWyNxdGs4YR2UtaVCfTMHLDTrssPAedmqEEnHgbqVcr325sIEfVmwWVA+v+8Dg==}
-
-  '@cropper/element-handle@2.0.0-rc.1':
-    resolution: {integrity: sha512-GuOHbjkg5CP1+oFzWQeD7VZffUE86dp4gKv5egLxkBEwnQp1VQxjO7L1Wkgj+KsQymoDczsl+x4bF12KDyDg2g==}
-
-  '@cropper/element-image@2.0.0-rc.1':
-    resolution: {integrity: sha512-ttzawKbUkR2A9U3bc2AN/jbNdszBP/yb83PIc5jekjOs+Z7kUBVdOo1SLIewpQ0DjUzhfCRXWUowP1McVQUXZw==}
-
-  '@cropper/element-selection@2.0.0-rc.1':
-    resolution: {integrity: sha512-AcRHRbsyt9xRfBD1QRyNDTS+vaYg6uAeuqhk/Ra58pqxlhtoimAV3oQ7uc/edwOlK60f/DxtKCc8rSOYFQ85bQ==}
-
-  '@cropper/element-shade@2.0.0-rc.1':
-    resolution: {integrity: sha512-nHv2WujETENoIfxWQn7TYiOnXm5YUnZsoG4r6njK5cxj0gIUfPudUSbjWCQSuB2oxxpeEK8oyTdfOZtP9cxK4g==}
-
-  '@cropper/element-viewer@2.0.0-rc.1':
-    resolution: {integrity: sha512-xTj0BObCygbVWXc7t7FYZ9k2eFyWN360it5uGeAkImXcwINRQGTFcLLOjs6i3SwedI7F1a1yNcTBfoT1B/sNAg==}
-
-  '@cropper/element@2.0.0-rc.1':
-    resolution: {integrity: sha512-OPKgjUgYC2Xmv77vEqtAR6bdfKOW+v9FrSjr4re3u95rcVj6NJ0JidIta41Ipp8KydHTXSmLetq4XDrA+vuIJQ==}
-
-  '@cropper/elements@2.0.0-rc.1':
-    resolution: {integrity: sha512-6qbtCq3iL3dETVav2XA03a8iLkHXWMIqHFxViMjlLr9CSuDjjaS5wp0JDuGtPv5FHxjsjyQ8Yayt8Ak5p09Zxg==}
-
-  '@cropper/utils@2.0.0-rc.1':
-    resolution: {integrity: sha512-kreB3wdrAhmTEscfB8/j7ksGBgYSKN+28t37CAI0Vb5DvX/aUDPDH+3e2kyD7YE+DIZgdnuY2FsMYJAQ9sTThg==}
-
-  '@cypress/request@3.0.0':
-    resolution: {integrity: sha512-GKFCqwZwMYmL3IBoNeR2MM1SnxRIGERsQOTWeQKoYBt2JLqcqiy7JXqO894FLrpjZYqGxW92MNwRH2BN56obdQ==}
-    engines: {node: '>= 6'}
-
-  '@cypress/xvfb@1.2.4':
-    resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==}
-
-  '@digitalbazaar/http-client@3.4.1':
-    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==}
-
-  '@emotion/use-insertion-effect-with-fallbacks@1.0.1':
-    resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
-    peerDependencies:
-      react: '>=16.8.0'
-
-  '@esbuild/aix-ppc64@0.19.11':
-    resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==}
-    engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [aix]
-
-  '@esbuild/aix-ppc64@0.21.5':
-    resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
-    engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [aix]
-
-  '@esbuild/aix-ppc64@0.23.0':
-    resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==}
-    engines: {node: '>=18'}
-    cpu: [ppc64]
-    os: [aix]
-
-  '@esbuild/android-arm64@0.18.20':
-    resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [android]
+  '@esbuild/android-arm64@0.18.20':
+    resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+    engines: {node: '>=12'}
+    cpu: [arm64]
+    os: [android]
 
   '@esbuild/android-arm64@0.19.11':
     resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==}
@@ -2380,6 +2104,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'}
@@ -2404,6 +2134,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'}
@@ -2428,6 +2164,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'}
@@ -2452,6 +2194,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'}
@@ -2476,6 +2224,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'}
@@ -2500,6 +2254,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'}
@@ -2524,6 +2284,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'}
@@ -2548,6 +2314,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'}
@@ -2572,6 +2344,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'}
@@ -2596,6 +2374,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'}
@@ -2620,6 +2404,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'}
@@ -2644,6 +2434,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'}
@@ -2668,6 +2464,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'}
@@ -2692,6 +2494,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'}
@@ -2716,6 +2524,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'}
@@ -2740,6 +2554,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'}
@@ -2764,12 +2584,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'}
@@ -2794,6 +2626,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'}
@@ -2818,6 +2656,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'}
@@ -2842,6 +2686,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'}
@@ -2866,7 +2716,13 @@ packages:
     cpu: [ia32]
     os: [win32]
 
-  '@esbuild/win32-x64@0.18.20':
+  '@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'}
     cpu: [x64]
@@ -2890,6 +2746,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}
@@ -2932,18 +2794,14 @@ 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==}
-
-  '@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.0':
+    resolution: {integrity: sha512-5wpgycrn+DXPkATGqUbXY9tyqLNgxo9S8f0EHUyIWvUacor2cXa3liYZggsqoyMXgpIqUbGLPBl+dN2hRcU9jQ==}
 
-  '@fastify/ajv-compiler@3.5.0':
-    resolution: {integrity: sha512-ebbEtlI7dxXF5ziNdr05mOY8NnDiPB1XvAlLHctRt/Rc+C3LCOVW5imUVX+mhvUhnNzmPBHewUkOFgGlCxgdAA==}
+  '@fastify/ajv-compiler@4.0.1':
+    resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==}
 
   '@fastify/busboy@1.2.1':
     resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==}
@@ -2953,47 +2811,53 @@ packages:
     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/cookie@10.0.0':
+    resolution: {integrity: sha512-S43spazwAfzm5nKlqq/spAGW+O6r+WQzg5vXXI1ArCXXFa8KBA/tiU3XRVQUehSNtbN5PA6+g183hzh5/dZ6Iw==}
 
-  '@fastify/cors@9.0.1':
-    resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==}
+  '@fastify/cors@10.0.0':
+    resolution: {integrity: sha512-kb9fkc/LVbLTQ3lhA+ZZjC/Styzysodo/MTCdVCvTtgHa/gBwxrEEkcp3fuoKIfAQt85wksrpXjUGbw5NQffEQ==}
 
-  '@fastify/deepmerge@1.3.0':
-    resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==}
+  '@fastify/deepmerge@2.0.0':
+    resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==}
 
-  '@fastify/error@3.4.0':
-    resolution: {integrity: sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==}
+  '@fastify/error@4.0.0':
+    resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==}
 
-  '@fastify/express@3.0.0':
-    resolution: {integrity: sha512-Ug6aulXCUiHgMyrHVYQqnQbGdsAV0aTad6nZxbOr6w3QjKn1mdQS3Kyzvc+I0xMjZ9yIyMUWHSooHgZ0l7nOng==}
+  '@fastify/express@4.0.0':
+    resolution: {integrity: sha512-e+IMKKV9+HRCVm7LVW8PaMrpEerHfqNLpRkbiVHYfVm0xeOphiwyNEoge4VA3Sh8gubtDfo9yKkpRzx6gx63kg==}
 
-  '@fastify/fast-json-stringify-compiler@4.3.0':
-    resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==}
+  '@fastify/fast-json-stringify-compiler@5.0.1':
+    resolution: {integrity: sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==}
 
-  '@fastify/http-proxy@9.5.0':
-    resolution: {integrity: sha512-1iqIdV10d5k9YtfHq9ylX5zt1NiM50fG+rIX40qt00R694sqWso3ukyTFZVk33SDoSiBW8roB7n11RUVUoN+Ag==}
+  '@fastify/http-proxy@10.0.0':
+    resolution: {integrity: sha512-n5/EPspNKtzpCUavuDflYtvtB+aEkablb2sZM83gDKbxM9GF+93maJYQrGozJ2HNRqpt7wfzsDeUuGVFFkYzMQ==}
 
-  '@fastify/multipart@8.3.0':
-    resolution: {integrity: sha512-A8h80TTyqUzaMVH0Cr9Qcm6RxSkVqmhK/MVBYHYeRRSUbUYv08WecjWKSlG2aSnD4aGI841pVxAjC+G1GafUeQ==}
+  '@fastify/merge-json-schemas@0.1.1':
+    resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==}
 
-  '@fastify/reply-from@9.0.1':
-    resolution: {integrity: sha512-q9vFNUiXZTY1x8omDPe59os2MYq+3y7KgO/kZoXpZlnud+45Nd8Ot/svEvrUATzjkizIggfS4K8LR9zXDyZZKg==}
+  '@fastify/multipart@9.0.0':
+    resolution: {integrity: sha512-B/rzOl1wmkj4LddH2i+zR8Gke8ZX1J8D7n4uJeis5VdIa7OR9Ys/TzUxI0/h1SF9ubHlNhBP+eO/FwnftarP9w==}
 
-  '@fastify/send@2.0.1':
-    resolution: {integrity: sha512-8jdouu0o5d0FMq1+zCKeKXc1tmOQ5tTGYdQP3MpyF9+WWrZT1KCBdh6hvoEYxOm3oJG/akdE9BpehLiJgYRvGw==}
+  '@fastify/reply-from@11.0.1':
+    resolution: {integrity: sha512-F2Qk88gcqIIiug9V+4I6WeeV1faj1Wu798JyOnwbJcjQhm4LYrHdkpFSVwJE0g1cVjYCFFmH3OVh1HHaninttQ==}
 
-  '@fastify/static@6.12.0':
-    resolution: {integrity: sha512-KK1B84E6QD/FcQWxDI2aiUCwHxMJBI1KeCUzm1BwYpPY1b742+jeKruGHP2uOluuM6OkBPI8CIANrXcCRtC2oQ==}
+  '@fastify/send@3.1.1':
+    resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==}
 
-  '@fastify/static@7.0.4':
-    resolution: {integrity: sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==}
+  '@fastify/static@8.0.0':
+    resolution: {integrity: sha512-VKGn1PQslB2VqzspyMKPu9xasF9vj+YuyGhVLb1ih6V60VVcRvcf0fFRcl3opt6c6YWwhKKdTUTfVE6COnpw6A==}
 
-  '@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.0':
+    resolution: {integrity: sha512-2KnfgpSbAImKV5kKdNAkSyjV+9kYUYLvgDLx/wlzgqel92bN9Z520cwG3g3bAkr0yVnEJu62dIm2qAL9FASS1w==}
+
+  '@fastify/view@10.0.1':
+    resolution: {integrity: sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==}
 
   '@github/webauthn-json@2.1.1':
     resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==}
@@ -3041,116 +2905,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]
 
@@ -3170,18 +3026,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==}
 
@@ -3267,8 +3111,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
@@ -3276,10 +3120,6 @@ packages:
       typescript:
         optional: true
 
-  '@jridgewell/gen-mapping@0.3.2':
-    resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
-    engines: {node: '>=6.0.0'}
-
   '@jridgewell/gen-mapping@0.3.5':
     resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
     engines: {node: '>=6.0.0'}
@@ -3288,10 +3128,6 @@ packages:
     resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
     engines: {node: '>=6.0.0'}
 
-  '@jridgewell/set-array@1.1.2':
-    resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
-    engines: {node: '>=6.0.0'}
-
   '@jridgewell/set-array@1.2.1':
     resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
     engines: {node: '>=6.0.0'}
@@ -3305,6 +3141,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==}
 
@@ -3324,8 +3163,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'}
 
   '@mcaptcha/core-glue@0.1.0-alpha-5':
@@ -3340,11 +3179,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':
@@ -3410,66 +3249,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.9':
+    resolution: {integrity: sha512-SSnyl/4ni/2ViHKkiZb8eajA/eN1DNFaHjhGiLUdZvDz6PKF4COSf/17xqSz64nOo2Ia29SA6B2KNCsyCbVmaQ==}
+    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.3':
+    resolution: {integrity: sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==}
     peerDependencies:
       class-transformer: '*'
       class-validator: '*'
@@ -3481,8 +3324,8 @@ packages:
       class-validator:
         optional: true
 
-  '@nestjs/core@10.3.10':
-    resolution: {integrity: sha512-ZbQ4jovQyzHtCGCrzK5NdtW1SYO2fHSsgSY1+/9WdruYCUra+JDkWEXgZ4M3Hv480Dl3OXehAmY1wCOojeMyMQ==}
+  '@nestjs/core@10.4.3':
+    resolution: {integrity: sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/microservices': ^10.0.0
@@ -3498,14 +3341,14 @@ packages:
       '@nestjs/websockets':
         optional: true
 
-  '@nestjs/platform-express@10.3.10':
-    resolution: {integrity: sha512-wK2ow3CZI2KFqWeEpPmoR300OB6BcBLxARV1EiClJLCj4S1mZsoCmS0YWgpk3j1j6mo0SI8vNLi/cC2iZPEPQA==}
+  '@nestjs/platform-express@10.4.3':
+    resolution: {integrity: sha512-ss7gkofVm3eO+1P9iRhmGq6Xcjg+mIN3dWisKJZYelSV+msb0QpJmqChLvWjLkWtlqDnx915FKUk0IzCa0TVzw==}
     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.3':
+    resolution: {integrity: sha512-SBNWrMU51YAlYmW86wyjlGZ2uLnASNiOPD0lBcNIlxxei0b05/aI3nh7OPuxbXQUdedUJfPq2d2jZj4TRG4S0w==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/core': ^10.0.0
@@ -3770,12 +3613,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'}
@@ -3794,8 +3641,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
@@ -3803,88 +3650,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:
@@ -3894,16 +3744,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==}
@@ -3945,6 +3795,9 @@ packages:
   '@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==}
 
@@ -3973,10 +3826,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'}
@@ -4229,115 +4078,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.3':
+    resolution: {integrity: sha512-cbpksmld7iADwDGXgojZ4r8LGI3YA3NP68duAHg2n1dtnx1oUaFK5wd6dbNuz7GdjyhIOIy3OKU1dAuylYNGOQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-backgrounds@8.2.6':
-    resolution: {integrity: sha512-61NFowA6EmCw+Eyzp0U4fat9MlPDdnT7aoDyzqSImLwWLITY9IvmWuTeo7XKJZN3fe22z1r7cZseKdYrtaHcKw==}
+  '@storybook/addon-backgrounds@8.3.3':
+    resolution: {integrity: sha512-aX0OIrtjIB7UgSaiv20SFkfC1iWwJIGMPsPSJ5ZPhXIIOWIEBtSujh8YXwjDEXSC4DOHalmeT4bitRRe5KrVKA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-controls@8.2.6':
-    resolution: {integrity: sha512-EHUwHy+oZZv3pXzN7fuXWrS/meHFjqcELY3RBvOyEkGf21agl6co6R1tnf6d5N5QoYAGfIbDO7dkauSL2RfNAw==}
+  '@storybook/addon-controls@8.3.3':
+    resolution: {integrity: sha512-78xRtVpY7eX/Lti00JLgwYCBRB6ZcvzY3SWk0uQjEqcTnQGoQkVg2L7oWFDlDoA1LBY18P5ei2vu8MYT9GXU4g==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-docs@8.2.6':
-    resolution: {integrity: sha512-qe7hxntaezqjKdU9QS+Q9NFL6i/uNdBxdvOnCKgPhBAY/zY6yhk5t3sOvonynPK5nkaNAowfSNPIzNxAXlJ1sA==}
+  '@storybook/addon-docs@8.3.3':
+    resolution: {integrity: sha512-REUandqq1RnMNOhsocRwx5q2fdlBAYPTDFlKASYfEn4Ln5NgbQRGxOAWl7yXAAFzbDmUDU7K20hkauecF0tyMw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-essentials@8.2.6':
-    resolution: {integrity: sha512-diGjGZcZNov+RCAVQBTm8JKP2kUtMRuJIQFBeXdPWpu6hYBk6lw1FlAf2GywWGCvdny1pJT90hfoD33qUMNuDg==}
+  '@storybook/addon-essentials@8.3.3':
+    resolution: {integrity: sha512-E/uXoUYcg8ulG3lVbsEKb4v5hnMeGkq9YJqiZYKgVK7iRFa6p4HeVB1wU1adnm7RgjWvh+p0vQRo4KL2CTNXqw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-highlight@8.2.6':
-    resolution: {integrity: sha512-03cV9USsfP3bS4wYV06DYcIaGPfoheQe53Q0Jr1B2yJUVyIPKvmO2nGjLBsqzeL3Wl7vSfLQn0/dUdxCcbqLsw==}
+  '@storybook/addon-highlight@8.3.3':
+    resolution: {integrity: sha512-MB084xJM66rLU+iFFk34kjLUiAWzDiy6Kz4uZRa1CnNqEK0sdI8HaoQGgOxTIa2xgJor05/8/mlYlMkP/0INsQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-interactions@8.2.6':
-    resolution: {integrity: sha512-YXpHf8jWPz9HJV+Fw4GaunaCWeE6uqF24aLXdAd8xuhN1UfWJeNV6AwAvFQ0hTLqvmz0yMhX/5JXDKeKESoYDA==}
+  '@storybook/addon-interactions@8.3.3':
+    resolution: {integrity: sha512-3w5tpCGYdF33wF44xEhTS3Zmcwd6nITtwy5q+PJvHCJAm3fpjzL3xrjtlHKDvXNwYacJPRCbWKn2QwtxZIdN0g==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-links@8.2.6':
-    resolution: {integrity: sha512-CUuU3nk8wyZ3bljCmOG/OCKazan+bPuNbCph8N763zyzdEx5M/CbBxV9d3pi3zjYpix7txlqrl2/YdMCejfyFw==}
+  '@storybook/addon-links@8.3.3':
+    resolution: {integrity: sha512-rz4KEbzr1ca4zZEZwbOnhKiaEsokCl1KkngxT/C1YIkpW908j/kg2nnIb5MrtlAW1nirXguAR74t6CGntvdU9w==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
-      storybook: ^8.2.6
+      storybook: ^8.3.3
     peerDependenciesMeta:
       react:
         optional: true
 
-  '@storybook/addon-mdx-gfm@8.2.6':
-    resolution: {integrity: sha512-PFVfJeuydxlV1VmxEuKNQ7z2vCDvQtHa2GB0ANM11ahxDSUz8QxsO0Y/L3LOn2JjJGYiVFrsHAaC+8NW43iArQ==}
+  '@storybook/addon-mdx-gfm@8.3.3':
+    resolution: {integrity: sha512-jdwVXoBSEdmuw8L4MxUeJ/qIInADfCwdtShnfTQIJBBRucOl8ykgfTKKNjllT79TFiK0gsWoiZmE05P4wuBofw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-measure@8.2.6':
-    resolution: {integrity: sha512-neI8YeSOAtOmzasLxo6O8ZLr2ebMaD7XVF+kYatl5+SpyuwwvUGcP9NkKe5S+mB8V2zxFUIsXS74XrhmQhRoaQ==}
+  '@storybook/addon-measure@8.3.3':
+    resolution: {integrity: sha512-R20Z83gnxDRrocES344dw1Of/zDhe3XHSM6TLq80UQTJ9PhnMI+wYHQlK9DsdP3KiRkI+pQA6GCOp0s2ZRy5dg==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-outline@8.2.6':
-    resolution: {integrity: sha512-uAlPtqDWlq7MQQ4zJT80qdjbSdLF/zsvtPhidX6h9cjLKNPWAv79xJQ14AJHaMv+Hzy5xKnM4wdEhgPbzKabQg==}
+  '@storybook/addon-outline@8.3.3':
+    resolution: {integrity: sha512-OwqYfieNuqSqWNtUZLu3UmsfQNnwA2UaSMBZyeC2Dte9Jd59PPYggcWmH+b0S6OTbYXWNAUK5U6WdK+X9Ypzdw==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-storysource@8.2.6':
-    resolution: {integrity: sha512-8H2kvRIM12oXN4kO/oowABu88IOY9Je7PphCUUs/nIfTIB+Ck1GrLxx5fCNNCSwUrTBEZY8bDOfxmkf9d4qngw==}
+  '@storybook/addon-storysource@8.3.3':
+    resolution: {integrity: sha512-yPYQH9NepSNxoSsV9E7OV3/EVFrbU/r2B3E5WP/mCfqTXPg/5noce7iRi+rWqcVM1tsN1qPnSjfQQc7noF0h0Q==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-toolbars@8.2.6':
-    resolution: {integrity: sha512-0JmRirMpxHS6VZzBk0kY871xWTpkk3TN4S1sxoFf5fcnCfVTHDjEJ5Ws/QWru1RJlIZHuJKRdQIA6Vuq5X+KfQ==}
+  '@storybook/addon-toolbars@8.3.3':
+    resolution: {integrity: sha512-4WyiVqDm4hlJdENIVQg9pLNLdfhnNKa+haerYYSzTVjzYrUx0X6Bxafshq+sud6aRtSYU14abwP56lfW8hgTlA==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/addon-viewport@8.2.6':
-    resolution: {integrity: sha512-IAxH9H8tVFzSmZhKf5E+EALiAdkp19RzGqP/rWluD8LH7oW5HumQE/4oN0ZhVMy1RxYsCKFYjWyAp7AuxeMRSw==}
+  '@storybook/addon-viewport@8.3.3':
+    resolution: {integrity: sha512-2S+UpbKAL+z1ppzUCkixjaem2UDMkfmm/kyJ1wm3A/ofGLYi4fjMSKNRckk+7NdolXGQJjBo0RcaotUTxFIFwQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/blocks@8.2.6':
-    resolution: {integrity: sha512-nMlZJjVTyfOJ6xwORptsNuS1AZZlDbJUVXc2R8uukGd5GIXxxCdrPk4NvUsjfQslMT9LhYuFld3z62FATsM2rw==}
+  '@storybook/blocks@8.3.3':
+    resolution: {integrity: sha512-8Vsvxqstop3xfbsx3Dn1nEjyxvQUcOYd8vpxyp2YumxYO8FlXIRuYL6HAkYbcX8JexsKvCZYxor52D2vUGIKZg==}
     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.3
     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.3':
+    resolution: {integrity: sha512-3yTXCLaB6bzhoPH3PqtacKkcaC1uV4L+IHTf1Zypx1NO1pLZHyhYf0T7dIOxTh2JZfqu1Pm9hTvOmWfR12m+9w==}
     peerDependencies:
       '@preact/preset-vite': '*'
-      storybook: ^8.2.6
+      storybook: ^8.3.3
       typescript: '>= 4.3.x'
       vite: ^4.0.0 || ^5.0.0
       vite-plugin-glimmerx: '*'
@@ -4349,190 +4180,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.3':
+    resolution: {integrity: sha512-i2JYtesFGkdu+Hwuj+o9fLuO3yo+LPT1/8o5xBVYtEqsgDtEAyuRUWjSz8d8NPtzloGPOv5kvR6MokWDfbeMfw==}
     peerDependencies:
-      prettier: ^2 || ^3
-    peerDependenciesMeta:
-      prettier:
-        optional: true
+      storybook: ^8.3.3
 
-  '@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.3':
+    resolution: {integrity: sha512-YL+gBuCS81qktzTkvw0MXUJW0bYAXfRzMoiLfDBTrEKZfcJOB4JAlMGmvRRar0+jygK3icD42Rl5BwWoZY6KFQ==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/core-server@8.1.11':
-    resolution: {integrity: sha512-L6dzQTmR0np/kagNONvvlm6lSvF1FNc9js3vxsEEPnEypLbhx8bDZaHmuhmBpYUzKyUMpRVQTE/WgjHLuBBuxA==}
+      storybook: ^8.3.3
 
-  '@storybook/core@8.2.6':
-    resolution: {integrity: sha512-XY71g3AcpD6IiER9k9Lt+vlUMYfPIYgWekd7e0Ggzz2gJkPuLunKEdQccLGDSHf5OFAobHhrTJc7ZsvWhmDMag==}
+  '@storybook/core@8.3.3':
+    resolution: {integrity: sha512-pmf2bP3fzh45e56gqOuBT8sDX05hGdUKIZ/hcI84d5xmd6MeHiPW8th2v946wCHcxHzxib2/UU9vQUh+mB4VNw==}
 
-  '@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.3':
+    resolution: {integrity: sha512-7AD7ojpXr3THqpTcEI4K7oKUfSwt1hummgL/cASuQvEPOwAZCVZl2gpGtKxcXhtJXTkn3GMCAvlYMoe7O/1YWw==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/csf-tools@8.1.11':
-    resolution: {integrity: sha512-6qMWAg/dBwCVIHzANM9lSHoirwqSS+wWmv+NwAs0t9S94M75IttHYxD3IyzwaSYCC5llp0EQFvtXXAuSfFbibg==}
+      storybook: ^8.3.3
 
   '@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.3':
+    resolution: {integrity: sha512-ZiODB9EwCQkl4PBxGJjBHXRTLxcNs68ZZvR+xeMr0eMFzzlJG+trXoX5kK95oA4BFhGN+3uM0Zl3MoRjBtJTNA==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/manager-api@8.1.11':
-    resolution: {integrity: sha512-QSgwKfAw01K9YvvZj30iGBMgQ4YaCT3vojmttuqdH5ukyXkiO7pENLJj4Y+alwUeSi0g+SJeadCI3PXySBHOGg==}
+      storybook: ^8.3.3
 
-  '@storybook/manager-api@8.2.6':
-    resolution: {integrity: sha512-uv36h/b5RhlajWtEg4cVPBYV8gZs6juux0nIE+6G9i7vt8Ild6gM9tW1KNabgZcaHFiyWJYCNWxJZoKjgUmXDg==}
+  '@storybook/manager-api@8.3.3':
+    resolution: {integrity: sha512-Na4U+McOeVUJAR6qzJfQ6y2Qt0kUgEDUriNoAn+curpoKPTmIaZ79RAXBzIqBl31VyQKknKpZbozoRGf861YaQ==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/manager@8.1.11':
-    resolution: {integrity: sha512-e02y9dmxowo7cTKYm9am7UO6NOHoHy6Xi7xZf/UA932qLwFZUtk5pnwIEFaZWI3OQsRUCGhP+FL5zizU7uVZeg==}
-
-  '@storybook/node-logger@8.1.11':
-    resolution: {integrity: sha512-wdzFo7B2naGhS52L3n1qBkt5BfvQjs8uax6B741yKRpiGgeAN8nz8+qelkD25MbSukxvbPgDot7WJvsMU/iCzg==}
-
-  '@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.3':
+    resolution: {integrity: sha512-GP2QlaF3BBQGAyo248N7549YkTQjCentsc1hUvqPnFWU4xfjkejbnFk8yLaIw0VbYbL7jfd7npBtjZ+6AnphMQ==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/preview@8.1.11':
-    resolution: {integrity: sha512-K/9NZmjnL0D1BROkTNWNoPqgL2UaocALRSqCARmkBLgU2Rn/FuZgEclHkWlYo6pUrmLNK+bZ+XzpNMu12iTbpg==}
+      storybook: ^8.3.3
 
-  '@storybook/react-dom-shim@8.2.6':
-    resolution: {integrity: sha512-B+x8UAEQPDp1yhN3tMh09NvSL38QNfJB7PAyLgKrfE7xIAzvewq+RLW2DfGkoZCy+Zr7QSHm1p7NOgud8+sQCg==}
+  '@storybook/react-dom-shim@8.3.3':
+    resolution: {integrity: sha512-0dPC9K7+K5+X/bt3GwYmh+pCpisUyKVjWsI+PkzqGnWqaXFakzFakjswowIAIO1rf7wYZR591x3ehUAyL2bJiQ==}
     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.3
 
-  '@storybook/react-vite@8.2.6':
-    resolution: {integrity: sha512-BpbteaIzsJZL1QN3iR7uuslrPfdtbZYXPhcU9awpfl5pW5MOQThuvl7728mwT8V7KdANeikJPgsnlETOb/afDA==}
+  '@storybook/react-vite@8.3.3':
+    resolution: {integrity: sha512-vzOqVaA/rv+X5J17eWKxdZztMKEKfsCSP8pNNmrqXWxK3pSlW0fAPxtn1kw3UNxGtAv71pcqvaCUtTJKqI1PYA==}
     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.3
       vite: ^4.0.0 || ^5.0.0
 
-  '@storybook/react@8.2.6':
-    resolution: {integrity: sha512-awJlzfiAMrf8l9AgiLhjXEJ+HvS3VKPxNNQaRwBELGq/vigjJe656tMrhvg4OIlJXtlS+6XPshd2knLwjIWNLw==}
+  '@storybook/react@8.3.3':
+    resolution: {integrity: sha512-fHOW/mNqI+sZWttGOE32Q+rAIbN7/Oib091cmE8usOM0z0vPNpywUBtqC2cCQH39vp19bhTsQaSsTcoBSweAHw==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
+      '@storybook/test': 8.3.3
       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.3
       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.3':
+    resolution: {integrity: sha512-NeP7l53mvnnfwi+91vtRaibZer+UJi6gkoaGRCpphL3L+3qVIXN3p41uXhAy+TahdFI2dbrWvLSNgtsvdXVaFg==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/theming@8.1.11':
-    resolution: {integrity: sha512-Chn/opjO6Rl1isNobutYqAH2PjKNkj09YBw/8noomk6gElSa3JbUTyaG/+JCHA6OG/9kUsqoKDb5cZmAKNq/jA==}
+  '@storybook/test@8.3.3':
+    resolution: {integrity: sha512-uZ8nMIovfI2ry989K2+cYAeEVD/3dpjj2+Rbmy7DiZWWVhFALfmqaTRkzZfShLmlH0TFv+rfcBPihGccBtw0FQ==}
     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.3
 
-  '@storybook/theming@8.2.6':
-    resolution: {integrity: sha512-ICnYuLIVsYifVCMQljdHgrp+5vAquNybHxDGWiPeOxBicotwHF8rLhTckD2CdVQbMp0jk6r6jetvjXbFJ2MbvQ==}
+  '@storybook/theming@8.3.3':
+    resolution: {integrity: sha512-gWJKetI6XJQgkrvvry4ez10+jLaGNCQKi5ygRPM9N+qrjA3BB8F2LCuFUTBuisa4l64TILDNjfwP/YTWV5+u5A==}
     peerDependencies:
-      storybook: ^8.2.6
-
-  '@storybook/types@8.1.11':
-    resolution: {integrity: sha512-k9N5iRuY2+t7lVRL6xeu6diNsxO3YI3lS4Juv3RZ2K4QsE/b3yG5ElfJB8DjHDSHwRH4ORyrU71KkOCUVfvtnw==}
+      storybook: ^8.3.3
 
-  '@storybook/types@8.2.6':
-    resolution: {integrity: sha512-9Kb5+nui8M7TP/EDGwiuOAHYQPg9U6iQl0OWwgbDIYGBpldwlCwVKAoQWzXz/LlhQijULXIpe1cLvEvJN2Uwhg==}
+  '@storybook/types@8.3.3':
+    resolution: {integrity: sha512-wV1kupG1tfTMOXaBrtVHXuqp19vURVDqWTQX6nqkoUFD7Xb1lz/YNVeGP1uT/zJdJy42/HIyoib9JPx9h0Vx9w==}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
 
-  '@storybook/vue3-vite@8.1.11':
-    resolution: {integrity: sha512-q0bqh8XEEunaTmp4YiDqM2+YZLwEIevTb5PnNe7G7f2qOiSCE1ncBDnBK717UlCd+iYr34NTztgV2/jIhz1i5w==}
+  '@storybook/vue3-vite@8.3.3':
+    resolution: {integrity: sha512-IFcoOGlUGuUkL3rpm9UFs8FK9JX1ZdfGpLXRObVOVRhW3t+MsNLpx4Fqp3a/re6WcCC3yvHzbLXgvGcjpapkbw==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
+      storybook: ^8.3.3
       vite: ^4.0.0 || ^5.0.0
 
-  '@storybook/vue3@8.1.11':
-    resolution: {integrity: sha512-xJtvfLiCOY3UqwDMd0hZdsadPm1q8dwjfM1UN2Q2ssRWNfXzww1oi+Msj902wz9zFZMYVZypfTfgrdRgWmfEjA==}
-    engines: {node: '>=18.0.0'}
-    peerDependencies:
-      vue: ^3.0.0
-
-  '@storybook/vue3@8.2.6':
-    resolution: {integrity: sha512-j4gMuWc1ZDzqWSdf79YswcZmcbhmbByq/6upqxwqXtjv1mHAiBnEs8bbnnylDrzg4GOvBC8w+FjArkzlFA7uXg==}
+  '@storybook/vue3@8.3.3':
+    resolution: {integrity: sha512-peu8MFGwmhpXoD3n42qG6TxeVHRhfHZ0/HW4+A6FXSB1c9w0CC4AzHs5f1w3yUvshtexNN5bkw9Q4nSVKtfU7A==}
     engines: {node: '>=18.0.0'}
     peerDependencies:
-      storybook: ^8.2.6
+      storybook: ^8.3.3
       vue: ^3.0.0
 
   '@swc/cli@0.3.12':
@@ -4782,34 +4538,17 @@ packages:
     resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
     engines: {node: '>=14.16'}
 
-  '@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==}
@@ -4844,6 +4583,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==}
 
@@ -4904,36 +4646,15 @@ packages:
   '@types/core-js@2.5.8':
     resolution: {integrity: sha512-VgnAj6tIAhJhZdJ8/IpxdatM8G4OD3VWGlp6xIxUGENZlpbob9Ty4VVdC1FIEp0aK6DBscDDjyzy5FB60TuNqg==}
 
-  '@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==}
 
@@ -4943,8 +4664,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==}
@@ -4958,8 +4679,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/form-data@2.5.0':
     resolution: {integrity: sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==}
@@ -4995,6 +4716,9 @@ packages:
   '@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==}
 
@@ -5016,6 +4740,9 @@ packages:
   '@types/jsrsasign@10.5.14':
     resolution: {integrity: sha512-lppSlfK6etu+cuKs40K4rg8As79PH6hzIB+v55zSqImbSH3SE6Fm8MBHCiI91cWlAP3Z4igtJK1VL3fSN09blQ==}
 
+  '@types/katex@0.16.7':
+    resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+
   '@types/keyv@3.1.4':
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
 
@@ -5058,9 +4785,6 @@ packages:
   '@types/mysql@2.15.22':
     resolution: {integrity: sha512-wK1pzsJVVAjYCSZWQoWHziQZbNggXFDUEIGf54g4ZM/ERuP86uGdWeKZWMYlqTPMZfHJJvLPyogXGvCOg87yLQ==}
 
-  '@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==}
 
@@ -5070,8 +4794,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.7.5':
+    resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==}
+
+  '@types/nodemailer@6.4.16':
+    resolution: {integrity: sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==}
 
   '@types/normalize-package-data@2.4.1':
     resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@@ -5094,15 +4821,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==}
 
@@ -5145,8 +4869,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==}
@@ -5223,6 +4947,9 @@ packages:
   '@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==}
 
@@ -5232,17 +4959,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@6.21.0':
     resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5276,16 +4992,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@6.21.0':
     resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5316,10 +5022,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@6.21.0':
     resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5332,16 +5034,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@6.21.0':
     resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5372,10 +5064,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@6.21.0':
     resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5388,15 +5076,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@6.21.0':
     resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5424,12 +5103,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@6.21.0':
     resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5448,10 +5121,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@6.21.0':
     resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
     engines: {node: ^16.0.0 || >=18.0.0}
@@ -5467,8 +5136,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
@@ -5482,6 +5151,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.2':
+    resolution: {integrity: sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==}
+
   '@vitest/runner@1.6.0':
     resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==}
 
@@ -5491,54 +5169,63 @@ 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.2':
+    resolution: {integrity: sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==}
+
   '@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.6':
+    resolution: {integrity: sha512-FxUfxaB8sCqvY46YjyAAV6c3mMIq/NWQMVvJ+uS4yxr1KzOvyg61gAuOnNvgCvO4TZ7HcLExBEsWcDu4+K4E8A==}
 
   '@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.6':
+    resolution: {integrity: sha512-Nsh7UW2ruK+uURIPzjJgF0YRGP5CX9nQHypA2OMqdM2FKy7rh+uv3XgPnWPw30JADbKvZ5HuBzG4gSbVDYVtiw==}
 
   '@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==}
-
-  '@vue/compiler-core@3.4.34':
-    resolution: {integrity: sha512-Z0izUf32+wAnQewjHu+pQf1yw00EGOmevl1kE+ljjjMe7oEfpQ+BI3/JNK7yMB4IrUsqLDmPecUrpj3mCP+yJQ==}
+  '@volar/typescript@2.4.6':
+    resolution: {integrity: sha512-NMIrA7y5OOqddL9VtngPWYmdQU03htNKFtAYidbYfWA0TOhyGVd9tfcP4TsLWQ+RBWDZCbBqsr8xzU0ZOxYTCQ==}
 
   '@vue/compiler-core@3.4.37':
     resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==}
 
-  '@vue/compiler-dom@3.4.34':
-    resolution: {integrity: sha512-3PUOTS1h5cskdOJMExCu2TInXuM0j60DRPpSCJDqOCupCfUZCJoyQmKtRmA8EgDNZ5kcEE7vketamRZfrEuVDw==}
+  '@vue/compiler-core@3.5.10':
+    resolution: {integrity: sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==}
 
   '@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-sfc@3.4.37':
     resolution: {integrity: sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==}
 
+  '@vue/compiler-sfc@3.5.10':
+    resolution: {integrity: sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==}
+
   '@vue/compiler-ssr@3.4.37':
     resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==}
 
+  '@vue/compiler-ssr@3.5.10':
+    resolution: {integrity: sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==}
+
   '@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:
@@ -5547,8 +5234,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:
@@ -5558,26 +5245,37 @@ packages:
   '@vue/reactivity@3.4.37':
     resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==}
 
+  '@vue/reactivity@3.5.10':
+    resolution: {integrity: sha512-kW08v06F6xPSHhid9DJ9YjOGmwNDOsJJQk0ax21wKaUYzzuJGEuoKNU2Ujux8FLMrP7CFJJKsHhXN9l2WOVi2g==}
+
   '@vue/runtime-core@3.4.37':
     resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==}
 
+  '@vue/runtime-core@3.5.10':
+    resolution: {integrity: sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==}
+
   '@vue/runtime-dom@3.4.37':
     resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==}
 
+  '@vue/runtime-dom@3.5.10':
+    resolution: {integrity: sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==}
+
   '@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/shared@3.4.34':
-    resolution: {integrity: sha512-x5LmiRLpRsd9KTjAB8MPKf0CDPMcuItjP0gbNqFCIgL1I8iYp4zglhj9w9FPCdIbHG2M91RVeIbArFfFTz9I3A==}
+  '@vue/server-renderer@3.5.10':
+    resolution: {integrity: sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==}
+    peerDependencies:
+      vue: 3.5.10
 
   '@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/test-utils@2.4.1':
     resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
     peerDependencies:
@@ -5587,20 +5285,6 @@ packages:
       '@vue/server-renderer':
         optional: true
 
-  '@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==}
 
@@ -5652,14 +5336,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'}
-
-  agent-base@6.0.2:
-    resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
-    engines: {node: '>= 6.0.0'}
-
   agent-base@7.1.0:
     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
     engines: {node: '>= 14'}
@@ -5685,14 +5361,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:
@@ -5755,9 +5423,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'}
@@ -5776,9 +5441,6 @@ packages:
     resolution: {integrity: sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==}
     engines: {node: '>= 14'}
 
-  archy@1.0.0:
-    resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
-
   arg@5.0.2:
     resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
 
@@ -5801,19 +5463,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:
@@ -5828,6 +5494,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'}
@@ -5852,12 +5522,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'}
@@ -5866,8 +5537,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:
@@ -5894,8 +5565,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==}
@@ -5909,20 +5584,15 @@ packages:
   axios@0.24.0:
     resolution: {integrity: sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==}
 
-  axios@1.6.0:
-    resolution: {integrity: sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==}
+  axios@1.7.4:
+    resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==}
 
-  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}
@@ -5937,21 +5607,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:
@@ -5986,10 +5641,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'}
@@ -6006,9 +5657,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==}
 
@@ -6025,16 +5673,16 @@ packages:
     resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
     engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
 
+  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:
     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
 
   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==}
 
@@ -6102,8 +5750,8 @@ packages:
     resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
     engines: {node: '>=6.14.2'}
 
-  bullmq@5.10.4:
-    resolution: {integrity: sha512-YEssEbWBbPXvSW2YMjIBKZdkIPZsOaTGWo1y2wpCFv/wUY+tRLKiSVuHgv09x0QEieybx844f9//UWuarG1JHg==}
+  bullmq@5.13.2:
+    resolution: {integrity: sha512-McGE8k3mrCvdUHdU0sHkTKDS1xr4pff+hbEKBY51wk5S6Za0gkuejYA620VQTo3Zz37E/NVWMgumwiXPQ3yZcA==}
 
   buraha@0.0.1:
     resolution: {integrity: sha512-G563A0mTbzknm2jDaNxfZuNKIdeArs8T+XQN6t+KbmgnOoevXSXhKDkyf8Md/36Jrx99ikwbCag37VGe3myExQ==}
@@ -6112,10 +5760,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'}
@@ -6155,6 +5799,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==}
 
@@ -6203,6 +5851,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'}
@@ -6233,8 +5885,8 @@ packages:
   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:
@@ -6261,6 +5913,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'}
@@ -6268,6 +5924,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'}
@@ -6280,8 +5940,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.10.4:
+    resolution: {integrity: sha512-nfgDpW5gQ4FtgV1lZXXfqLjONKDCh2K4vwI3dbZrtU1ObOL9THyAzpIdnK9LRcNSeisDLX+XFCryfMg1Ql2U2g==}
     hasBin: true
     peerDependencies:
       '@chromatic-com/cypress': ^0.*.* || ^1.0.0
@@ -6342,17 +6002,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'}
@@ -6402,6 +6054,10 @@ packages:
     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==}
 
@@ -6435,14 +6091,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==}
 
@@ -6492,8 +6140,9 @@ packages:
     resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
     engines: {node: '>= 0.6'}
 
-  core-js-compat@3.37.1:
-    resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==}
+  cookie@0.7.2:
+    resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+    engines: {node: '>= 0.6'}
 
   core-util-is@1.0.2:
     resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
@@ -6523,8 +6172,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==}
@@ -6544,10 +6193,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}
@@ -6606,8 +6251,13 @@ packages:
   csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
-  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
 
@@ -6623,6 +6273,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'}
@@ -6667,6 +6329,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'}
@@ -6708,6 +6379,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==}
 
@@ -6718,17 +6393,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'}
@@ -6737,8 +6409,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==}
@@ -6760,10 +6433,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'}
@@ -6772,14 +6441,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==}
 
@@ -6825,22 +6486,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==}
@@ -6887,13 +6561,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==}
 
@@ -6919,11 +6597,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==}
 
@@ -6934,26 +6607,46 @@ 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'}
 
-  esbuild-plugin-alias@0.2.1:
-    resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==}
-
   esbuild-register@3.5.0:
     resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==}
     peerDependencies:
@@ -6979,10 +6672,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==}
 
@@ -7023,8 +6725,8 @@ 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.12.0:
+    resolution: {integrity: sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==}
     engines: {node: '>=4'}
     peerDependencies:
       '@typescript-eslint/parser': '*'
@@ -7044,8 +6746,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': '*'
@@ -7060,6 +6762,12 @@ packages:
     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==}
 
@@ -7082,6 +6790,7 @@ packages:
   eslint@8.57.0:
     resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
     hasBin: true
 
   eslint@9.8.0:
@@ -7172,8 +6881,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:
@@ -7195,6 +6904,10 @@ packages:
     resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
     engines: {node: '>= 0.10.0'}
 
+  express@4.21.0:
+    resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==}
+    engines: {node: '>= 0.10.0'}
+
   ext-list@2.2.2:
     resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==}
     engines: {node: '>=0.10.0'}
@@ -7215,8 +6928,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==}
@@ -7234,8 +6947,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==}
@@ -7250,8 +6963,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==}
@@ -7260,8 +6973,8 @@ packages:
     resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==}
     hasBin: true
 
-  fast-xml-parser@4.4.0:
-    resolution: {integrity: sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==}
+  fast-xml-parser@4.4.1:
+    resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==}
     hasBin: true
 
   fastify-multer@2.0.3:
@@ -7271,15 +6984,18 @@ packages:
   fastify-plugin@2.3.4:
     resolution: {integrity: sha512-I+Oaj6p9oiRozbam30sh39BiuiqBda7yK2nmSPVwDCfIBlKnT8YB3MY+pRQc2Fcd07bf6KPGklHJaQ2Qu81TYQ==}
 
-  fastify-plugin@4.5.0:
-    resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==}
+  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.1:
+    resolution: {integrity: sha512-HCxs+YnRaWzCl+cWRYFnHmeRFyR5GVnJTAaCJQiYzQSDwK9MgJdyAsuL3nh0EWRCYMgQ5MeziymvmAhUHYHDUQ==}
+
+  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==}
@@ -7287,9 +7003,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==}
 
@@ -7301,9 +7014,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'}
@@ -7320,15 +7030,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:
@@ -7354,25 +7061,21 @@ packages:
     resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
     engines: {node: '>= 0.8'}
 
-  find-cache-dir@2.1.0:
-    resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==}
-    engines: {node: '>=6'}
+  finalhandler@1.3.1:
+    resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
+    engines: {node: '>= 0.8'}
 
   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.1.0:
+    resolution: {integrity: sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==}
     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'}
@@ -7400,10 +7103,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'}
@@ -7417,6 +7116,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==}
 
@@ -7435,10 +7143,6 @@ packages:
     resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==}
     engines: {node: '>= 18'}
 
-  form-data@2.3.3:
-    resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
-    engines: {node: '>= 0.12'}
-
   form-data@4.0.0:
     resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
     engines: {node: '>= 6'}
@@ -7497,6 +7201,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==}
 
@@ -7514,6 +7222,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'}
@@ -7542,6 +7254,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==}
 
@@ -7551,10 +7267,6 @@ packages:
   getpass@0.1.7:
     resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==}
 
-  giget@1.1.2:
-    resolution: {integrity: sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==}
-    hasBin: true
-
   github-slugger@2.0.0:
     resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
 
@@ -7572,9 +7284,6 @@ 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'}
@@ -7610,8 +7319,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:
@@ -7622,10 +7331,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'}
-
   gopd@1.0.1:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
 
@@ -7658,11 +7363,6 @@ 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==}
 
@@ -7688,10 +7388,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'}
@@ -7700,6 +7407,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@1.0.3:
     resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
     engines: {node: '>= 0.4.0'}
@@ -7707,13 +7418,14 @@ 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==}
 
@@ -7733,8 +7445,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:
@@ -7766,9 +7478,15 @@ packages:
     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==}
 
@@ -7784,8 +7502,8 @@ packages:
     resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
     engines: {node: '>= 14'}
 
-  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:
@@ -7800,18 +7518,10 @@ packages:
     resolution: {integrity: sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==}
     engines: {node: '>=16'}
 
-  https-proxy-agent@5.0.1:
-    resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
-    engines: {node: '>= 6'}
-
   https-proxy-agent@7.0.2:
     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'}
@@ -7832,8 +7542,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:
@@ -7920,6 +7630,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==}
 
@@ -7931,8 +7645,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:
@@ -7965,6 +7679,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==}
 
@@ -7993,6 +7711,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'}
@@ -8032,10 +7758,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'}
@@ -8046,14 +7768,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==}
 
@@ -8077,10 +7799,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'}
@@ -8101,6 +7819,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'}
@@ -8121,8 +7843,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:
@@ -8133,6 +7855,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==}
 
@@ -8173,10 +7899,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==}
 
@@ -8366,6 +8088,9 @@ packages:
   joi@17.11.0:
     resolution: {integrity: sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==}
 
+  joi@17.13.3:
+    resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
+
   js-beautify@1.14.9:
     resolution: {integrity: sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==}
     engines: {node: '>=12'}
@@ -8398,14 +8123,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==}
@@ -8416,10 +8136,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'}
@@ -8431,6 +8147,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==}
 
@@ -8493,6 +8212,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==}
 
@@ -8502,8 +8226,8 @@ packages:
   jws@4.0.0:
     resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==}
 
-  katex@0.16.9:
-    resolution: {integrity: sha512-fsSYjWS0EEOwvy81j3vRA8TEAhQhKiqO+FQaKWp0m39qwOzHVBgAUBIXWj1pB+O2W3fIpNa6Y9KSKCVbfPhyAQ==}
+  katex@0.16.10:
+    resolution: {integrity: sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==}
     hasBin: true
 
   keyv@4.5.4:
@@ -8535,10 +8259,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'}
@@ -8551,8 +8271,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.1.0:
+    resolution: {integrity: sha512-+NFuhlOGoEwxeQfJ/pobkVFxcnKyDtiX847hLjuB/IzBxIl3q4VJeFI8uRCgb3AlTWL1lgOr+u5+8QdUcr33ng==}
 
   lilconfig@3.1.1:
     resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
@@ -8574,10 +8294,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'}
@@ -8586,9 +8302,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==}
 
@@ -8631,6 +8344,9 @@ packages:
   loupe@2.3.7:
     resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
 
+  loupe@3.1.2:
+    resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==}
+
   lowercase-keys@2.0.0:
     resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
     engines: {node: '>=8'}
@@ -8679,16 +8395,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'}
@@ -8781,12 +8496,15 @@ 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'}
@@ -8794,6 +8512,9 @@ packages:
   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==}
 
@@ -8893,8 +8614,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:
@@ -8910,6 +8631,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'}
@@ -9032,8 +8758,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==}
@@ -9042,10 +8768,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==}
 
@@ -9081,6 +8803,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==}
 
@@ -9128,9 +8860,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==}
 
@@ -9159,17 +8888,10 @@ packages:
     resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
     engines: {node: ^16 || ^18 || >= 20}
 
-  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.7.0:
     resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
     engines: {node: 4.x || >=6.0.0}
@@ -9195,8 +8917,8 @@ packages:
     resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==}
     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
 
@@ -9206,8 +8928,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:
@@ -9215,8 +8937,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
 
@@ -9253,10 +8975,6 @@ packages:
     resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
     engines: {node: '>=10'}
 
-  normalize-url@8.0.0:
-    resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==}
-    engines: {node: '>=14.16'}
-
   normalize-url@8.0.1:
     resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
     engines: {node: '>=14.16'}
@@ -9277,6 +8995,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'}
+
   nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
 
@@ -9304,6 +9026,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'}
@@ -9316,15 +9042,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:
@@ -9344,10 +9075,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==}
 
@@ -9380,10 +9107,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'}
@@ -9394,12 +9117,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.2:
+    resolution: {integrity: sha512-KixtXWN9RGdS8WHPfDo7qsOYiivCbl+VeLBT+7HBTtJebBO6aXr/bpZXr+TwY2COecdY82VeBghm31mLYQVZlQ==}
 
   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'}
@@ -9428,10 +9154,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'}
@@ -9483,6 +9205,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==}
 
@@ -9499,10 +9224,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'}
@@ -9534,43 +9255,49 @@ packages:
     resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
     engines: {node: 20 || >=22}
 
+  path-to-regexp@0.1.10:
+    resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
+
   path-to-regexp@0.1.7:
     resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
 
   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==}
 
@@ -9580,8 +9307,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==}
@@ -9591,14 +9318,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'}
@@ -9607,8 +9337,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'
@@ -9629,6 +9359,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'}
@@ -9641,10 +9374,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==}
 
@@ -9666,18 +9395,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==}
 
@@ -9696,6 +9417,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}
@@ -9873,8 +9598,8 @@ packages:
     resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==}
     engines: {node: ^10 || ^12 || >=14}
 
-  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:
@@ -9933,10 +9658,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'}
@@ -9947,8 +9668,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:
@@ -9958,12 +9679,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'}
@@ -10075,21 +9796,17 @@ 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'}
 
   querystringify@2.2.0:
@@ -10115,9 +9832,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'}
@@ -10133,12 +9847,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==}
@@ -10245,30 +9963,16 @@ 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.14.0:
     resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
 
-  regenerator-transform@0.15.2:
-    resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==}
-
   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.3:
+    resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==}
+    engines: {node: '>= 0.4'}
 
   rehype-external-links@3.0.0:
     resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==}
@@ -10346,8 +10050,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:
@@ -10361,18 +10065,16 @@ 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@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
     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
 
@@ -10395,6 +10097,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==}
 
@@ -10404,8 +10110,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==}
@@ -10417,8 +10127,8 @@ packages:
   sanitize-html@2.13.0:
     resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==}
 
-  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
 
@@ -10435,6 +10145,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==}
 
@@ -10464,20 +10177,41 @@ packages:
     engines: {node: '>=10'}
     hasBin: true
 
+  semver@7.6.3:
+    resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
+    engines: {node: '>=10'}
+    hasBin: true
+
   send@0.18.0:
     resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
     engines: {node: '>= 0.8.0'}
 
+  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==}
     engines: {node: '>= 0.8.0'}
 
+  serve-static@1.16.2:
+    resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
+    engines: {node: '>= 0.8.0'}
+
   set-blocking@2.0.0:
     resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
 
   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==}
 
@@ -10488,13 +10222,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==}
@@ -10524,6 +10254,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==}
 
@@ -10635,10 +10369,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'}
@@ -10647,6 +10377,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'}
@@ -10677,6 +10410,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==}
 
@@ -10724,6 +10461,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}
@@ -10738,8 +10480,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
 
@@ -10754,9 +10496,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
@@ -10776,8 +10515,8 @@ packages:
       react-dom:
         optional: true
 
-  storybook@8.2.6:
-    resolution: {integrity: sha512-8j30wDxQmkcqI0fWcSYFsUCjErsY1yTWbTW+yjbwM8DyW18Cud6CwbFRCxjFsH+2M0CjP6Pqs/m1PGI0vcQscQ==}
+  storybook@8.3.3:
+    resolution: {integrity: sha512-FG2KAVQN54T9R6voudiEftehtkXtLO+YVGP2gBPfacEdDQjY++ld7kTbHzpTT/bpCDx7Yq3dqOegLm9arVJfYw==}
     hasBin: true
 
   stream-browserify@3.0.0:
@@ -10789,10 +10528,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'}
@@ -10826,12 +10561,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@1.1.1:
     resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
 
@@ -10899,8 +10645,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:
@@ -10941,8 +10687,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
@@ -10961,20 +10707,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
 
@@ -11001,8 +10735,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==}
@@ -11017,10 +10751,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==}
 
@@ -11031,10 +10761,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'}
@@ -11107,8 +10845,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==}
@@ -11149,20 +10887,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'}
@@ -11202,10 +10940,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'}
@@ -11222,17 +10956,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==}
 
@@ -11321,14 +11071,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==}
 
@@ -11353,28 +11103,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.20.0:
+    resolution: {integrity: sha512-AITZfPuxubm31Sx0vr8bteSalEbs9wQb/BOBi9FPlD9Qpd6HxZ4Q0+hI742jBhkPb4RT2v5MQzaW5VhRVyj+9A==}
+    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:
@@ -11388,10 +11129,6 @@ 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==}
 
@@ -11472,8 +11209,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
@@ -11485,6 +11222,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==}
 
@@ -11510,8 +11251,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:
@@ -11519,6 +11260,7 @@ packages:
       less: '*'
       lightningcss: ^1.21.0
       sass: '*'
+      sass-embedded: '*'
       stylus: '*'
       sugarss: '*'
       terser: ^5.4.0
@@ -11531,6 +11273,8 @@ packages:
         optional: true
       sass:
         optional: true
+      sass-embedded:
+        optional: true
       stylus:
         optional: true
       sugarss:
@@ -11617,9 +11361,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==}
 
@@ -11645,12 +11386,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:
@@ -11659,8 +11394,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'
@@ -11673,6 +11408,14 @@ packages:
       typescript:
         optional: true
 
+  vue@3.5.10:
+    resolution: {integrity: sha512-Vy2kmJwHPlouC/tSnIgXVg03SG+9wSqT1xu1Vehc+ChsXsRd7jLkKgMltVEFOzUdBr3uFwBCG+41LJtfAcBRng==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
   vuedraggable@4.1.0:
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
     peerDependencies:
@@ -11682,29 +11425,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'}
@@ -11763,6 +11500,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
@@ -11790,9 +11531,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'}
@@ -11808,15 +11546,12 @@ 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}
 
-  ws@8.14.2:
-    resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
+  ws@8.17.1:
+    resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
     engines: {node: '>=10.0.0'}
     peerDependencies:
       bufferutil: ^4.0.1
@@ -11933,7 +11668,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:
@@ -11945,17 +11680,11 @@ snapshots:
 
   '@ampproject/remapping@2.2.1':
     dependencies:
-      '@jridgewell/gen-mapping': 0.3.2
-      '@jridgewell/trace-mapping': 0.3.18
-
-  '@apidevtools/openapi-schemas@2.1.0': {}
+      '@jridgewell/gen-mapping': 0.3.5
+      '@jridgewell/trace-mapping': 0.3.25
 
   '@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
@@ -12465,13 +12194,13 @@ snapshots:
 
   '@babel/code-frame@7.23.5':
     dependencies:
-      '@babel/highlight': 7.23.4
+      '@babel/highlight': 7.24.7
       chalk: 2.4.2
 
   '@babel/code-frame@7.24.7':
     dependencies:
       '@babel/highlight': 7.24.7
-      picocolors: 1.0.0
+      picocolors: 1.0.1
 
   '@babel/compat-data@7.23.5': {}
 
@@ -12524,17 +12253,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
@@ -12551,70 +12269,19 @@ 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@8.1.1)
-      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':
     dependencies:
       '@babel/types': 7.24.7
 
-  '@babel/helper-function-name@7.23.0':
-    dependencies:
-      '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
-
   '@babel/helper-function-name@7.24.7':
     dependencies:
       '@babel/template': 7.24.7
       '@babel/types': 7.24.7
 
-  '@babel/helper-hoist-variables@7.22.5':
-    dependencies:
-      '@babel/types': 7.24.7
-
   '@babel/helper-hoist-variables@7.24.7':
     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
@@ -12628,796 +12295,222 @@ snapshots:
 
   '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5)':
     dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-module-imports': 7.22.15
-      '@babel/helper-simple-access': 7.22.5
-      '@babel/helper-split-export-declaration': 7.22.6
-      '@babel/helper-validator-identifier': 7.24.7
-
-  '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-environment-visitor': 7.24.7
-      '@babel/helper-module-imports': 7.24.7
-      '@babel/helper-simple-access': 7.24.7
-      '@babel/helper-split-export-declaration': 7.24.7
-      '@babel/helper-validator-identifier': 7.24.7
-    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
-
-  '@babel/helper-simple-access@7.24.7':
-    dependencies:
-      '@babel/traverse': 7.24.7
-      '@babel/types': 7.24.7
-    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
-
-  '@babel/helper-split-export-declaration@7.24.7':
-    dependencies:
-      '@babel/types': 7.24.7
-
-  '@babel/helper-string-parser@7.24.7': {}
-
-  '@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
-      '@babel/traverse': 7.23.5
-      '@babel/types': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/helpers@7.24.7':
-    dependencies:
-      '@babel/template': 7.24.7
-      '@babel/types': 7.24.7
-
-  '@babel/highlight@7.23.4':
-    dependencies:
-      '@babel/helper-validator-identifier': 7.24.7
-      chalk: 2.4.2
-      js-tokens: 4.0.0
-
-  '@babel/highlight@7.24.7':
-    dependencies:
-      '@babel/helper-validator-identifier': 7.24.7
-      chalk: 2.4.2
-      js-tokens: 4.0.0
-      picocolors: 1.0.0
-
-  '@babel/parser@7.24.7':
-    dependencies:
-      '@babel/types': 7.24.7
-
-  '@babel/plugin-bugfix-firefox-class-in-computed-class-key@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-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-
-  '@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)':
-    dependencies:
-      '@babel/core': 7.23.5
-      '@babel/helper-plugin-utils': 7.22.5
-
-  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)':
-    dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.22.5
-    optional: true
-
-  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5)':
-    dependencies:
-      '@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/core': 7.23.5
+      '@babel/helper-environment-visitor': 7.24.7
+      '@babel/helper-module-imports': 7.22.15
+      '@babel/helper-simple-access': 7.22.5
+      '@babel/helper-split-export-declaration': 7.24.7
+      '@babel/helper-validator-identifier': 7.24.7
 
-  '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.7)':
+  '@babel/helper-module-transforms@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
+      '@babel/helper-environment-visitor': 7.24.7
+      '@babel/helper-module-imports': 7.24.7
+      '@babel/helper-simple-access': 7.24.7
+      '@babel/helper-split-export-declaration': 7.24.7
+      '@babel/helper-validator-identifier': 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/helper-plugin-utils@7.22.5': {}
 
-  '@babel/plugin-transform-flow-strip-types@7.23.3(@babel/core@7.24.7)':
+  '@babel/helper-simple-access@7.22.5':
     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/types': 7.24.7
 
-  '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)':
+  '@babel/helper-simple-access@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/traverse': 7.24.7
+      '@babel/types': 7.24.7
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.7)':
+  '@babel/helper-split-export-declaration@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/types': 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/helper-string-parser@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/helper-string-parser@7.25.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/helper-validator-identifier@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/helper-validator-identifier@7.25.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/helper-validator-option@7.23.5': {}
 
-  '@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/helper-validator-option@7.24.7': {}
 
-  '@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.7)':
+  '@babel/helpers@7.23.5':
     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
+      '@babel/template': 7.22.15
+      '@babel/traverse': 7.24.7
+      '@babel/types': 7.24.7
     transitivePeerDependencies:
       - supports-color
 
-  '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.7)':
+  '@babel/helpers@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/template': 7.24.7
+      '@babel/types': 7.24.7
 
-  '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.7)':
+  '@babel/highlight@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/helper-validator-identifier': 7.24.7
+      chalk: 2.4.2
+      js-tokens: 4.0.0
+      picocolors: 1.0.1
 
-  '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)':
+  '@babel/parser@7.24.7':
     dependencies:
-      '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/types': 7.24.7
 
-  '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.7)':
+  '@babel/parser@7.25.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/types': 7.25.7
 
-  '@babel/plugin-transform-numeric-separator@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/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7)
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-async-generators@7.8.4(@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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)':
     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/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-bigint@7.8.3(@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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5)':
     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/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)':
     dependencies:
       '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
+      '@babel/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)':
     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/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-import-meta@7.10.4(@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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-property-literals@7.24.7(@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-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)':
     dependencies:
       '@babel/core': 7.24.7
-      '@babel/helper-plugin-utils': 7.24.7
-      regenerator-transform: 0.15.2
+      '@babel/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-reserved-words@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-shorthand-properties@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-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)':
+  '@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.24.7
-      '@babel/helper-skip-transparent-expression-wrappers': 7.24.7
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-sticky-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-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)':
+  '@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.24.7
+      '@babel/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-typeof-symbol@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-plugin-utils': 7.24.7
+      '@babel/core': 7.23.5
+      '@babel/helper-plugin-utils': 7.22.5
 
-  '@babel/plugin-transform-typescript@7.23.5(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-numeric-separator@7.10.4(@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-typescript': 7.23.3(@babel/core@7.24.7)
-    transitivePeerDependencies:
-      - supports-color
+      '@babel/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-object-rest-spread@7.8.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-object-rest-spread@7.8.3(@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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/plugin-transform-unicode-regex@7.24.7(@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-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-optional-catch-binding@7.8.3(@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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/preset-env@7.24.7(@babel/core@7.24.7)':
+  '@babel/plugin-syntax-optional-chaining@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-chaining@7.8.3(@babel/core@7.24.7)':
     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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@babel/preset-modules@0.1.6-no-external-plugins(@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/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.24.7)':
     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/helper-plugin-utils': 7.22.5
+    optional: true
 
-  '@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:
@@ -13425,7 +12518,7 @@ snapshots:
 
   '@babel/template@7.22.15':
     dependencies:
-      '@babel/code-frame': 7.23.5
+      '@babel/code-frame': 7.24.7
       '@babel/parser': 7.24.7
       '@babel/types': 7.24.7
 
@@ -13443,12 +12536,12 @@ snapshots:
 
   '@babel/traverse@7.23.5':
     dependencies:
-      '@babel/code-frame': 7.23.5
+      '@babel/code-frame': 7.24.7
       '@babel/generator': 7.24.7
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-function-name': 7.23.0
-      '@babel/helper-hoist-variables': 7.22.5
-      '@babel/helper-split-export-declaration': 7.22.6
+      '@babel/helper-environment-visitor': 7.24.7
+      '@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/types': 7.24.7
       debug: 4.3.5(supports-color@8.1.1)
@@ -13477,26 +12570,32 @@ snapshots:
       '@babel/helper-validator-identifier': 7.24.7
       to-fast-properties: 2.0.0
 
+  '@babel/types@7.25.7':
+    dependencies:
+      '@babel/helper-string-parser': 7.25.7
+      '@babel/helper-validator-identifier': 7.25.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:
@@ -13516,73 +12615,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
@@ -13590,14 +12689,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
@@ -13618,24 +12717,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.3.0':
     dependencies:
       tslib: 2.6.3
     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
 
@@ -13645,6 +12738,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
 
@@ -13657,6 +12753,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
 
@@ -13669,6 +12768,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
 
@@ -13681,6 +12783,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
 
@@ -13693,6 +12798,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
 
@@ -13705,6 +12813,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
 
@@ -13717,6 +12828,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
 
@@ -13729,6 +12843,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
 
@@ -13741,6 +12858,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
 
@@ -13753,6 +12873,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
 
@@ -13765,6 +12888,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
 
@@ -13777,6 +12903,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
 
@@ -13789,6 +12918,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
 
@@ -13801,6 +12933,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
 
@@ -13813,6 +12948,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
 
@@ -13825,6 +12963,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
 
@@ -13837,6 +12978,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
 
@@ -13849,9 +12993,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
 
@@ -13864,6 +13014,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
 
@@ -13876,6 +13029,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
 
@@ -13888,6 +13044,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
 
@@ -13900,6 +13059,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
 
@@ -13912,6 +13074,9 @@ 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@8.57.0)':
     dependencies:
       eslint: 8.57.0
@@ -13970,20 +13135,18 @@ snapshots:
 
   '@eslint/object-schema@2.1.4': {}
 
-  '@fal-works/esbuild-plugin-global-externals@2.1.2': {}
+  '@fastify/accept-negotiator@2.0.0': {}
 
-  '@fastify/accept-negotiator@1.0.0': {}
-
-  '@fastify/accepts@4.3.0':
+  '@fastify/accepts@5.0.0':
     dependencies:
       accepts: 1.3.8
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.1
 
-  '@fastify/ajv-compiler@3.5.0':
+  '@fastify/ajv-compiler@4.0.1':
     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@1.2.1':
     dependencies:
@@ -13991,94 +13154,99 @@ snapshots:
 
   '@fastify/busboy@2.1.0': {}
 
-  '@fastify/cookie@9.3.1':
+  '@fastify/busboy@3.0.0': {}
+
+  '@fastify/cookie@10.0.0':
     dependencies:
       cookie-signature: 1.2.1
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.1
 
-  '@fastify/cors@9.0.1':
+  '@fastify/cors@10.0.0':
     dependencies:
-      fastify-plugin: 4.5.0
-      mnemonist: 0.39.6
+      fastify-plugin: 5.0.1
+      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.0':
     dependencies:
       express: 4.19.2
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.1
     transitivePeerDependencies:
       - supports-color
 
-  '@fastify/fast-json-stringify-compiler@4.3.0':
+  '@fastify/fast-json-stringify-compiler@5.0.1':
     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.1
       fast-querystring: 1.1.2
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.1
       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/reply-from@9.0.1':
+  '@fastify/multipart@9.0.0':
     dependencies:
-      '@fastify/error': 3.4.0
+      '@fastify/busboy': 3.0.0
+      '@fastify/deepmerge': 2.0.0
+      '@fastify/error': 4.0.0
+      fastify-plugin: 5.0.1
+      secure-json-parse: 3.0.0
+
+  '@fastify/reply-from@11.0.1':
+    dependencies:
+      '@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.20.0
 
-  '@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':
+  '@fastify/static@8.0.0':
     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
-      glob: 8.1.0
-      p-limit: 3.1.0
+      fastify-plugin: 5.0.1
+      fastq: 1.17.1
+      glob: 11.0.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.1
       fastq: 1.17.1
-      glob: 10.3.10
+      glob: 11.0.0
 
-  '@fastify/view@8.2.0':
+  '@fastify/view@10.0.0':
     dependencies:
-      fastify-plugin: 4.5.0
-      hashlru: 2.3.0
+      fastify-plugin: 5.0.1
+      toad-cache: 3.7.0
 
-  '@fastify/view@9.1.0':
+  '@fastify/view@10.0.1':
     dependencies:
-      fastify-plugin: 4.5.0
+      fastify-plugin: 5.0.1
       toad-cache: 3.7.0
 
   '@github/webauthn-json@2.1.1': {}
@@ -14121,79 +13289,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.3.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':
@@ -14221,18 +13389,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':
@@ -14289,7 +13445,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
@@ -14373,7 +13529,7 @@ snapshots:
 
   '@jest/source-map@29.6.3':
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/trace-mapping': 0.3.25
       callsites: 3.1.0
       graceful-fs: 4.2.11
 
@@ -14404,7 +13560,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
@@ -14420,21 +13576,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
-
-  '@jridgewell/gen-mapping@0.3.2':
-    dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
-      '@jridgewell/trace-mapping': 0.3.18
+      typescript: 5.6.2
 
   '@jridgewell/gen-mapping@0.3.5':
     dependencies:
@@ -14444,8 +13594,6 @@ snapshots:
 
   '@jridgewell/resolve-uri@3.1.0': {}
 
-  '@jridgewell/set-array@1.1.2': {}
-
   '@jridgewell/set-array@1.2.1': {}
 
   '@jridgewell/source-map@0.3.6':
@@ -14457,6 +13605,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
@@ -14475,7 +13625,7 @@ snapshots:
 
   '@lukeed/csprng@1.0.1': {}
 
-  '@lukeed/ms@2.0.1': {}
+  '@lukeed/ms@2.0.2': {}
 
   '@mcaptcha/core-glue@0.1.0-alpha-5': {}
 
@@ -14489,23 +13639,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
@@ -14526,20 +13676,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.30.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.30.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:
@@ -14590,88 +13740,97 @@ snapshots:
       outvariant: 1.4.2
       strict-event-emitter: 0.5.1
 
-  '@napi-rs/canvas-android-arm64@0.1.53':
+  '@mswjs/interceptors@0.35.9':
+    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.3(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.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(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.3(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.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)
     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.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)':
     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.3(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(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.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3))':
     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.3(reflect-metadata@0.2.2)(rxjs@7.8.1)
+      '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(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.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)
 
   '@noble/hashes@1.4.0': {}
 
@@ -14691,7 +13850,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:
@@ -14994,7 +14153,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
@@ -15010,88 +14169,92 @@ snapshots:
       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)
@@ -15109,16 +14272,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
@@ -15203,6 +14366,10 @@ snapshots:
     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': {}
@@ -15231,8 +14398,6 @@ snapshots:
 
   '@sindresorhus/is@7.0.0': {}
 
-  '@sindresorhus/merge-streams@2.3.0': {}
-
   '@sindresorhus/merge-streams@4.0.0': {}
 
   '@sinonjs/commons@2.0.0':
@@ -15622,134 +14787,124 @@ snapshots:
 
   '@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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/csf-plugin': 8.3.3(storybook@8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
+    dependencies:
+      '@storybook/addon-actions': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-backgrounds': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-controls': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-docs': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-highlight': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-measure': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-outline': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-toolbars': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/addon-viewport': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      storybook: 8.3.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/test': 8.3.3(storybook@8.3.3(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.3(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.3(react@18.3.1)(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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
@@ -15758,7 +14913,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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       telejson: 7.2.0
       ts-dedent: 2.2.0
       util-deprecate: 1.0.2
@@ -15766,57 +14921,9 @@ 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.3(storybook@8.3.3(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.3(storybook@8.3.3(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
@@ -15824,163 +14931,35 @@ snapshots:
       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
-      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
+      storybook: 8.3.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(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)
+      esbuild: 0.23.1
+      esbuild-register: 3.5.0(esbuild@0.23.1)
       express: 4.19.2
+      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:
@@ -15988,305 +14967,153 @@ snapshots:
       - supports-color
       - utf-8-validate
 
-  '@storybook/csf-plugin@8.1.11':
+  '@storybook/csf-plugin@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@storybook/csf-tools': 8.1.11
+      storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
       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))':
-    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)
-      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.3(storybook@8.3.3(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.2
+      storybook: 8.3.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(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.3(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.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)
       find-up: 5.0.0
       magic-string: 0.30.10
       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.3(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.3(@storybook/test@8.3.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/react-dom-shim': 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/theming': 8.3.3(storybook@8.3.3(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.7.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)
+      storybook: 8.3.3(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/test': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      typescript: 5.6.2
 
-  '@storybook/router@8.1.11':
-    dependencies:
-      '@storybook/client-logger': 8.1.11
-      memoizerific: 1.11.3
-      qs: 6.11.1
-
-  '@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.3(storybook@8.3.3(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/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))':
-    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
+      lodash: 4.17.21
+      prettier: 3.3.3
+      storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
 
-  '@storybook/theming@8.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
+  '@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))':
     dependencies:
-      '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.3.1)
-      '@storybook/client-logger': 8.1.11
+      '@storybook/csf': 0.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.3(storybook@8.3.3(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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
+      util: 0.12.5
 
-  '@storybook/types@8.1.11':
+  '@storybook/theming@8.3.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.3(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.3(storybook@8.3.3(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.10(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(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))
+      storybook: 8.3.3(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.10(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))':
-    dependencies:
-      '@storybook/docs-tools': 8.1.11(encoding@0.1.13)(prettier@3.3.3)
-      '@storybook/global': 5.0.0
-      '@storybook/preview-api': 8.1.11
-      '@storybook/types': 8.1.11
-      '@vue/compiler-core': 3.4.34
-      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))':
+  '@storybook/vue3@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(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.3(storybook@8.3.3(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.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@vue/compiler-core': 3.4.37
+      storybook: 8.3.3(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.10(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)':
@@ -16473,7 +15300,7 @@ snapshots:
     dependencies:
       defer-to-connect: 2.0.1
 
-  '@testing-library/dom@10.1.0':
+  '@testing-library/dom@10.4.0':
     dependencies:
       '@babel/code-frame': 7.24.7
       '@babel/runtime': 7.23.4
@@ -16486,7 +15313,7 @@ snapshots:
 
   '@testing-library/dom@9.3.4':
     dependencies:
-      '@babel/code-frame': 7.23.5
+      '@babel/code-frame': 7.24.7
       '@babel/runtime': 7.23.4
       '@types/aria-query': 5.0.1
       aria-query: 5.1.3
@@ -16495,34 +15322,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.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(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.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))
+      vue: 3.5.10(typescript@5.6.2)
     optionalDependencies:
-      '@vue/compiler-sfc': 3.4.37
+      '@vue/compiler-sfc': 3.5.10
     transitivePeerDependencies:
       - '@vue/server-renderer'
 
@@ -16538,6 +15359,8 @@ snapshots:
 
   '@twemoji/parser@15.0.0': {}
 
+  '@twemoji/parser@15.1.0': {}
+
   '@twemoji/parser@15.1.1': {}
 
   '@types/accepts@1.3.7':
@@ -16609,40 +15432,24 @@ snapshots:
 
   '@types/core-js@2.5.8': {}
 
-  '@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:
@@ -16666,7 +15473,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
 
@@ -16710,6 +15517,11 @@ snapshots:
       expect: 29.7.0
       pretty-format: 29.7.0
 
+  '@types/jest@29.5.13':
+    dependencies:
+      expect: 29.7.0
+      pretty-format: 29.7.0
+
   '@types/js-yaml@4.0.9': {}
 
   '@types/jsdom@21.1.7':
@@ -16728,6 +15540,8 @@ snapshots:
 
   '@types/jsrsasign@10.5.14': {}
 
+  '@types/katex@0.16.7': {}
+
   '@types/keyv@3.1.4':
     dependencies:
       '@types/node': 20.14.12
@@ -16766,8 +15580,6 @@ snapshots:
     dependencies:
       '@types/node': 20.14.12
 
-  '@types/node@18.17.15': {}
-
   '@types/node@20.11.5':
     dependencies:
       undici-types: 5.26.5
@@ -16780,7 +15592,11 @@ snapshots:
     dependencies:
       undici-types: 5.26.5
 
-  '@types/nodemailer@6.4.15':
+  '@types/node@22.7.5':
+    dependencies:
+      undici-types: 6.19.8
+
+  '@types/nodemailer@6.4.16':
     dependencies:
       '@types/node': 20.14.12
 
@@ -16805,9 +15621,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
@@ -16819,8 +15635,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/proxy-addr@2.0.3':
@@ -16861,7 +15675,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
 
@@ -16926,6 +15740,10 @@ snapshots:
     dependencies:
       '@types/node': 20.14.12
 
+  '@types/ws@8.5.12':
+    dependencies:
+      '@types/node': 20.14.12
+
   '@types/yargs-parser@21.0.0': {}
 
   '@types/yargs@17.0.19':
@@ -16937,26 +15755,6 @@ 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)':
-    dependencies:
-      '@eslint-community/regexpp': 4.11.0
-      '@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
-      debug: 4.3.4(supports-color@5.5.0)
-      eslint: 9.8.0
-      graphemer: 1.4.0
-      ignore: 5.3.1
-      natural-compare: 1.4.0
-      semver: 7.5.4
-      ts-api-utils: 1.3.0(typescript@5.3.3)
-    optionalDependencies:
-      typescript: 5.3.3
-    transitivePeerDependencies:
-      - supports-color
-
   '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6)':
     dependencies:
       '@eslint-community/regexpp': 4.11.0
@@ -17015,16 +15813,21 @@ snapshots:
     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
 
@@ -17067,10 +15870,18 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/scope-manager@6.11.0':
+  '@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@8.1.1)
+      eslint: 9.8.0
+    optionalDependencies:
+      typescript: 5.6.2
+    transitivePeerDependencies:
+      - supports-color
 
   '@typescript-eslint/scope-manager@6.21.0':
     dependencies:
@@ -17087,18 +15898,6 @@ 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)':
-    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)
-      debug: 4.3.5(supports-color@8.1.1)
-      eslint: 9.8.0
-      ts-api-utils: 1.3.0(typescript@5.3.3)
-    optionalDependencies:
-      typescript: 5.3.3
-    transitivePeerDependencies:
-      - supports-color
-
   '@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.1.6)':
     dependencies:
       '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
@@ -17135,7 +15934,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  '@typescript-eslint/types@6.11.0': {}
+  '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)':
+    dependencies:
+      '@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@8.1.1)
+      eslint: 9.8.0
+      ts-api-utils: 1.3.0(typescript@5.6.2)
+    optionalDependencies:
+      typescript: 5.6.2
+    transitivePeerDependencies:
+      - supports-color
 
   '@typescript-eslint/types@6.21.0': {}
 
@@ -17143,20 +15952,6 @@ snapshots:
 
   '@typescript-eslint/types@7.17.0': {}
 
-  '@typescript-eslint/typescript-estree@6.11.0(typescript@5.3.3)':
-    dependencies:
-      '@typescript-eslint/types': 6.11.0
-      '@typescript-eslint/visitor-keys': 6.11.0
-      debug: 4.3.5(supports-color@8.1.1)
-      globby: 11.1.0
-      is-glob: 4.0.3
-      semver: 7.5.4
-      ts-api-utils: 1.3.0(typescript@5.3.3)
-    optionalDependencies:
-      typescript: 5.3.3
-    transitivePeerDependencies:
-      - supports-color
-
   '@typescript-eslint/typescript-estree@6.21.0(typescript@5.1.6)':
     dependencies:
       '@typescript-eslint/types': 6.21.0
@@ -17202,19 +15997,20 @@ 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@8.1.1)
+      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@6.21.0(eslint@8.57.0)(typescript@5.1.6)':
     dependencies:
@@ -17255,10 +16051,16 @@ snapshots:
       - supports-color
       - typescript
 
-  '@typescript-eslint/visitor-keys@6.11.0':
+  '@typescript-eslint/utils@7.17.0(eslint@9.8.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.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@6.21.0':
     dependencies:
@@ -17277,27 +16079,46 @@ 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.10(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.10(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))':
+  '@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
-      debug: 4.3.4(supports-color@5.5.0)
+      debug: 4.3.5(supports-color@8.1.1)
       istanbul-lib-coverage: 3.2.2
       istanbul-lib-report: 3.0.1
       istanbul-lib-source-maps: 5.0.4
       istanbul-reports: 3.1.6
       magic-string: 0.30.10
       magicast: 0.3.4
-      picocolors: 1.0.0
+      picocolors: 1.0.1
+      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.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.79.3)(terser@5.33.0))':
+    dependencies:
+      '@ampproject/remapping': 2.2.1
+      '@bcoe/v8-coverage': 0.2.3
+      debug: 4.3.5(supports-color@8.1.1)
+      istanbul-lib-coverage: 3.2.2
+      istanbul-lib-report: 3.0.1
+      istanbul-lib-source-maps: 5.0.4
+      istanbul-reports: 3.1.6
+      magic-string: 0.30.10
+      magicast: 0.3.4
+      picocolors: 1.0.1
       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)(sass@1.79.3)(terser@5.33.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -17307,6 +16128,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.2':
+    dependencies:
+      tinyrainbow: 1.2.0
+
   '@vitest/runner@1.6.0':
     dependencies:
       '@vitest/utils': 1.6.0
@@ -17323,6 +16159,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
@@ -17330,47 +16170,44 @@ 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.2
+      tinyrainbow: 1.2.0
+
+  '@vitest/utils@2.1.2':
+    dependencies:
+      '@vitest/pretty-format': 2.1.2
+      loupe: 3.1.2
+      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.6':
     dependencies:
-      '@volar/source-map': 2.4.0-alpha.18
+      '@volar/source-map': 2.4.6
 
   '@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.6': {}
 
   '@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.6':
     dependencies:
-      '@volar/language-core': 2.4.0-alpha.18
+      '@volar/language-core': 2.4.6
       path-browserify: 1.0.1
       vscode-uri: 3.0.8
 
-  '@vue/compiler-core@3.4.31':
-    dependencies:
-      '@babel/parser': 7.24.7
-      '@vue/shared': 3.4.31
-      entities: 4.5.0
-      estree-walker: 2.0.2
-      source-map-js: 1.2.0
-
-  '@vue/compiler-core@3.4.34':
-    dependencies:
-      '@babel/parser': 7.24.7
-      '@vue/shared': 3.4.34
-      entities: 4.5.0
-      estree-walker: 2.0.2
-      source-map-js: 1.2.0
-
   '@vue/compiler-core@3.4.37':
     dependencies:
       '@babel/parser': 7.24.7
@@ -17379,16 +16216,24 @@ snapshots:
       estree-walker: 2.0.2
       source-map-js: 1.2.0
 
-  '@vue/compiler-dom@3.4.34':
+  '@vue/compiler-core@3.5.10':
     dependencies:
-      '@vue/compiler-core': 3.4.34
-      '@vue/shared': 3.4.34
+      '@babel/parser': 7.25.7
+      '@vue/shared': 3.5.10
+      entities: 4.5.0
+      estree-walker: 2.0.2
+      source-map-js: 1.2.0
 
   '@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-sfc@3.4.37':
     dependencies:
       '@babel/parser': 7.24.7
@@ -17398,7 +16243,19 @@ snapshots:
       '@vue/shared': 3.4.37
       estree-walker: 2.0.2
       magic-string: 0.30.10
-      postcss: 8.4.40
+      postcss: 8.4.47
+      source-map-js: 1.2.0
+
+  '@vue/compiler-sfc@3.5.10':
+    dependencies:
+      '@babel/parser': 7.25.7
+      '@vue/compiler-core': 3.5.10
+      '@vue/compiler-dom': 3.5.10
+      '@vue/compiler-ssr': 3.5.10
+      '@vue/shared': 3.5.10
+      estree-walker: 2.0.2
+      magic-string: 0.30.11
+      postcss: 8.4.47
       source-map-js: 1.2.0
 
   '@vue/compiler-ssr@3.4.37':
@@ -17406,47 +16263,59 @@ snapshots:
       '@vue/compiler-dom': 3.4.37
       '@vue/shared': 3.4.37
 
+  '@vue/compiler-ssr@3.5.10':
+    dependencies:
+      '@vue/compiler-dom': 3.5.10
+      '@vue/shared': 3.5.10
+
   '@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.34
-      '@vue/shared': 3.4.34
+      '@vue/compiler-dom': 3.4.37
+      '@vue/shared': 3.4.37
       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
-      '@vue/compiler-dom': 3.4.34
+      '@volar/language-core': 2.4.6
+      '@vue/compiler-dom': 3.4.37
       '@vue/compiler-vue2': 2.7.16
-      '@vue/shared': 3.4.34
+      '@vue/shared': 3.4.37
       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.10':
+    dependencies:
+      '@vue/shared': 3.5.10
+
   '@vue/runtime-core@3.4.37':
     dependencies:
       '@vue/reactivity': 3.4.37
       '@vue/shared': 3.4.37
 
+  '@vue/runtime-core@3.5.10':
+    dependencies:
+      '@vue/reactivity': 3.5.10
+      '@vue/shared': 3.5.10
+
   '@vue/runtime-dom@3.4.37':
     dependencies:
       '@vue/reactivity': 3.4.37
@@ -17454,40 +16323,36 @@ snapshots:
       '@vue/shared': 3.4.37
       csstype: 3.1.3
 
+  '@vue/runtime-dom@3.5.10':
+    dependencies:
+      '@vue/reactivity': 3.5.10
+      '@vue/runtime-core': 3.5.10
+      '@vue/shared': 3.5.10
+      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/shared@3.4.34': {}
+  '@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2))':
+    dependencies:
+      '@vue/compiler-ssr': 3.5.10
+      '@vue/shared': 3.5.10
+      vue: 3.5.10(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/test-utils@2.4.1(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))':
     dependencies:
       js-beautify: 1.14.9
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.10(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))
-
-  '@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
+      '@vue/server-renderer': 3.5.10(vue@3.5.10(typescript@5.6.2))
 
   abbrev@1.1.1: {}
 
@@ -17529,14 +16394,6 @@ snapshots:
 
   acorn@8.12.1: {}
 
-  address@1.2.2: {}
-
-  agent-base@6.0.2:
-    dependencies:
-      debug: 4.3.5(supports-color@8.1.1)
-    transitivePeerDependencies:
-      - supports-color
-
   agent-base@7.1.0:
     dependencies:
       debug: 4.3.5(supports-color@8.1.1)
@@ -17566,14 +16423,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
@@ -17633,8 +16490,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: {}
@@ -17661,8 +16516,6 @@ snapshots:
       tar-stream: 3.1.6
       zip-stream: 6.0.1
 
-  archy@1.0.0: {}
-
   arg@5.0.2: {}
 
   argon2@0.40.1:
@@ -17690,25 +16543,32 @@ snapshots:
       call-bind: 1.0.2
       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:
@@ -17733,6 +16593,17 @@ snapshots:
       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: {}
@@ -17754,27 +16625,21 @@ snapshots:
       pvutils: 1.1.3
       tslib: 2.6.3
 
-  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
-
+  assert-never@1.2.1: {}
+
+  assert-plus@1.0.0: {}
+
   assertion-error@1.1.0: {}
 
+  assertion-error@2.0.1: {}
+
   ast-types@0.16.1:
     dependencies:
       tslib: 2.6.3
 
   astral-regex@2.0.0: {}
 
-  astring@1.8.6: {}
+  astring@1.9.0: {}
 
   async-mutex@0.5.0:
     dependencies:
@@ -17792,14 +16657,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@8.1.1)
+      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:
@@ -17813,21 +16678,21 @@ 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.0:
+  axios@1.7.4:
     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:
       - 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:
@@ -17835,10 +16700,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
@@ -17883,30 +16744,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
@@ -17973,8 +16810,6 @@ snapshots:
     dependencies:
       open: 8.4.2
 
-  big-integer@1.6.51: {}
-
   bin-check@4.1.0:
     dependencies:
       execa: 0.7.0
@@ -17993,12 +16828,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: {}
@@ -18024,14 +16853,27 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  body-parser@1.20.3:
+    dependencies:
+      bytes: 3.1.2
+      content-type: 1.0.5
+      debug: 2.6.9
+      depd: 2.0.0
+      destroy: 1.2.0
+      http-errors: 2.0.0
+      iconv-lite: 0.4.24
+      on-finished: 2.4.1
+      qs: 6.13.0
+      raw-body: 2.5.2
+      type-is: 1.6.18
+      unpipe: 1.0.0
+    transitivePeerDependencies:
+      - supports-color
+
   boolbase@1.0.0: {}
 
   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
@@ -18113,7 +16955,7 @@ snapshots:
       node-gyp-build: 4.8.1
     optional: true
 
-  bullmq@5.10.4:
+  bullmq@5.13.2:
     dependencies:
       cron-parser: 4.8.1
       ioredis: 5.4.1
@@ -18131,8 +16973,6 @@ snapshots:
     dependencies:
       streamsearch: 1.1.0
 
-  bytes@3.0.0: {}
-
   bytes@3.1.2: {}
 
   cac@6.7.14: {}
@@ -18163,7 +17003,7 @@ snapshots:
       http-cache-semantics: 4.1.1
       keyv: 4.5.4
       mimic-response: 4.0.0
-      normalize-url: 8.0.0
+      normalize-url: 8.0.1
       responselike: 3.0.0
 
   cacheable-request@12.0.1:
@@ -18193,6 +17033,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: {}
@@ -18240,6 +17088,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.2
+      pathval: 2.0.0
+
   chalk-template@1.1.0:
     dependencies:
       chalk: 5.3.0
@@ -18270,32 +17126,34 @@ snapshots:
     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:
@@ -18307,6 +17165,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.20.0
+      whatwg-mimetype: 4.0.0
+
   cheerio@1.0.0-rc.12:
     dependencies:
       cheerio-select: 2.1.0
@@ -18331,7 +17203,7 @@ snapshots:
 
   chownr@2.0.0: {}
 
-  chromatic@11.5.6: {}
+  chromatic@11.10.4: {}
 
   ci-info@3.7.1: {}
 
@@ -18389,18 +17261,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: {}
@@ -18441,6 +17305,8 @@ snapshots:
 
   commander@10.0.1: {}
 
+  commander@12.1.0: {}
+
   commander@2.20.3: {}
 
   commander@6.2.1: {}
@@ -18465,22 +17331,6 @@ snapshots:
       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
-
   computeds@0.0.1: {}
 
   concat-map@0.0.1: {}
@@ -18527,9 +17377,7 @@ snapshots:
 
   cookie@0.6.0: {}
 
-  core-js-compat@3.37.1:
-    dependencies:
-      browserslist: 4.23.0
+  cookie@0.7.2: {}
 
   core-util-is@1.0.2: {}
 
@@ -18566,10 +17414,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:
@@ -18599,13 +17447,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:
@@ -18631,49 +17475,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:
@@ -18685,9 +17529,54 @@ snapshots:
 
   csstype@3.1.3: {}
 
-  cypress@13.13.1:
+  cypress@13.14.2:
+    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.5(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.0
+      supports-color: 8.1.1
+      tmp: 0.2.3
+      untildify: 4.0.0
+      yauzl: 2.10.0
+
+  cypress@13.15.0:
     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
@@ -18741,6 +17630,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
@@ -18765,12 +17672,22 @@ snapshots:
     optionalDependencies:
       supports-color: 5.5.0
 
+  debug@4.3.5(supports-color@5.5.0):
+    dependencies:
+      ms: 2.1.2
+    optionalDependencies:
+      supports-color: 5.5.0
+
   debug@4.3.5(supports-color@8.1.1):
     dependencies:
       ms: 2.1.2
     optionalDependencies:
       supports-color: 8.1.1
 
+  debug@4.3.7:
+    dependencies:
+      ms: 2.1.3
+
   decamelize-keys@1.1.1:
     dependencies:
       decamelize: 1.2.0
@@ -18814,6 +17731,8 @@ snapshots:
     dependencies:
       type-detect: 4.0.8
 
+  deep-eql@5.0.2: {}
+
   deep-equal@2.2.0:
     dependencies:
       call-bind: 1.0.2
@@ -18838,16 +17757,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: {}
 
@@ -18856,7 +17772,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.0
+      object-keys: 1.1.1
 
   delayed-stream@1.0.0: {}
 
@@ -18868,23 +17788,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@8.1.1)
-    transitivePeerDependencies:
-      - supports-color
-
   devlop@1.1.0:
     dependencies:
       dequal: 2.0.3
@@ -18919,6 +17826,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
@@ -18927,17 +17840,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: {}
 
@@ -18977,10 +17908,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
@@ -19002,8 +17938,6 @@ snapshots:
 
   env-paths@2.2.1: {}
 
-  envinfo@7.8.1: {}
-
   err-code@2.0.3: {}
 
   error-ex@1.3.2:
@@ -19052,6 +17986,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.3
+      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
@@ -19066,28 +18055,40 @@ 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
       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
       is-date-object: 1.0.5
       is-symbol: 1.0.4
 
-  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@8.1.1)
-      esbuild: 0.19.11
+      esbuild: 0.23.1
     transitivePeerDependencies:
       - supports-color
 
@@ -19195,8 +18196,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: {}
@@ -19235,43 +18265,44 @@ 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.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.5.4)
+      '@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.8.0)(typescript@5.6.2))(eslint@9.8.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-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.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.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.8.0)(typescript@5.6.2)
     transitivePeerDependencies:
       - eslint-import-resolver-typescript
       - eslint-import-resolver-webpack
@@ -19291,6 +18322,20 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  eslint-plugin-vue@9.28.0(eslint@9.8.0):
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0)
+      eslint: 9.8.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.8.0)
+      xml-name-validator: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+
   eslint-rule-docs@1.1.235: {}
 
   eslint-scope@7.2.2:
@@ -19421,7 +18466,7 @@ snapshots:
 
   estree-walker@3.0.3:
     dependencies:
-      '@types/estree': 1.0.5
+      '@types/estree': 1.0.6
 
   esutils@2.0.3: {}
 
@@ -19505,16 +18550,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
@@ -19536,34 +18581,70 @@ snapshots:
 
   exponential-backoff@3.1.1: {}
 
-  express@4.19.2:
+  express@4.19.2:
+    dependencies:
+      accepts: 1.3.8
+      array-flatten: 1.1.1
+      body-parser: 1.20.2
+      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
+      escape-html: 1.0.3
+      etag: 1.8.1
+      finalhandler: 1.2.0
+      fresh: 0.5.2
+      http-errors: 2.0.0
+      merge-descriptors: 1.0.1
+      methods: 1.1.2
+      on-finished: 2.4.1
+      parseurl: 1.3.3
+      path-to-regexp: 0.1.7
+      proxy-addr: 2.0.7
+      qs: 6.11.0
+      range-parser: 1.2.1
+      safe-buffer: 5.2.1
+      send: 0.18.0
+      serve-static: 1.15.0
+      setprototypeof: 1.2.0
+      statuses: 2.0.1
+      type-is: 1.6.18
+      utils-merge: 1.0.1
+      vary: 1.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  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
@@ -19595,7 +18676,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: {}
 
@@ -19609,18 +18690,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: {}
 
@@ -19632,7 +18714,7 @@ snapshots:
 
   fast-safe-stringify@2.1.1: {}
 
-  fast-uri@2.2.0: {}
+  fast-uri@2.4.0: {}
 
   fast-uri@3.0.1: {}
 
@@ -19640,7 +18722,7 @@ snapshots:
     dependencies:
       strnum: 1.0.5
 
-  fast-xml-parser@4.4.0:
+  fast-xml-parser@4.4.1:
     dependencies:
       strnum: 1.0.5
 
@@ -19659,34 +18741,33 @@ snapshots:
     dependencies:
       semver: 7.6.0
 
-  fastify-plugin@4.5.0: {}
+  fastify-plugin@4.5.1: {}
+
+  fastify-plugin@5.0.1: {}
 
-  fastify-raw-body@4.3.0:
+  fastify-raw-body@5.0.0:
     dependencies:
-      fastify-plugin: 4.5.0
-      raw-body: 2.5.2
+      fastify-plugin: 5.0.1
+      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.1
+      '@fastify/error': 4.0.0
+      '@fastify/fast-json-stringify-compiler': 5.0.1
       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.1.0
+      light-my-request: 6.1.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:
@@ -19696,10 +18777,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
@@ -19713,8 +18790,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
@@ -19731,20 +18806,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
 
@@ -19780,11 +18851,17 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  find-cache-dir@2.1.0:
+  finalhandler@1.3.1:
     dependencies:
-      commondir: 1.0.1
-      make-dir: 2.1.0
-      pkg-dir: 3.0.0
+      debug: 2.6.9
+      encodeurl: 2.0.0
+      escape-html: 1.0.3
+      on-finished: 2.4.1
+      parseurl: 1.3.3
+      statuses: 2.0.1
+      unpipe: 1.0.0
+    transitivePeerDependencies:
+      - supports-color
 
   find-cache-dir@3.3.2:
     dependencies:
@@ -19792,18 +18869,14 @@ snapshots:
       make-dir: 3.1.0
       pkg-dir: 4.2.0
 
-  find-my-way@8.2.0:
+  find-my-way@9.1.0:
     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
@@ -19840,16 +18913,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@8.1.1)
+      debug: 4.3.7
 
   for-each@0.3.3:
     dependencies:
@@ -19866,12 +18939,6 @@ snapshots:
 
   form-data-encoder@4.0.2: {}
 
-  form-data@2.3.3:
-    dependencies:
-      asynckit: 0.4.0
-      combined-stream: 1.0.8
-      mime-types: 2.1.35
-
   form-data@4.0.0:
     dependencies:
       asynckit: 0.4.0
@@ -19935,6 +19002,13 @@ snapshots:
       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: {}
 
   gensync@1.0.0-beta.2: {}
@@ -19950,6 +19024,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.2
+
   get-package-type@0.1.0: {}
 
   get-stream@3.0.0: {}
@@ -19972,6 +19054,12 @@ snapshots:
       call-bind: 1.0.2
       get-intrinsic: 1.2.1
 
+  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:
       resolve-pkg-maps: 1.0.0
@@ -19984,18 +19072,6 @@ snapshots:
     dependencies:
       assert-plus: 1.0.0
 
-  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:
@@ -20011,8 +19087,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
@@ -20059,11 +19133,11 @@ snapshots:
 
   globals@14.0.0: {}
 
-  globals@15.8.0: {}
+  globals@15.9.0: {}
 
   globalthis@1.0.3:
     dependencies:
-      define-properties: 1.2.0
+      define-properties: 1.2.1
 
   globby@11.1.0:
     dependencies:
@@ -20074,18 +19148,9 @@ 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
-
   gopd@1.0.1:
     dependencies:
-      get-intrinsic: 1.2.1
+      get-intrinsic: 1.2.4
 
   got@11.8.5:
     dependencies:
@@ -20139,15 +19204,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
@@ -20173,28 +19229,40 @@ snapshots:
 
   has-property-descriptors@1.0.0:
     dependencies:
-      get-intrinsic: 1.2.1
+      get-intrinsic: 1.2.4
+
+  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@1.0.3:
     dependencies:
       function-bind: 1.1.2
 
   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
@@ -20213,7 +19281,7 @@ snapshots:
 
   highlight.js@10.7.3: {}
 
-  highlight.js@11.9.0: {}
+  highlight.js@11.10.0: {}
 
   hosted-git-info@2.8.9: {}
 
@@ -20235,6 +19303,13 @@ snapshots:
 
   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
@@ -20242,6 +19317,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:
@@ -20261,11 +19343,11 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  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:
@@ -20279,13 +19361,6 @@ snapshots:
 
   http_ece@1.2.0: {}
 
-  https-proxy-agent@5.0.1:
-    dependencies:
-      agent-base: 6.0.2
-      debug: 4.3.5(supports-color@8.1.1)
-    transitivePeerDependencies:
-      - supports-color
-
   https-proxy-agent@7.0.2:
     dependencies:
       agent-base: 7.1.0
@@ -20293,13 +19368,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  https-proxy-agent@7.0.4:
-    dependencies:
-      agent-base: 7.1.0
-      debug: 4.3.5(supports-color@8.1.1)
-    transitivePeerDependencies:
-      - supports-color
-
   https-proxy-agent@7.0.5:
     dependencies:
       agent-base: 7.1.0
@@ -20315,7 +19383,7 @@ snapshots:
 
   human-signals@5.0.0: {}
 
-  human-signals@7.0.0: {}
+  human-signals@8.0.0: {}
 
   iconv-lite@0.4.24:
     dependencies:
@@ -20393,6 +19461,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.4
+
   intersection-observer@0.12.2: {}
 
   ioredis@5.4.1:
@@ -20414,7 +19488,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
 
@@ -20441,6 +19515,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: {}
@@ -20468,6 +19547,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
@@ -20500,8 +19587,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
@@ -20510,13 +19595,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:
@@ -20531,10 +19613,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: {}
@@ -20552,6 +19630,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: {}
@@ -20564,9 +19646,9 @@ snapshots:
     dependencies:
       has-tostringtag: 1.0.0
 
-  is-svg@5.0.1:
+  is-svg@5.1.0:
     dependencies:
-      fast-xml-parser: 4.4.0
+      fast-xml-parser: 4.4.1
 
   is-symbol@1.0.4:
     dependencies:
@@ -20580,6 +19662,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: {}
@@ -20590,7 +19676,7 @@ snapshots:
 
   is-weakref@1.0.2:
     dependencies:
-      call-bind: 1.0.2
+      call-bind: 1.0.7
 
   is-weakset@2.0.2:
     dependencies:
@@ -20611,8 +19697,6 @@ snapshots:
 
   isexe@3.1.1: {}
 
-  isobject@3.0.1: {}
-
   isstream@0.1.2: {}
 
   istanbul-lib-coverage@3.2.2: {}
@@ -20755,7 +19839,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
@@ -20814,7 +19898,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
@@ -20838,7 +19922,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
@@ -21015,6 +20099,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
+
   js-beautify@1.14.9:
     dependencies:
       config-chain: 1.1.13
@@ -21043,32 +20135,36 @@ snapshots:
 
   jschardet@3.0.0: {}
 
-  jscodeshift@0.15.1(@babel/preset-env@7.24.7(@babel/core@7.24.7)):
+  jsdoc-type-pratt-parser@4.1.0: {}
+
+  jsdom@24.1.1:
     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)
+      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
 
   jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3):
     dependencies:
@@ -21127,14 +20223,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: {}
@@ -21206,6 +20304,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:
@@ -21219,7 +20325,7 @@ snapshots:
       jwa: 2.0.0
       safe-buffer: 5.2.1
 
-  katex@0.16.9:
+  katex@0.16.10:
     dependencies:
       commander: 8.3.0
 
@@ -21243,12 +20349,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
@@ -21260,10 +20360,10 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
-  light-my-request@5.11.0:
+  light-my-request@6.1.0:
     dependencies:
-      cookie: 0.5.0
-      process-warning: 2.2.0
+      cookie: 0.7.2
+      process-warning: 4.0.0
       set-cookie-parser: 2.6.0
 
   lilconfig@3.1.1: {}
@@ -21288,11 +20388,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
@@ -21301,8 +20396,6 @@ snapshots:
     dependencies:
       p-locate: 5.0.0
 
-  lodash.debounce@4.0.8: {}
-
   lodash.defaults@4.2.0: {}
 
   lodash.get@4.4.2: {}
@@ -21341,6 +20434,8 @@ snapshots:
     dependencies:
       get-func-name: 2.0.2
 
+  loupe@3.1.2: {}
+
   lowercase-keys@2.0.0: {}
 
   lowercase-keys@3.0.0: {}
@@ -21378,6 +20473,10 @@ snapshots:
     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
@@ -21386,11 +20485,6 @@ 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
@@ -21546,7 +20640,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:
@@ -21556,6 +20650,8 @@ snapshots:
     dependencies:
       map-or-similar: 1.5.0
 
+  mensch@0.3.4: {}
+
   meow@9.0.0:
     dependencies:
       '@types/minimist': 1.2.2
@@ -21573,6 +20669,8 @@ snapshots:
 
   merge-descriptors@1.0.1: {}
 
+  merge-descriptors@1.0.3: {}
+
   merge-stream@2.0.0: {}
 
   merge2@1.4.1: {}
@@ -21774,7 +20872,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  micromatch@4.0.7:
+  micromatch@4.0.8:
     dependencies:
       braces: 3.0.3
       picomatch: 2.3.1
@@ -21787,6 +20885,8 @@ snapshots:
 
   mime@1.6.0: {}
 
+  mime@2.6.0: {}
+
   mime@3.0.0: {}
 
   mimic-fn@2.1.0: {}
@@ -21893,7 +20993,7 @@ snapshots:
       pkg-types: 1.0.3
       ufo: 1.3.2
 
-  mnemonist@0.39.6:
+  mnemonist@0.39.8:
     dependencies:
       obliterator: 2.0.4
 
@@ -21901,8 +21001,6 @@ snapshots:
 
   module-details-from-path@1.0.3: {}
 
-  mri@1.2.0: {}
-
   ms@2.0.0: {}
 
   ms@2.1.2: {}
@@ -21927,12 +21025,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
@@ -21952,7 +21050,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.9
+      '@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: {}
 
@@ -21996,8 +21116,6 @@ snapshots:
 
   negotiator@0.6.3: {}
 
-  neo-async@2.6.2: {}
-
   nested-property@4.0.0: {}
 
   netmask@2.0.2: {}
@@ -22027,14 +21145,8 @@ snapshots:
 
   node-addon-api@7.1.0: {}
 
-  node-dir@0.1.17:
-    dependencies:
-      minimatch: 3.1.2
-
   node-domexception@1.0.0: {}
 
-  node-fetch-native@1.0.2: {}
-
   node-fetch@2.7.0(encoding@0.1.13):
     dependencies:
       whatwg-url: 5.0.0
@@ -22055,7 +21167,7 @@ snapshots:
 
   node-gyp-build@4.8.1: {}
 
-  node-gyp@10.1.0:
+  node-gyp@10.2.0:
     dependencies:
       env-paths: 2.2.1
       exponential-backoff: 3.1.1
@@ -22063,7 +21175,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
@@ -22074,7 +21186,7 @@ snapshots:
 
   node-releases@2.0.14: {}
 
-  nodemailer@6.9.14: {}
+  nodemailer@6.9.15: {}
 
   nodemon@3.0.2:
     dependencies:
@@ -22089,10 +21201,10 @@ 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.4(supports-color@5.5.0)
+      debug: 4.3.5(supports-color@5.5.0)
       ignore-by-default: 1.0.1
       minimatch: 3.1.2
       pstree.remy: 1.1.8
@@ -22134,8 +21246,6 @@ snapshots:
 
   normalize-url@6.1.0: {}
 
-  normalize-url@8.0.0: {}
-
   normalize-url@8.0.1: {}
 
   npm-run-path@2.0.2:
@@ -22154,6 +21264,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
+
   nth-check@2.1.1:
     dependencies:
       boolbase: 1.0.0
@@ -22178,6 +21293,8 @@ snapshots:
 
   object-inspect@1.12.3: {}
 
+  object-inspect@1.13.2: {}
+
   object-is@1.1.5:
     dependencies:
       call-bind: 1.0.2
@@ -22192,24 +21309,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: {}
 
@@ -22223,8 +21347,6 @@ snapshots:
     dependencies:
       ee-first: 1.1.1
 
-  on-headers@1.0.2: {}
-
   once@1.4.0:
     dependencies:
       wrappy: 1.0.2
@@ -22266,23 +21388,11 @@ snapshots:
   optionator@0.9.4:
     dependencies:
       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
+      fast-levenshtein: 2.0.6
+      levn: 0.4.1
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+      word-wrap: 1.2.5
 
   os-filter-obj@2.0.0:
     dependencies:
@@ -22292,12 +21402,14 @@ snapshots:
 
   ospath@1.2.2: {}
 
-  otpauth@9.3.1:
+  otpauth@9.3.2:
     dependencies:
       '@noble/hashes': 1.4.0
 
   outvariant@1.4.2: {}
 
+  outvariant@1.4.3: {}
+
   p-cancelable@2.1.1: {}
 
   p-cancelable@3.0.0: {}
@@ -22318,10 +21430,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
@@ -22375,6 +21483,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: {}
@@ -22387,8 +21499,6 @@ snapshots:
 
   path-browserify@1.0.1: {}
 
-  path-exists@3.0.0: {}
-
   path-exists@4.0.0: {}
 
   path-is-absolute@1.0.1: {}
@@ -22411,32 +21521,36 @@ snapshots:
       lru-cache: 11.0.0
       minipass: 7.1.2
 
+  path-to-regexp@0.1.10: {}
+
   path-to-regexp@0.1.7: {}
 
   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: {}
@@ -22444,18 +21558,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
@@ -22474,11 +21590,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:
@@ -22494,6 +21610,8 @@ snapshots:
 
   picocolors@1.0.1: {}
 
+  picocolors@1.1.0: {}
+
   picomatch@2.3.1: {}
 
   pid-port@1.0.0:
@@ -22502,8 +21620,6 @@ snapshots:
 
   pify@2.3.0: {}
 
-  pify@4.0.1: {}
-
   pino-abstract-transport@1.2.0:
     dependencies:
       readable-stream: 4.3.0
@@ -22533,18 +21649,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
@@ -22565,140 +21673,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.15
       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.15:
@@ -22711,15 +21821,15 @@ 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: {}
@@ -22730,11 +21840,11 @@ snapshots:
       picocolors: 1.0.0
       source-map-js: 1.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: {}
 
@@ -22776,8 +21886,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
@@ -22797,7 +21905,7 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  proc-log@3.0.0: {}
+  proc-log@4.2.0: {}
 
   process-exists@5.0.0:
     dependencies:
@@ -22805,10 +21913,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: {}
 
   promise-limit@2.7.0: {}
@@ -22942,24 +22050,19 @@ snapshots:
 
   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:
     dependencies:
       side-channel: 1.0.4
 
-  qs@6.11.1:
+  qs@6.13.0:
     dependencies:
-      side-channel: 1.0.4
+      side-channel: 1.0.6
 
   querystringify@2.2.0: {}
 
@@ -22975,8 +22078,6 @@ snapshots:
 
   quick-lru@5.1.1: {}
 
-  ramda@0.29.0: {}
-
   random-seed@0.3.0:
     dependencies:
       json-stringify-safe: 5.0.1
@@ -22992,15 +22093,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
 
@@ -23009,9 +22117,9 @@ 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:
@@ -23133,36 +22241,20 @@ snapshots:
 
   reflect-metadata@0.2.2: {}
 
-  regenerate-unicode-properties@10.1.0:
-    dependencies:
-      regenerate: 1.4.2
-
-  regenerate@1.4.2: {}
-
   regenerator-runtime@0.14.0: {}
 
-  regenerator-transform@0.15.2:
-    dependencies:
-      '@babel/runtime': 7.23.4
-
   regexp.prototype.flags@1.5.0:
     dependencies:
       call-bind: 1.0.2
       define-properties: 1.2.0
       functions-have-names: 1.2.3
 
-  regexpu-core@5.3.2:
+  regexp.prototype.flags@1.5.3:
     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:
-    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:
@@ -23266,7 +22358,7 @@ snapshots:
       onetime: 5.1.2
       signal-exit: 3.0.7
 
-  ret@0.4.3: {}
+  ret@0.5.0: {}
 
   retry@0.12.0: {}
 
@@ -23274,34 +22366,32 @@ snapshots:
 
   rfdc@1.3.0: {}
 
-  rimraf@2.6.3:
-    dependencies:
-      glob: 7.2.3
+  rfdc@1.4.1: {}
 
   rimraf@3.0.2:
     dependencies:
       glob: 7.2.3
 
-  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: {}
@@ -23328,6 +22418,13 @@ snapshots:
       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
+
   safe-buffer@5.1.2: {}
 
   safe-buffer@5.2.1: {}
@@ -23338,9 +22435,15 @@ snapshots:
       get-intrinsic: 1.2.1
       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: {}
 
@@ -23355,7 +22458,7 @@ snapshots:
       parse-srcset: 1.0.2
       postcss: 8.4.38
 
-  sass@1.77.8:
+  sass@1.79.3:
     dependencies:
       chokidar: 3.5.3
       immutable: 4.2.2
@@ -23373,6 +22476,8 @@ snapshots:
 
   secure-json-parse@2.7.0: {}
 
+  secure-json-parse@3.0.0: {}
+
   seedrandom@3.0.5: {}
 
   semver-regex@4.0.5: {}
@@ -23393,6 +22498,8 @@ snapshots:
     dependencies:
       lru-cache: 6.0.0
 
+  semver@7.6.3: {}
+
   send@0.18.0:
     dependencies:
       debug: 2.6.9
@@ -23411,6 +22518,24 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  send@0.19.0:
+    dependencies:
+      debug: 2.6.9
+      depd: 2.0.0
+      destroy: 1.2.0
+      encodeurl: 1.0.2
+      escape-html: 1.0.3
+      etag: 1.8.1
+      fresh: 0.5.2
+      http-errors: 2.0.0
+      mime: 1.6.0
+      ms: 2.1.3
+      on-finished: 2.4.1
+      range-parser: 1.2.1
+      statuses: 2.0.1
+    transitivePeerDependencies:
+      - supports-color
+
   serve-static@1.15.0:
     dependencies:
       encodeurl: 1.0.2
@@ -23420,10 +22545,35 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
+  serve-static@1.16.2:
+    dependencies:
+      encodeurl: 2.0.0
+      escape-html: 1.0.3
+      parseurl: 1.3.3
+      send: 0.19.0
+    transitivePeerDependencies:
+      - supports-color
+
   set-blocking@2.0.0: {}
 
   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: {}
@@ -23433,35 +22583,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:
@@ -23495,6 +22641,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: {}
@@ -23586,8 +22739,6 @@ snapshots:
 
   slash@3.0.0: {}
 
-  slash@5.1.0: {}
-
   slice-ansi@3.0.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -23600,6 +22751,8 @@ 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:
@@ -23631,6 +22784,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
@@ -23683,6 +22838,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
@@ -23695,16 +22862,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@8.1.1)
+      debug: 4.3.7
       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
 
@@ -23716,53 +22883,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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.3(storybook@8.3.3(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.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/components': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/core-events': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/manager-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))
+      '@storybook/types': 8.3.3(storybook@8.3.3(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.3(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.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)
     transitivePeerDependencies:
-      - '@babel/preset-env'
       - bufferutil
       - supports-color
       - utf-8-validate
@@ -23782,8 +22919,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  stream-wormhole@1.1.0: {}
-
   streamsearch@1.1.0: {}
 
   streamx@2.15.0:
@@ -23820,18 +22955,37 @@ snapshots:
       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
       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
       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@1.1.1:
     dependencies:
       safe-buffer: 5.1.2
@@ -23885,17 +23039,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:
@@ -23931,7 +23085,7 @@ snapshots:
 
   symbol-tree@3.2.4: {}
 
-  systeminformation@5.22.11: {}
+  systeminformation@5.23.5: {}
 
   tar-stream@3.1.6:
     dependencies:
@@ -23956,20 +23110,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
@@ -24000,7 +23141,7 @@ snapshots:
     dependencies:
       real-require: 0.2.0
 
-  three@0.167.0: {}
+  three@0.169.0: {}
 
   throttle-debounce@5.0.2: {}
 
@@ -24010,16 +23151,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: {}
@@ -24087,11 +23230,15 @@ 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: {}
 
-  ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.23.0)(jest@29.7.0(@types/node@20.14.12))(typescript@5.1.6):
+  ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.12))(typescript@5.1.6):
     dependencies:
       bs-logger: 0.2.6
       fast-json-stable-stringify: 2.1.0
@@ -24107,7 +23254,7 @@ snapshots:
       '@babel/core': 7.24.7
       '@jest/types': 29.6.3
       babel-jest: 29.7.0(@babel/core@7.24.7)
-      esbuild: 0.23.0
+      esbuild: 0.23.1
 
   ts-map@1.0.3: {}
 
@@ -24133,7 +23280,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
@@ -24143,12 +23290,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
@@ -24178,8 +23325,6 @@ snapshots:
 
   type-fest@0.8.1: {}
 
-  type-fest@1.4.0: {}
-
   type-fest@2.19.0: {}
 
   type-fest@4.20.1: {}
@@ -24195,6 +23340,12 @@ snapshots:
       get-intrinsic: 1.2.1
       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
@@ -24202,6 +23353,14 @@ snapshots:
       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
@@ -24210,12 +23369,30 @@ snapshots:
       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
       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: {}
 
   typedoc@0.25.13(typescript@5.1.6):
@@ -24226,7 +23403,7 @@ snapshots:
       shiki: 0.14.7
       typescript: 5.1.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
@@ -24245,7 +23422,7 @@ snapshots:
       yargs: 17.7.2
     optionalDependencies:
       ioredis: 5.4.1
-      pg: 8.12.0
+      pg: 8.13.0
     transitivePeerDependencies:
       - supports-color
 
@@ -24257,10 +23434,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: {}
 
@@ -24274,7 +23450,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
@@ -24283,22 +23459,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: {}
+  undici@6.20.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: {}
-
-  unicorn-magic@0.1.0: {}
+  unicorn-magic@0.3.0: {}
 
   unified@11.0.4:
     dependencies:
@@ -24318,10 +23487,6 @@ snapshots:
     dependencies:
       imurmurhash: 0.1.4
 
-  unique-string@3.0.0:
-    dependencies:
-      crypto-random-string: 4.0.0
-
   unist-util-is@6.0.0:
     dependencies:
       '@types/unist': 3.0.2
@@ -24409,21 +23574,22 @@ 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.10(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.10(typescript@5.6.2)
+      vue-demi: 0.14.7(vue@3.5.10(typescript@5.6.2))
 
   v8-to-istanbul@9.2.0:
     dependencies:
-      '@jridgewell/trace-mapping': 0.3.18
+      '@jridgewell/trace-mapping': 0.3.25
       '@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
@@ -24448,18 +23614,19 @@ 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@8.1.1)
       pathe: 1.1.2
-      picocolors: 1.0.0
-      vite: 5.3.5(@types/node@20.14.12)(sass@1.77.8)(terser@5.31.3)
+      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
@@ -24467,25 +23634,25 @@ 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.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.3
+      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
@@ -24504,8 +23671,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
@@ -24515,6 +23682,43 @@ 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.79.3)(terser@5.33.0):
+    dependencies:
+      '@vitest/expect': 1.6.0
+      '@vitest/runner': 1.6.0
+      '@vitest/snapshot': 1.6.0
+      '@vitest/spy': 1.6.0
+      '@vitest/utils': 1.6.0
+      acorn-walk: 8.3.2
+      chai: 4.3.10
+      debug: 4.3.4(supports-color@5.5.0)
+      execa: 8.0.1
+      local-pkg: 0.5.0
+      magic-string: 0.30.10
+      pathe: 1.1.2
+      picocolors: 1.0.0
+      std-env: 3.7.0
+      strip-literal: 2.1.0
+      tinybench: 2.6.0
+      tinypool: 0.8.4
+      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
+      happy-dom: 10.0.3
+      jsdom: 24.1.1
+    transitivePeerDependencies:
+      - less
+      - lightningcss
+      - sass
+      - sass-embedded
       - stylus
       - sugarss
       - supports-color
@@ -24549,41 +23753,39 @@ 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.10(typescript@5.6.2)):
     dependencies:
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.10(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.10(typescript@5.6.2)):
     dependencies:
       '@babel/parser': 7.24.7
       '@babel/types': 7.24.7
-      '@vue/compiler-dom': 3.4.34
-      '@vue/compiler-sfc': 3.4.37
+      '@vue/compiler-dom': 3.4.37
+      '@vue/compiler-sfc': 3.5.10
       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.10(typescript@5.6.2)
+      vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.10(typescript@5.6.2))
 
   vue-eslint-parser@9.4.3(eslint@9.8.0):
     dependencies:
@@ -24598,28 +23800,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.10(typescript@5.6.2)):
     dependencies:
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.10(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.6
+      '@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:
@@ -24631,40 +23826,39 @@ snapshots:
     optionalDependencies:
       typescript: 5.5.4
 
-  vuedraggable@4.1.0(vue@3.4.37(typescript@5.5.4)):
+  vue@3.5.10(typescript@5.6.2):
+    dependencies:
+      '@vue/compiler-dom': 3.5.10
+      '@vue/compiler-sfc': 3.5.10
+      '@vue/runtime-dom': 3.5.10
+      '@vue/server-renderer': 3.5.10(vue@3.5.10(typescript@5.6.2))
+      '@vue/shared': 3.5.10
+    optionalDependencies:
+      typescript: 5.6.2
+
+  vuedraggable@4.1.0(vue@3.5.10(typescript@5.6.2)):
     dependencies:
       sortablejs: 1.14.0
-      vue: 3.4.37(typescript@5.5.4)
+      vue: 3.5.10(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
@@ -24675,6 +23869,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:
@@ -24735,6 +23937,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
@@ -24761,8 +23971,6 @@ snapshots:
 
   word-wrap@1.2.5: {}
 
-  wordwrap@1.0.0: {}
-
   wrap-ansi@6.2.0:
     dependencies:
       ansi-styles: 4.3.0
@@ -24783,18 +23991,12 @@ 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
       signal-exit: 3.0.7
 
-  ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.4):
+  ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
     optionalDependencies:
       bufferutil: 4.0.8
       utf-8-validate: 6.0.4
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 6b19c64e82b9177897e9b402a101b7e1f8dccdb1..c1e062a78908949860260c59d2983523170bbfda 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,6 +1,8 @@
 packages:
  - 'packages/backend'
+ - 'packages/frontend-shared'
  - 'packages/frontend'
+ - 'packages/frontend-embed'
  - 'packages/sw'
  - 'packages/misskey-js'
  - 'packages/misskey-js/generator'
diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs
index fcf29cef2209bf60d176dceb2be71a1d0cb2d327..66380a0e17fca34e9bca35ee527fdf05b5396321 100644
--- a/scripts/build-assets.mjs
+++ b/scripts/build-assets.mjs
@@ -15,6 +15,7 @@ import { build as buildLocales } from '../locales/index.js';
 import generateDTS from '../locales/generateDTS.js';
 import meta from '../package.json' with { type: "json" };
 import buildTarball from './tarball.mjs';
+import { localesVersion } from '../locales/version.js';
 
 const configDir = fileURLToPath(new URL('../.config', import.meta.url));
 const configPath = process.env.MISSKEY_CONFIG_YML
@@ -56,10 +57,10 @@ async function copyFrontendLocales() {
 
   await fs.mkdir('./built/_frontend_dist_/locales', { recursive: true });
 
-  const v = { '_version_': meta.version };
+  const v = { '_version_': localesVersion };
 
   for (const [lang, locale] of Object.entries(locales)) {
-    await fs.writeFile(`./built/_frontend_dist_/locales/${lang}.${meta.version}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
+    await fs.writeFile(`./built/_frontend_dist_/locales/${lang}.${localesVersion}.json`, JSON.stringify({ ...locale, ...v }), 'utf-8');
   }
 }
 
@@ -72,11 +73,13 @@ async function buildBackendScript() {
 
   for (const file of [
     './packages/backend/src/server/web/boot.js',
+    './packages/backend/src/server/web/boot.embed.js',
     './packages/backend/src/server/web/bios.js',
     './packages/backend/src/server/web/cli.js'
   ]) {
     let source = await fs.readFile(file, { encoding: 'utf-8' });
-    source = source.replaceAll('LANGS', JSON.stringify(Object.keys(locales)));
+    source = source.replaceAll(/\bLANGS\b/g, JSON.stringify(Object.keys(locales)));
+    source = source.replaceAll(/\bLANGS_VERSION\b/g, JSON.stringify(localesVersion));
     const { code } = await terser.minify(source, { toplevel: true });
     await fs.writeFile(`./packages/backend/built/server/web/${path.basename(file)}`, code);
   }
@@ -87,6 +90,7 @@ async function buildBackendStyle() {
 
   for (const file of [
     './packages/backend/src/server/web/style.css',
+    './packages/backend/src/server/web/style.embed.css',
     './packages/backend/src/server/web/bios.css',
     './packages/backend/src/server/web/cli.css',
     './packages/backend/src/server/web/error.css'
diff --git a/scripts/clean-all.js b/scripts/clean-all.js
index 3df2f2ceff694efdcac29bf764c3b98b4158d769..a35868e9e78807bf21d62a409cbeaa820efeec1f 100644
--- a/scripts/clean-all.js
+++ b/scripts/clean-all.js
@@ -10,9 +10,15 @@ const fs = require('fs');
 	fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/backend/node_modules', { recursive: true, force: true });
 
+	fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/frontend-shared/node_modules', { recursive: true, force: true });
+
 	fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/frontend/node_modules', { recursive: true, force: true });
 
+	fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/frontend-embed/node_modules', { recursive: true, force: true });
+
 	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
 
diff --git a/scripts/clean.js b/scripts/clean.js
index 6d8182fec2aaf4ea26ad59de2999082303ce36ac..ebff55dacddb071b9e8b03eb86c96d38a0f3e2bd 100644
--- a/scripts/clean.js
+++ b/scripts/clean.js
@@ -7,7 +7,9 @@ const fs = require('fs');
 
 (async () => {
 	fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/frontend-shared/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/frontend-embed/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
index a5c071fa7ef00fd51b71df115cd158ab3f333f9c..ea3b017ba5bb061e4c5825671e3e2fb3a675e907 100644
--- a/scripts/dev.mjs
+++ b/scripts/dev.mjs
@@ -70,12 +70,24 @@ execa('pnpm', ['--filter', 'backend', 'dev'], {
 	stderr: process.stderr,
 });
 
+execa('pnpm', ['--filter', 'frontend-shared', 'watch'], {
+	cwd: _dirname + '/../',
+	stdout: process.stdout,
+	stderr: process.stderr,
+});
+
 execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], {
 	cwd: _dirname + '/../',
 	stdout: process.stdout,
 	stderr: process.stderr,
 });
 
+execa('pnpm', ['--filter', 'frontend-embed', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], {
+	cwd: _dirname + '/../',
+	stdout: process.stdout,
+	stderr: process.stderr,
+});
+
 execa('pnpm', ['--filter', 'sw', 'watch'], {
 	cwd: _dirname + '/../',
 	stdout: process.stdout,
diff --git a/sharkey-locales/ar-SA.yml b/sharkey-locales/ar-SA.yml
new file mode 100644
index 0000000000000000000000000000000000000000..552ad8864f4f1cce8c4c3da10ab12e1956b140fb
--- /dev/null
+++ b/sharkey-locales/ar-SA.yml
@@ -0,0 +1,4 @@
+---
+forwardReport: "وجّه البلاغ إلى المثيل البعيد"
+forwardReportIsAnonymous: "في المثيل البعيد سيظهر المبلّغ كحساب مجهول."
+abuseMarkAsResolved: "علّم البلاغ كمحلول"
diff --git a/sharkey-locales/bn-BD.yml b/sharkey-locales/bn-BD.yml
new file mode 100644
index 0000000000000000000000000000000000000000..be6c2c401fc06b7cd791f91ea9eb26090a50c0c6
--- /dev/null
+++ b/sharkey-locales/bn-BD.yml
@@ -0,0 +1,5 @@
+---
+disableDrawer: "ড্রয়ার মেনু প্রদর্শন করবেন না"
+forwardReport: "রিমোট ইন্সত্যান্সে অভিযোগটি পাঠান"
+forwardReportIsAnonymous: "আপনার তথ্য রিমোট ইন্সত্যান্সে পাঠানো হবে না এবং একটি বেনামী সিস্টেম অ্যাকাউন্ট হিসাবে প্রদর্শিত হবে।"
+abuseMarkAsResolved: "অভিযোগটিকে সমাধাকৃত হিসাবে চিহ্নিত করুন"
diff --git a/sharkey-locales/ca-ES.yml b/sharkey-locales/ca-ES.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1eff1cd2c1742168d0a4eb3284a7068f5b6fb935
--- /dev/null
+++ b/sharkey-locales/ca-ES.yml
@@ -0,0 +1,10 @@
+---
+disableDrawer: "No mostrar els menús en calaixos"
+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."
+abuseMarkAsResolved: "Marca la denúncia com a resolta"
+replies: "Respostes"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "Es poden afegir reaccions fent clic al botó '{reaction}'. Prova reaccionant a aquesta nota!"
+    reactDone: "Pots desfer una reacció fent clic al botó '{undo}'."
diff --git a/sharkey-locales/cs-CZ.yml b/sharkey-locales/cs-CZ.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b1bee8efec106c0bc7dc69dcdfc9cc9bd56e9d1e
--- /dev/null
+++ b/sharkey-locales/cs-CZ.yml
@@ -0,0 +1,6 @@
+---
+disableDrawer: "Nepoužívat šuplíkové menu"
+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č."
+abuseMarkAsResolved: "Označit nahlášení jako vyřešené"
+replies: "Odpovědi"
diff --git a/sharkey-locales/da-DK.yml b/sharkey-locales/da-DK.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/da-DK.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/de-DE.yml b/sharkey-locales/de-DE.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6f52905083183cc81a3999984fba841129bdec50
--- /dev/null
+++ b/sharkey-locales/de-DE.yml
@@ -0,0 +1,53 @@
+---
+introMisskey: "Willkommen! Sharkey ist eine dezentralisierte Open-Source Microblogging-Platform.\nVerfasse „Notizen“ um mitzuteilen, was gerade passiert oder um Ereignisse mit anderen zu teilen. 📡\nMit „Reaktionen“ kannst du außerdem schnell deine Gefühle über Notizen anderer Benutzer zum Ausdruck bringen. 👍\nEine neue Welt wartet auf dich! 🚀"
+poweredByMisskeyDescription: "{name} ist einer der durch die Open-Source-Plattform <b>Sharkey</b> betriebenen Dienste die auf Misskey basiert ist (meist als \"Misskey-Instanz\" bezeichnet)."
+renotedBy: "Geboostet von {user}"
+renote: "Boost"
+unrenote: "Boost zurücknehmen"
+renoted: "Boost getätigt."
+cantRenote: "Boosten dieses Beitrags nicht möglich."
+cantReRenote: "Boosten von einen Boost nicht möglich."
+inChannelRenote: "Kanal-interner Boost"
+flagAsBotDescription: "Aktiviere diese Option, falls dieses Benutzerkonto durch ein Programm gesteuert wird. Falls aktiviert, agiert es als Flag für andere Entwickler zur Verhinderung von endlosen Kettenreaktionen mit anderen Bots und lässt Sharkeys interne Systeme dieses Benutzerkonto als Bot behandeln."
+intro: "Sharkey ist installiert! Lass uns nun ein Administratorkonto einrichten."
+aboutMisskey: "Ãœber Sharkey"
+disableDrawer: "Keine ausfahrbaren Menüs verwenden"
+scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Sharkey überprüfen."
+forwardReport: "Meldung an fremde Instanz weiterleiten"
+forwardReportIsAnonymous: "Anstatt deines Benutzerkontos wird bei der fremden Instanz ein anonymes Systemkonto als Melder angezeigt."
+abuseMarkAsResolved: "Meldung als gelöst markieren"
+renotedCount: "Anzahl erhaltener Boosts"
+sendErrorReportsDescription: "Ist diese Option aktiviert, so werden beim Auftreten von Fehlern detaillierte Fehlerinformationen an Sharkey weitergegeben, was zur Verbesserung der Qualität von Sharkey beiträgt.\nEnthalten in diesen Informationen sind u.a. die Version deines Betriebssystems, welchen Browser du verwendest und ein Verlauf deiner Aktivitäten innerhalb Sharkey."
+misskeyUpdated: "Sharkey wurde aktualisiert!"
+didYouLikeMisskey: "Gefällt dir Sharkey?"
+pleaseDonate: "Sharkey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
+pleaseDonateInstance: "Du kannst {host} auch direkt unterstützen, indem du an deine Instanz Administration spendest."
+goToMisskey: "Zu Sharkey"
+donation: "Spenden"
+donationUrl: "Spenden-URL"
+_accountMigration:
+  moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Sharkey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon,  kann hiervon abweichen.)"
+_achievements:
+  _types:
+    _notes1:
+      title: "Hallo Sharkey!"
+      flavor: "Hab eine schöne Zeit mit Sharkey!"
+    _login1000:
+      flavor: "Danke, dass du Sharkey nutzt!"
+    _iLoveMisskey:
+      title: "I Love Sharkey"
+      description: "Sende \"I ❤ #Sharkey\""
+      flavor: "Danke, dass du Sharkey verwendest! - vom Entwicklerteam"
+    _client30min:
+      description: "Habe Sharkey für mindestens 30 Minuten geöffnet"
+    _client60min:
+      title: "Munter mit Sharkey"
+      description: "Habe Sharkey für mindestens 60 Minuten geöffnet"
+    _brainDiver:
+      flavor: "Sharkey-Sharkey La-Tu-Ma"
+_aboutMisskey:
+  about: "Sharkey ist Open-Source-Software basiert auf Misskey welche von syuilo seit 2014 entwickelt wird."
+  translation: "Sharkey übersetzen"
+  donate: "An Sharkey spenden"
+_notification:
+  youRenoted: "Boost deiner Notiz von {name}"
diff --git a/sharkey-locales/el-GR.yml b/sharkey-locales/el-GR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/el-GR.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml
new file mode 100644
index 0000000000000000000000000000000000000000..163fd0b0ae8ba90879280aeba31212c3dc72a4e2
--- /dev/null
+++ b/sharkey-locales/en-US.yml
@@ -0,0 +1,399 @@
+---
+introMisskey: "Welcome! Sharkey is an open source, decentralized microblogging service.\nCreate \"notes\" to share your thoughts with everyone around you. 📡\nWith \"reactions\", you can also quickly express your feelings about everyone's notes. 👍\nLet's explore a new world! 🚀"
+poweredByMisskeyDescription: "{name} is one of the services powered by the open source platform <b>Sharkey</b> which is based on Misskey (referred to as a \"Misskey instance\")."
+renotedBy: "Boosted by {user}"
+approvals: "Approvals"
+copyLinkRenote: "Copy boost link"
+deleteAndEditConfirm: "Are you sure you want to redraft this note? This means you will lose all reactions, boosts, and replies to it."
+openRemoteProfile: "Open remote profile"
+trustedLinkUrlPatterns: "Link to external site warning exclusion list"
+trustedLinkUrlPatternsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition. Using surrounding keywords with slashes will turn them into a regular expression. If you write only the domain name, it will be a backward match."
+mutuals: "Mutuals"
+isLocked: "Private account"
+isAdmin: "Administrator"
+isBot: "Bot user"
+open: "Open"
+emailDestination: "Destination address"
+date: "Date"
+renote: "Boost"
+unrenote: "Remove boost"
+renoted: "Boosted."
+quoted: "Quoted."
+rmboost: "Unboosted."
+renotedToX: "Boosted to {name}"
+cantRenote: "This post can't be boosted."
+cantReRenote: "A boost can't be boosted."
+inChannelRenote: "Channel-only Boost"
+muted: "Muted"
+renoteMute: "Mute Boosts"
+renoteMuted: "Boosts muted"
+renoteUnmute: "Unmute Boosts"
+markAsNSFW: "Mark all media from user as NSFW"
+markInstanceAsNSFW: "Mark as NSFW"
+nsfwConfirm: "Are you sure that you want to mark all media from this account as NSFW?"
+unNsfwConfirm: "Are you sure that you want to unmark all media from this account as NSFW?"
+approveConfirm: "Are you sure that you want to approve this account?"
+flagAsBotDescription: "Enable this option if this account is controlled by a program. If enabled, it will act as a flag for other developers to prevent endless interaction chains with other bots and adjust Sharkey's internal systems to treat this account as a bot."
+flagSpeakAsCat: "Speak as a cat"
+flagSpeakAsCatDescription: "Your posts will get nyanified when in cat mode. If this isn't working, then please check that you dont have 'Disable cat speak' on under General/Note Display"
+continueOnRemote: "Continue on remote instance"
+chooseServerOnMisskeyHub: "Choose a instance from Misskey Hub"
+mediaSilenceThisInstance: "Silence media from this instance"
+rejectReports: "Reject reports from this instance"
+silencedInstancesDescription: "List the host names of the instances that you want to silence, separated by a new line. All accounts belonging to the listed instances 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 instances."
+mediaSilencedInstances: "Media-silenced instances"
+mediaSilencedInstancesDescription: "List the host names of the instances that you want to media-silence, separated by a new line. All accounts belonging to the listed instances will be treated as sensitive, and can't use custom emojis. This will not affect the blocked instances."
+intro: "Installation of Sharkey has been finished! Please create an admin user."
+blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)."
+silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)."
+mediaSilencedByBase: "This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s)."
+driveSearchbarPlaceholder: "Search drive"
+fileNotSelected: "No file selected"
+background: "Background"
+antennaUsersDescription: "List one username per line. Use \"*@instance.com\" to specify all users of an instance"
+aboutMisskey: "About Sharkey"
+expandAllCws: "Show content for all replies"
+collapseAllCws: "Hide content for all replies"
+attachAsFileQuestion: "The text in clipboard is long. Would you like to attach it as a text file?"
+signinOrContinueOnRemote: "To continue, you need to go to your instance to perform this action or sign up / log in to the instance you are trying to interact with."
+disableDrawer: "Don't use drawer-style menus"
+showReactionsCount: "Show the number of reactions in notes"
+cornerRadius: "Corner roundness"
+warnForMissingAltText: "Warn you when you forget to put alt text"
+deeplFreeMode: "Use DeepLX-JS (No Auth Key)"
+deeplFreeModeDescription: "Need Help? Check our documentation to know how to setup DeepLX-JS."
+useSoundOnlyWhenActive: "Output sounds only if Sharkey is active."
+scratchpadDescription: "The Scratchpad provides an environment for AiScript experiments. You can write, execute, and check the results of it interacting with Sharkey in it."
+deleteAllFilesQueued: "Deletion of all files queued"
+systemAccountTitle: "This is a system account"
+systemAccountDescription: "This account is created and managed automatically by the system, and cannot be logged into."
+postFiltered: "post is hidden by a filter"
+enableFaviconNotificationDot: "Enable favicon notification dot"
+verifyNotificationDotWorkingButton: "Check if the notification dot works on your instance"
+notificationDotNotWorking: "Unfortunately, this instance does not support the notification dot feature at this time."
+notificationDotWorking: "The notification dot is functioning properly on this instance."
+notificationDotNotWorkingAdvice: "If the notification dot doesn't work, ask an admin to check our documentation {link}"
+reportAbuseRenote: "Report boost"
+forwardReport: "Forward report to remote instance"
+forwardReportIsAnonymous: "Instead of your account, an anonymous system account will be displayed as reporter at the remote instance."
+abuseMarkAsResolved: "Mark report as resolved"
+i18nInfoSharkey: "Sharkey specific changes are translated in its own {link}."
+renotesCount: "Number of boosts sent"
+renotedCount: "Number of boosts received"
+showTickerOnReplies: "Show instance ticker on replies"
+disableCatSpeak: "Disable cat speak"
+searchEngine: "Search Engine For Search MFM"
+searchEngineOther: "Other"
+searchEngineCustomURIDescription: "The custom URI must be input in the format like \"https://www.google.com/search?q=\\{query}\" or \"https://www.google.com/search?q=%s\"."
+searchEngineCusomURI: "Custom URI"
+makeIndexable: "Make public notes not indexable"
+makeIndexableDescription: "Stop note search from indexing your public notes."
+sendErrorReportsDescription: "When turned on, detailed error information will be shared with Sharkey when a problem occurs, helping to improve the quality of Sharkey.\nThis will include information such the version of your OS, what browser you're using, your activity in Sharkey, etc."
+noInquiryUrlWarning: "Contact URL is not set."
+misskeyUpdated: "Sharkey has been updated!"
+usernameInfo: "A name that identifies your account from others on this server. You can use the alphabet (a~z, A~Z), digits (0~9) or underscores (_). Usernames cannot be changed later."
+approvalRequiredForSignup: "Require approval for new users"
+voteConfirmMulti: "Confirm your vote for \"{choice}\"?\n You can choose more options after confirmation."
+pendingUserApprovals: "There are users awaiting approval."
+approveAccount: "Approve"
+denyAccount: "Deny & Delete"
+approved: "Approved"
+notApproved: "Not Approved"
+approvalStatus: "Approval Status"
+numberOfReplies: "Number of replies in a thread"
+numberOfRepliesDescription: "Increasing this number will display more replies. Setting this too high can cause replies to be cramped and unreadable."
+boostSettings: "Boost Settings"
+showVisibilitySelectorOnBoost: "Show Visibility Selector"
+showVisibilitySelectorOnBoostDescription: "Shows the visiblity selector if enabled when clicking boost, if disabled it will use the default visiblity defined below and the selector will not show up."
+visibilityOnBoost: "Default boost visibility"
+defaultLike: "Default like emoji"
+didYouLikeMisskey: "Have you taken a liking to Sharkey?"
+pleaseDonate: "{host} uses the free software, Sharkey. We would highly appreciate your donations so development of Sharkey can continue!"
+pleaseDonateInstance: "You can also support {host} directly by donating to your instance administration."
+thisPostIsMissingAltTextCancel: "Cancel"
+thisPostIsMissingAltTextIgnore: "Post anyway"
+thisPostIsMissingAltText: "One of the files attached to this post is missing alt text. Please ensure all the attachments have alt text."
+collapseRenotes: "Collapse boosts you've already seen"
+collapseRenotesDescription: "Collapse boosts that you have boosted or reacted to"
+collapseNotesRepliedTo: "Collapse notes replied to"
+collapseFiles: "Collapse files"
+uncollapseCW: "Uncollapse CWs on notes"
+expandLongNote: "Always expand long notes"
+autoloadConversation: "Load conversation on replies"
+approvalRequiredToRegister: "This instance is only accepting users who specify a reason for registration."
+limitWidthOfReaction: "Limits the maximum width of reactions and display them in reduced size."
+oneko: "Cat friend :3"
+renotesList: "Boosts"
+lookupConfirm: "Are you sure that you want to look this up?"
+openTagPageConfirm: "Are you sure you want to open this hashtags page?"
+specifyHost: "Specify a host"
+goToMisskey: "To Sharkey"
+enableAchievements: "Enable Achievements"
+turnOffAchievements: "Turning this off will disable the achievement system"
+enableBotTrending: "Populate Hashtags with Bots"
+turnOffBotTrending: "Turning this off will stop Bots from populating Hashtags"
+replies: "Replies"
+renotes: "Boosts"
+clickToOpen: "Click to open notes"
+showBots: "Show bots in timeline"
+showRenotes: "Show boosts"
+sourceCodeIsNotYetProvided: "The source code is not yet available. Please contact your administrator to fix this problem."
+repositoryUrlDescription: "If there is a repository where the source code is publicly available, enter its URL. If you are using Sharkey as-is (without any changes to the source code), enter https://activitypub.software/TransFem-org/Sharkey/."
+donation: "Donate"
+donationUrl: "Donation URL"
+showBelowAvatar: "Show Below Avatar"
+seasonalScreenEffect: "Seasonal screen effects"
+noDescription: "No description"
+sensitiveMediaRevealConfirm: "This media might be sensitive. Are you sure you want to reveal it?"
+severAllFollowRelations: "Break following relationships"
+severAllFollowRelationsConfirm: "Really break all follow relationships? This is irreversible! This will break {followingCount} following and {followersCount} follower relations on {instanceName}!"
+severAllFollowRelationsQueued: "Severing all follow relations with {host} queued."
+pendingFollowRequests: "Pending follow requests"
+showQuotes: "Show quotes"
+showReplies: "Show replies"
+showNonPublicNotes: "Show non-public"
+allowClickingNotifications: "Allow clicking on pop-up notifications"
+pinnedOnly: "Pinned"
+blockingYou: "Blocking you"
+warnExternalUrl: "Show warning when opening external URLs"
+_delivery:
+  stop: "Suspend delivery"
+  resume: "Resume delivery"
+_initialAccountSetting:
+  youCanContinueTutorial: "You can proceed to a tutorial on how to use {name} (Sharkey) or you can exit the setup here and start using it immediately."
+_initialTutorial:
+  _landing:
+    description: "Here, you can learn the basics of using Sharkey and its features."
+  _note:
+    description: "Posts on Sharkey are called 'Notes.' Notes are arranged chronologically on the timeline and are updated in real-time."
+  _reaction:
+    letsTryReacting: "Reactions can be added by clicking the '{reaction}' button on the note. Try reacting to this sample note!"
+    reactDone: "You can undo a reaction by pressing the '{undo}' button."
+  _timeline:
+    description1: "Sharkey provides multiple timelines based on usage (some may not be available depending on the server's policies)."
+    bubble: "You can view notes from connected servers picked by your admins."
+  _postNote:
+    description1: "When posting a note on Sharkey, various options are available. The posting form looks like this."
+    _visibility:
+      home: "Public only on the Home timeline. People visiting your profile, via followers, and through boosts can see it."
+      followers: "Visible to followers only. Only followers can see it and no one else, and it cannot be boosted by others."
+  _done:
+    title: "The tutorial is complete! 🎉"
+    description: "The functions introduced here are just a small part. For a more detailed understanding of using Sharkey, please refer to {link}."
+_timelineDescription:
+  bubble: "In the Bubble timeline, you can see notes from connected servers picked by your admins."
+_serverSettings:
+  sidebarLogoUrl: "Logo URL"
+  sidebarLogoDescription: "Specifies the logo to use instead of the regular icon in high definition, dynamic-width scenarios."
+  sidebarLogoUsageExample: "E.g. In the sidebar, to visitors and in the \"About\" page."
+  inquiryUrl: "Contact URL"
+  inquiryUrlDescription: "Specify the URL of a web page that contains a contact form or the instance operators' contact information."
+_accountMigration:
+  moveAccountDescription: "This will migrate your account to a different one.\n ・Followers from this account will automatically be migrated to the new account\n ・This account will unfollow all users it is currently following\n ・You will be unable to create new notes etc. on this account\n\nWhile migration of followers is automatic, you must manually prepare some steps to migrate the list of users you are following. To do so, carry out a follows export that you will later import on the new account in the settings menu. The same procedure applies to your lists as well as your muted and blocked users.\n\n(This explanation applies to Sharkey v13.12.0 and later. Other ActivityPub software, such as Mastodon, might function differently.)"
+_achievements:
+  _types:
+    _notes1:
+      title: "just setting up my shonk"
+      flavor: "Have a good time with Sharkey!"
+    _login1000:
+      flavor: "Thank you for using Sharkey!"
+    _iLoveMisskey:
+      title: "I Love Sharkey"
+      description: "Post \"I ❤ #Sharkey\""
+      flavor: "Sharkey's development team greatly appreciates your support!"
+    _client30min:
+      description: "Keep Sharkey opened for at least 30 minutes"
+    _client60min:
+      title: "No \"Miss\" in Sharkey"
+      description: "Keep Sharkey opened for at least 60 minutes"
+    _brainDiver:
+      flavor: "Sharkey-Sharkey La-Tu-Ma"
+    _tutorialCompleted:
+      title: "Sharkey Elementary Course Diploma"
+_role:
+  _options:
+    btlAvailable: "Can view the bubble timeline"
+    canImportNotes: "Can import notes"
+    canUpdateBioMedia: "Allow users to edit their avatar or banner"
+  _condition:
+    isLocked: "Private account"
+    isExplorable: "Account is discoverable"
+_emailUnavailable:
+  banned: "This email address is banned"
+_signup:
+  approvalPending: "Your account has been created and is awaiting approval."
+  reasonInfo: "Please enter a reason as to why you want to join the instance."
+_aboutMisskey:
+  about: "Sharkey is open-source software based on Misskey which has been in development by syuilo since 2014."
+  original: "Misskey original"
+  original_sharkey: "Sharkey original"
+  thisIsModifiedVersion: "{name} uses a modified version of the original Sharkey."
+  translation: "Translate Sharkey"
+  donate_sharkey: "Donate to Sharkey"
+  testers: "Testers"
+_serverDisconnectedBehavior:
+  disabled: "Disable warning"
+_channel:
+  allowRenoteToExternal: "Allow boosts and quote outside the channel"
+_instanceMute:
+  instanceMuteDescription: "This will mute any notes/boosts from the listed instances, including those of users replying to a user from a muted instance."
+_theme:
+  keys:
+    renote: "Boost"
+_soundSettings:
+  driveFileDurationWarnDescription: "Long audio may disrupt using Sharkey. Still continue?"
+  driveFileError: "The audio couldn't be loaded. Please make sure you selected an audio file."
+_2fa:
+  moreDetailedGuideHere: "Click here for a detailed guide"
+_widgets:
+  search: "Search"
+_poll:
+  multiple: "Multiple choices"
+_profile:
+  updateBanner: "Update banner"
+  removeBanner: "Remove banner"
+  changeBackground: "Change background"
+  updateBackground: "Update background"
+  removeBackground: "Remove background"
+_timelines:
+  bubble: "Bubble"
+_pages:
+  blocks:
+    dynamicDescription: "This block type has been removed. Please use {play} from now on."
+_notification:
+  youRenoted: "Boost from {name}"
+  renotedBySomeUsers: "Boosted by {n} users"
+  edited: "Note got edited"
+  _types:
+    renote: "Boosts"
+    edited: "Edits"
+  _actions:
+    renote: "Boost"
+_webhookSettings:
+  _events:
+    renote: "When boosted"
+  _systemEvents:
+    abuseReportResolved: "When resolved abuse reports"
+_abuseReport:
+  _notificationRecipient:
+    _recipientType:
+      _captions:
+        mail: "Send an email to the moderators when an abuse report is received."
+        webhook: "Send a notification to the SystemWebhook when an abuse report is received or resolved."
+_moderationLogTypes:
+  approve: "Approved"
+  setRemoteInstanceNSFW: "Set remote instance as NSFW"
+  unsetRemoteInstanceNSFW: "Set remote instance as NSFW"
+  rejectRemoteInstanceReports: "Rejected reports from remote instance"
+  acceptRemoteInstanceReports: "Accepted reports from remote instance"
+_mfm:
+  uncommonFeature: "This is not a widespread feature, it may not display properly on most other fedi software, including other Misskey forks"
+  intro: "MFM is a markup language used on Misskey, Sharkey, Firefish, Akkoma, and more that can be used in many places. Here you can view a list of all available MFM syntax."
+  dummy: "Sharkey expands the world of the Fediverse"
+  mention: "Mention"
+  mentionDescription: "You can specify a user by using an At-Symbol and a username."
+  hashtag: "Hashtag"
+  hashtagDescription: "You can specify a hashtag using a number sign and text."
+  url: "URL"
+  urlDescription: "URLs can be displayed."
+  link: "Link"
+  linkDescription: "Specific parts of text can be displayed as a URL."
+  bold: "Bold"
+  boldDescription: "Highlights letters by making them thicker."
+  small: "Small"
+  smallDescription: "Displays content small and thin."
+  center: "Center"
+  centerDescription: "Displays content centered."
+  inlineCode: "Code (Inline)"
+  inlineCodeDescription: "Displays inline syntax highlighting for (program) code."
+  blockCode: "Code (Block)"
+  blockCodeDescription: "Displays syntax highlighting for multi-line (program) code in a block."
+  inlineMath: "Math (Inline)"
+  inlineMathDescription: "Display math formulas (KaTeX) in-line"
+  blockMath: "Math (Block)"
+  blockMathDescription: "Display math formulas (KaTeX) in a block"
+  quote: "Quote"
+  quoteDescription: "Displays content as a quote."
+  emoji: "Custom Emoji"
+  emojiDescription: "By surrounding a custom emoji name with colons, custom emoji can be displayed."
+  search: "Search"
+  searchDescription: "Displays a search box with pre-entered text."
+  flip: "Flip"
+  flipDescription: "Flips content horizontally or vertically."
+  jelly: "Animation (Jelly)"
+  jellyDescription: "Gives content a jelly-like animation."
+  tada: "Animation (Tada)"
+  tadaDescription: "Gives content a \"Tada!\"-like animation."
+  jump: "Animation (Jump)"
+  jumpDescription: "Gives content a jumping animation."
+  bounce: "Animation (Bounce)"
+  bounceDescription: "Gives content a bouncy animation."
+  shake: "Animation (Shake)"
+  shakeDescription: "Gives content a shaking animation."
+  twitch: "Animation (Twitch)"
+  twitchDescription: "Gives content a strongly twitching animation."
+  spin: "Animation (Spin)"
+  spinDescription: "Gives content a spinning animation."
+  x2: "Big"
+  x2Description: "Displays content bigger."
+  x3: "Very big"
+  x3Description: "Displays content even bigger."
+  x4: "Unbelievably big"
+  x4Description: "Displays content even bigger than bigger than big."
+  blur: "Blur"
+  blurDescription: "Blurs content. It will be displayed clearly when hovered over."
+  font: "Font"
+  fontDescription: "Sets the font to display content in."
+  rainbow: "Rainbow"
+  rainbowDescription: "Makes the content appear in rainbow colors."
+  sparkle: "Sparkle"
+  sparkleDescription: "Gives content a sparkling particle effect."
+  rotate: "Rotate"
+  rotateDescription: "Turns content by a specified angle."
+  position: "Position"
+  positionDescription: "Move content by a specified amount."
+  crop: "Crop"
+  cropDescription: "Crop content."
+  followMouse: "Follow Mouse"
+  followMouseDescription: "Content will follow the mouse. On mobile it will follow wherever the user taps."
+  scale: "Scale"
+  scaleDescription: "Scale content by a specified amount."
+  foreground: "Foreground color"
+  foregroundDescription: "Change the foreground color of text."
+  fade: 'Fade'
+  fadeDescription: 'Fade text in and out.'
+  background: "Background color"
+  backgroundDescription: "Change the background color of text."
+  plain: "Plain"
+  plainDescription: "Deactivates the effects of all MFM contained within this MFM effect."
+_animatedMFM:
+  play: "Play MFM Animation"
+  stop: "Stop MFM Animation"
+  _alert:
+    text: "Animated MFMs could include flashing lights and fast moving text/emojis."
+    confirm: "Animate"
+_dataRequest:
+  title: "Request Data"
+  warn: "Data requests are only possible every 3 days."
+  text: "Once the data is ready to download, an email will be sent to the email address registered to this account."
+  button: "Request"
+_dataSaver:
+  _avatar:
+    description: "Stop avatar image animation. Animated images can be larger in file size than normal images, potentially leading to further reductions in data traffic."
+_urlPreviewSetting:
+  timeoutDescription: "If it takes longer than this value to get the preview, the preview won't be generated."
+  requireContentLength: "Generate the preview only if we can get Content-Length"
+  summaryProxy: "Endpoint for proxy to generate previews"
+  summaryProxyDescription: "Generate previews using Summaly Proxy, instead of Sharkey itself."
+  summaryProxyDescription2: "The following parameters are sent to the proxy as a query string. If the proxy does not support them, the values are ignored."
+_externalNavigationWarning:
+  title: "Navigate to an external site"
+  description: "Leave {host} and go to an external site"
+  trustThisDomain: "Trust this domain on this device in the future"
+
+remoteFollowersWarning: "Remote followers may have incomplete or outdated activity"
+
+_auth:
+  allowed: "Allowed"
+_announcement:
+  new: "New"
diff --git a/sharkey-locales/es-ES.yml b/sharkey-locales/es-ES.yml
new file mode 100644
index 0000000000000000000000000000000000000000..75aa932e449ec79714b7d9336941c4349dfce5a3
--- /dev/null
+++ b/sharkey-locales/es-ES.yml
@@ -0,0 +1,14 @@
+---
+antennaUsersDescription: "Elegir nombres de usuarios separados por una linea nueva. Utilice \"*@instance.com\" para especificar todos los usuarios de una instancia."
+disableDrawer: "No mostrar los menús en cajones"
+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"
+abuseMarkAsResolved: "Marcar reporte como resuelto"
+replies: "Respuestas"
+renotes: "Renotas"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "Puedes añadir reacciones pulsando en el botón '{reaction}' de la nota. ¡Intenta reaccionar a esta nota de ejemplo!"
+    reactDone: "Puedes deshacer una reacción pulsando en el botón '{undo}'."
+_poll:
+  multiple: "Opciones múltiples"
diff --git a/sharkey-locales/fr-FR.yml b/sharkey-locales/fr-FR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c823fca00f15f15646120de577841afd91f345bb
--- /dev/null
+++ b/sharkey-locales/fr-FR.yml
@@ -0,0 +1,13 @@
+---
+disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
+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."
+abuseMarkAsResolved: "Marquer le signalement comme résolu"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "Des réactions peuvent être ajoutées en cliquant sur le bouton « {reaction} » de la note. Essayez d'ajouter une réaction à cet exemple de note !"
+    reactDone: "Vous pouvez annuler la réaction en cliquant sur le bouton « {undo} » ."
+_achievements:
+  _types:
+    _notes1:
+      title: "Je viens tout juste de configurer mon shonk"
diff --git a/sharkey-locales/hr-HR.yml b/sharkey-locales/hr-HR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/hr-HR.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/ht-HT.yml b/sharkey-locales/ht-HT.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/ht-HT.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/hu-HU.yml b/sharkey-locales/hu-HU.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/hu-HU.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/id-ID.yml b/sharkey-locales/id-ID.yml
new file mode 100644
index 0000000000000000000000000000000000000000..6e2c283d6fcd397df43f0fdb8f26fba531d4d561
--- /dev/null
+++ b/sharkey-locales/id-ID.yml
@@ -0,0 +1,10 @@
+---
+disableDrawer: "Jangan gunakan menu bergaya laci"
+forwardReport: "Teruskan laporan ke instansi luar"
+forwardReportIsAnonymous: "Untuk melindungi privasi akun kamu, akun anonim dari sistem akan digunakan sebagai pelapor pada instansi luar."
+abuseMarkAsResolved: "Tandai laporan sebagai selesai"
+replies: "Balasan"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "Reaksi dapat ditambahkan dengan mengklik tombol '{reaction}' pada catatan. Coba lakukan mereaksi contoh catatan ini!"
+    reactDone: "Kamu dapat mengurungkan reaksi dengan menekan tombol '{undo}'."
diff --git a/sharkey-locales/it-IT.yml b/sharkey-locales/it-IT.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0a340cf43507f787bb4bdcc4becde188f9b5c9da
--- /dev/null
+++ b/sharkey-locales/it-IT.yml
@@ -0,0 +1,18 @@
+---
+openRemoteProfile: "Apri profilo remoto"
+disableDrawer: "Non mostrare il menù sul drawer"
+forwardReport: "Inoltro di un report a un'istanza remota."
+forwardReportIsAnonymous: "L'istanza remota non vedrà le tue informazioni, apparirai come profilo di sistema, anonimo."
+abuseMarkAsResolved: "Risolvi segnalazione"
+makeIndexable: "Non indicizzare le note pubbliche"
+makeIndexableDescription: "Le tue note pubbliche non saranno cercabili"
+defaultLike: "Emoji predefinita per \"mi piace\""
+_delivery:
+  stop: "Sospendi la distribuzione di attività"
+  resume: "Riprendi la distribuzione di attività"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "Puoi aggiungere una Reazione cliccando il bottone \"{reaction}\" della relativa Nota. Prova ad aggiungerne una a questa Nota di esempio!"
+    reactDone: "Annulla la tua Reazione premendo il bottone \"{undo}\""
+_serverDisconnectedBehavior:
+  disabled: "Non visualizzare l'avviso"
diff --git a/sharkey-locales/ja-JP.yml b/sharkey-locales/ja-JP.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0977a2e5648fc6e73bd5fd9ff5b83a4c445bbf12
--- /dev/null
+++ b/sharkey-locales/ja-JP.yml
@@ -0,0 +1,377 @@
+---
+introMisskey: "ようこそ!Sharkeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀"
+poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつです。"
+renotedBy: "{user}がブースト"
+approvals: "承認"
+copyLinkRenote: "ブーストのリンクをコピー"
+deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、ブースト、返信も全て削除されます。"
+openRemoteProfile: "リモートプロフィールを開く"
+trustedLinkUrlPatterns: "外部サイトへのリンク警告 除外リスト"
+trustedLinkUrlPatternsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。スラッシュで囲むと正規表現になります。ドメイン名だけ書くと後方一致になります。"
+mutuals: "Mutuals"
+renote: "ブースト"
+unrenote: "ブースト解除"
+renoted: "ブーストしました。"
+quoted: "引用。"
+rmboost: "ブースト解除しました。"
+cantRenote: "この投稿はブーストできません。"
+cantReRenote: "ブーストをブーストすることはできません。"
+inChannelRenote: "チャンネル内ブースト"
+muted: "Muted"
+renoteMute: "ブーストをミュート"
+renoteMuted: "Boosts muted"
+renoteUnmute: "ブーストのミュートを解除"
+markAsNSFW: "ユーザーのすべてのメディアをNSFWとしてマークする"
+markInstanceAsNSFW: "Mark as NSFW"
+nsfwConfirm: "このアカウントからのすべてのメディアをNSFWとしてマークしてもよろしいですか?"
+unNsfwConfirm: "このアカウントのすべてのメディアをNSFWとしてマーク解除してもよろしいですか?"
+approveConfirm: "このアカウントを承認してもよろしいですか?"
+flagAsBotDescription: "このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Sharkeyのシステム上での扱いがBotに合ったものになります。"
+flagSpeakAsCat: "猫語で話す"
+flagSpeakAsCatDescription: "有効にすると、あなたの投稿の 「な」を「にゃ」にします。"
+continueOnRemote: "リモートで続行"
+chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選択"
+mediaSilenceThisInstance: "サーバーをメディアサイレンス"
+rejectReports: "Reject reports from this instance"
+silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになります。ブロックしたインスタンスには影響しません。"
+mediaSilencedInstances: "メディアサイレンスしたサーバー"
+mediaSilencedInstancesDescription: "メディアサイレンスしたいサーバーのホストを改行で区切って設定します。メディアサイレンスされたサーバーに所属するアカウントによるファイルはすべてセンシティブとして扱われ、カスタム絵文字が使用できないようになります。ブロックしたインスタンスには影響しません。"
+intro: "Sharkeyのインストールが完了しました!管理者アカウントを作成しましょう。"
+blockedByBase: "This host is blocked implicitly because a base domain is blocked. To unblock this host, first unblock the base domain(s)."
+silencedByBase: "This host is silenced implicitly because a base domain is silenced. To un-silence this host, first un-silence the base domain(s)."
+mediaSilencedByBase: "This host's media is silenced implicitly because a base domain's media is silenced. To un-silence this host, first un-silence the base domain(s)."
+driveSearchbarPlaceholder: "検索ドライブ"
+fileNotSelected: "ファイルが選択されていません"
+background: "背景"
+antennaUsersDescription: "ユーザー名を改行で区切って指定します"
+aboutMisskey: "Sharkeyについて"
+expandAllCws: "すべての返信の内容を表示する"
+collapseAllCws: "すべての返信の内容を隠す"
+attachAsFileQuestion: "クリップボードのテキストが長いです。テキストファイルとして添付しますか?"
+signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります"
+disableDrawer: "メニューをドロワーで表示しない"
+showReactionsCount: "ノートのリアクション数を表示する"
+cornerRadius: "コーナーの丸み"
+warnForMissingAltText: "代替テキストを入れ忘れたときに警告する"
+deeplFreeMode: "DeepLX-JS を使用する (認証キー不要)"
+deeplFreeModeDescription: "DeepLX-JSの設定方法については、ドキュメントを参照してください。"
+useSoundOnlyWhenActive: "Sharkeyがアクティブな時のみサウンドを出力する"
+scratchpadDescription: "スクラッチパッドは、AiScriptの実験環境を提供します。Sharkeyと対話するコードの記述、実行、結果の確認ができます。"
+deleteAllFilesQueued: "キューに入れられたすべてのファイルの削除"
+systemAccountTitle: "This is a system account"
+systemAccountDescription: "This account is created and managed automatically by the system, and cannot be logged into."
+postFiltered: "post is hidden by a filter"
+enableFaviconNotificationDot: "未読の通知があるときにタブのアイコンを目立たせる"
+verifyNotificationDotWorkingButton: "タブアイコン強調機能の動作確認"
+notificationDotNotWorking: "このサーバーは現時点ではタブアイコン強調機能をサポートしていません。"
+notificationDotWorking: "タブアイコン強調機能は、このサーバーで正しく機能しています。"
+notificationDotNotWorkingAdvice: "タブアイコン強調機能が機能しない場合は、管理者にドキュメントを確認するように依頼してください {link}"
+reportAbuseRenote: "ブーストを通報"
+forwardReport: "リモートサーバーに通報を転送する"
+forwardReportIsAnonymous: "リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。"
+abuseMarkAsResolved: "対応済みにする"
+i18nInfo: "Sharkeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。"
+renotesCount: "ブーストした数"
+renotedCount: "ブーストされた数"
+showTickerOnReplies: "返信にサーバー情報を表示する"
+disableCatSpeak: "猫の話し方を無効にする"
+searchEngine: "検索MFMの検索エンジン"
+searchEngineOther: "カスタム"
+searchEngineCustomURIDescription: "カスタム検索エンジンのURIは、\"https://www.google.com/search?q=\\{query}\" や \"https://www.google.com/search?q=%s\" のような形式で入力する必要があります。"
+searchEngineCusomURI: "カスタム検索エンジン URI"
+makeIndexable: "公開ノートをインデックス不可にする"
+makeIndexableDescription: "ノート検索があなたの公開ノートをインデックス化しないようにします。"
+sendErrorReportsDescription: "オンにすると、問題が発生したときにエラーの詳細情報がSharkeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。"
+noInquiryUrlWarning: "問い合わせ先URLが設定されていません。"
+misskeyUpdated: "Sharkeyが更新されました!"
+usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
+approvalRequiredForSignup: "アカウント登録を承認制にする"
+voteConfirmMulti: "「{choice}」に投票しますか?\n 確認後、選択肢を増やすことができます。"
+pendingUserApprovals: "承認待ちのユーザーがいます。"
+approveAccount: "承認する"
+denyAccount: "拒否と削除"
+approved: "承認済み"
+notApproved: "承認されていない"
+approvalStatus: "承認状況"
+numberOfReplies: "スレッド内の返信数"
+numberOfRepliesDescription: "この数値を大きくすると、より多くの返信が表示されます。この値を大きくしすぎると、UIが窮屈になって読みにくくなることがあります。"
+boostSettings: "ブースト設定"
+showVisibilitySelectorOnBoost: "公開範囲セレクターを表示"
+showVisibilitySelectorOnBoostDescription: "無効の場合、以下で設定したデフォルトの公開範囲が使用され、セレクターは表示されません。"
+visibilityOnBoost: "デフォルトのブースト公開範囲"
+defaultLike: "絵文字のようなデフォルト"
+didYouLikeMisskey: "Sharkeyを気に入っていただけましたか?"
+pleaseDonate: "Sharkeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
+pleaseDonateInstance: "インスタンス管理者への寄付によって{host}を直接サポートすることもできます。"
+thisPostIsMissingAltTextCancel: "やめる"
+thisPostIsMissingAltTextIgnore: "このまま投稿"
+thisPostIsMissingAltText: "代替テキストがないファイルが添付されています。すべての添付ファイルに代替テキストを含むようにしてください。"
+collapseRenotes: "ブーストのスマート省略"
+collapseRenotesDescription: "リアクションやブーストをしたことがあるノートをたたんで表示します。"
+collapseNotesRepliedTo: "返信元のノートを折りたたむ"
+collapseFiles: "ファイルを折りたたむ"
+uncollapseCW: "CWを展開する"
+expandLongNote: "長い投稿を常に展開する"
+autoloadConversation: "会話スレッドを自動で読み込む"
+approvalRequiredToRegister: "現在このサーバーは承認制です。参加したい理由を記入し、承認された方のみ登録できます。"
+limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
+oneko: "にゃんこフレンド :3"
+renotesList: "ブースト一覧"
+lookupConfirm: "照会しますか?"
+openTagPageConfirm: "ハッシュタグのページを開きますか?"
+specifyHost: "ホスト指定"
+goToMisskey: "Sharkeyへ"
+enableAchievements: "実績を有効にする"
+turnOffAchievements: "オフにすると実績システムは無効になります。"
+enableBotTrending: "botのハッシュタグ追加を許可する"
+turnOffBotTrending: "オフにするとボットがハッシュタグを入力しなくなります。"
+replies: "返信"
+renotes: "ブースト"
+clickToOpen: "クリックしてノートを開く"
+showBots: "ボットをタイムラインに表示"
+showRenotes: "ブーストを表示"
+sourceCodeIsNotYetProvided: "ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。"
+repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Sharkeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://activitypub.software/TransFem-org/Sharkey/ と記入します。"
+donation: "寄付する"
+donationUrl: "寄付URL"
+showBelowAvatar: "アイコンの後ろに表示"
+allowClickingNotifications: "ポップアップ通知のクリックを許可する"
+seasonalScreenEffect: "季節に応じた画面の演出"
+noDescription: "説明文はありません"
+sensitiveMediaRevealConfirm: "センシティブなメディアです。表示しますか?"
+severAllFollowRelations: "以下の関係をすべて断ち切る"
+severAllFollowRelationsConfirm: "すべての人間関係を壊す?これは不可逆です!これは{instanceName}の{followingCount}フォローと{followersCount}フォロワーの関係を壊す!"
+severAllFollowRelationsQueued: "キューに入れられた{host}とのすべてのフォロー関係を切断する。"
+_delivery:
+  stop: "配信停止"
+  resume: "配信再開"
+_initialAccountSetting:
+  youCanContinueTutorial: "このまま{name}(Sharkey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。"
+_initialTutorial:
+  _landing:
+    description: "ここでは、Sharkeyの基本的な使い方や機能を確認できます。"
+  _note:
+    description: "Sharkeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。"
+  _reaction:
+    letsTryReacting: "リアクションは、ノートの「{reaction}」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!"
+    reactDone: "「{undo}」ボタンを押すとリアクションを取り消すことができます。"
+  _timeline:
+    description1: "Sharkeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。"
+    bubble: "管理者が選択した他の接続サーバーの投稿を見ることができます。"
+  _postNote:
+    description1: "Sharkeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。"
+    _visibility:
+      home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・ブーストから、他のユーザーも見ることができます。"
+      followers: "フォロワーにのみ公開。本人以外がブーストすることはできず、またフォロワー以外は閲覧できません。"
+  _done:
+    title: "チュートリアルは終了です🎉"
+    description: "ここで紹介した機能はほんの一部にすぎません。Sharkeyの使い方をより詳しく知るには、{link}をご覧ください。"
+_timelineDescription:
+  bubble: "バブルタイムラインでは、管理者が選択した接続サーバーからの投稿を表示できます。"
+_serverSettings:
+  sidebarLogoUrl: "ロゴURL"
+  sidebarLogoDescription: "高精細、ダイナミック幅のシナリオで通常のアイコンの代わりに使用するロゴを指定します。"
+  sidebarLogoUsageExample:  "例:サイドバー、訪問者用、「情報」ページ"
+  inquiryUrl: "問い合わせ先URL"
+  inquiryUrlDescription: "サーバー運営者へのお問い合わせフォームのURLや、運営者の連絡先等が記載されたWebページのURLを指定します。"
+_accountMigration:
+  moveAccountDescription: "新しいアカウントへ移行します。\n ・フォロワーが新しいアカウントを自動でフォローします\n ・このアカウントからのフォローは全て解除されます\n ・このアカウントではノートの作成などができなくなります\n\nフォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。\nリスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。\n\n(この説明はこのサーバー(Sharkey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。)"
+_achievements:
+  _types:
+    _notes1:
+      title: "just setting up my shonk"
+      flavor: "良いSharkeyライフを!"
+    _login1000:
+      flavor: "Sharkeyを使ってくれてありがとう!"
+    _iLoveMisskey:
+      title: "I Love Sharkey"
+      description: "\"I ❤ #Sharkey\"を投稿した"
+      flavor: "Sharkeyを使ってくださりありがとうございます! by 開発チーム"
+    _client30min:
+      description: "クライアントを起動してから30分以上経過した"
+    _client60min:
+      title: "Sharkeyの見すぎ"
+      description: "クライアントを起動してから60分以上経過した"
+    _brainDiver:
+      flavor: "Misskey-Misskey La-Tu-Ma"
+    _tutorialCompleted:
+      title: "Sharkey初心者講座 修了証"
+_role:
+  _options:
+    btlAvailable: "バブルタイムラインの閲覧"
+    canImportNotes: "ノートのインポートが可能"
+    canUpdateBioMedia: "アイコンとバナーの更新を許可"
+  _condition:
+    isLocked: "鍵アカウントユーザー"
+    isExplorable: "「アカウントを見つけやすくする」が有効なユーザー"
+_emailUnavailable:
+  banned: "このメールアドレスでは登録できません"
+_signup:
+  approvalPending: "アカウントが作成され、承認待ちの状態です。"
+  reasonInfo: "インスタンスに参加したい理由を入力してください。"
+_aboutMisskey:
+  about: "Sharkeyは、Misskeyをベースにしたオープンソースのソフトウェアです。"
+  original: "Misskey オリジナル"
+  original_sharkey: "Sharkey オリジナル"
+  thisIsModifiedVersion: "{name}はオリジナルのSharkeyを改変したバージョンを使用しています。"
+  translation: "Sharkeyを翻訳"
+  donate_sharkey: "Sharkeyに寄付"
+  testers: "テスター"
+_serverDisconnectedBehavior:
+  disabled: "警告を無効にする"
+_channel:
+  allowRenoteToExternal: "チャンネル外へのブーストと引用ブーストを許可する"
+_instanceMute:
+  instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとブーストをミュートします。"
+_theme:
+  keys:
+    renote: "Boost"
+_soundSettings:
+  driveFileDurationWarnDescription: "長い音声を使用するとSharkeyの使用に支障をきたす可能性があります。それでも続行しますか?"
+  driveFileError: "音声が読み込めませんでした。設定を変更してください"
+_2fa:
+  moreDetailedGuideHere: "詳細なガイドはこちら"
+_widgets:
+  search: "検索"
+_poll:
+  multiple: "複数の選択肢"
+_profile:
+  updateBanner: "更新バナー"
+  removeBanner: "バナーを削除"
+  changeBackground: "背景を変更する"
+  updateBackground: "背景を更新する"
+  removeBackground: "背景を削除する"
+_timelines:
+  bubble: "バブル"
+_pages:
+  blocks:
+    dynamicDescription: "このブロックは廃止されています。今後は{play}を利用してください。"
+_notification:
+  youRenoted: "{name}がBoostしました"
+  renotedBySomeUsers: "{n}人がリノートしました"
+  _types:
+    renote: "Boost"
+    edited: "編集済み"
+  _actions:
+    renote: "ブースト"
+_webhookSettings:
+  _events:
+    renote: "Boostされたとき"
+  _systemEvents:
+    abuseReportResolved: "ユーザーからの通報を処理したとき"
+_abuseReport:
+  _notificationRecipient:
+    _recipientType:
+      _captions:
+        mail: "モデレーター権限を持つユーザーのメールアドレスに通知を送ります(通報を受けた時のみ)"
+        webhook: "指定したSystemWebhookに通知を送ります(通報を受けた時と通報を解決した時にそれぞれ発信)"
+_moderationLogTypes:
+  approve: "承認済み"
+  setRemoteInstanceNSFW: "Set remote instance as NSFW"
+  unsetRemoteInstanceNSFW: "Set remote instance as NSFW"
+  rejectRemoteInstanceReports: "Rejected reports from remote instance"
+  acceptRemoteInstanceReports: "Accepted reports from remote instance"
+_mfm:
+  uncommonFeature: "この機能は一般的に普及していないため、他のMisskeyフォークを含めた多くのFediverseソフトウェアで表示できないことがあります。"
+  intro: "MFM はMisskey, Sharkey, Firefish, Akkomaなど、多くの場所で使用できるマークアップ言語です。ここでは、利用できるMFM構文の一覧をご覧いただけます。"
+  dummy: "SharkeyでFediverseの世界が広がります"
+  mention: "メンション"
+  mentionDescription: "アットマーク + ユーザー名で、特定のユーザーを示すことができます。"
+  hashtag: "ハッシュタグ"
+  hashtagDescription: "ナンバーサイン + タグで、ハッシュタグを示すことができます。"
+  url: "URL"
+  urlDescription: "URLを示すことができます。"
+  link: "リンク"
+  linkDescription: "文章の特定の範囲を、URLに紐づけることができます。"
+  bold: "太字"
+  boldDescription: "文字を太く表示して強調することができます。"
+  small: "小文字"
+  smallDescription: "内容を小さく・薄く表示させることができます。"
+  center: "中央寄せ"
+  centerDescription: "内容を中央寄せで表示させることができます。"
+  inlineCode: "コード(インライン)"
+  inlineCodeDescription: "プログラムなどのコードをインラインでシンタックスハイライトします。"
+  blockCode: "コード(ブロック)"
+  blockCodeDescription: "複数行のプログラムなどのコードをブロックでシンタックスハイライトします。"
+  inlineMath: "数式(インライン)"
+  inlineMathDescription: "数式 (KaTeX形式)をインラインで表示します。"
+  blockMath: "数式(ブロック)"
+  blockMathDescription: "数式 (KaTeX形式)をブロックで表示します。"
+  quote: "引用"
+  quoteDescription: "内容が引用であることを示すことができます。"
+  emoji: "カスタム絵文字"
+  emojiDescription: "コロンでカスタム絵文字名を囲むと、カスタム絵文字を表示させることができます。"
+  search: "検索"
+  searchDescription: "検索ボックスを表示できます。"
+  flip: "反転"
+  flipDescription: "内容を上下または左右に反転させます。"
+  jelly: "アニメーション(びよんびよん)"
+  jellyDescription: "ゼリーが揺れるような感じのアニメーションをさせます。"
+  tada: "アニメーション(じゃーん)"
+  tadaDescription: "「じゃーん!」と強調するような感じのアニメーションをさせます。"
+  jump: "アニメーション(ジャンプ)"
+  jumpDescription: "跳ねるアニメーションをさせます。"
+  bounce: "アニメーション(バウンド)"
+  bounceDescription: "跳ねて着地するようなアニメーションをさせます。"
+  shake: "アニメーション(ぶるぶる)"
+  shakeDescription: "震えるアニメーションをさせます。"
+  twitch: "アニメーション(ガタガタ)"
+  twitchDescription: "より激しく震えるアニメーションをさせます。"
+  spin: "アニメーション(回転)"
+  spinDescription: "内容を回転させます。"
+  x2: "大"
+  x2Description: "内容を大きく表示させます。"
+  x3: "特大"
+  x3Description: "内容をより大きく表示させます。"
+  x4: "超特大"
+  x4Description: "内容をさらに大きく表示させます。"
+  blur: "ぼかし"
+  blurDescription: "内容をぼかすことができます。ポインターを上に乗せるとはっきり見えるようになります。"
+  font: "フォント"
+  fontDescription: "内容のフォントを指定することができます。"
+  rainbow: "レインボー"
+  rainbowDescription: "内容を虹色で表示させます。"
+  sparkle: "キラキラ"
+  sparkleDescription: "キラキラと星型のパーティクルを表示させます。"
+  rotate: "角度変更"
+  rotateDescription: "指定した角度で回転させます。"
+  position: "位置変更"
+  positionDescription: "位置をずらすことができます。"
+  crop: "切り取り"
+  cropDescription: "内容を切り抜きます。"
+  followMouse: "マウス追従"
+  followMouseDescription: "内容がマウスに追従します。スマホの場合はタップした場所に追従します。"
+  scale: "拡大"
+  scaleDescription: "内容を引き伸ばして表示します。"
+  foreground: "文字色"
+  foregroundDescription: "文字色を変更します。"
+  fade: 'フェード'
+  fadeDescription: '内容をフェードイン・フェードアウトさせます。'
+  background: "背景色"
+  backgroundDescription: "背景色を変更します。"
+  plain: "Plain"
+  plainDescription: "内側の構文を全て無効にします。"
+_animatedMFM:
+  play: "MFMアニメーションを再生"
+  stop: "MFMアニメーション停止"
+  _alert:
+    text: "MFMアニメーションには、高速で点滅したり動いたりするテキスト・絵文字を含む場合があります。"
+    confirm: "再生する"
+_dataRequest:
+  title: "データリクエスト"
+  warn: "データリクエストは3日ごとに可能です。"
+  text: "データの保存が完了すると、このアカウントに登録されているメールアドレスにメールが送信されます。"
+  button: "データリクエスト実行"
+_dataSaver:
+  _avatar:
+    description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。"
+_urlPreviewSetting:
+  timeoutDescription: "プレビュー取得の所要時間がこの値を超えた場合、プレビューは生成されません。"
+  requireContentLength: "Content-Lengthが取得できた場合のみプレビューを生成"
+  summaryProxy: "プレビューを生成するプロキシのエンドポイント"
+  summaryProxyDescription: "Misskey本体ではなく、サマリープロキシを使用してプレビューを生成します。"
+  summaryProxyDescription2: "プロキシには下記パラメータがクエリ文字列として連携されます。プロキシ側がこれらをサポートしない場合、設定値は無視されます。"
+_externalNavigationWarning:
+  title: "外部サイトに移動します"
+  description: "{host}を離れて外部サイトに移動します"
+  trustThisDomain: "このデバイスで今後このドメインを信頼する"
diff --git a/sharkey-locales/ja-KS.yml b/sharkey-locales/ja-KS.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c9f9e898983bd109292fdaec2a1df481220e53b2
--- /dev/null
+++ b/sharkey-locales/ja-KS.yml
@@ -0,0 +1,79 @@
+---
+introMisskey: "ようお越し!Sharkeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のノートに素早く反応を追加したりもできるで✌\nほな、新しい世界を探検しよか🚀"
+poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつなんやで。"
+renotedBy: "{user}がブーストしたで"
+copyLinkRenote: "ブーストのリンクをコピーするで?"
+deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、ブースト、返信も全部消えるんやけどそれでもええん?"
+inChannelRenote: "チャンネルの中でブースト"
+renoteMute: "ブーストは見いひん"
+renoteUnmute: "ブーストもやっぱ見るわ"
+flagAsBotDescription: "もしこのアカウントをプログラム使うて運用するんやったら、このフラグをオンにしてや。オンにすれば、反応がバーッて連鎖せんように開発者が使うたり、Sharkeyのシステム上での扱いがBotに合ったもんになるからな。"
+flagSpeakAsCat: "猫語で話すで"
+flagSpeakAsCatDescription: "有効にすると、あなたの投稿の 「な」を「にゃ」にするでー。"
+intro: "Sharkeyのインストールが完了したで!管理者アカウントを作ってや。"
+aboutMisskey: "Sharkeyってなんや?"
+disableDrawer: "メニューをドロワーで表示せえへん"
+useSoundOnlyWhenActive: "Sharkeyがアクティブなときだけ音出す"
+scratchpadDescription: "スクラッチパッドではAiScriptを色々試すことができるんや。Sharkeyに対して色々できるコードを書いて動かしてみたり、結果を見たりできるで。"
+reportAbuseRenote: "ブースト苦情だすで?"
+forwardReport: "リモートサーバーに通報を転送するで"
+forwardReportIsAnonymous: "リモートサーバーからはあんたの情報は見えんなって、匿名のシステムアカウントとして表示されるで。"
+abuseMarkAsResolved: "対応したで"
+renotesCount: "ブーストした数やで"
+renotedCount: "ブーストされた数やで"
+sendErrorReportsDescription: "オンにしたら、なんか変なことが起きたとき、詳しいのが全部Sharkeyに送られて、ソフトウェアをもっと良うするで。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴なんかが含まれるな。"
+misskeyUpdated: "Sharkeyが更新されたで!\nモデレーターの人らに感謝せなあかんで"
+didYouLikeMisskey: "Sharkey気に入ってくれた?"
+pleaseDonate: "Sharkeyは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
+collapseRenotes: "見たことあるブーストは飛ばして表示するで"
+collapseRenotesDescription: "リアクションやブーストをしたことがあるノートをたたんで表示するで。"
+renotesList: "ブースト一覧"
+goToMisskey: "Sharkeyへ"
+renotes: "ブースト"
+showRenotes: "ブースト出す"
+_initialAccountSetting:
+  youCanContinueTutorial: "こんまま{name}(Sharkey)の使い方のチュートリアルにも行けるけど、ここでやめてすぐに使い始めてもええで。"
+_initialTutorial:
+  _landing:
+    description: "ここでは、Sharkeyのカンタンな使い方とか機能を確かめれんで。"
+  _note:
+    description: "Sharkeyでの投稿は「ノート」って呼ばれてんで。ノートは順々にタイムラインに載ってて、リアルタイムで新しくなってってんで。"
+  _reaction:
+    letsTryReacting: "ノートの「{reaction}」ボタンでツッコめるわ。試しに下のノートにツッコんでみ。"
+    reactDone: "「ー」ボタンでツッコミやめれるで。"
+  _timeline:
+    description1: "Sharkeyには、いろいろタイムラインがあんで(ただ、サーバーによっては無効化されてるところもあるな)。"
+  _postNote:
+    description1: "Sharkeyにノートを投稿するとき、いろんなオプションが付けれるで。投稿画面はこんな感じや。"
+    _visibility:
+      home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、ブーストからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。"
+      followers: "フォロワーにだけ見せるで。自分以外はブーストできへんし、フォロワー以外は絶対に見れへん。"
+_achievements:
+  _types:
+    _notes1:
+      flavor: "Sharkeyを楽しんでな~"
+    _login1000:
+      flavor: "Sharkeyようさん使てもろておおきにな!"
+    _iLoveMisskey:
+      title: "Sharkey好きやねん"
+      description: "\"I ❤ #Sharkey\"を投稿した"
+      flavor: "Sharkeyを使ってくれておおきにな~ by 開発チーム"
+    _client60min:
+      title: "Sharkeyの見過ぎや!"
+    _tutorialCompleted:
+      title: "Sharkeyひよっこ講座 修了証"
+_aboutMisskey:
+  about: "Sharkeyは、Misskeyをベースにしたオープンソースなソフトウェアや。"
+  thisIsModifiedVersion: "{name}はオリジナルのSharkeyをいじったバージョンをつこうてるで。"
+  translation: "Sharkeyを翻訳"
+  donate: "Sharkeyに寄付"
+_channel:
+  allowRenoteToExternal: "チャンネルの外にブーストできるようにする"
+_soundSettings:
+  driveFileDurationWarnDescription: "長い音使うたらSharkey使うのに良うないかもしれへんで。それでもええか?"
+_notification:
+  youRenoted: "{name}がブーストしたみたいやで"
+  renotedBySomeUsers: "{n}人がブーストしたで"
+_webhookSettings:
+  _events:
+    renote: "ブーストされるとき~!"
diff --git a/sharkey-locales/jbo-EN.yml b/sharkey-locales/jbo-EN.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/jbo-EN.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/kab-KAB.yml b/sharkey-locales/kab-KAB.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/kab-KAB.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/kn-IN.yml b/sharkey-locales/kn-IN.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/kn-IN.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/ko-GS.yml b/sharkey-locales/ko-GS.yml
new file mode 100644
index 0000000000000000000000000000000000000000..65440e07cdc371131cfeafa00f2adfe3e661a7ce
--- /dev/null
+++ b/sharkey-locales/ko-GS.yml
@@ -0,0 +1,4 @@
+---
+disableDrawer: "드로어 메뉴 쓰지 않기"
+forwardReport: "웬겍 서버에 신고 보내기"
+fromUrl: "Van URL"
diff --git a/sharkey-locales/ko-KR.yml b/sharkey-locales/ko-KR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cc14b78c1f16745886a7809a391eb83ce041e867
--- /dev/null
+++ b/sharkey-locales/ko-KR.yml
@@ -0,0 +1,11 @@
+---
+disableDrawer: "드로어 메뉴를 사용하지 않기"
+forwardReport: "리모트 서버에도 신고 내용 보내기"
+forwardReportIsAnonymous: "리모트 서버에서는 나의 정보를 볼 수 없으며, 익명의 시스템 계정으로 표시됩니다."
+abuseMarkAsResolved: "해결됨으로 표시"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "리액션은 노트의 '{reaction}' 버튼을 클릭하여 붙일 수 있습니다. 지금 표시되는 샘플 노트에 리액션을 달아 보세요!"
+    reactDone: "'{undo}' 버튼을 눌러서 리액션을 취소할 수 있습니다."
+_urlPreviewSetting:
+  requireContentLength: "Content-Length를 얻었을 때만 미리보기 만들기"
diff --git a/sharkey-locales/lo-LA.yml b/sharkey-locales/lo-LA.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/lo-LA.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/nl-NL.yml b/sharkey-locales/nl-NL.yml
new file mode 100644
index 0000000000000000000000000000000000000000..514def8d7b9f2ef0503f026442a24f9c55978d63
--- /dev/null
+++ b/sharkey-locales/nl-NL.yml
@@ -0,0 +1,2 @@
+---
+replies: "Antwoorden"
diff --git a/sharkey-locales/no-NO.yml b/sharkey-locales/no-NO.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/no-NO.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/pl-PL.yml b/sharkey-locales/pl-PL.yml
new file mode 100644
index 0000000000000000000000000000000000000000..05071db880bbf424746c34412bce7e1c7d99d0e6
--- /dev/null
+++ b/sharkey-locales/pl-PL.yml
@@ -0,0 +1,8 @@
+---
+disableDrawer: "Nie używaj menu w stylu szuflady"
+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."
+abuseMarkAsResolved: "Oznacz zgłoszenie jako rozwiązane"
+usernameInfo: "Nazwa, która identyfikuje Twoje konto spośród innych na tym serwerze. Możesz użyć alfabetu (a~z, A~Z), cyfr (0~9) lub podkreślników (_). Nazwy użytkownika nie mogą być później zmieniane."
+replies: "Odpowiedzi"
+renotes: "Udostępnień"
diff --git a/sharkey-locales/pt-PT.yml b/sharkey-locales/pt-PT.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b3f7611ee32acb16dd9806fcb669126788bf95f3
--- /dev/null
+++ b/sharkey-locales/pt-PT.yml
@@ -0,0 +1,9 @@
+---
+disableDrawer: "Não mostrar o menu em formato de gaveta"
+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."
+abuseMarkAsResolved: "Marcar denúncia como resolvida"
+openRemoteProfile: "Abrir perfil remoto"
+allowClickingNotifications: "Permitir clicar em notificações"
+pinnedOnly: "Fixado"
+blockingYou: "Bloqueando você"
diff --git a/sharkey-locales/ro-RO.yml b/sharkey-locales/ro-RO.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d1d04b081ecd100f75b020015ff5f6e026a64574
--- /dev/null
+++ b/sharkey-locales/ro-RO.yml
@@ -0,0 +1,6 @@
+---
+disableDrawer: "Nu folosi meniuri în stil sertar"
+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ă."
+abuseMarkAsResolved: "Marchează raportul ca rezolvat"
+replies: "Răspunsuri"
diff --git a/sharkey-locales/ru-RU.yml b/sharkey-locales/ru-RU.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/ru-RU.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/si-LK.yml b/sharkey-locales/si-LK.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/si-LK.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/sk-SK.yml b/sharkey-locales/sk-SK.yml
new file mode 100644
index 0000000000000000000000000000000000000000..3d2739a3333874aa5e33efc4e6c908deaa3ca037
--- /dev/null
+++ b/sharkey-locales/sk-SK.yml
@@ -0,0 +1,6 @@
+---
+disableDrawer: "Nepoužívať šuflíkové menu"
+forwardReport: "Preposlať nahlásenie na server"
+forwardReportIsAnonymous: "Namiesto vášho účtu bude zobrazený anonymný systémový účet na vzdialenom serveri ako autor nahlásenia."
+abuseMarkAsResolved: "Označiť nahlásenia ako vyriešené"
+replies: "Odpovede"
diff --git a/sharkey-locales/sv-SE.yml b/sharkey-locales/sv-SE.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a2bb34bb2cd39fc4892bd0cf7f3788e8eafb19ee
--- /dev/null
+++ b/sharkey-locales/sv-SE.yml
@@ -0,0 +1,2 @@
+---
+replies: "Svar"
diff --git a/sharkey-locales/th-TH.yml b/sharkey-locales/th-TH.yml
new file mode 100644
index 0000000000000000000000000000000000000000..17a9a633e7e03dcee78d7d8dc44eaa4cffc7ec63
--- /dev/null
+++ b/sharkey-locales/th-TH.yml
@@ -0,0 +1,13 @@
+---
+disableDrawer: "ไม่แสดงเมนูในรูปแบบลิ้นชัก"
+forwardReport: "ส่งต่อรายงานไปยังเซิร์ฟเวอร์ระยะไกล"
+forwardReportIsAnonymous: "ข้อมูลของคุณจะไม่ปรากฏบนเซิร์ฟเวอร์ระยะไกลและปรากฏเป็นบัญชีระบบที่ไม่ระบุชื่อ"
+abuseMarkAsResolved: "ทำเครื่องหมายรายงานว่าแก้ไขแล้ว"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “{reaction}” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!"
+    reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “{undo}”"
+_achievements:
+  _types:
+    _notes1:
+      title: "just setting up my shonk"
diff --git a/sharkey-locales/tr-TR.yml b/sharkey-locales/tr-TR.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/tr-TR.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/ug-CN.yml b/sharkey-locales/ug-CN.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ed97d539c095cf1413af30cc23dea272095b97dd
--- /dev/null
+++ b/sharkey-locales/ug-CN.yml
@@ -0,0 +1 @@
+---
diff --git a/sharkey-locales/uk-UA.yml b/sharkey-locales/uk-UA.yml
new file mode 100644
index 0000000000000000000000000000000000000000..f730399e1f4b7673243364966e8fe45a021c168f
--- /dev/null
+++ b/sharkey-locales/uk-UA.yml
@@ -0,0 +1,5 @@
+---
+disableDrawer: "Не використовувати висувні меню"
+forwardReport: "Переслати звіт на віддалений інстанс"
+forwardReportIsAnonymous: "Замість вашого облікового запису анонімний системний обліковий запис буде відображатися як доповідач на віддаленому інстансі"
+abuseMarkAsResolved: "Позначити скаргу як вирішену"
diff --git a/sharkey-locales/uz-UZ.yml b/sharkey-locales/uz-UZ.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4e38de281322f2a13e9632a43c5a5ceeb9ad32cc
--- /dev/null
+++ b/sharkey-locales/uz-UZ.yml
@@ -0,0 +1,6 @@
+---
+disableDrawer: "Slayd menyusidan foydalanmang"
+forwardReport: "Xabarni masofadagi serverga yuborish"
+forwardReportIsAnonymous: "Sizning yuborayotgan xabaringiz o'z akkountingiz emas balki anonim tarzda qoladi"
+abuseMarkAsResolved: "Yuborilgan xabarni hal qilingan deb belgilash"
+replies: "Javoblar"
diff --git a/sharkey-locales/vi-VN.yml b/sharkey-locales/vi-VN.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ec56a58c7183f54a81151233b3e8473b6feae32b
--- /dev/null
+++ b/sharkey-locales/vi-VN.yml
@@ -0,0 +1,9 @@
+---
+disableDrawer: "Không dùng menu thanh bên"
+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."
+abuseMarkAsResolved: "Đánh dấu đã xử lý"
+_achievements:
+  _types:
+    _notes1:
+      title: "just setting up my shonk"
diff --git a/sharkey-locales/zh-CN.yml b/sharkey-locales/zh-CN.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aca700337d806950aca9382ad1c1e0b06e506d5a
--- /dev/null
+++ b/sharkey-locales/zh-CN.yml
@@ -0,0 +1,9 @@
+---
+disableDrawer: "不显示抽屉菜单"
+forwardReport: "将该举报信息转发给远程服务器"
+forwardReportIsAnonymous: "在远程实例上显示的报告者是匿名的系统账号,而不是您的账号。"
+abuseMarkAsResolved: "处理完毕"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "回应可以通过点击帖子中的「{reaction}」按钮来添加。试着给这个示例帖子添加一个回应!"
+    reactDone: "通过按下「{undo}」按钮,可以取消已经添加的回应"
diff --git a/sharkey-locales/zh-TW.yml b/sharkey-locales/zh-TW.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c36f8dde2122d6c3760d1755197f1dbca80ea0db
--- /dev/null
+++ b/sharkey-locales/zh-TW.yml
@@ -0,0 +1,9 @@
+---
+disableDrawer: "不顯示下拉式選單"
+forwardReport: "將報告轉送給遠端伺服器"
+forwardReportIsAnonymous: "在遠端實例上看不到您的資訊,顯示的報告者是匿名的系统帳戶。"
+abuseMarkAsResolved: "處理完畢"
+_initialTutorial:
+  _reaction:
+    letsTryReacting: "可以透過點擊貼文上的「{reaction}」按鈕來添加反應。請嘗試在此範例貼文添加反應!"
+    reactDone: "按下「{undo}」按鈕可以取消反應。"